Midignusbuino

From SGMK-SSAM-WIKI
Jump to: navigation, search

The Midignusbuino is a plug-n-play USB-MIDI device that can be used to build MIDI controllers. Most operating systems have built in MIDI support, so no drivers are needed, and almost any music software understands MIDI… It is built on the Gnusbuino platform, is more or less compatible with the Arduino environment and can be programmed through the Arduino IDE


Building / Installing

Hardware schematics and source code on Github at https://github.com/mirdej/gnusbuino The Gnusbuino MIDI library is at https://github.com/mirdej/GnusbuinoMIDI

You can either download the two folders and add their contents to the corresponding folders in your Arduino working directory, or you can pull the latest version directly through git.

cd "the/path/to/Arduino/"    (for me on a Mac it is cd ~/Users/me/Documents/Arduino)
git clone https://github.com/mirdej/gnusbuino.git  hardware/gnusbuino
git clone https://github.com/mirdej/GnusbuinoMIDI.git  libraries/GnusbuinoMIDI

later on, you can always update to the latest version by typing

git pull


Some notes on etching and building the circuit

PCB layout and Eagle files are in harware/gnusbuino/electronics/

  • USB resistors should be 68 Ohms and 1k6 Ohms, the other resistor values are not critical
  • Zeners should be 3.3-3.6V
  • Electrolytic capacitor max 10uF (as per USB spec)

Burning the bootloader

The Gnusbuino board does not have a SPI interface connector (as you'd only need it once for burning the bootloader - afterwards you can program it directly over USB). So it is a good idea to burn the bootloader BEFORE you solder the chip on. Reopen the Arduino Application and you should see the Midignusbuino in the board menu:

Chose gnusbuino board.png

Use your favorite AVR programmer and chose "Burn Bootloader" from the Tools menu. I normally use a Gnusb-Prog (a modified gnusb) to flash AVR chips, see here: http://www.anyma.ch/blogs/research/2011/08/19/gnusb-procreation/

Programming the Midignusbuino

Plug in the Midignusbuino and press the reset button. The yellow LED lights up, indicating that we are in bootloader mode. When the Gnusbuino is in bootloader mode, it will call itself "USBasp" in the USB devices list. In fact, the bootloader mimics the popular USBasp AVR programmer, but it does not program another chip but itself… Because of this, you don't have to chose a serial interface in the Tools menu

Write your Arduino sketch and hit upload as with any normal Arduino. When the upload is finished, both LEDs light up for a short time, then the yellow led goes out and the green one stays on. This means the Gnusbuino has successfully enumerated as a standard MIDI-USB device and is ready to use. On the Mac, you can use the "System Profiler" application (in /Application/Utilities/ ) to check if the gnusb is really there:

Gnusb system profiler.png

On linux you can use the "lsusb" command on command line to check if the device is there. It will show up as "VOTI shared ID for use with libusb":

Gnusb lsusb.png

Setting USB rights on linux

When trying to upload your sketch, you may run into an error message when the Arduino IDE tries ti upload the sketch: avrdude: Warning: cannot query manufacturer for device

The problem is that your user has not the rights to write to the usb device. The solution is to write an udev rule in /etc/udev/rules.d/ directory.

Create a file as root or with sudo called 10-gnusbuino.rules, then enter this text and save the file:

# USBasp gnusbuino Programmer rules
SUBSYSTEMS=="usb", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="05dc", GROUP="plugdev", MODE="0660"

The user running the Arduino IDE has to be memner of the GROUP you name in the file (in the case above, it has to be member of "plugdev"). The file name has to start with a number followed by a minus and needs an extension of ".rules". The name you give in between can be anything.

restart udev:

sudo restart udev
  • You will have to plug out your gnusbuino board and plug it in again after restarting udev. And don't forget to hit the RESET button again.

The MIDI library

The Midignusbuino comes with a small library (aptly called GnusbuinoMIDI) to help building MIDI controllers and interfaces.

Please note that this library will not work on other Arduinos - it s tightly integrated with the Gnusbuino core to communicate through the V-USB virtual USB driver (though with some tweaks it could maybe be made to allow for standard old-school MIDI on DIN jacks through the serial interface on Arduinos…)

You have to include this library at the top of your sketch to be able to use it

#include "GnusbuinoMIDI.h"


Main functions of the MIDI class (library)

MIDI.write

MIDI.write(command,key,value);

Sends a MIDI Message back to the host computer. Does not return anything.


MIDI.read

result = MIDI.read(&message)

Receives MIDI data from host computer and puts it into the variable MidiMessage.

Returns 1 if there is new data or 0 if no MIDI data is present.

Note that you need to declare a global variable (called message in this example) of type MIDIMessage and pass a pointer to this variable to the MIDI.read() function. That's why there is an ampersand character (&) in front of "message" in the function call.

MIDIMessage message
…
if (MIDI.read(&message)) {
     do something with message
}

The MIDIMessage type has three fields that can accessed this way:

message.command           -> the MIDI message type, e.g. NOTE ON or CONTROL CHANGE
message.key               -> first byte of data, usually the key number pressed (pitch) or the controller number
message.value             -> the velocity of NOTE ON messages, or the controller value


Constants

The MIDI library defines the following constants to help in creating / sorting out MIDI messages:


MIDI messages

Constant Data Description
MIDI_NOTEOFF pitch, velocity Note Off - a key has been released. Please note that many MIDI implementations actually send a Note On message with a velocity of 0 instead of Note off
MIDI_NOTEON pitch, velocity A key has been pressed.
MIDI_POLYAFTERTOUCH pitch, pressure This message is most often sent by pressing down on the key after it "bottoms out"
MIDI_CONTROLCHANGE controller number, value This message is sent when a controller value changes. Controllers include devices such as pedals and levers.
MIDI_PROGRAMCHANGE program number Program Change. This message sent when the patch number changes
MIDI_CHANNELAFTERTOUCH pressure Channel Pressure (After-touch). This message is most often sent by pressing down on the key after it "bottoms out". This message is different from polyphonic after-touch. Use this message to send the single greatest pressure value (of all the current depressed keys)
MIDI_PITCHBEND LSB, MSB Pitch Wheel Change. This message is sent to indicate a change in the pitch wheel. The pitch wheel is measured by a fourteen bit value. Center (no pitch change) is 2000H. Sensitivity is a function of the transmitter. (LSB) are the least significant 7 bits. (MSB) are the most significant 7 bits.

additionally, the following MIDI Real Time messages are defined, but not thoroughly tested, yet: MIDI_QUARTERFRAME MIDI_SONGPOS , MIDI_SONGSELECT ,MIDI_TIMINGCLOCK , MIDI_START ,MIDI_CONTINUE ,MIDI_STOP


Note Names

Notes can both be specified as pitch numbers (60 equals a C4) or by using the note name prepended by an underscore

Constant Pitch value
_C-1 0
_Cis-1 1
_D-1 2
etc..
_C0 12
_Cis0 13
_D0 14
_Dis0 15
etc..
_F9 125
_Fis9 126
_G9 127



Examples

Reading Analog Pin 0 (e.g. reading values from Potentiometer)

Example code for sending MIDI controller values from a potentiometer connected to the Midignusbuino:

/*---------------------------------------------------------------------------------------------
 
  Gnusbuino MIDI Library 2012 by Michael Egger
 
  SEND CONTROL CHANGE EXAMPLE
  Read a potentiometer and send its value as a continuous controller message  
 
  This example code is in the public domain.
 
--------------------------------------------------------------------------------------------- */
/* The circuit:
 * Potentiometer attached to analog input 0, center pin of the potentiometer to the analog pin
 * one side pin (either one) to ground,  the other side pin to +5V
 */
 
 
#include "MIDI.h"            // you have to include the Gnusbuino MIDI library
 
 
int sensorValue = 0;         // variable to store the value coming from the sensor
int sentValue = -1;          // we only want to send data when there's something changing
                             // so we have to keep track of the last value that was sent to the host
 
void setup() {               // nothing to do in setup, pins are inputs by default
}
 
 
void loop() {
 
  sensorValue = analogRead(A1) / 8;                       // analogRead returns 0-1023, we need 0-127
  if (sensorValue != sentValue) {                         // compare actual readout to last sent value    
 
      //MIDI.write(MIDI_CONTROLCHANGE, controller number , controller value )
 
        MIDI.write(MIDI_CONTROLCHANGE,1,sensorValue);     // put new control change message into MIDI sending queue
        sentValue = sensorValue;                          // store last sent value
  }
 
    delay(10);              // give some time for sending, otherwhise the MIDI queue could fill up
}

Reading Digital pins (e.g. from buttons)

/********************************************
*
*     This code will send button states as MIDI CONTROL CHANGE
*     on Channel 1, controller number 1
*     each button is using two values for on and off, so we can send 
*     up to 63 buttons on the same controler number
*
*     Example: button attached to pin 8 on: MIDI CC number 1, value 2
*              button attached to pin 8 off: MIDI CC number 1, value 1
*
********************************************/ 
 
/* The circuit:
 * Buttons (or switches) with one pin attached to digital pins 7, 6, 5, other pin to GND
 * no resistors needed, because we are using internal pull-ups from the ATMega
 */
 
 
 
#include "MIDI.h"            // you have to include the Gnusbuino MIDI library
 
#define numButtons 8
 
uint8_t digButtonValues[numButtons], sentDigButtonValues[numButtons];
unsigned long digButtonReleaseTime[numButtons]; // for debouncing the buttons
 
uint8_t digButtons[numButtons] = {8, 7, 6, 5, 4, 9, 2, 0};  // in the array are the input pin numbers
 
 
void setup() {               // nothing to do in setup, pins are inputs by default
  for (uint8_t i = 0; i < numButtons;i++) { 
    pinMode(digButtons[i], INPUT);
    digitalWrite(digButtons[i], HIGH); // enable internal pull-up resistors
    sentDigButtonValues[i] = 1; // initialize array
    digButtonReleaseTime[i] = 0; // initialize array
  }
}
 
 
void loop() {
  // read button values
  for (uint8_t i = 0; i < numButtons;i++) { 
    digButtonValues[i] = ( digitalRead(digButtons[i]) );  // button on drives pin to gnd
    if ( (digButtonValues[i] != sentDigButtonValues[i]) && (millis() > digButtonReleaseTime[i]) ) {
      digButtonReleaseTime[i] = millis() + 250;  // don't send another midi message dring this time in ms to debonce the button
      MIDI.write( MIDI_CONTROLCHANGE, 1, 2*(i+1)-digButtonValues[i] );  // all digital buttons are sending on CHANNEL 1
      delay(10);              // give some time for sending, otherwhise the MIDI queue could fill up
      sentDigButtonValues[i] = digButtonValues[i];
    }
  }
}


The library includes various more examples to help getting started with sending/receiving MIDI over USB. You can access these examples through the File->Examples menu:

MIDI-library-examples.png

Behind the scenes

The Gnusbuino runs a modified Arduino core.

I have adapted the pins_arduino.h file to reflect the ATMEGA644 processor (inspired by the Sanguinolulu [1] board, which also runs a 644, but is not (yet?) compatible with current versions of the Arduino IDE. I'm still quite new to the Arduino environment and I have certainly missed some important points - any expert advice is appreciated (contact me through my research blog [2]).

In order to get the whole thing to compile I had to convert quite some Arduino .c files to .cpp (because of linking issues with the assembler part of V-USB). Also, to keep the usb-driver happy, the function usbPoll() has to be called at least every 20ms - otherwise the connection breaks down. Arduino functions like delay() and analogRead() rely on while() loops that potentially time out long after the needed interval. So I did modifications all over the code. This has been done in a hurry and on a trial and error basis, so some solutions aren't that elegant and I certainly missed some stuff. Still, for the moment it seems to work.

There's a function doPeriodical() in main.cpp that does all time-sensitive stuff like usb polling, resetting the watchdog timer, working on the MIDI send queue, etc… that gets called (probably much too often now) from inside any nasty loop like delay() - so you can still use delay() without breaking the USB connection.

MIDI sending and receiving is done through FIFO queues. A call to MIDI.write() inserts a command into the queue, and it will be sent some time later, whenever a usbInterrupt is ready. Same for receiving MIDI: messages arrive trough the bulk in endpoint and are added to a queue. It is up to the user to make sure that Midi.read() gets called often enough to not stall this queue.

Compatibility

Because the Midignusbuino enumerates as a MIDI device and not as a serial device, all Arduino serial code will not work over USB.

Another issue could be that some pins on PORTD are used by the USB driver and should not be accessible from the arduino-side of things.

Digital Pin 13 actually has a LED connected, like many Arduinos, but both LEDs are controlled internally from main.cpp: The green led is meant to stay on to indicate a working USB connection and the yellow LED flashes when receiving and sending MIDI data. It is therefore not a very clever idea to use the built-in LEDs for debugging or other purposes.

I haven't tested a lot of features for compatibility. What's known to work:

  • analogRead() - at least on A0 and A1
  • analogWrite() on pin 3
  • digitalRead() and write on pins 0-7 (16-23 should work as well)

Todo's

  • Thoroughly test MIDI latency and optimize for speed of the MIDI subsystem
  • Clean up code
  • Try to reduce the memory footprint which I assume is quite high
  • Test more for core arduino compatbility
  • Clean up code
  • It would be nice to have a mechanism to automatically jim into bootloader whern trying to upload code
  • "gnubl" command seems to leave Gnusbuino in unstable state instead of jumping to bootloader