I use Ableton live a lot to jam and to DJ, and using the mouse to
control all the parameters got in the way quite often. You can’t
select a scene and turn a knob at the same time, switching between
views was tiresome. I first thought about building a midibox, but then decided to build my own midi system
using an AVR microcontroller, which I’m more used to. Blueloop helped me a lot
with the schematics, showing me a nice trick to control LEDs using the
same matrix as for the switches (this is not integrated yet,
though).
The midi controller is quite simple. It features 33 switches and 56
potentiometers. The switches and potentiometers are laid out in a
matrix. You can select a horizontal row by pulling down a port to 0 V,
and then reading the switch values on another port, or by going
through the ADCs in the case of the potentiometers. The actual circuit
for the microcontroller is really really simple. There is an
oscillator and a resistor for the MIDI output. The rest of the I/O
pins go to a pinhead which connects the front panel to the circuit
board.
There is a second circuit board in the midi controller that
converts the MIDI output of the first circuit to USB. It was an old
USB programmer board I built 2 years ago that was lying around. I
directly connected the power of the first circuit board to the USB
connector, and connected the MIDI output to the RX pin of the USB
programmer. I had no 12 Mhz oscillator (the one which I normally use),
so I used a 20 Mhz quartz out of an old network card. The board is
really simple too, and consists of a PDIUSBD11 chip by Phillips, which
gets commands over I2C and outputs USB, and an ATMEGA8
microcontroller. Sadly, the PDIUSBD11 is not manufactured any more,
which is kind of sad, as it is the only USB controller I am aware of
that uses I2C, which spared pins on the microcontroller side. I
implemented the USB MIDI
specification out of the specification, and the controller seems
to work under Linux and Windows (there is a problem with MacOSX, but I
last checked two years ago and haven’t tried/debugged it since
then). I can’t publish the whole code for the usb-midi adapter though,
as most of it (including the USB stack) was written by Till Harbaum.
The matrices are soldered on the front panel, which I made out of a
plexiglas sheet through which I drilled holes for the switches and
pots. I had no real problems with the plexiglas, except that you have
to drill quite slowly through it in order not to split it, and that I
had to remove the melt plastic around the holes using a rasp. Each
switch has a diode at one of its outputs, so that two knobs pressed at
the same time can be distinguished on the controller side. The
potentiometers are connected to VCC one one side, to the select port
on the other side, and the variable output is connected to the ADC
using a diode. When selecting a switch or a pot, the select pin is
set to GND, all the other pins are set to VCC. Because of the diodes,
the pressed switches or the pots which are not selected are always
high, and the diode won’t allow them to influence the signal.
The code on the microcontroller is almost as simple as the circuit
board. A simple ring-buffer interrupt-based UART handling is used to
handle outgoing MIDI data (the midi controller can’t receive MIDI
events).
#define RB_SIZE 16
#define MIDI_BAUD 31250UL /* 31.25 kbps */
volatile char tx_buf[RB_SIZE];
volatile char tx_wr = 0, tx_rd = 0;
#define TX_INCR(a, b) (((a) + (b)) & (RB_SIZE - 1))
#define UART_TX_OK (UCSRA & _BV(UDRE))
#define TX_FREE (!(TX_INCR(tx_wr, 1) == tx_rd))
SIGNAL(SIG_UART_TRANS) {
if (tx_rd != tx_wr) {
UDR = tx_buf[tx_rd];
tx_rd = TX_INCR(tx_rd, 1);
}
}
void uart_putc(char c) {
if (UART_TX_OK && (tx_wr == tx_rd)) {
UDR = c;
} else {
while (TX_INCR(tx_wr, 1) == tx_rd) ;
tx_buf[tx_wr] = c;
tx_wr = TX_INCR(tx_wr, 1);
}
}
void uart_init(void) {
tx_wr = tx_rd = 0;
/* set baudrate */
UBRRL = (((F_CPU) / (16 * MIDI_BAUD)) - 1);
/* enable TX, interrupts */
UCSRB = _BV(TXEN) | _BV(TXCIE);
sei();
}
The static buffer “tx_buf” is used to buffer outgoing serial
data. “tx_wr” and “tx_rd” are used as indices into the ring
buffer. “TX_INCR” handles the indices when a character is added to the
ring buffer. “TX_FREE” checks if there is free space in the ring
buffer. “UART_TX_OK” checks if the serial interface is ready to send
another character. “SIGNAL(SIG_UART_TRANS)” is the interrupt routine
that is called when a character has successfully been sent. It checks
if the ring buffer is empty, and sends the next character if it is
not. “uart_putc” is the API to the UART code, and directly sends the
character if the serial interface is ready and the ring buffer is
empty. If not, it waits until there is space in the ring buffer, and
adds the new character to the ring buffer. “uart_init” handles the
initialization of the serial interface, sets the correctly baudrate,
and enables TX and the TX_TRANS interrupt. Finally, interrupts on the
AVR are enabled.
void io_init(void) {
/* disable watchdog */
WDTCR = 0;
/* DD = 1, output */
/* PORT = 1 wenn DD = 0, dann pull-up */
/* portd = output */
DDRD = 0XFF;
/* enable ADC */
DDRA = 0;
PORTA = 0xFF;
ADMUX = _BV(ADLAR);
ADCSRA = _BV(ADEN)|_BV(ADPS2)|_BV(ADPS1)|_BV(ADPS0);
// ADCSRA = _BV(ADEN);
uart_init();
}
The routine “io_init” initializes the Atmel, first disabling the
watchdog, then setting PORTD to output (which is not really necessary,
as the status of the port is set by the switch polling routine). The
ADC is enabled by setting the PORTA to input, enabling pullup
resistors, and enabling the ADC in a 10 bit mode, and using AVCC as
reference voltage. Finally the UART is initialized.
/*** MIDI zeugs ***/#define MIDI_NOTE_ON 0x9
#define MIDI_NOTE_OFF 0x8
#define MIDI_AFTERTOUCH 0xA
#define MIDI_CC 0xB
#define MIDI_PROG_CHANGE 0xC
#define MIDI_CHAN_PRESS 0xD
#define MIDI_PITCH_WHEEL 0xE#define MIDI_STATUS_TYPE(c) (((c) & 0xF0) >> 4)
#define MIDI_STATUS_CHAN(c) ((c) & 0xF)static inline midi_is_status(char c) {
return (c & 0x80);
}void midi_send_note_on(unsigned char channel, unsigned char note, unsigned char velocity) {
uart_putc((MIDI_NOTE_ONThe midi controller sends three types of midi messages. When a switch
is pressed, it sends a note on message, when a switch is release, it
sends a note off message, and when a pot is modified it sends a
control channel message. All three messages are three bytes long and
sent directly to the uart using "uart_putc".#define LED_BUTTON_PORT D
#define PORTLED_BUTTON_PORT PORTD
#define DDRLED_BUTTON_PORT DDRD
#define PINLED_BUTTON_PORT PIND#define SET_PIN_OUTPUT(port, pin) { DDR ## port |= _BV(pin); }
#define SET_PIN_INPUT(port, pin) { DDR ## port &= ~(_BV(pin)); }
#define SET_PIN_INPUT_PULLUP(port, pin) { SET_PIN_INPUT(port, pin); PORT ## port |= _BV(pin); }
#define SET_PIN_LOW(port, pin) { PORT ## port &= ~(_BV(pin)); }
#define SET_PIN_HIGH(port, pin) { PORT ## port |= _BV(pin); }
#define PIN_VALUE(port, pin) ((PIN ## port & _BV(pin)) ? 1 : 0)#define NUM_BUTTONS 30
#define BUTTON_STATUS(i) (button_status[(i)] & 0xF)
#define SET_BUTTON_STATUS(i, val) { button_status[(i)] = button_status[(i)] & 0xF0 | (val); }
#define LED_STATUS(i) ((button_status[(i)] >> 4) & 0xF)
#define SET_LED_STATUS(i, val) { button_status[(i)] = button_status[(i)] & 0xF | ((val) 1 (bounce?) -> 2
wenn knopf auf 1 dann nachricht senden (note on), wieder auf 0 (note off)
*/#define BUTTON_ROWS 5
unsigned char button_status[BUTTON_ROWS * 8];void init_buttons(void) {
int i, j;
for (i = 0; iThe button polling routines are a bit longer. The routine
"ask_buttons" polls through all the buttons, and calls the routine
"ask_button" to get the status of a single switch. The result of the
polling is stored in the array button_status. A simple state machine
is used to handle press/release status. "ask_button" sets the
corresponding select pin low, enables the input pin and enables the
internal pullup resistor. The value is then read and handled. After
all buttons have been read, midi_buttons is called, which reads the
switch status in the "button_status" array, and sends the
corresponding midi message.#define POTI_SEL_PORT B
#define PORTPOTI_SEL_PORT PORTB
#define DDRPOTI_SEL_PORT DDRB#define NUM_POTIS 64
unsigned char poti_status[NUM_POTIS];void init_potis(void) {
int i, j;
for (i = 0; i MAX_THRESHOLD) return 0x7F;
uint32_t bla = (uint32_t)((uint32_t)adc - (uint32_t)MIN_THRESHOLD);
bla *= 0x7F;
bla /= 0xDFF0;
return bla;
}void ask_poti(unsigned char sel_pin, unsigned char adc) {
SET_PIN_LOW(POTI_SEL_PORT, sel_pin);
_delay_us(50);
ADMUX = _BV(ADLAR) | adc;
ADCSRA |= _BV(ADSC);
loop_until_bit_is_set(ADCSRA, ADIF);
loop_until_bit_is_clear(ADCSRA, ADSC);
last_adc = ADC;
SET_PIN_HIGH(POTI_SEL_PORT, sel_pin);
unsigned char val = convert_to_cc(last_adc);
unsigned char poti = sel_pin + 8 * adc;
unsigned char old = poti_status[poti] & 0x7f;
if ((val != old) && ((val == 00) || (val == 0x7F) || (abs(val - old) > 1)))
poti_status[poti] = 0x80 | val;
}void ask_potis(void) {
int i, j;DDRPOTI_SEL_PORT = 0xFF;
PORTPOTI_SEL_PORT = 0xFF;for (i = 0; i
The code to read the potentiometers is pretty similar, except that the
routine "ask_poti" reads the value using the ADC. The diodes on the
variable pins of the potentiometers have a voltage drop of 0.6V, which
we account for by subtracting MIN_THRESHOLD from the read ADC
value. The value is then scaled to 0-127, and sent if it differs more
than one from the previous value. The value is stored into the array
"poti_status". Finally, the routine "midi_potis" sends the value of
the potis which have changed (signalled by the flag 0x80)./*** Main ***/int main(void) {
io_init();init_buttons();
init_potis();DDRPOTI_SEL_PORT = 0xFF;
PORTPOTI_SEL_PORT = 0xFF;
DDRA = 0x00;
PORTA = 0xFF;
ADMUX = 0;int i;
for (i =0 ; iThe main routine initializes the buttons, potis, I/O, waits for 100
iterations of the main loop to let the pots and switch bounce around,
and then loops asking for switch and pot status, and sending the
corresponding MIDI messages. You can find the code for the project
hereThe main problem with the midi controller is that I used "cheap"
pots (though it was quite expensive to buy 56), and some of them have
short circuits or jump around or don't work at all. Another problem is
that I probably will have to convert the linear output of the pots to
something more approaching logarithmic scale because the resolution in
the high-end is too bad. Finally, another problem with the pots is
that the value sometimes oscillate due to environment noise or so, and
that using live in record mode tends to overwrite some parameter
automation envelopes. The next version will probably use less pots,
maybe some rotary encoders, though I have to find somewhere were I can
get them without being too expensive.All in all, it was a lot of work to build the midi controller, and
you can buy the same stuff for around 120 EUR from Behringer. The next
version will have some features that are not easily found. I
absolutely want to be able to record "automation macros" directly on
the hardware. Press "record pattern A", then move some pots and knobs
for a few bars, stop the recording, and then be able to play in back
"in sync". The next midi controller I am building will be a bad-ass
step sequencer with those macros features and other mad stuff. I'll
post about it when it's ready and working.




geil 🙂 mehr krach!
+++ neingeist
Das Ding ist ja echt kühl geworden und tolle Dokumentation!
hallo
ich bastel auch gerade an nem midi controller, hab allerdings ziemliche probleme mim uart des ATMEGA 16. Bekomme einfach nix rauß geschrieben.
das liegt bestimmt an der krummen baudrate der midi spezifikation. hast du den internen oszilator des avr`s genutz zur uart taktung oder nen quarz ?
Mit 8 Mhz als Quartz kriegst du saubere 31250 bps hin. Muesste eigentlich kein problem sein. Vielleicht reicht der Pin output nicht fuer den receiver? Hast du einen Transistor eingebaut?
really cool ,any ideas on implementing an lcd?
how are the revisions and new features coming along?? vey keen to hear your progress.
🙂
Hallo,
könnte ich die unterlagen vom AVR-Midi-USB Adapter bekommen?
Gruß Raphael
Hallo Raphael,
leider nicht, die oben dokumentierte Version ist eh mit einem Chip gebaut, den es nicht mehr gibt, und der Code ist leider zum Teil nicht meiner. Allerdings habe ich eine neue Version mit dem AT90USB1286, setz dich bitte mit mir in Verbindung manuel@bl0rg.net
I need to repeat your device but with different number of buttons and pots. Can you compile a firmware for atmega644? With 8 pots (each one on 1 ADC line) and 8×8 buttons matrix. I’m asking because you said that you will not give a full source. Thank you.
I need to repeat your device but with different number of buttons and pots. Can you compile a firmware for atmega644? With 8 pots (each one on 1 ADC line) and 8×8 buttons matrix. I’m asking because you said that you will not give a full source. Thank you. Mail it here slamlmd (doggy) yandex.ru
Well the code for the MIDI application itself is completely available in the posting on top (yeah that’s all the firmware), so just modify it for your need. What I’m not posting is the USB MIDI code which is obsolete anyway. Cheers, manuel
some forums says that it’s not possible to make USB MIDI HID device on atmega because MIDI requres bulky endpoints. I don’t know what does it mean. Can you give me any hints how to do that?
Hi well done! … Today I finished the firmware for my own device and it passed all usb9 tests and windows knows it as audio device. So far so good … now I have a working USB midi device. I want to connect it to Traktor 3, but i can’t. It is not available inside. It is no prob of tracktor. I just can’t interact with the midipart of the device. I thought the device is mapped by a standard driver of windows so I can connect it to Traktor. What did you do to connect your device under windows to ableton live? … is there a special software required?
Alex
Hi alibabashack,
I just used the standard windows driver. Maybe there is something wrong with your usb midi descriptors? It is quite tricky… Maybe if you send me a copy of them I could go over them. Did you define only one midi output? I guess windows needs to have a port with both in and out, else it won’t be recognized correctly.
thank you for quickly answering…
my descriptors are ok … they passed the usb9 test, but your last idea is right maybe … i just defined a midi IN port because I just want to controll tracktor and need no respond from pc .. I’ll send you a dump of my descriptors and try to define an out endpoint in addition. hope it will work then… thank you very much! 🙂
hmmm doesn’t work also with out pipe …
Hi I am currently starting my own project similar to yours and was scouring the web for resources. Your controller looks awesome btw! Just one thing, I can’t grasp why you have the output of 2 pots connected to one terminal, i.e. I think the schematic shows there are 16 pots connected to an 8-bit A/D port, have you used a multiplexer and just not shown it or do your rotary pots interact as pairs?
ahh i just read over it, and the penny dropped. Would you advise not to use mux’s because of propogation delays?
this still blows my mind everytime i bring this page up. hopefully i can get started on my knob-box soon!
I will soon start my own midi project too. 🙂
It is no prob of tracktor. I just can’t interact with the midipart of the device. I thought the device is mapped by a standard driver of windows so I can connect it to Traktor.
wholesale jewelry
handmade jewelry