My guide on using an analogue-to-digital converter

In this example, I’ve wired up an Analogue-to-digital converter (ADC) and showed you some code that will get this device talking to an Arduino using the SPI connection. There’s a potentiometer here that you can use to change the input voltage and it’s read once per second. Note that ADCs are capable of reading at much higher rates than this – according to the data sheet, the ADS1286 can sample at up to 20kHz but higher rates are possible.

ADC (ADS1286) circuit diagram. Device connected to Arduino Uno board with communication provided via SPI.

SPI stands for Serial Peripheral Interface and is a really impressive invention that allows ICs to talk to one another over short distances (on a PCB) using three wires: (1) clock, (2) master-out-slave-in (MOSI)/master-in-slave-out (MISO) and (3) chip select. In our case, since we’re dealing with receiving data from the slave (ADC), then we only use the MISO line. Wikipedia and Arduino have really good entries explaining this. However, I think that the timing diagram on Wikipedia is really helpful. Basically, when we want to talk to a chip over SPI, we send the chip select pin low, then send/read data over the MOSI/MISO data line. The whole thing is synchronised by clock pulses on the clock line. Each time the clock switches state, we end up reading another bit. The exact protocol of whether we (a) read bits on a low-high or high-low clock pulse and (b) whether bits are read on the leading or trailing edge of the clock is set in the SPI settings in the code (see below). Here is the exact operating sequence from the datasheet:

ADS1286 operating sequence straight from the datasheet. It’s not clear from this diagram but the clock polarity is 0 ie. it’s normally low and data bits are read on the trailing edge of the clock pulse. We read two bytes of data here and the most significant byte (MSB) comes out first.

In the code below, you can see that the process goes like this:

  1. Setup the SPI interface first given the settings discussed above and also set the clock speed as recommended by the manufacturers (It doesn’t seem to work if I don’t set this manually).
  2. Take the CS pin LOW and read out two bytes via SPI (MSB first). Then return the CS pin to HIGH again.
  3. Do some bit maths on these two bytes to convert them into a usable binary number.
  4. Cast this binary number into an int.
  5. Print the result to the serial window.
  6. Repeat.

Here’s some example raw data that we might read from the unit. The whole thing looks like this…

Format of data read from the ADC. Note that we read 16 bits but only 12 of them are actually useful. What follows in the code is an effort to shift things around and get rid of the junk bits in steps (1) to (4).

So what we need to do is keep only the data inside the box and get rid of everthing else. The key line in the code is this one…

ADCval = ((MSB & 0x3e)<<8 | LSB) >> 1;

Notice that we do a bitwise AND first with the MSB and 0x3e which is hex for 00011111 (step 1). This effectively does an AND operation with each bit in the MSB  and this number. Therefore anything in the first three bits gets switched to a 0 because any bit AND 0 -> 0. Then we shift the MSB left 8 places (step 2) and do a bitwise OR with the LSB (step 3). This effectively moves the MSB into its correct position as the most significant data and stuffs the LSB onto the end. Lastly, we shift everything right one place to get rid of the junk hanging off the end (step 4). This effectively shifts it off the end. Note that this will only work because we have declared ADCval as an unsigned int. If not, instead of a 0 being moved in from the left as we shift everything right, a 1 would end up there instead.

And here’s the rest of the code which includes the steps for addressing the ADC.

#include <SPI.h>
//pin connections
//Arduino       ADC
//12 MISO       6 (with 10k pull-up)
//10 CS         5
//13 SCK        7
const int CS = 8;

void setup() {
  // set the CS as an output:
  pinMode (CS, OUTPUT);
  Serial.begin(9600);     // opens serial port, sets data rate to 9600 bps
  
  SPI.begin();
  SPI.beginTransaction(SPISettings(20000, MSBFIRST, SPI_MODE0));
}

//function to read state of ADC
int ADCread(void)
{
  unsigned int ADCval=0;
  byte MSB,LSB;

  //now write to DAC
  // take the CS pin low to select the chip:
  digitalWrite(CS,LOW);
  delay(10);
  //  send in the address and value via SPI:
  
  MSB = SPI.transfer(0x00); //most sig byte
  LSB = SPI.transfer(0x00); //least sig byte

  //print out the raw measurement
  for (int i=7;i>=0;i--)
    Serial.print(bitRead(MSB,i));   
  for (int i=7;i>=0;i--)
    Serial.print(bitRead(LSB,i));
  Serial.println();

  //now get rid of the first three digits (most sig bits)
  //combine with the LSB
  //shift everything right one to get rid of junk least sig bit
  ADCval = ((MSB & 0x3e)<<8 | LSB) >> 1;

  //print out data again
  for (int i=15;i>=0;i--)
  Serial.print(bitRead(ADCval,i));
  Serial.println();
  
  delay(10);
  // take the CS pin high to de-select the chip:
  digitalWrite(CS,HIGH);

  return(ADCval);
}

void loop()
{
  int data = ADCread();
  Serial.println(data);
  delay(1000);
}

Here’s an example of the kind of thing that you get on the serial monitor…

1100110110101101
0000011001010110
1622
1100110110101010
0000011001010101
1621
1100110110101101
0000011001010110
1622
1100110110101101
0000011001010110
1622
1100110110101111
0000011001010111
1623
1100110110100111
0000011001010011
1619
1100011111001010
0000001101100101
869
1100010011110000
0000001001111000
632
1100000100101000
0000000000010100
20
1100000000000000
0000000000000000
0
1100000000000000
0000000000000000
0

…first, you get the raw data read from the ADC followed by the shifted bits after we do our maths. Then you get the decimal value representing the voltage which is Vin/Vref*4095.

I decided it might be a good idea to check the linearity of the device. I wanted to be able to relate the binary output from the ADC to a real voltage. Testing this is pretty easy.  I monitored the wiper terminal (ADC input) using a digital voltmeter and compared it to the ADC output from the serial window. Here is what I got…

As you can see, everything looks rosy. The response is linear to a high degree of accuracy and the gradient is 812 V^-1 = 4095 binary / 5.042V. I hope this post will help explain the basic process of how to implement an ADC. Get in touch if I’ve missed anything.

7 Replies to “My guide on using an analogue-to-digital converter”

Leave a Reply

Your email address will not be published. Required fields are marked *