Page 1 of 7

USB MIDI Interface

Posted: Mon Apr 07, 2008 11:30 am
by horo

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

Posted: Mon Apr 07, 2008 4:10 pm
by christian
Please get in contact with me at the support address. We can sponsor an ID for the MIDI device class.

Re: USB MIDI Interface

Posted: Sun May 18, 2008 5:00 pm
by butrus.butrus
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?

Re: USB MIDI Interface

Posted: Tue May 20, 2008 3:03 pm
by horo
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

V-USB-MIDI is online

Posted: Tue May 20, 2008 10:38 pm
by horo
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 ;)

great! and bandwidth?

Posted: Wed Sep 24, 2008 5:06 am
by meh
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!

Posted: Thu Oct 09, 2008 6:32 pm
by horo
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

Posted: Fri Oct 10, 2008 4:38 pm
by horo
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

Posted: Fri Oct 10, 2008 5:24 pm
by christian
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).

Posted: Wed Oct 29, 2008 8:08 pm
by lpx
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

Posted: Fri Oct 31, 2008 4:55 pm
by horo
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

Posted: Sun Jan 18, 2009 9:28 pm
by psc
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

Posted: Wed Jan 21, 2009 9:10 pm
by horo
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

Posted: Sun Feb 01, 2009 11:00 pm
by psc
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

Posted: Mon Feb 09, 2009 11:44 am
by horo
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