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.

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:

In the code below, you can see that the process goes like this:
- 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).
- Take the CS pin LOW and read out two bytes via SPI (MSB first). Then return the CS pin to HIGH again.
- Do some bit maths on these two bytes to convert them into a usable binary number.
- Cast this binary number into an int.
- Print the result to the serial window.
- Repeat.
Here’s some example raw data that we might read from the unit. The whole thing looks like this…
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.
Thanks, great article.
Thanks for the feedback. Glad I could help you out!
Dave
Thank you for sharing nicely written guide!
cheers dude!