USB MIDI Interface

General discussions about V-USB, our firmware-only implementation of a low speed USB device on Atmel's AVR microcontrollers
Post Reply
horo
Rank 2
Rank 2
Posts: 63
Joined: Tue Mar 04, 2008 2:26 pm
Location: Berlin & Lindau, Germany

USB MIDI Interface

Post by horo » Mon Apr 07, 2008 11:30 am


UPDATE 2017: Go directly to the latest project status -> V-USB-MIDI-ATtiny85.

Hi,

I got my USB MIDI interface running. It's build upon an ATMega16, powered from USB through 2 diodes (3.5 volts). I have potentiometers (AVCC/AGND) connected to the analog inputs (PORTA) which are converted to a range from 0x00..0x7F to create up to eight midi controls (CC). On my STK500 I've also tested a simple key routine (PORTB), which sends eight "white" keys (60, 62, 64, 65, 67, 69, 71, 72). Serial debugging is enabled.
This interface shows up on my linux PC and works fine with vanilla kernel >= 2.6.23.x or with a little patch for my 2.6.22.1-rt9. To get the first key press sent after power up I defined USB_INITIAL_DATATOKEN to USBPID_DATA1.
MIDI messages are also received from PC but for now only a LED is toggled (and a debug message sent to UART).
This device is class compliant, therefore I use device type audio/midi stream. This doesn't go with an obdev USBID - so I used a LabOnly VOTI ID (16C0/03E8). I would like to public my source code (with USBIDs commented out) but doesn't want to have a clash with the obdev license. It would be nice if christian or someone else could give some directions how to do.

Ciao Martin

Links:
Some of my older projects including V-USB-MIDI
My bitbucket repo
Last edited by horo on Sat Apr 15, 2017 10:30 am, edited 3 times in total.

christian
Objective Development
Objective Development
Posts: 1443
Joined: Thu Nov 09, 2006 11:46 am

Post by christian » Mon Apr 07, 2008 4:10 pm

Please get in contact with me at the support address. We can sponsor an ID for the MIDI device class.

butrus.butrus
Posts: 10
Joined: Wed Sep 12, 2007 7:57 pm
Contact:

Re: USB MIDI Interface

Post by butrus.butrus » Sun May 18, 2008 5:00 pm

horo wrote:Hi,

I got my USB MIDI interface running. It's build upon an ATMega16, powered from USB through 2 diodes (3.5 volts). I have potentiometers (AVCC/AGND) connected to the analog inputs (PORTA) which are converted to a range from 0x00..0x7F to create up to eight midi controls (CC). On my STK500 I've also tested a simple key routine (PORTB), which sends eight "white" keys (60, 62, 64, 65, 67, 69, 71, 72). Serial debugging is enabled.
This interface shows up on my linux PC and works fine with vanilla kernel >= 2.6.23.x or with a little patch for my 2.6.22.1-rt9. To get the first key press sent after power up I defined USB_INITIAL_DATATOKEN to USBPID_DATA1.

...

Ciao Martin


Could You tell us, what is the kernel patch used for?

Also, isn't the usb-midi interface using BULK transfers?

horo
Rank 2
Rank 2
Posts: 63
Joined: Tue Mar 04, 2008 2:26 pm
Location: Berlin & Lindau, Germany

Re: USB MIDI Interface

Post by horo » Tue May 20, 2008 3:03 pm

butrus.butrus wrote:Also, isn't the usb-midi interface using BULK transfers?

Yes, it's true, but newer kernels change low-speed bulk transfer automagically to int transfers to save bandwidth (and AVR cycles).
butrus.butrus wrote:Could You tell us, what is the kernel patch used for?

The patches below (extracted from kernel version 2.6.24.3) do this bulk->int change and let the kernel recognize low-speed midi devices, the old 2.6.22.1-rt9 kernel said: "unknown device speed".
If you're using a new kernel >=2.6.23 there is no need for this patch. But I use the older 2.6.22.1-rt9 kernel because it works much better with my old laptop and jack, with newer ones I get xruns.
The patch consists of two parts: one for /usr/src/linux/drivers/usb/core/config.c:

Code: Select all

--- config.c   2007-07-10 20:56:30.000000000 +0200
+++ config.c.2.6.24.3   2008-01-24 23:58:37.000000000 +0100
@@ -124,6 +130,21 @@
       endpoint->desc.bInterval = n;
    }
 
+   /* Some buggy low-speed devices have Bulk endpoints, which is
+    * explicitly forbidden by the USB spec.  In an attempt to make
+    * them usable, we will try treating them as Interrupt endpoints.
+    */
+   if (to_usb_device(ddev)->speed == USB_SPEED_LOW &&
+         usb_endpoint_xfer_bulk(d)) {
+      dev_warn(ddev, "config %d interface %d altsetting %d "
+          "endpoint 0x%X is Bulk; changing to Interrupt\n",
+          cfgno, inum, asnum, d->bEndpointAddress);
+      endpoint->desc.bmAttributes = USB_ENDPOINT_XFER_INT;
+      endpoint->desc.bInterval = 1;
+      if (le16_to_cpu(endpoint->desc.wMaxPacketSize) > 8)
+         endpoint->desc.wMaxPacketSize = cpu_to_le16(8);
+   }
+
    /* Skip over any Class Specific or Vendor Specific descriptors;
     * find the next endpoint or interface descriptor */
    endpoint->extra = buffer;

the second one for /usr/src/linux/sound/usb/usbaudio.c and .../usbmidi.c:

Code: Select all

--- usbaudio.c   2007-07-10 20:56:30.000000000 +0200
+++ usbaudio.c.2.6.24.3   2008-01-24 23:58:37.000000000 +0100
@@ -3381,7 +3390,8 @@
 
    *rchip = NULL;
 
-   if (snd_usb_get_speed(dev) != USB_SPEED_FULL &&
+   if (snd_usb_get_speed(dev) != USB_SPEED_LOW &&
+       snd_usb_get_speed(dev) != USB_SPEED_FULL &&
        snd_usb_get_speed(dev) != USB_SPEED_HIGH) {
       snd_printk(KERN_ERR "unknown device speed %d\n", snd_usb_get_speed(dev));
       return -ENXIO;
@@ -3455,7 +3465,9 @@
       usb_make_path(dev, card->longname + len, sizeof(card->longname) - len);
 
    strlcat(card->longname,
-      snd_usb_get_speed(dev) == USB_SPEED_FULL ? ", full speed" : ", high speed",
+      snd_usb_get_speed(dev) == USB_SPEED_LOW ? ", low speed" :
+      snd_usb_get_speed(dev) == USB_SPEED_FULL ? ", full speed" :
+      ", high speed",
       sizeof(card->longname));
 
    snd_usb_audio_create_proc(chip);


--- linux-2.6.22.1-rt9/sound/usb/usbmidi.c   2007-07-10 20:56:30.000000000 +0200
+++ linux-2.6.24.3-rt3/sound/usb/usbmidi.c   2008-01-24 23:58:37.000000000 +0100
@@ -963,8 +983,10 @@
       snd_usbmidi_out_endpoint_delete(ep);
       return -ENOMEM;
    }
-   /* we never use interrupt output pipes */
-   pipe = usb_sndbulkpipe(umidi->chip->dev, ep_info->out_ep);
+   if (ep_info->out_interval)
+      pipe = usb_sndintpipe(umidi->chip->dev, ep_info->out_ep);
+   else
+      pipe = usb_sndbulkpipe(umidi->chip->dev, ep_info->out_ep);
    if (umidi->chip->usb_id == USB_ID(0x0a92, 0x1020)) /* ESI M4U */
       /* FIXME: we need more URBs to get reasonable bandwidth here: */
       ep->max_transfer = 4;
@@ -976,8 +998,14 @@
       snd_usbmidi_out_endpoint_delete(ep);
       return -ENOMEM;
    }
-   usb_fill_bulk_urb(ep->urb, umidi->chip->dev, pipe, buffer,
-           ep->max_transfer, snd_usbmidi_out_urb_complete, ep);
+   if (ep_info->out_interval)
+      usb_fill_int_urb(ep->urb, umidi->chip->dev, pipe, buffer,
+             ep->max_transfer, snd_usbmidi_out_urb_complete,
+             ep, ep_info->out_interval);
+   else
+      usb_fill_bulk_urb(ep->urb, umidi->chip->dev,
+              pipe, buffer, ep->max_transfer,
+              snd_usbmidi_out_urb_complete, ep);
    ep->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
 
    spin_lock_init(&ep->buffer_lock);
@@ -1323,6 +1351,13 @@
          endpoints[epidx].out_ep = ep->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
          if ((ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT)
             endpoints[epidx].out_interval = ep->bInterval;
+         else if (snd_usb_get_speed(umidi->chip->dev) == USB_SPEED_LOW)
+            /*
+             * Low speed bulk transfers don't exist, so
+             * force interrupt transfers for devices like
+             * ESI MIDI Mate that try to use them anyway.
+             */
+            endpoints[epidx].out_interval = 1;
          endpoints[epidx].out_cables = (1 << ms_ep->bNumEmbMIDIJack) - 1;
          snd_printdd(KERN_INFO "EP %02X: %d jack(s)\n",
                 ep->bEndpointAddress, ms_ep->bNumEmbMIDIJack);
@@ -1336,6 +1371,8 @@
          endpoints[epidx].in_ep = ep->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
          if ((ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT)
             endpoints[epidx].in_interval = ep->bInterval;
+         else if (snd_usb_get_speed(umidi->chip->dev) == USB_SPEED_LOW)
+            endpoints[epidx].in_interval = 1;
          endpoints[epidx].in_cables = (1 << ms_ep->bNumEmbMIDIJack) - 1;
          snd_printdd(KERN_INFO "EP %02X: %d jack(s)\n",
                 ep->bEndpointAddress, ms_ep->bNumEmbMIDIJack);



I hadn't much time for this project the last weeks - so there isn't much to see until now. I'll try to set up a webpage with schematics and a (sample) source code this weekend.

Ciao Martin

horo
Rank 2
Rank 2
Posts: 63
Joined: Tue Mar 04, 2008 2:26 pm
Location: Berlin & Lindau, Germany

V-USB-MIDI is online

Post by horo » Tue May 20, 2008 10:38 pm

Hi,

the first documentation and software is available at http://cryptomys.de/horo/V-USB-MIDI

Ciao Martin

EDIT 2009-04-23: new name and new URL
EDIT 2016-02-15: new name also in header ;)
Last edited by horo on Mon Feb 15, 2016 12:59 pm, edited 2 times in total.

meh

great! and bandwidth?

Post by meh » Wed Sep 24, 2008 5:06 am

This is awesome, it only took me a few hours to get it working on an Atmega8515 running at 16MHz. Thank you!

I was hoping to find that I could use this standard interface for output to my device from MAX/MSP at a rate higher than regular MIDI, but it seems that the maximum transfer rate is on the order of a regular MIDI interface ~1ms/command. Do you know what might be responsible for this limitation? I have a few ideas but haven't explored them all...
*timing from the OS due to the USB-MIDI specification
*timing from MAX due to the midiin/midiout objects
*AVR CPU usage limitations
*something to do with the poll rate
*low-speed USB bandwidth/packet-header limitations
*USB packet-size limitations due to the AVR's buffer size...?

Do you know what's responsible for bandwidth-throttling on USB (i.e. if this were a USB-UART adapter, somehow the adapter would have to tell the PC when its buffer was full... I assume similar would be true for a MIDI interface, unless all buffering/timing is handled on the PC...)

Thanks again for puting this together!

horo
Rank 2
Rank 2
Posts: 63
Joined: Tue Mar 04, 2008 2:26 pm
Location: Berlin & Lindau, Germany

Post by horo » Thu Oct 09, 2008 6:32 pm

Hi meh,

the slow transfer rate comes from the usb standard. A low-speed device has no bulk transfer - it uses interrupt transfer at a rate of maximal 1000 messages/s. The midi protocol puts 1 or 2 midi commands (4 byte) into one message (8 byte) -> voilà! :idea:
I hacked this project and it worked for my needs. It's suited for standard applications like cc or some notes pressed by a human.
One of the usb gurus here will probably explain the timing much more detailed, maybe we get a nice solution. :?:
I think that dedicated usb chips will bring a speedup for you - or you switch to something completely different like OSC.

Ciao Martin

horo
Rank 2
Rank 2
Posts: 63
Joined: Tue Mar 04, 2008 2:26 pm
Location: Berlin & Lindau, Germany

Post by horo » Fri Oct 10, 2008 4:38 pm

Hi,

me again ;)
I've digged in the foum and found this thread:
http://forums.obdev.at/viewtopic.php?t=1030
about bulk/interrupt and timing.
Funny, usb interrupt is not really interrupt. For my understanding interrupt has something to do with a door bell rung by a visitor and the usb spec behaves like someone who opens the door every minute to see if there's someone standing outside.

Ciao Martin

christian
Objective Development
Objective Development
Posts: 1443
Joined: Thu Nov 09, 2006 11:46 am

Post by christian » Fri Oct 10, 2008 5:24 pm

USB is a host driven bus. Every activity must be initiated by the host. This gives ugly semantics for things like interrupts but makes the bus logic (and thus the controller chips) much simpler (= cheaper).

lpx
Posts: 7
Joined: Fri Apr 20, 2007 8:58 am

Post by lpx » Wed Oct 29, 2008 8:08 pm

Hi Horo,

Its with great pleasure that I found your project.

In the last year I was struggling to make a midi controller with 28 pots and 12 buttons and a LCD display, using MIDI communication via HID to then make the conversion to MIDI somehow.

With lack of time i couldnt do the driver.

However, with your project, it seems to be possible to do that without so many work.

Can you also run it on windows? Does it get recognized as a MIDI device?

What do you think in merging the two projects?

Check some photos of my controller in

http://www.myspace.com/sinosoidal

Check for photos.

Congratulations!

With my best regards,

Nuno

horo
Rank 2
Rank 2
Posts: 63
Joined: Tue Mar 04, 2008 2:26 pm
Location: Berlin & Lindau, Germany

Post by horo » Fri Oct 31, 2008 4:55 pm

Hi Nuno,

after some problems viewing your pics I found them - nice pice of hardware. How do you connect the 28 pots to the AVR - do you use a mux?

That's what my project is intended for - letting musicians create their own midi gadgets.
Do you know the pepper project?
http://web.me.com/kuwatay/morecat_lab./ ... idi-e.html
This guy uses AVR-MIDI for driving his Gakken SX-150 synth - a fascinating little toy. He states that it works flawlessly with OSX and Win XP.

Another similiar gadget is the Monome http://monome.org/ - Franq from Barcelona gave me this hint - thank you.

Currently I'm very busy so AVR-MIDI is on "hold" for some weeks but I'm very interested in your success - good luck!

Ciao Martin

psc
Rank 1
Rank 1
Posts: 32
Joined: Sat Nov 15, 2008 9:51 pm

Post by psc » Sun Jan 18, 2009 9:28 pm

hi horo,

a really simple question:
is it possible to add a MIDI OUT port (physical connection with an opto-isolater). i just need the MIDI OUT, but would it be possible to have MIDI IN too?

pat

horo
Rank 2
Rank 2
Posts: 63
Joined: Tue Mar 04, 2008 2:26 pm
Location: Berlin & Lindau, Germany

Post by horo » Wed Jan 21, 2009 9:10 pm

Hi pat,

yes, it's possible to add an midi out, it's handled in this function. You have to add your UART_out routine and feed it with the data:

Code: Select all

/*-----------------------------------------------------------------------*/
/* usbFunctionWriteOut                                                      */
/* this Function is called if a MIDI Out message (from PC) arrives. */
/*-----------------------------------------------------------------------*/
void usbFunctionWriteOut(uchar * data, uchar len)
{
   // DEBUG LED
   PORTC ^= 0x20;
+        while (len--)
+                UART_out( *data++ );
}


It maybe necessary to do some buffering?

For MIDI in just parse the UART and send the data like shown in main():
Didn't try but I see no problem.

Ciao Martin

psc
Rank 1
Rank 1
Posts: 32
Joined: Sat Nov 15, 2008 9:51 pm

Post by psc » Sun Feb 01, 2009 11:00 pm

hi horo,

thank you very much, i am working hard to learn avr, obdev usb firmware and the midi protocol. i have successfully added a midi out to your project using the code from this project: http://x37v.info/projects/microcontroller/avr-midi/

how to specify the midi channel in your code:

Code: Select all

midiMsg[0] = 0x09;
midiMsg[1] = 0x90;
midiMsg[2] = 36;
midiMsg[3] = 127;
sendEmptyFrame = 0;
usbSetInterrupt(midiMsg, 4);


what is the difference between:
usbFunctionWrite and usbFunctionWriteOut

from what i know, usbFunctionWriteOut is the one being called when sending midi message from my application to AVR-MIDI. what is the use of usbFunctionWrite?

what about usbFunctionRead? what is the use of it?

thank you for your time!
patrick

horo
Rank 2
Rank 2
Posts: 63
Joined: Tue Mar 04, 2008 2:26 pm
Location: Berlin & Lindau, Germany

Post by horo » Mon Feb 09, 2009 11:44 am

Hi Patrick,

just returned from holidays - now I'm trying to dig into my code. I didn't use usbFunctionRead and usbFunctionWrite - if I understand correctly you can transfer some kind of config data in bigger chunks up to 254 bytes. These threads describe the programming:
http://forums.obdev.at/viewtopic.php?t=64
http://forums.obdev.at/viewtopic.php?t=1072
or look at the description of usbFunctionSetup, usbFunctionRead and usbFunctionWrite in usbdrv.h
According your question about the channel - this document describes the data format on page 16 and 17:
http://www.usb.org/developers/devclass_docs/midi10.pdf
so you send a note-on-msg as: ... midiMsg[1] = 0x90|channel ... with channel = 0..15.

HTH

Ciao Martin

Post Reply