Jump to content
43oh

How to set up MIDI over USB


Recommended Posts

Hi everyone,

TLDNR:

  • How do I set up MIDI descriptors for MIDI over USB so that I can get my PC to recognize my F5510 as a class-compliant MIDI device, either using the TI USB Dev package, api, and descriptor tool, or manually?

 

I'm working on my first MSP430 project and hoping to get some help setting up the USB interface in CCS. I'm working with an MSP430F5510 on a custom board and trying to set it up so that it's recognized as a class-compliant midi device on the PC - then I will send midi messages to the PC to control music programs, etc. I'm stuck on where to start for configuring the USB on the MSP430 and setting up the MIDI descriptors.

 

So far, I've managed to run the empty USB example from TI's USB Developers Package using HID settings from the TI Descriptor Tool - my PC recognized it as an HID device and installed it. It shows in device manager (yet has an error, which I suspect is just due to not running any code yet: "This device cannot start. (Code 10)"). Okay, this seems good - I imagine my hardware is working then.

Now how do I set it up to be recognized as a MIDI device? The TI examples and descriptor tool don't support MIDI, but I was hoping I could possibly modify the code to make it work - to save re-inventing some of the wheel. Their code seems quite convoluted and abstracted which is making it difficult for me to figure out what I need to do. I've found some resources on MIDI over USB such as the ones below, but am looking for some direction on how to actually implement it.

Resources I've found:

Main questions on the topic:

  • Should I be trying to use TI's examples? Also the API?
  • Where do I put the descriptors and how?
  • Are there other resources I should be following?

 

Thanks heaps in advance for any insight you can give! Congrats if you read this far - you're the best =).

Thanks,

Josh

 

Link to post
Share on other sites

TI USB Stack can generate any descriptor using prepared descriptor tool. AFAIK, MIDI is not supported. You must change manually descriptors.h / descriptors.c files (inside TI USB Stack example that fits most to your application), and than try to enumerate. Reach logging will help, breakpoint debugging not so much.

Link to post
Share on other sites
Should I be trying to use TI's examples? Also the API?

 

TI's USB stack is designed for be used with the four supported device classes.

To implement another device class, you have to insert custom code at all the appropriate places. However, this is still easier than writing the entire USB stack from scratch.

 

USB MIDI is similar to CDC, which just transfers bytes over bulk pipes. In the case of USB MIDI, you have the four-byte event packets instead of bytes, but the overall management of the USB packets should be identical.

 

Where do I put the descriptors and how?

 

First, use the descriptor tool to generate a program for some other device class.

Then manually replace the descriptors in the USB_config/descriptors.* files.

 

Are there other resources I should be following?

 

Start writing code!  :D

Link to post
Share on other sites

Thanks for the replies! I've managed to sort out the descriptors. I've got the device showing up in the device manager as a Generic USB Audio device and I've checked the descriptors with USBlyzer and they seem okay. Device manager reports the device is working properly and I'm able to successfully view the device and it's input and output ports in various MIDI programs (tested with midi-ox and ableton live). 

Now I'm trying to send a midi message to the PC but am stuck again. In my test code I'm trying to spam a note on command (cable 0, channel 1: 0x09905840) in a while loop. Being naive, I tried using the CDC functions from the API example with no success. The example used "USBCDC_sendDataInBackground", but there is also "USBCDC_sendDataAndWaitTillDone", "USBCDC_sendData", and "CdcToHostFromBuffer". All the functions in the API are overwhelming! Looking for some direction here on where to go... Clavier, you mentioned CDC data transfer is essentially the same as MIDI - should I be able to use these functions to send my data?

 

I'm also a little confused on how the MIDI endpoints are working... to send a note-on message to the host PC: am I sending data with the embedded MIDI IN Jack through the MIDIStreaming Bulk OUT endpoint? And does this have any relevance for how to actually send this data?

 

 

TI USB Stack can generate any descriptor using prepared descriptor tool. AFAIK, MIDI is not supported. You must change manually descriptors.h / descriptors.c files (inside TI USB Stack example that fits most to your application), and than try to enumerate. Reach logging will help, breakpoint debugging not so much.

You're right - MIDI is not supported, trying to alter the files to make it work. What's Reach Logging? (sorry, i'm a noob!)

 

TI's USB stack is designed for be used with the four supported device classes.

To implement another device class, you have to insert custom code at all the appropriate places. However, this is still easier than writing the entire USB stack from scratch.

 

USB MIDI is similar to CDC, which just transfers bytes over bulk pipes. In the case of USB MIDI, you have the four-byte event packets instead of bytes, but the overall management of the USB packets should be identical.

 

 

First, use the descriptor tool to generate a program for some other device class.

Then manually replace the descriptors in the USB_config/descriptors.* files.

 

 

Start writing code!  :D

Thanks for the tips! Made some comments above.

Thanks again for all the support on this.  :)

Edit: Added some of my code...

 

  // Simplified main.c

  uint8_t midiTest[4] = {
    		0x09,			//cable 0, note-on
		0x90,			//note-on, channel 1
		0x88, 			//middle C
		0x40			//non-sensitive velocity
  };

    while (1)
    {
 
            if (USBCDC_sendDataAndWaitTillDone(midiTest, 4, 0, 1000))
            {
              _NOP(); 
            } else {   
            	success = 1;    // by setting a watch, this does actually execute 
                                // at some point, but usually the call errors instead.
            }
        }
    }  
/*-----------------------------------------------------------------------------+
| Device Descriptor 
|-----------------------------------------------------------------------------*/
uint8_t const abromDeviceDescriptor[SIZEOF_DEVICE_DESCRIPTOR] = {
    SIZEOF_DEVICE_DESCRIPTOR,               // Length of this descriptor
    DESC_TYPE_DEVICE,                       // Type code of this descriptor
    0x10, 0x01,                             // Release of USB spec
    0x00,                                   // Device's base class code
    0x00,                                   // Device's sub class code
    0x00,                                   // Device's protocol type code
    EP0_PACKET_SIZE,                        // End point 0's packet size
    USB_VID&0xFF, USB_VID>>8,               // Vendor ID for device, TI=0x0451
    USB_PID&0xFF, USB_PID>>8,               // Product ID for device
    VER_FW_L, VER_FW_H,                     // Revision level of device
    1,                                      // Index of manufacturer name string desc
    2,                                      // Index of product name string desc
    0x00,                   // Index of serial number string desc
    1                                       // Number of configurations supported
};

/*-----------------------------------------------------------------------------+
| Configuration Descriptor                                                     |
|-----------------------------------------------------------------------------*/
const struct abromConfigurationDescriptorGroup abromConfigurationDescriptorGroup=
{
    /* Generic part */
    {
        // CONFIGURATION DESCRIPTOR (9 bytes)
        SIZEOF_CONFIG_DESCRIPTOR,                       
        DESC_TYPE_CONFIG,                                
        0x65, 0x00,                  
        USB_NUM_INTERFACES,                         
        USB_CONFIG_VALUE,                               
        0x00,                              
        USB_SUPPORT_SELF_POWERED | USB_SUPPORT_REM_WAKE,   
        USB_MAX_POWER   
    },
    {
        /* start MIDI[0] */
        {
        //INTERFACE DESCRIPTOR (9 bytes)
        0x09,           
        0x04,        
        0x00,   
        0x00,       
        0x00,       
        0x01,            
        0x01,         
        0x00,           
        0x00,        

        //Class-specific AC Interface Descriptor (9)
        0x09,   
        0x24,                       
        0x01,          
        0x00,                        
        0x01,
0x09, 0x00,
0x01,
0x01,

//MIDIStreaming Interface Descriptors
        //Standard MS Interface Descriptor (9)
        0x09,  
        0x04,        
        0x01,    
0x00,
0x02,
0x01,
0x03,
0x00,
0x00,

        //Class-specific MS Interface Descriptor (7)
        0x07,
        0x24, 
        0x01, 
0x00, 0x01,
0x41, 0x00,

        //MIDI IN Jack Descriptor (6)
        0x06,
        0x24,
        0x02,
0x01,
0x01,
0x00,

        //MIDI IN Jack Descriptor (6)
        0x06,
        0x24, 
        0x02,
0x02,
0x02,
0x00,

        //MIDI OUT Jack Descriptor (9)
        0x09,
        0x24,
0x03,
0x01,
0x03,
0x01,
0x02,
0x01,
0x00,

//MIDI OUT Jack Descriptor (9)
0x09,
0x24,
0x03,
0x02,
0x04,
0x01,
0x01,
0x01,
0x00,

// Standard Bulk OUT (9)
0x09,
0x05,
0x01,
0x02,
0x40, 0x00,
0x00,
0x00,
0x00,

// Class MS Bulk OUT (5)
0x05,
0x25,
0x01,
0x01,
0x01,

// Standard Bulk IN (9)
0x09,
0x05,
0x81,
0x02,
0x40, 0x00,
0x00,
0x00,
0x00,

// Class MS Bulk IN (5)
0x05,
0x25,
0x01,
0x01,
0x03

}
Link to post
Share on other sites

CdcToHostFromBuffer() is an internal function.

 

The most basic function is USBCDC_sendData(). It works asynchronously, i.e., it just starts the transmit operation; and it fails if the previous operation has not yet finished.

USBCDC_sendDataInBackground() waits for the previous transmit to finish, then calls USBCDC_sendData().

USBCDC_sendDataAndWaitTillDone() calls USBCDC_sendData(), then waits for this transmit to finish.

 

In CDC, nobody cares about how the bytes are packetized, but with MIDI, the four-byte event packets must not be split. However, this is not a problem because the TI USB stack will not split the data unless needed, and the USB packet size is divisible by four.

 

You should look at the returned error code to find out what went wrong. But I guess it's 1 ("timeout") because 1000 loop cycles is not a long time.

 

Please do not leave wrong comments in the code; either change them to match the code, or delete them.

Link to post
Share on other sites

You should look at the returned error code to find out what went wrong. But I guess it's 1 ("timeout") because 1000 loop cycles is not a long time.

 

Thanks for the message. Stepping through the code with breakpoints, I see that "USBCDC_sendDataAndWaitTillDone(midiTest, 4, 0, 100000)" returns successful two times, and then failed once with error 1: TIMEOUT, and then fails with error 3: USBCDC_INTERFACE_BUSY_ERROR every time after that. I do not see the two "successful" data transfers in USBlyzer - although I'm not exactly sure what I would see. Note that I've changed the time out to be much longer, but this error pattern is the same with the original 1000 loop timeout. After this, I noticed I have interface 0 selected in "USBCDC_sendDataAndWaitTillDone"; however, the MIDI descriptor required an audio interface before the MIDI Streaming interface - so should I use interface 1? I tried this, I changed CDC_NUM_INTERFACES to 2 so CdcWriteCtrl[] has room for the second interface and continued debugging...

 

Looking into the interface busy error, the sendData function checks to see if there was previous data in the CdcWriteCtrl[] array, which there is - and its valid but full of data from a previous operations - so it returns this error. So why is it not being transferred out of this array? 

 

The CdcToHostFromBuffer function has the job of doing this. I put a breakpoint in this function, and it is never called. I did see it get called when I was using interface 0 before - which is why it returned successful twice I think. Twice because the CdcToHostFromBuffer function tries using 2 buffers (X & Y) to send the data, I think. Each gets filled up (and not emptied) before the BUSY error starts.

 

This function is only called in the interrupt: USB_UBM_VECTOR, which is also not being reached with a breakpoint. This interrupt and consequently the CdcToHostFromBuffer function were called 3 times when using interface 0.

My thoughts/questions: 

  • Is sending data via interface 1 correct (or interface 0)? Is there any more setup customization I need to do for this (such as CDC0_INTFNUM, which is passed to the hostfrombuffer function in the interrupt)?
  • Why isn't USB_UBM_VECTOR and CdcToHostFromBuffer being called? 
  • Do I also need to specify the bulk endpoint number or midi jack somewhere?
  • I also noticed CdcToHostFromBuffer has code to send a cdc Zero Packet - does this apply to MIDI and will it screw the MIDI data transfer up if it's there?

 

p.s. Clavier - I cleaned up the code comments

Link to post
Share on other sites

After a timeout error, the data is still in the buffer. As long as there still is data in the buffer, USBCDC_sendDataXxx will fail. You have to ensure that the host reads the data; and as long as that doesn't happen, there will be no interrupt.

 

The USB Audio specification requires that the device has two interfaces, an Audio Control interface (which doesn't do much for a MIDI device; it just lists the other interface), and a MIDI streaming interface. If you never see actual data being transferred to the host, this might be the problem (but then it shouldn't have enumerated any MIDI port).

 

The interface number you give to the CDC functions is used as an index into several arrays. So when you have a single "CDC" interfeace, you must use zero for these parameters; it is apparently not necessary for it to have the same value as the USB interface number.

 

Zero packets are not used with MIDI. However, they would be generated only when a USBCDC_sendXxx call happens to completely fill its last USB packet (64 bytes), which is not possible for shorter messages. In any case, the host should just ignore them.

Link to post
Share on other sites

You have to ensure that the host reads the data; and as long as that doesn't happen, there will be no interrupt.

 

The USB Audio specification requires that the device has two interfaces, an Audio Control interface (which doesn't do much for a MIDI device; it just lists the other interface), and a MIDI streaming interface. If you never see actual data being transferred to the host, this might be the problem (but then it shouldn't have enumerated any MIDI port).

 

Do you think I have an issue with my descriptors? I've copied directly from the example in appendix B of the MIDI spec (linked), and below are some screenshots of what I see using various apps on the host. I did look over each bit and the unidentified parts in the USBlyzer HTML report are just because they don't have a parser for it I am thinking - but any thoughts after looking at them?

post-47060-0-44385100-1448735846_thumb.jpg

post-47060-0-38583000-1448735847_thumb.jpg

post-47060-0-06000700-1448735848_thumb.jpg

USB Composite Device.html

Link to post
Share on other sites

You're right - MIDI is not supported, trying to alter the files to make it work. What's Reach Logging? (sorry, i'm a noob!)

 

Resolving enumeration problems with stopping execution with breakpoints will not help much. It is easier to debug enumeration using high speed UART or Mailbox for logs. But you already resolved enumeration.

 

Find some working example (open source, similar to yours) on any other platform, to clarify how data flow is going and how it is related to endpoints. Then will be much easier to update TI USB Stack with MIDI support.

Link to post
Share on other sites

Your descriptors look OK (except for the product ID; you should use one from the user experimentation area (see section 2.6 of the MSP430 USB API Examples Guide)).

 

It is important that the endpoint information in the stUsbHandle array is correct.

 

I've been looking into this and trying to compare to examples for a bit now. Here's where I'm at:

 

Some comments on stUsbHandle:

struct tUsbHandle
{
    uint8_t ep_In_Addr;               // Input EP Addr - only referenced in phdc functions
    uint8_t ep_Out_Addr;              // Output EP Addr - only referenced in phdc functions
    uint8_t edb_Index;                // The EDB index - 1 - used lots - just an index, dont think its an issue
    uint8_t dev_Class;                // Device Class- 2 for CDC, 3 for HID 
    uint16_t intepEP_X_Buffer;         // Interupt X Buffer Addr - initialized as additional interrupt end point for CDC - doesnt seem to be used after that
    uint16_t intepEP_Y_Buffer;         // Interupt Y Buffer Addr ""
    uint16_t oep_X_Buffer;             // Output X buffer Addr - used in recieve operation - not testing at the moment
    uint16_t oep_Y_Buffer;             // Output Y buffer Addr ""
    uint16_t iep_X_Buffer;             // Input X Buffer Addr - used in send operation
    uint16_t iep_Y_Buffer;             // Input  Y Buffer Addr ""
};

And it's initialization:

const struct tUsbHandle stUsbHandle[]=
{
    {
        CDC0_INEP_ADDR,
        CDC0_OUTEP_ADDR,
        1,
        CDC_CLASS,
        IEP2_X_BUFFER_ADDRESS,
        IEP2_Y_BUFFER_ADDRESS,
        OEP1_X_BUFFER_ADDRESS,
        OEP1_Y_BUFFER_ADDRESS,
        IEP1_X_BUFFER_ADDRESS,
        IEP1_Y_BUFFER_ADDRESS
    }
};

Here, I've swapped IEP1 and IEP2 because we've declared in our descriptor that IEP1 will be used for bulk MIDI in transfers, which I believe should connected to the input endpoint 1 buffer, not to the interrupt endpoint buffer (intepEP_X_Buffer) which doesn't seem to be used. "IEP1_x_BUFFER_ADDRESS" is defined in defMSP430USB.h so I imagine it's constant and correct.

 

Keeping with this switch, I copied the interrupt handler code to EP1:

...
switch (__even_in_range(USBVECINT & 0x3f, USBVECINT_OUTPUT_ENDPOINT7))
{
...
case USBVECINT_INPUT_ENDPOINT1:
//send saved bytes from buffer...
bWakeUp = CdcToHostFromBuffer(CDC0_INTFNUM);
break;
...
}

I thought that "CDC0_INEP_ADDR" and "CDC0_OUTEP_ADDR" might be important as they are set to 0x82 and 0x02, and that they would need to be 0x81 and 0x01 to match the descriptor for out and in endpoints #1. However, these defines are used in the first 2 spots of stUsbHandle which is only referenced by a PHDC function that's never called.

 

Still doesn't work - same issue =(. Any thoughts?

 

One other thing I noticed in Message Analyzer was that the MIDI endpoints have logs for being opened and closed - why closed? Could this be part of the issue? See attached image.

post-47060-0-95297800-1449010378_thumb.jpg

Link to post
Share on other sites

The endpoint index must be one less than the number.

 

Thank you - this worked! Amazing how it was such a small thing... 

Also, looked again and I don't see any documentation explaining what you just did. I know it's sort-of internal to the API/Descriptor Tool, but off the top of your head did I just miss looking at something?

 

Anyways, I'm going to clean this up and write some MIDI functions. Will post an update later.

post-47060-0-40712900-1449086806_thumb.jpg

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...