Week 15 - Apr 25th 2012 - Networking and Communications

Weekly Assignment - Build a wired &/or wireless network with at least two nodes

This week I wanted to test out some protocols I might need for an alternative final project, so decided to build a network consisting of a V-USB-enabled hub with several indicator nodes that would make use of the MIDI messaging protocol and a simple asynchronous bus. The aim is that the hub will be recognised by the host computer as a class compliant USB MIDI device, so MIDI messages will be sent to it. The hub will decode the messages and send them across the bus, where different nodes will represent 'notes' and will light up when the corresponding key is pressed.

I needed two circuits; the first simply contained a tiny45 microcontroller with associated components, a power LED and a ouput LED.

The second circuit was a standard V-USB setup circuit; the fabISP is a great layout for this and since I was using the ISP header for the network communication, I needed no additional components. I had also previously made a V-USB prototyping board (for use in breadboarding), and used this for this week's assignment. With each board it is important to correctly set the D+ and D- pins in the usbconfig.h file.

Programming for the USB board was based on an example here with modifications for an external crystal clock source. Here is the code:
#include #include #include #include #include #include #include "usbdrv/usbdrv.h" //lifted from NeilG #define output(directions,pin) (directions |= pin) // set port direction for output #define input(directions,pin) (directions &= (~pin)) // set port direction for input #define set(port,pin) (port |= pin) // set port pin #define clear(port,pin) (port &= (~pin)) // clear port pin #define pin_test(pins,pin) (pins & pin) // test for port pin #define bit_test(byte,bit) (byte & (1 << bit)) // test for bit set #define bit_delay_time 20 // bit delay for 9600 with overhead #define bit_delay() _delay_us(bit_delay_time) // RS232 bit delay #define half_bit_delay() _delay_us(bit_delay_time/2) // RS232 half bit delay #define led_port PORTA #define led_direction DDRA #define led_pin (1 << PA7) #define serial_port PORTA #define serial_direction DDRA #define serial_pins PINA #define serial_pin_out (1 << PA6) // This descriptor is based on http://www.usb.org/developers/devclass_docs/midi10.pdf // // Appendix B. Example: Simple MIDI Adapter (Informative) // B.1 Device Descriptor // static PROGMEM char deviceDescrMIDI[] = { /* USB device descriptor */ 18, /* sizeof(usbDescriptorDevice): length of descriptor in bytes */ USBDESCR_DEVICE, /* descriptor type */ 0x10, 0x01, /* USB version supported */ 0, /* device class: defined at interface level */ 0, /* subclass */ 0, /* protocol */ 8, /* max packet size */ USB_CFG_VENDOR_ID, /* 2 bytes */ USB_CFG_DEVICE_ID, /* 2 bytes */ USB_CFG_DEVICE_VERSION, /* 2 bytes */ 1, /* manufacturer string index */ 2, /* product string index */ 0, /* serial number string index */ 1, /* number of configurations */ }; // B.2 Configuration Descriptor static PROGMEM char configDescrMIDI[] = { /* USB configuration descriptor */ 9, /* sizeof(usbDescrConfig): length of descriptor in bytes */ USBDESCR_CONFIG, /* descriptor type */ 101, 0, /* total length of data returned (including inlined descriptors) */ 2, /* number of interfaces in this configuration */ 1, /* index of this configuration */ 0, /* configuration name string index */ 0, //USBATTR_BUSPOWER, USB_CFG_MAX_BUS_POWER / 2, /* max USB current in 2mA units */ // B.3 AudioControl Interface Descriptors // The AudioControl interface describes the device structure (audio function topology) // and is used to manipulate the Audio Controls. This device has no audio function // incorporated. However, the AudioControl interface is mandatory and therefore both // the standard AC interface descriptor and the classspecific AC interface descriptor // must be present. The class-specific AC interface descriptor only contains the header // descriptor. // B.3.1 Standard AC Interface Descriptor // The AudioControl interface has no dedicated endpoints associated with it. It uses the // default pipe (endpoint 0) for all communication purposes. Class-specific AudioControl // Requests are sent using the default pipe. There is no Status Interrupt endpoint provided. /* AC interface descriptor follows inline: */ 9, /* sizeof(usbDescrInterface): length of descriptor in bytes */ USBDESCR_INTERFACE, /* descriptor type */ 0, /* index of this interface */ 0, /* alternate setting for this interface */ 0, /* endpoints excl 0: number of endpoint descriptors to follow */ 1, /* */ 1, /* */ 0, /* */ 0, /* string index for interface */ // B.3.2 Class-specific AC Interface Descriptor // The Class-specific AC interface descriptor is always headed by a Header descriptor // that contains general information about the AudioControl interface. It contains all // the pointers needed to describe the Audio Interface Collection, associated with the // described audio function. Only the Header descriptor is present in this device // because it does not contain any audio functionality as such. /* AC Class-Specific descriptor */ 9, /* sizeof(usbDescrCDC_HeaderFn): length of descriptor in bytes */ 36, /* descriptor type */ 1, /* header functional descriptor */ 0x0, 0x01, /* bcdADC */ 9, 0, /* wTotalLength */ 1, /* */ 1, /* */ // B.4 MIDIStreaming Interface Descriptors // B.4.1 Standard MS Interface Descriptor /* interface descriptor follows inline: */ 9, /* length of descriptor in bytes */ USBDESCR_INTERFACE, /* descriptor type */ 1, /* index of this interface */ 0, /* alternate setting for this interface */ 2, /* endpoints excl 0: number of endpoint descriptors to follow */ 1, /* AUDIO */ 3, /* MS */ 0, /* unused */ 0, /* string index for interface */ // B.4.2 Class-specific MS Interface Descriptor /* MS Class-Specific descriptor */ 7, /* length of descriptor in bytes */ 36, /* descriptor type */ 1, /* header functional descriptor */ 0x0, 0x01, /* bcdADC */ 65, 0, /* wTotalLength */ // B.4.3 MIDI IN Jack Descriptor 6, /* bLength */ 36, /* descriptor type */ 2, /* MIDI_IN_JACK desc subtype */ 1, /* EMBEDDED bJackType */ 1, /* bJackID */ 0, /* iJack */ 6, /* bLength */ 36, /* descriptor type */ 2, /* MIDI_IN_JACK desc subtype */ 2, /* EXTERNAL bJackType */ 2, /* bJackID */ 0, /* iJack */ //B.4.4 MIDI OUT Jack Descriptor 9, /* length of descriptor in bytes */ 36, /* descriptor type */ 3, /* MIDI_OUT_JACK descriptor */ 1, /* EMBEDDED bJackType */ 3, /* bJackID */ 1, /* No of input pins */ 2, /* BaSourceID */ 1, /* BaSourcePin */ 0, /* iJack */ 9, /* bLength of descriptor in bytes */ 36, /* bDescriptorType */ 3, /* MIDI_OUT_JACK bDescriptorSubtype */ 2, /* EXTERNAL bJackType */ 4, /* bJackID */ 1, /* bNrInputPins */ 1, /* baSourceID (0) */ 1, /* baSourcePin (0) */ 0, /* iJack */ // B.5 Bulk OUT Endpoint Descriptors //B.5.1 Standard Bulk OUT Endpoint Descriptor 9, /* bLenght */ USBDESCR_ENDPOINT, /* bDescriptorType = endpoint */ 0x1, /* bEndpointAddress OUT endpoint number 1 */ 3, /* bmAttributes: 2:Bulk, 3:Interrupt endpoint */ 8, 0, /* wMaxPacketSize */ 10, /* bIntervall in ms */ 0, /* bRefresh */ 0, /* bSyncAddress */ // B.5.2 Class-specific MS Bulk OUT Endpoint Descriptor 5, /* bLength of descriptor in bytes */ 37, /* bDescriptorType */ 1, /* bDescriptorSubtype */ 1, /* bNumEmbMIDIJack */ 1, /* baAssocJackID (0) */ //B.6 Bulk IN Endpoint Descriptors //B.6.1 Standard Bulk IN Endpoint Descriptor 9, /* bLenght */ USBDESCR_ENDPOINT, /* bDescriptorType = endpoint */ 0x81, /* bEndpointAddress IN endpoint number 1 */ 3, /* bmAttributes: 2: Bulk, 3: Interrupt endpoint */ 8, 0, /* wMaxPacketSize */ 10, /* bIntervall in ms */ 0, /* bRefresh */ 0, /* bSyncAddress */ // B.6.2 Class-specific MS Bulk IN Endpoint Descriptor 5, /* bLength of descriptor in bytes */ 37, /* bDescriptorType */ 1, /* bDescriptorSubtype */ 1, /* bNumEmbMIDIJack (0) */ 3, /* baAssocJackID (0) */ }; uchar usbFunctionDescriptor(usbRequest_t * rq) { if (rq->wValue.bytes[1] == USBDESCR_DEVICE) { usbMsgPtr = (uchar *) deviceDescrMIDI; return sizeof(deviceDescrMIDI); } else { /* must be config descriptor */ usbMsgPtr = (uchar *) configDescrMIDI; return sizeof(configDescrMIDI); } } //network protocols lifted from NeilG void get_char(volatile unsigned char *pins, unsigned char pin, char *rxbyte) { // read character into rxbyte on pins pin - assumes line driver (inverts bits) *rxbyte = 0; while (pin_test(*pins,pin)) ;// wait for start bit half_bit_delay(); // delay to middle of first data bit bit_delay(); // unrolled loop to read data bits if pin_test(*pins,pin) *rxbyte |= (1 << 0); else *rxbyte |= (0 << 0); bit_delay(); if pin_test(*pins,pin) *rxbyte |= (1 << 1); else *rxbyte |= (0 << 1); bit_delay(); if pin_test(*pins,pin) *rxbyte |= (1 << 2); else *rxbyte |= (0 << 2); bit_delay(); if pin_test(*pins,pin) *rxbyte |= (1 << 3); else *rxbyte |= (0 << 3); bit_delay(); if pin_test(*pins,pin) *rxbyte |= (1 << 4); else *rxbyte |= (0 << 4); bit_delay(); if pin_test(*pins,pin) *rxbyte |= (1 << 5); else *rxbyte |= (0 << 5); bit_delay(); if pin_test(*pins,pin) *rxbyte |= (1 << 6); else *rxbyte |= (0 << 6); bit_delay(); if pin_test(*pins,pin) *rxbyte |= (1 << 7); else *rxbyte |= (0 << 7); // wait for stop bit bit_delay(); half_bit_delay(); } void put_char(volatile unsigned char *port, unsigned char pin, char txchar) { // send character in txchar on port pin - assumes line driver (inverts bits) clear(*port,pin); // start bit bit_delay(); // unrolled loop to write data bits if bit_test(txchar,0) set(*port,pin); else clear(*port,pin); bit_delay(); if bit_test(txchar,1) set(*port,pin); else clear(*port,pin); bit_delay(); if bit_test(txchar,2) set(*port,pin); else clear(*port,pin); bit_delay(); if bit_test(txchar,3) set(*port,pin); else clear(*port,pin); bit_delay(); if bit_test(txchar,4) set(*port,pin); else clear(*port,pin); bit_delay(); if bit_test(txchar,5) set(*port,pin); else clear(*port,pin); bit_delay(); if bit_test(txchar,6) set(*port,pin); else clear(*port,pin); bit_delay(); if bit_test(txchar,7) set(*port,pin); else clear(*port,pin); bit_delay(); // stop bit set(*port,pin); bit_delay(); // char delay bit_delay(); } /* ------------------------------------------------------------------------- */ /* ----------------------------- USB interface ----------------------------- */ /* ------------------------------------------------------------------------- */ uchar usbFunctionSetup(uchar data[8]) { return 0xff; } /*---------------------------------------------------------------------------*/ /* usbFunctionRead */ /*---------------------------------------------------------------------------*/ uchar usbFunctionRead(uchar * data, uchar len) { data[0] = 0; data[1] = 0; data[2] = 0; data[3] = 0; data[4] = 0; data[5] = 0; data[6] = 0; return 7; } /*---------------------------------------------------------------------------*/ /* usbFunctionWrite */ /*---------------------------------------------------------------------------*/ uchar usbFunctionWrite(uchar * data, uchar len) { return 1; } /*---------------------------------------------------------------------------*/ /* usbFunctionWriteOut */ /* */ /* this Function is called if a MIDI Out message (from PC) arrives. */ /* See http://www.usb.org/developers/devclass_docs/midi10.pdf for a */ /* description of the data structure */ /* */ /*---------------------------------------------------------------------------*/ void usbFunctionWriteOut(uchar * data, uchar len) { uchar recmsg[len]; // prepare to read in received message int i; for (i=0; i<len; i++) recmsg[i] = *(data+i); // read in each byte of message output(serial_direction, serial_pin_out); put_char(&serial_port, serial_pin_out, recmsg[1]); put_char(&serial_port, serial_pin_out, recmsg[2]); put_char(&serial_port, serial_pin_out, recmsg[3]); input(serial_direction, serial_pin_out); } void hadUsbReset(void) { cli(); sei(); } void initTXRX() { set(serial_port, serial_pin_out); input(serial_direction, serial_pin_out); } void initUSB() { uchar i; wdt_enable(WDTO_2S); usbInit(); usbDeviceDisconnect(); // enforce re-enumeration, do this while interrupts are disabled! i = 0; while (--i) { // fake USB disconnect for > 250 ms wdt_reset(); _delay_ms(1); } usbDeviceConnect(); } int main() { initTXRX(); initUSB(); // Globally enable interrupts sei(); // Endless loop for (;;) { wdt_reset(); usbPoll(); } return 0; }

The important part of this code is the void usbFunctionWriteOut(uchar * data, uchar len) function, which is what the microcontroller does when it receives a USB message. In this function I have instructed the microcontroller to broadcast some details from the received message across the bus network.

The node microcontrollers wait for messages on the bus; if a message is received with a note ID corresponding the microcontroller's ID, the LED is switched on/off according to whether it is a noteOn/noteOff instruction.

#include #include //basic definitions lifted from NeilG #define output(directions,pin) (directions |= pin) // set port direction for output #define input(directions,pin) (directions &= (~pin)) // set port direction for input #define set(port,pin) (port |= pin) // set port pin #define clear(port,pin) (port &= (~pin)) // clear port pin #define pin_test(pins,pin) (pins & pin) // test for port pin #define bit_test(byte,bit) (byte & (1 << bit)) // test for bit set #define bit_delay_time 20 // bit delay changed from 100 which was for 9600 with overhead #define bit_delay() _delay_us(bit_delay_time) // RS232 bit delay #define half_bit_delay() _delay_us(bit_delay_time/2) // RS232 half bit delay #define serial_port PORTB #define serial_direction DDRB #define serial_pins PINB #define serial_pin_in (1 << PB0) // also MOSI pin #define led_port PORTB #define led_direction DDRB #define led_pins PINB #define led_pin (1 << PB4) #define node_id 0x3C // C3 (middle C) char recmsg0; // store incoming bit 0 - MIDI command char recmsg1; // store incoming bit 1 - MIDI note char recmsg2; // store incoming bit 2 - MIDI velocity //network listening protocol lifted from NeilG void get_char(volatile unsigned char *pins, unsigned char pin, char *rxbyte) { // read character into rxbyte on pins pin - assumes line driver (inverts bits) *rxbyte = 0; while (pin_test(*pins,pin)) // wait for start bit ; // delay to middle of first data bit half_bit_delay(); bit_delay(); // unrolled loop to read data bits if pin_test(*pins,pin) *rxbyte |= (1 << 0); else *rxbyte |= (0 << 0); bit_delay(); if pin_test(*pins,pin) *rxbyte |= (1 << 1); else *rxbyte |= (0 << 1); bit_delay(); if pin_test(*pins,pin) *rxbyte |= (1 << 2); else *rxbyte |= (0 << 2); bit_delay(); if pin_test(*pins,pin) *rxbyte |= (1 << 3); else *rxbyte |= (0 << 3); bit_delay(); if pin_test(*pins,pin) *rxbyte |= (1 << 4); else *rxbyte |= (0 << 4); bit_delay(); if pin_test(*pins,pin) *rxbyte |= (1 << 5); else *rxbyte |= (0 << 5); bit_delay(); if pin_test(*pins,pin) *rxbyte |= (1 << 6); else *rxbyte |= (0 << 6); bit_delay(); if pin_test(*pins,pin) *rxbyte |= (1 << 7); else *rxbyte |= (0 << 7); // wait for stop bit bit_delay(); half_bit_delay(); } void init_pins() { output(led_direction, led_pin); } int main(void) { init_pins(); while(1) { get_char(&serial_pins, serial_pin_in, &recmsg0); get_char(&serial_pins, serial_pin_in, &recmsg1); get_char(&serial_pins, serial_pin_in, &recmsg2); if (recmsg1 == node_id) { if (((recmsg0 & 0x90) == 0x90) && (recmsg2)) { set(led_port, led_pin); } else { clear(led_port, led_pin); } } } }

Here is a short video of the system in action. Note that, because noteOn and noteOff messages are sent separately, more than one light can be on at once. This system would provide a very convenient method of controlling all sorts of things, much more exciting than LEDs - orchestrating fireworks is a good example!

<<< Week 14

Week 16 >>>