Midignusbuino: Difference between revisions

From SGMK-SSAM-WIKI
Jump to navigation Jump to search
No edit summary
 
(24 intermediate revisions by 2 users not shown)
Line 4: Line 4:


==Building / Installing==
==Building / Installing==
Hardware schematics and source code are in the SV repository at http://gnusb.svn.sourceforge.net/viewvc/gnusb/branches/gnusbuino/
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 svn.
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)
  cd "the/path/to/Arduino/"    (for me on a Mac it is cd ~/Users/me/Documents/Arduino)
  - svn co https://gnusb.svn.sourceforge.net/svnroot/gnusb/branches/gnusbuino/hardware
  git clone <nowiki>https://github.com/mirdej/gnusbuino.git </nowiki> hardware/anyma/gnusbuino
  - svn co https://gnusb.svn.sourceforge.net/svnroot/gnusb/branches/gnusbuino/libraries
  git clone <nowiki>https://github.com/mirdej/GnusbuinoMIDI.git </nowiki> libraries/GnusbuinoMIDI
 
for pre-1.5 versions you'll have to use this
git clone <nowiki>https://github.com/mirdej/gnusbuino.git</nowiki>  hardware/gnusbuino


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


  - svn update
  git pull




===Some notes on etching and building the circuit===
===Some notes on etching and building the circuit===


PCB layout and Eagle files are in harware/gnusbuino/pcb/
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
*USB resistors should be 68 Ohms and 1k6 Ohms, the other resistor values are not critical
Line 32: Line 36:


Use your favorite AVR programmer and chose "Burn Bootloader" from the Tools menu.
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/
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===
===Programming the Midignusbuino===
Line 44: Line 47:
[[File:gnusb_system_profiler.png|400px]]
[[File:gnusb_system_profiler.png|400px]]


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":
[[File:gnusb_lsusb.png|400px]]
===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:
<code>avrdude: Warning: cannot query manufacturer for device</code>
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 <code>/etc/udev/rules.d/</code> directory.
Create a file as root or with sudo called <code>10-gnusbuino.rules</code>, then enter this text and save the file:
<pre>
# USBasp gnusbuino Programmer rules
SUBSYSTEMS=="usb", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="05dc", GROUP="plugdev", MODE="0660"
</pre>
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:


<pre>
sudo restart udev
</pre>


* 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 MIDI library==
The Midignusbuino comes with a small library (aptly called MIDI)  to help building MIDI controllers and interfaces.
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…)
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…)
Line 55: Line 83:
You have to include this library at the top of your sketch to be able to use it
You have to include this library at the top of your sketch to be able to use it


- #include "MIDI.h"
#include "GnusbuinoMIDI.h"




Line 64: Line 92:
==== MIDI.write====
==== MIDI.write====


  MIDI.write(command,key,value);
  MIDI.write(command, key, value);
MIDI.write(command, key, value, channel);


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


====MIDI.read====
====MIDI.read====
Line 85: Line 113:
  }
  }


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


  message.command          -> the MIDI message type, e.g. NOTE ON or CONTROL CHANGE
  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.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
  message.value            -> the velocity of NOTE ON messages, or the controller value
 
message.channel            -> the MIDI channel the command was sent on
 


===Constants===
===Constants===
Line 186: Line 213:
===Examples===
===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:
Example code for sending MIDI controller values from a potentiometer connected to the Midignusbuino:


Line 232: Line 260:
</source>
</source>


====Reading Digital pins (e.g. from buttons)====
<source lang="c">
/********************************************
*
*    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];
    }
  }
}




</source>




Line 239: Line 322:


[[File:MIDI-library-examples.png|400px]]
[[File:MIDI-library-examples.png|400px]]
==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 [http://reprap.org/wiki/Sanguinololu] 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 [http://www.anyma.ch/blogs/research]).
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

Latest revision as of 20:29, 22 November 2016

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/anyma/gnusbuino
git clone https://github.com/mirdej/GnusbuinoMIDI.git  libraries/GnusbuinoMIDI

for pre-1.5 versions you'll have to use this

git clone https://github.com/mirdej/gnusbuino.git  hardware/gnusbuino

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:

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:

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":

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);
MIDI.write(command, key, value, channel);

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 four 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
message.channel             -> the MIDI channel the command was sent on

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:

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