Friday, January 3, 2014

Analog to Digital Goodness: The ADC MUX in action

When I saw the Sparkfun 16 channel MUX breakout I knew I needed it for those times when there are more analog inputs than an Atmel '328 can take in.

I also knew I needed to control it somehow, and the Atmel Attiny 84 is a perfect fit. Literally - it just fits underneath the breakout board!

I exposed, and etched the PCB in a day over the Christmas break, and did the drills and parts stuffing in another day. I think the actual PCB design took a couple of days here and there, too. The actual ADC connections are jazzed up a bit with +5V and GND pins for each channel, since it's very likely that you'll need to get either one or both to the sensor, in addition to bringing the analog signal back. Here is what it looks like:

The '84 runs my now-standard I2C slave software, so all the administrivia is taken care of. That leaves just a little more coding for the actual ADC part. Here is what it does:

  • 16 bit mask for which channels are enabled
  • Averages 3 reads per channel on each pass
  • 8 bit conversion of the 10 bit ADC reading, with per channel min and max cutoffs for the conversion.
The variable mapping means that if you are only interested in ADC values from say 53 to 740, you can set those so they map to the [1..254] range. Values below 53 are set to zero - Off Scale Low or OSL, and values above 740 are set to 255 - Off Scale High or OSH.

There is access via I2C register readings of both the 16 bit and 8 bit versions of each channels data. If a channel is mapped out via the 16 bit mask, then it reads as zeros.

Configured for the demo with all 16 channels running it takes about 23 ms to read them all and do the math. Realistically I'd guess that I'd read it either at 50ms or 100ms intervals in the Rover. 

Here is a video of the board in action. I'm using an Arduino Leonardo as the I2C master / USB(serial) slave, so it does the I2C reads on a fixed interval, and then when commanded from a Processing sketch it barfs out the 16 bytes (and a stop byte) back to Processing for graphing.

Here is some of Arduino #defines on the Attiny 84, to give you an idea of what's possible… the actual ADC data shows up starting at register positions 16..31 for 8 bit data, and 32..63 for the original 16 bit data. I figured that sometimes 8 bits is enough.

// Defines for register positions

#define MOD 0      // mod number / type / ver
#define I2C_ADDR 1      // addr I2C Address
#define VCC5 2      // use this?
#define ENAMAP1 3      // bitmap of enabled channels
#define ENAMAP2 4      // bitmap of enabled channels
#define CHDELAY 5      // ms between chans
#define TEMPGAIN 6      // gain factor for internal temp sensor
#define TEMPOFFSET 7      // offset value for internal temp sensor, +127
#define CYCLE 8      // ms between full cycle

#define HEARTBEAT 9       // heartbeat counter
#define CTIME 10 // actual reported cycle time
#define RUNTIMELO 11 // lo byte seconds runtime
#define RUNTIMEHI 12 // hi byte seconds runtime
#define INTTEMP 13 // internal avr temp
#define CMDSTATUS 14 // result of last command
#define CMD 15 // command reg

#define BYTEOFFSET 16 // register start of scaled byte data

#define RAWOFFSET 32 // register start of raw adc data 

#define  CHANCFGOFFSET  64      // register start of channel config info ( 4 bytes per channel)
                                // Bytes offsets
                                // 0 - min lo byte (usually 0)
                                // 1 - min hi byte (usually 0)
                                // 2 - max lo byte (0xff for 1023)
                                // 3 - max hi byte (0x03 for 1023)

#define CMDNULL 0 // no command
#define CMDRESTART 1 // restart (call setup)
#define CMDCLEARSYSCLOCK 2 // clear the runtime clock
#define CMDHEARTBEAT 3 // increment the heartbeat counter
#define CMDCLEARHEARTBEAT 4 // clear the heartbeat counter
#define CMDWINK 5 // set the LED to wink mode (clear with null cmd)
#define CMDREADEEPROM           6       // re-read from EEPROM
#define CMDWRITEEEPROM          7       // write cfg to EEPROM
#define CMDWRITEEXTEEPROM       8       // write the high range from regs to eeprom
#define CMDFROMEEPROM           9       // re-run initEEPROM for defaults
#define CMDLEDMASK              10      // use whatever is in cmdstatus as the mask (write to status ok)
#define CMDREADEXTEEPROM        11      // read the high range of eeprom back into registers

#define CMD_I2C_UNLOCK       255        // done with i2c, unlock it (all others lock)

#define LEDMASK_OK 254         // 11111110
#define LEDMASK_SLOWBLINK 240  // 11110000
#define LEDMASK_MEDBLINK 204   // 11001100
#define LEDMASK_FASTBLINK 170  // 10101010
#define LEDMASK_WINK 160       // 10100000
#define LEDMASK_STROBE 128     // 10000000
#define LEDMASK_OFF 0          // 00000000