2 December 2009

SOMO-14D audio module


The Somo 14D is a cute little module from 4D - plays audio files you put on a micro SD card. Aside from some difficulties with the brand of card you use, it kind of worked... but wouldn't play the files I requested.

Now, bring in the Logic tool. Honestly, I can't believe I didn't buy this or something like this years ago.

Turns out this module is very particular also about the timing of commands you send it. The clock pulses have to be 200us. And that's exactly what I had in my code:


void somo_14d_send_data(uns16 data) {

// Signal start
clear_pin(somo_14d_clk_port, somo_14d_clk_pin);
delay_ms(2); // tSTART = 2ms

for (uns8 count = 0; count < 16; count++) {
if (data.15) {
set_pin(somo_14d_data_port, somo_14d_data_pin);
} else {
clear_pin(somo_14d_data_port, somo_14d_data_pin);
}
delay_us(1); // tDS = 1us
set_pin(somo_14d_clk_port, somo_14d_clk_pin);
delay_us(200); // tCH = 200us
clear_pin(somo_14d_clk_port, somo_14d_clk_pin);
delay_us(200); // tCL = 200us
data = data << 1;
}
// Signal end
set_pin(somo_14d_clk_port, somo_14d_clk_pin);
delay_ms(2); // tSTOP = 2ms
}


All looks perfectly okay, right?

Now look at what Logic says. The pulse is actually about 100us. Half the...hang on. In my config.h I had the clock rate of the chip at 20Mhz when in fact it's running at 40Mhz. Ha! Once again, Logic to the rescue.

29 October 2009

Smashing Pumpkins




Combine hobby electronics, photography and fruit and vegetables dropped from a second floor balcony, what do you get?

My brother does fun stuff with cameras for a living - see www.seesawphoto.com.au - but we decided to combine his photography skills and equipment and some gear from Sparkfun and the PicPack library to see if we couldn't smash a few things up.











What could be more fun than smashing eggs, watermelons and pumpkins all in the name of a good photo?












The trick of course is to capture the image just at the right moment. We rigged up a microphone to a Pic demo board (which rather nicely has buttons, an LCD display, and a relay to trigger the flash. We grabbed a wired connection to a camera-mounted flash - wired that to the relay output from the board and then used that to trigger Paul's top knotch professional studio flash gear. The microphone was wired to the analogue input and sampled as quickly as the Pic could manage it.











Knocking together the Pic application didn't take long with the library functions. The buttons allowed a configurable delay in milliseconds between hearing sound and triggering the flash, and sound sensitivity. Of course some experimentation was required here - more things to smash! In the end we built an audible delay in so that we had a chance to "arm" the system, then get out of the way before the pumpkins came down.












I think you'll agree the photos are pretty fun. Source code will be published in the next drop of PicPack.

26 January 2009

PicPack 2.0 released

Now available... Including better USB documentation and a cracking USB serial (CDC) demo.

You can download from the right hand side "download" window.

You can also get PicPack from GitHub - updated regularly.

Enjoy!

USB Part 5 – Using the USB Serial library

Now that we’ve explored some of the ins and outs of the PicPack USB library, it’s time to look at a more complex and probably more useful example. Up until now we’ve relied on using serial ports to act as our debug interface. It would be nice if we could use USB for this purpose entirely? This is where the Communication Device Class (CDC) comes in.

The CDC covers many things, including the thing that we’re interested in, a connection to a modem-like device. In fact, we define our “modem” as so dumb it can’t even make calls on a phone line – which sounds pretty much like a serial port to me.

If you want to get cracking, open up the usb_serial project in the demos\usb_serial directory. It’s all set for burning into a 18f4550 on a board like the TechToys USB demo board. If you burn the .hex file, then plug your device into a PC, it will ask for a driver file. Just point the install program at the picpack_cdc.inf file in the tools\usb_cdc_inf directory. For reasons best known to Microsoft, Windows requires this file, even though all it says is “this is a standard USB serial port”. Go figure. Linux doesn’t require any sort of driver information at all.

Using the PicPack CDC routines is pretty easy. You can use them just like you’ve been using the pic_serial routines up til now.

In your system setup routine, put:

// Setup Communication Device Class routines
usb_cdc_setup();

// Setup USB
usb_setup();

// Turn on interrupts
turn_usb_ints_on();
turn_global_ints_on();

In your interrupt service routine you will need to include:

usb_handle_isr();

which handles both reception and transmission.

In your main() routine, kick off USB negotiations using:

usb_enable_module()

You can wait for the negotiations to finish using

while (usb_configured == 0) {
delay_ms(250);
}

We’ll use the callback that’s triggered when the configuration is complete to set the usb_configured variable:that we were checking in that last loop:

void usb_device_configured_callback() {
usb_configured = 1;
}

And that’s it. Once the link is up, you can use the usb_cdc_ routines like usb_cdc_putc just like their pic_serial equivalents. In fact, aside from these routines, you can see that the complete program looks remarkably similar to the pic_serial demo itself.

You can #define USB_DEBUG and CDC_DEBUG in the config.h file if you’d like to get more detail on the USB process itself. This is handy if you want to understand how the CDC class code and the PicPack USB stack itself work.

In the next tutorial we’ll delve a bit deeper into the CDC routines. The CDC is a more complex example than our previous USB joy mouse which sent data in only one direction, and didn’t make use of class control transfers and multiple endpoints.

12 January 2009

USB part 4 – Inner workings of the PicPack USB stack

This tutorial covers the main USB functionality and how the library actually goes about setting up the pic to carry out USB transactions and transfers. You don’t need to understand exactly how all this happens, but it’s helpful to know what the functions do and how to use them.

Our first point is to setup the USB sub-system. As is our convention with the PicPack library, naturally, you call the usb_setup routine:

  void usb_setup() {
      usb_state = st_POWERED;
  

Our state is now powered, meaning that we have power applied over the USB bus. The next task is to initialise the hardware:

      // init hardware
    clear_bit(ucfg, UTRDIS); // enable internal tranceiver
  

Most designs involving pics use the internal transceiver. You can connect the pic to an external piece of hardware, or connect it directly to the chip itself. Here we assume that you’re doing the direct connection. USB has (as of version 2.0) three speeds – low speed (around 1Mbps), full speed (around 12Mbs) and high speed (around 480Mbps). The pics that we’re playing with only operate in the low/full range, meaning that we have to select between low and full speed.

      set_bit(ucfg, FSEN); // clear for low speed, set for high speed
  

In all the examples here, we select full speed. There’s no reason not to, really, and as it happens, selecting full speed gives us many more choices for clock frequency selection. More about that in a later tutorial.

USB indicates its speed request by pull-ups on the data pins. The pic hardware can do this function for us (meaning we don’t need to include external pull-up resistors), providing we enable the on-chip pull-ups:

      set_bit (ucfg, UPUEN); // enable on-chip pull-ups
  

The pic USB Serial Interface Engine (SIE) can double buffer transfers, meaning that one buffer is being used by the SIE while the other is available to the microcontroller. This means faster transfer at the cost of more complex code. In the initial PicPack library, we switch double buffering off, for the sake of simplicity:

    clear_bit(ucfg, PPB1); // disable double buffering
    clear_bit(ucfg, PPB0);

Finally, we set up end point 0 ready to receive and send control transfers:

    set_bit(uep0, EPHSHK); // EP0 handshaking on
    set_bit(uep0, EPOUTEN); // EP0 OUT enable
    set_bit(uep0, EPINEN); // EP0 IN enable
    clear_bit(uep0, EPCONDIS); // EP0 control transfers on (and IN and OUT)
  

Here we enable handshaking (required for control transfers – and all endpoints type except isochronous), enable OUT (from host) and IN (to host) transfers, and enable control transfers on this endpoint.

Due to a bug in the BoostC compiler that prevents compile time initialisation of structures that don’t point to char*, we need some run-time initialisation of this structure.

    ep_out_bd_location[0] = &bd0out;

This continues for all buffer descriptors for IN and OUT endpoints.

In order to kick off interrupts for the USB module, in our main program we turn on interrupts from the USB module:

    turn_usb_ints_on();
    turn_global_ints_on();
  

Finally, to actually start the USB module doing its things, we call usb_enable_module().

void usb_enable_module() {
    uir = 0;
    set_bit(ucon, USBEN); // enable USB serial interface engine (SIE)
    usb_state = st_DEFAULT;
}

Here we clear any previously triggered interrupts, enable the USB SIE module and set our state to Default.

It’s at this point that all the fun starts. The SIE now attaches to the bus by pulling one of the data lines high, and negotiations begin.

9 January 2009

USB Part 3 – Configuring the PicPack USB library

As a personal preference, I think that state machines are evil and should be avoided like the plague. Mostly this is because of my view the source code should show precisely what should happen. State machines are different, since they show what should happen under certain circumstances and they specifically hide the workings of the “machine” as a whole. It means that the understanding of how the whole system works is in fact “outside of the code”. You need to know something beyond what is written to comprehend what happens and why. This makes debugging these systems extremely difficult.

Despite my misgivings, state machines are excellent at enabling us to create hardware cheaply since it doesn’t need to understand about what it is doing or why – it just needs to respond quickly. This is (unfortunately) the world we find ourselves in when we start playing with USB.

Let’s have a look at how pic devices, in combination with the PicPack library handle the tangled web that is USB. Our first port of call is the config.h file, which is where the PicPack library finds all its configuration details – and the USB library is no exception. Let’s pull apart the USB section from the Joy Mouse demo.

#define USB_HIGHEST_EP 1

In order to configure the appropriate data structures, the library needs to know what the highest end point number will be. In this case, it’s endpoint 1.

// #define USB_SELF_POWERED
#define USB_BUS_POWERED

Is the device powered from the USB supply? Select your option here. This changes what the pic reports upstream when queried.

#define USB_EP0_OUT_SIZE 8
#define USB_EP0_OUT_ADDR 0x0500
#define USB_EP0_IN_SIZE 8
#define USB_EP0_IN_ADDR 0x0508
  #define USB_EP1_IN_SIZE 8
#define USB_EP1_IN_ADDR 0x0510

Here it starts to get a little more complex. Each endpoint for IN and OUT transactions has its own buffer. For each endpoint IN and endpoint OUT you need to decide how big transactions can be, and where the buffer will be located. You’ll need to check out the datasheet for your particular pic. In the case of the 18f4550, 0x0500 is a good place to start all the buffers. You can see they run sequentially from there. Eight bytes is just plenty for a mouse. Since we have only an IN endpoint 1 (no OUT), we only declare the IN.

//#define USB_CALLBACK_ON_SOF
// if you define it, you'll need to include this routine in your code:
// void usb_SOF_callback(uns16 frame) {
// }

Each 1ms, the USB starts a new “frame”. If you want a nice clean 1ms wake up call without having to use timers, define USB_CALLBACK_ON_SOF and create a function called usb_SOF_callback in your code.

//#define USB_CALLBACK_ON_DEVICE_CONFIGURED
// if you define it, you'll need to include this routine in your code:
// void usb_device_configured_callback() {
// }

USB has a state called “configured”. It means that the host has chosen a particular configuration (usually it has a choice of just one) and the PicPack library has set up the endpoints ready for action. If you’d like to get notified when this state occurs, define USB_CALLBACK_ON_DEVICE_CONFIGURED in your code and declare usb_device_configured_callback() in your code.

#define USB_CALLBACK_ON_CLASS_CTRL
// if you define it, you'll need to include these routines in your code:
//void usb_handle_class_ctrl_read_callback();
//void usb_handle_class_ctrl_write_callback(uns8 *data, uns16 count);
//void usb_handle_class_request_callback(setup_data_packet sdp);

A number of different types of devices are defined by the USB standard. These are known as class compliant devices. Just to keep things fun, USB handles some transactions for class devices over endpoint 0 using control transfers. While the Joy Mouse demo does indeed handle class control transfers, it doesn’t do a lot with them. For a more complex example, including how to send data back to the host after it has made a request using a control transfer and assuming class ownership of the control transfer, see the serial (CDC) PicPack demo which we’ll cover in the next tutorial.

If your device needs to handle class control transfers, then define USB_CALLBACK_ON_CLASS_CTRL and declare the appropriate functions. Note that usb_handle_class_request_callback will be called when a class request has been received and needs action; usb_handle_class_ctrl_read_callback will be called when a transaction sending data to the host (IN) has completed, and usb_handle_class_ctrl_write_callback will be called when a transaction receiving data from the host has occurred.

//#define USB_EP_DATA_CALLBACK
// if you define it, you'll need to include these routines in your code:
//void usb_ep_data_out_callback(uns8 end_point, uns8 *buffer_location, uns16 byte_count);
//void usb_ep_data_in_callback(uns8 end_point, uns16 byte_count);

To handle data transactions that occur over endpoints other than endpoint 0, that is, non-control transfers, define USB_EP_DATA_CALLBACK. When a successful data transaction has occurred that received data from the host (OUT), usb_ep_data_out_callback will be called. You’ll be told the endpoint number, where the buffer is and how many bytes were transferred. Note that the endpoint will not be re-primed (armed) until you return from this function. When a successful data transaction has occurred that sent data to the host (IN), usb_ep_data_in_callback will be called. The Joy Mouse demo doesn’t use either of these functions.