domenica 22 novembre 2009

Arduinome x16adc mod

Some times ago I hacked my arduinome to add 16 potentiometers to it.
I writed about it only on monome's forum but I think it can be a good subject for my first article on this blog.

Basically, it's a good way to fill arduino's free analog input pins. They should be free if, like me, you are playing with an unsped shield.

Arduinomes works on three levels:

  • Hardware
  • Firmware
  • Software
we must touch all these levels to add our potentiometers.

Hardware
An arduino board has 6 analog input pins, but we can add more inputs with multiplexing.
I choosed cd4067 multiplexer, it's an analog multiplexer 100% arduino compatible.
Luckily I found some free information about how to use it, "Introduction to Physical Computing" was very usefull.
I developed a simple circuit to add it to your arduinome, here is the schematic:



As you can see I used analog pins to drive cd4067, actually only ANALOG_0 is an analog input, others choose multiplexer's channel only, so they act like digital output pin. You can use every arduino's digital pin to do it, but I found them comfortable.
C2 (I know, it shoul be C1) is there for supply decoupling.

Firmware
Obviously I had to change arduinome's firmware. Arduinome firmware is part of an open source project, the original version can be found on sourceforge. The pde file with my modifications can be downloaded through monome's forum, it's in the x16adc_code.zip archive.
I will not write here all modifications I wrote, I will discuss most important modifications only.

1. I added to arduinome's firmware the same code of the tutorial linked above to handle cd4067 pins. These lines are crucial:


void cd4_setChannel(int cd4_whichChannel)
{
  for (int cd4_bitPosition = 0; cd4_bitPosition < 4; cd4_bitPosition++)
  {
    int cd4_bitValue = (cd4_whichChannel >> cd4_bitPosition) & 1;
    int cd4_pinNumber = cd4_firstPin + cd4_bitPosition;
    digitalWrite(cd4_pinNumber, cd4_bitValue);
  }
}

cd4_whichChannel can be a number from 0 to 15, each value is shifted 4 times and masked. These 2 operations are needed to generate 16 different combinations of the 4 control pins. Read this bit math tutorial if you don't know nothing about bit shifting and masking.
The following table shoul clearify what the function do:



Every value is shifted of 0 and masked, then is shifted of 1 and masked, and so on, until 3. The last four columns represents the pin states of the 4 control pins in the 16 calls, as you can see every row is different from each other. This means that every call returns a different control pin pattern.

2. This was the most important modification:


void sendADC(int port, int value)
{
  Serial.print((1 << 4) | (port & 0xF), BYTE);
  Serial.print(((value >> 2) & 0xFF), BYTE);
}
the original code was:

void sendADC(int port, int value)
{
  Serial.print((1 << 4) | ((port << 2) & 0x0C) | ((value >> 8) & 0x03), BYTE);
  Serial.print(value & 0xFF, BYTE);
}

This function sends ADC values to Arduinome Serial following monome's serial protocol. As you can see on the link above, adc_val stream has "value" 10 bit long and "port" only 2 bit long. This means that we can have 1024 different adc values, but only 4 ports. This is why monomes can handle 4 potentiometers only. A 10 bit resolution is a very good resolution, it's the same of arduino's analog digital converters, so this protocol passes down adc values at the maximum resolution. I had to sacrifice resolution to add more than 4 potentiometers, I taked 2 bits from "value" and put it to "port", so now I have a 8 bit resolution for values and up to 16 ports.
In my version of sendADC "address" is the same, "port" has 4 relevants bits ("& 0xF" means "& 1111") and there's no need to add the first 2 bits of "value" to "data0".
"data1" contains the 8 bits of value, and all bits are relevant ("& 0xFF" means "& 11111111").

3. I'm not a C developer, I can't modify Arduinome Serial GUI to have 16 switch, so I had to bypass the ADCEnableState control in Arduinome Firmware to always send ADC values.
For that reason I've commented 610, 611, 617 lines.

Software
Now that the protocol is different, it's necessary to change Arduinome Serial too.
I can't "speak" C, but luckily modifications were easy to find. I had to change only 6 lines on 3 different files of the source code. You can download Arduinome serial souce and edit the files yourself or download my modified files from monomes's forum (Mac OS X only, look inside x16adc_code.zip). In any way you have to compile the source.
Here is all modified lines:

message.c lines: 28 to 32


void messagePackAdcVal(t_message *message, uint8 port, uint8 val)
{
  message-&gt;data0 = (kMessageTypeAdcVal &lt;&lt; 4) | (port &amp; 0x0F);
  message-&gt;data1 = (uint8)val;
}

message.h lines: 67, 68

#define messageGetAdcPort(message) ((message).data0 & 0xF)
#define messageGetAdcVal(message) ((uint16)(message).data1)
line 89
void messagePackAdcVal(t_message *message, uint8 port, uint8 val);
(uint8 val was uint16 val)

ApplicationController.mm line 193


handleAdcValueChangeEvent(device, messageGetAdcPort(*message), (float)(messageGetAdcVal(*message)) / (float)0xFF);
(was 0x3FF)

Basically there are the same "serial stream" modifications, but from the point of view of the reciver.
I changed uint16 to uint8 because now "value" has 8 bits only.
Value's relevant bits are 8 and not 10, so I changed 0x3FF to 0xFF.
END!

Now is time for a good picture of the finished product!


The thing woks, but it's not perfect. It's an hack more than a mod, and some error can occur.
I tried it with Pd on Mac Os X only, and sometimes values seems to flicker. I don't know why, maybe Pd serial communication is slow? I think so because if I only watch values on the console monitor without playing there's no flickering. I can't be sure about it because I need testers! If you are interested, you can download my Pd patch to try the mod here.
It's a 8 step sequencer with filter and sample rate control.
If you don't have modded your arduinome you can play with the original version of this patch, it's called stePd and is free.

This is the maximum I was able to do, I think it can be better, so it would be nice if someone would try to build and refine it.
If you like potentiometers and arduinomes, drop me a line!

3 commenti:

  1. ma il multiplexer analogico lo controlli con una porta analogica o digitale non si capisce bene... Cmq carina la modifica! Andrebbe bene sul mio arduinome:
    http://www.flickr.com/photos/robynhub/4188374920/

    ah, nei giudizi ti sei dimenticato la voce "geek" :D

    RispondiElimina
  2. In effetti stavo pensando di mettere geek al posto di guru... :)

    La confusione nasce dal fatto che effettivamente quelli che guidano il multiplexer sono pin analogici, ma in questa applicazione sono usati come comuni pin digitali.
    http://www.arduino.cc/en/Reference/DigitalWrite (leggi le note).
    Ci sarebbero stati altri pin digitali liberi, ma ho preferito fare in questa maniera perchè mi veniva comodo usare pin vicini.
    L'unico pin analogico usato come tale è ANALOG_0.

    Bell'enclosure, complimenti! Il faceplate dove l'hai preso?

    RispondiElimina
  3. Hello,
    Thanks for your tutorial, great works and thanks for the ideas.
    I have build an Arduinome and know i'm trying to add 16 potentiometer like you and 16 others asignable pads.
    If everything work, i will send here another message.
    Here is my work on the arduinome (written in french) :
    http://www.killerloops.net/index%20-%20articles.html

    RispondiElimina