Pages: [1] 2 3   Go Down
Author Topic: interfacing a 12 Bit SPI ADC  (Read 11357 times)
0 Members and 1 Guest are viewing this topic.
New Zealand
Offline Offline
Jr. Member
**
Karma: 1
Posts: 69
arduino: far too much like electrical engineering.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Quote

#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);
}
Logged


-- Alec

New Zealand
Offline Offline
Jr. Member
**
Karma: 1
Posts: 69
arduino: far too much like electrical engineering.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Just found out that sparkfun also have the MCP3202
« Last Edit: March 29, 2008, 05:58:22 am by halabut » Logged


-- Alec

New Zealand
Offline Offline
Jr. Member
**
Karma: 1
Posts: 69
arduino: far too much like electrical engineering.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.

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(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);
}
« Last Edit: May 10, 2008, 03:12:12 am by halabut » Logged


-- Alec

0
Offline Offline
Newbie
*
Karma: 0
Posts: 10
Arduino rocks my world!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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
Logged

New Zealand
Offline Offline
Jr. Member
**
Karma: 1
Posts: 69
arduino: far too much like electrical engineering.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

« Last Edit: May 13, 2008, 04:03:58 am by halabut » Logged


-- Alec

0
Offline Offline
Newbie
*
Karma: 0
Posts: 10
Arduino rocks my world!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 10
Arduino rocks my world!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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
Logged

New Zealand
Offline Offline
Jr. Member
**
Karma: 1
Posts: 69
arduino: far too much like electrical engineering.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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-grin
Logged


-- Alec

0
Offline Offline
Newbie
*
Karma: 0
Posts: 10
Arduino rocks my world!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

cheers
Logged

0
Offline Offline
Jr. Member
**
Karma: 0
Posts: 50
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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



thanks,
Phil
Logged

New Zealand
Offline Offline
Jr. Member
**
Karma: 1
Posts: 69
arduino: far too much like electrical engineering.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged


-- Alec

0
Offline Offline
Jr. Member
**
Karma: 0
Posts: 50
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

  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
Logged

Australia
Offline Offline
Jr. Member
**
Karma: 0
Posts: 99
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Code:
// 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.
Logged

0
Offline Offline
Jr. Member
**
Karma: 0
Posts: 50
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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
Logged

New Zealand
Offline Offline
Jr. Member
**
Karma: 1
Posts: 69
arduino: far too much like electrical engineering.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

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);
}
 
Logged


-- Alec

Pages: [1] 2 3   Go Up
Jump to: