interfacing a 12 Bit SPI ADC

I've been working with gyros on the Arduino, but 10 bits of ADC wasn't really enough, so I added a 12 bit SPI ADC. I used the Microchip MCP3202 a 2 channel 12 bit SPI ADC (also available as 3201, 3204 and 3208 with 1,4, and 8 channels), they're cheap and easily available from Farnell (http://www.farnell.com/). Because it is 12 bit and the built in SPI only likes 8 bit bytes, I manually manipulated the pins, it seems quite fast despite using mainly high-level commands. The code should work with the other 320* chips, but I think that the 04 and 08 have 1 more setup bit, but that would take about 30 sec to change.

If anyone has ideas to make it less processor intensive they're most welcome. Code below.

Alec

#define SELPIN 10 //Selection Pin
#define DATAOUT 11//MOSI
#define DATAIN 12//MISO
#define SPICLOCK 13//Clock
int readvalue;
int commandbits[] = {1,1,0,0}; //command bits (null,mode,channel,MSB/LSB)

void setup(){
//set pin modes
pinMode(SELPIN, OUTPUT);
pinMode(DATAOUT, OUTPUT);
pinMode(DATAIN, INPUT);
pinMode(SPICLOCK, OUTPUT);
//disable device to start with
digitalWrite(SELPIN,HIGH);
digitalWrite(DATAOUT,LOW);
digitalWrite(SPICLOCK,LOW);

Serial.begin(9600);
}

int read_adc(int channel){
int tempbit = 0;
int adcvalue = 0;

//allow channel selection
if(channel==1){
commandbits[2]=0;
} else {
commandbits[2]=1;
}

digitalWrite(SELPIN,LOW); //Select adc

// setup bits to be writen
for (int i=0; i<=3; i++){
if(commandbits*==1){*

  • digitalWrite(DATAOUT,HIGH);*

  • } else {*

  • digitalWrite(DATAOUT,LOW);*

  • }*

  • //cycle clock*

  • digitalWrite(SPICLOCK,HIGH);*

  • digitalWrite(SPICLOCK,LOW); *

  • }*
    //read bits from adc

  • for (int i=0; i<=12; i++){*

  • tempbit=digitalRead(DATAIN);*

  • //ignores 1st null bit (always 0)*
    _ adcvalue+=tempbit*(1<<(12-i));_

  • //cycle clock*

  • digitalWrite(SPICLOCK,HIGH);*

  • digitalWrite(SPICLOCK,LOW);*

  • }*

  • digitalWrite(SELPIN, HIGH); //turn off device*

  • return adcvalue;*
    }
    void loop() {

  • readvalue = read_adc(1);*

  • Serial.println(readvalue,DEC);*

  • readvalue = read_adc(2);*

  • Serial.println(readvalue,DEC);*

  • Serial.println(" ");*

  • delay(250);*
    }
    [/quote]

Just found out that sparkfun also have the MCP3202

Ok, just thought i'd put in the latest, much neater code (also much more processor and code efficient). Note this is for the MCP3208, the 8 channel version.

#define SELPIN 10 //Selection Pin 
#define DATAOUT 11//MOSI 
#define DATAIN  12//MISO 
#define SPICLOCK  13//Clock 
int readvalue; 

void setup(){ 
 //set pin modes 
 pinMode(SELPIN, OUTPUT); 
 pinMode(DATAOUT, OUTPUT); 
 pinMode(DATAIN, INPUT); 
 pinMode(SPICLOCK, OUTPUT); 
 //disable device to start with 
 digitalWrite(SELPIN,HIGH); 
 digitalWrite(DATAOUT,LOW); 
 digitalWrite(SPICLOCK,LOW); 

 Serial.begin(9600); 
} 

int read_adc(int channel){
  int adcvalue = 0;
  byte commandbits = B11000000; //command bits - start, mode, chn (3), dont care (3)
  
  //allow channel selection
  commandbits|=((channel-1)<<3);

  digitalWrite(ADCSEL,LOW); //Select adc
  // setup bits to be written
  for (int i=7; i>=3; i--){
    digitalWrite(DATAOUT,commandbits&1<<i);
    //cycle clock
    digitalWrite(SPICLOCK,HIGH);
    digitalWrite(SPICLOCK,LOW);    
  }

  digitalWrite(SPICLOCK,HIGH);    //ignores 2 null bits
  digitalWrite(SPICLOCK,LOW);
  digitalWrite(SPICLOCK,HIGH);  
  digitalWrite(SPICLOCK,LOW);

  //read bits from adc
  for (int i=11; i>=0; i--){
    adcvalue+=digitalRead(DATAIN)<<i;
    //cycle clock
    digitalWrite(SPICLOCK,HIGH);
    digitalWrite(SPICLOCK,LOW);
  }
  digitalWrite(ADCSEL, HIGH); //turn off device
  return adcvalue;
}

 digitalWrite(SELPIN, HIGH); //turn off device 
 return adcvalue; 
} 

void loop() { 
 readvalue = read_adc(1); 
 Serial.println(readvalue,DEC); 
 readvalue = read_adc(2); 
 Serial.println(readvalue,DEC); 
 Serial.println(" "); 
 delay(250); 
}

Hi halabut,
any chance you've got a wiring diagram or piccy's of your hookup as i'd like to use the ADC to increase my analogue inputs but am unsure how to wire up the MCP3208.

Any help much appreciated.
Regards

The MCP3208 comes as an 16 pin DIP (also soic) pinout below, get the datasheet here http://ww1.microchip.com/downloads/en/DeviceDoc/21298D.pdf With a little reading you should be able to come up with a similar solution to mine.

NOTE: code is for single ended operation, read the datasheet for differential operation.

D10 indicates arduino digital pin 10 etc.

Pinout
1-8 - chan 0-7 -> the 8 levels to be measured
9 DGND -> GND
10 CS chip select -> D10
11 Din MOSI -> D11
12 Dout MISO -> D12
13 CLC clock -> D13
14 AGN -> GND
15 Vref -> reference voltage (that gives max adc reading)
16 Vdd -> supply voltage max 5.5V so Arduino 5V is fine

   ___
1 | u | 16
2 |   | 15
3 |   | 14
4 |   | 13
5 |   | 12
6 |   | 11
7 |   | 10
8 |___| 9

Thanks halabut,
the ADC is perfect for the hardware side of things. now i just got to work out how/if MaxMSP can essentially identify each analog input into the ADC seperately as they will all be (hopefully) triggering independent sounds in Max.

Thanks Again

HI halabut,
so now I have my MCP3208 all wired up and have entered the code but am getting this error message in the digitalwrite section of the code:

error:'ADCSEL' was not declared in this scope
In function 'int read_adc(int)':
error: 'ADCSEL' was not declared in this scope AT global scope:

any ideas on what i could be doing wrong?

thanks again
frances

oops, I pulled that code from a project sketch, and looks like i renamed a constant without changing the rest of the code.
Replace ADCSEL with SELPIN and it will work :smiley:

thanks again halabut.. that got it working..
now i've just got to get it talking to Max/msp.

cheers

Anyone know of a good 12bit ADC that has +VREF as well as -VREF?

thanks,
Phil

If I understand right, you want to measure between a lower and upper voltage. On the MCP3208 you can set analogue and digital ground separately, so you should be able to set AGND to your lower ref voltage and VREF to your upper ref voltage. Read the datasheet for more info.
You could also try differential operation, useful for some of those sensors with small voltage swings and large offsets, I'm using it on some freescale pressure sensors which have a 2.5V offset and a 20mV swing (0.02V!) and get fairly stable results.

Thank you halabut,

I'll have to check that out.

I'm using a pressure sensor from Allsensors. It maxes out at about 4.88V and its minimum is a "don't care" in my project.

I got some freescale pressure sensors sample that I have yet to play with.

Phil

If I understand right, you want to measure between a lower and upper voltage. On the MCP3208 you can set analogue and digital ground separately, so you should be able to set AGND to your lower ref voltage and VREF to your upper ref voltage. Read the datasheet for more info.

Also if you are trying to measure +/- values, try to get the zero-point in the centre of the ADC range (by suitable selection of VREF & AGND). Then you can convert the ADC value from offset binary to two's complement signed fairly quickly with the following:

// Convert offset binary (zero value in centre of range) to two's complement signed
// val = read ADC val
// bits = resolution of ADC
//
int offsetBinary2TwosComp( int val, char bits ) {
      val ^= 1 << (bits - 1); // Toggle the high bit
      if (val & (1 << (bits - 1))) val |= (0xFFFF << bits); // Sign extend
      return val;
}

Conversion involves inverting the MSB then sign-extending if the result is negative.

Hello again Halabut,

I just got a few of these ADC's, tried to run the code, but I'm getting a strange error:

error: expected constructor, destructor, or type conversion before '(' token

It highlights this portion of the code:

digitalWrite(SELPIN, HIGH); //turn off device

Any ideas?

thanks,
Phil

Try this, I just spotted another error in the code.

#define SELPIN 10 //Selection Pin 
#define DATAOUT 11//MOSI 
#define DATAIN  12//MISO 
#define SPICLOCK  13//Clock 
int readvalue; 

void setup(){ 
 //set pin modes 
 pinMode(SELPIN, OUTPUT); 
 pinMode(DATAOUT, OUTPUT); 
 pinMode(DATAIN, INPUT); 
 pinMode(SPICLOCK, OUTPUT); 
 //disable device to start with 
 digitalWrite(SELPIN,HIGH); 
 digitalWrite(DATAOUT,LOW); 
 digitalWrite(SPICLOCK,LOW); 

 Serial.begin(9600); 
} 

int read_adc(int channel){
  int adcvalue = 0;
  byte commandbits = B11000000; //command bits - start, mode, chn (3), dont care (3)
  
  //allow channel selection
  commandbits|=((channel-1)<<3);

  digitalWrite(SELPIN,LOW); //Select adc
  // setup bits to be written
  for (int i=7; i>=3; i--){
    digitalWrite(DATAOUT,commandbits&1<<i);
    //cycle clock
    digitalWrite(SPICLOCK,HIGH);
    digitalWrite(SPICLOCK,LOW);    
  }

  digitalWrite(SPICLOCK,HIGH);    //ignores 2 null bits
  digitalWrite(SPICLOCK,LOW);
  digitalWrite(SPICLOCK,HIGH);  
  digitalWrite(SPICLOCK,LOW);

  //read bits from adc
  for (int i=11; i>=0; i--){
    adcvalue+=digitalRead(DATAIN)<<i;
    //cycle clock
    digitalWrite(SPICLOCK,HIGH);
    digitalWrite(SPICLOCK,LOW);
  }
  digitalWrite(SELPIN, HIGH); //turn off device
  return adcvalue;
}

void loop() { 
 readvalue = read_adc(1); 
 Serial.println(readvalue,DEC); 
 readvalue = read_adc(2); 
 Serial.println(readvalue,DEC); 
 Serial.println(" "); 
 delay(250); 
}

Thanks. It works great now.

Phil

Hey, nice work on the code. What is the name of the gyro that you are trying to interface with the arduino? Could you include your code for the gyro as well? I'm having trouble interfacing the MLX 90609 rate gyro through SPI.

Thanks!!

I'm using the sparkfun analogue IMU, hence the ADC. The important thing with SPI is to read the datasheet carefully, nearly all SPI devices aren't 'standard'.

Hello Halabut,

I recently bought a Wiring Mini board and hooked up one of these mcp3208's to it .. using the same code you provided. It compiled with no errors and successfully uploaded to the wiring board. It doesn't seem to work though. All I see in the serial monitor of the Wiring IDE is either 0 or 4095 ... and the occasional random number every once in a while.

I'm using the same pin #'s ... 10,11,12,13

It works 100% on my arduino.

thanks,
Phil

I don't have any experience with wiring, but from the sounds of it this is a hardware problem. Check all your wires are in correctly and that the ADC has its ref voltage and power supplies connected and they're at the right levels. Other than that I can't think what it'd be.