Interfacing an SPI ADC (MCP3008) chip to the Raspberry Pi using C++ (spidev)

In this entry I will demonstrate how to interface the MCP3008; an SPI-based analog to digital converter (ADC) integrated chip, to the Raspberry Pi. This enables  the Raspberry Pi to interpret analog voltages that are in turn typically emitted by analog-based sensors to reflect a measure of  a physical characteristic such as acceleration, light intensity or temperature. We will start by briefly examining the SPI interface.

A gentle introduction to the Serial Peripheral Interface (SPI) 

The Serial Peripheral Interface (SPI) is a communication bus that is used to interface one or more slave peripheral integrated circuits (ICs) to a single master SPI device; usually a microcontroller or microprocessor of some sort. Many SPI  Peripheral ICs exist. They include, analog to digital converters (ADC), digital to analog converters (DAC), general purpose input/output (GPIO) expansion ICs, temperature sensing ICs, accelerometers and many more.  In this regards the SPI bus is similar to the I2C bus. the SPI’s main advantage over the I2C bus is that the SPI bus speeds can be very fast; 10Mbps is not unusual and maximum speeds can go as high as the hardware (master controller, slave peripheral, and printed circuit board traces connecting them) will go. Speeds that exceed 50Mbps are not unusual.

Interfacing an SPI ADC (MCP3008) chip to the Raspberry Pi using C++ (spidev)

Having said that, the main disadvantage of the SPI bus w.r.t the I2C bus is the numbers of wires required by the bus. The I2C bus is a ‘2-wire’ bus that theoretically can be used to connect up to 127 devices, and significantly more if a 9-bit addressing scheme is used instead of the classical 7-bit address. The SPI bus is a ‘3+n wire’ bus. where ‘n’ is the number of slave devices attached to the master SPI device. For example in the example shown in Figure 1, a total of 6 wires are required by the SPI bus and six corresponding pins required on the master SPI device to interface the 3 slave SPI devices to the master SPI controller. This means that not only is the number of wires on an SPI bus larger than on an I2C bus, but that the number of wires continue to increase linearly as we add more slave devices on to the bus. Notice that slave SPI devices almost always require 4 pins to attach themselves to the SPI bus.

The 3 SPI wires shared by all devices on the SPI  bus are:

  • Master in slave out (MISO). Data is moved from slave to master on this wire.
  • Master out slave in (MOSI). Data is moved from master to slave on this wire.
  • Serial clock (SCLK). This clock is always generated by the master controller and is used to synchronize the transmission of data between devices on the bus.

In addition to these wires we have ‘n’ wires for ‘n’ slave devices on the bus. Each one of these wires carries the chip select signal (SS or CS) for its respective device. Only one slave device can have its chip select signal asserted by the master controller at a time.

2 SPI bus operation

The operation of the SPI bus is conceptually simple. Both the master controller and each slave device contain a shift register. When the chip select signal of a slave device is asserted (usually by being pulled low), the MISO, MOSI wires are used to connect its shift register with that of the master device. Clock pulses are then generated (by the master device)  to shift data between the two shift registers enabling communication. In this sense the read and write operation are combined. For example by shifting the contents of the master device shift register to  that of the slave device, we are  also shifting the data in the slave device shift register to that of the master.

Finally, there are 4 different SPI modes that can be used. Each mode defines a particular clock phase (CPHA) and polarity (CPOL) with respect to the data. For the purpose of this tutorial we will be utilizing SPI mode 0 which is also known as mode (0,0) or mode (CPHA=0,CPOL=0).

To learn more about the SPI bus, I refer the reader to these excellent resources:

The MCP3008 SPI ADC chip

The MCP3008 chip is an SPI based analogue to digital converter (ADC). It has 8 analog input channels that can be configured for single ended and differential ADC conversions. The MCP3008 is a 10-bit ADC  that can convert  up to 200 kilo samples per second (200ksps) (@ 5V!!). The MCP3008 comes in 28 PDIP and SOIC packages. A pinout is provided in Figure 3. The datasheet for the MCP3008 can be can be downloaded from.

Typically the VDD pin is connected to  3.3V power. The AGND and DGND pins can be connected directly to the ground reference point. The VREF pin is the reference voltage which is the largest possible voltage that the ADC can interpret. In our scenario we will connect the VREF pin to 3.3V (same as VDD). So if 3.3V was sampled on any of the ADC’s channels it would be interpreted as the maximum digital value that can be represented by this 10-bit ADC i.e. 2^10 – 1 = 1023. Similarly the smallest analog voltage that the ADC can detect (also known as the ‘LSB size’) is VREF/1024. Which in our case is 3.3V/1024= 3.22mV and represents a digital value of 1. The equation that converts between the analog voltage and its digital interpretation is given by “Digital output code = 1024*VIN/VREF”; where VIN is the analog input voltage and VREF is the reference voltage.

A complete SPI transaction for the MCP3008 (SPI mode 0) is depicted in Figure 4. The complete transaction consists of 3 bytes being transmitted from master (Raspberry Pi) to slave (MCP3008) and 3 bytes transmitted from slave to master. Recall that due to the nature of the shift register  operation of the SPI bus, shifting 3 bytes into the slave device (writing to the slave MCP3008) will by default cause the 3 bytes in the slave device to be shifted out into the master device (Raspberry Pi).

  1. Raspberry Pi asserts the chip select signal connected to the MCP3008 (CS0 in our case) by setting it to 0V. This is typically taken care of internally by the spidev driver whenever the proper ioctl() function is called.
  2. Raspberry Pi sends a byte containing a value of ‘1’ to the MCP3008. This is a start bit. At the same time the MCP3008 sends back a ‘don’t care’ byte to the Raspberry Pi.
  3. Raspberry Pi then sends a second byte whose most significant nibble (SGL/DIFF,D2,D1 & D0 bits) indicate the channel that we want to convert and whether we want  single-ended or differential conversion (See Figure 5). For example if this nibble is “1000”, the conversion will be single-ended and take place on channel 0 (CH0 pin). The least significant nibble is sent as ‘don’t care’. At the same time, the MCP3008 sends back the two most significant bits of the digital value (result) of the conversion (bits 8 and 9).
  4. Raspberry Pi sends another  ‘don’t care’ byte to the MCP3008. At the same time the MCP3008 send back a byte containing bits 7 through 0 0f the digital value (result) of the conversion.
  5. The Raspberry Pi then merges bits 8 & 9 from the second received byte with bits 7  through 0 from the third received byte to create the 10-bit digital value resulting from the conversion.

Interfacing an SPI ADC (MCP3008) chip to the Raspberry Pi using C++ (spidev) schematic***********************************************************************

 * mcp3008SpiTest.cpp. Sample program that tests the mcp3008Spi class.
 * an mcp3008Spi class object (a2d) is created. the a2d object is instantiated
 * using the overloaded constructor. which opens the spidev0.0 device with
 * SPI_MODE_0 (MODE 0) (defined in linux/spi/spidev.h), speed = 1MHz &
 * bitsPerWord=8.
 *
 * call the spiWriteRead function on the a2d object 20 times. Each time make sure
 * that conversion is configured for single ended conversion on CH0
 * i.e. transmit ->  byte1 = 0b00000001 (start bit)
 *                   byte2 = 0b1000000  (SGL/DIF = 1, D2=D1=D0=0)
 *                   byte3 = 0b00000000  (Don't care)
 *      receive  ->  byte1 = junk
 *                   byte2 = junk + b8 + b9
 *                   byte3 = b7 - b0
 *    
 * after conversion must merge data[1] and data[2] to get final result
 *
 *
 *
 * *********************************************************************/
#include "mcp3008Spi.h"
using namespace std;
int main(void)
{
    mcp3008Spi a2d("/dev/spidev0.0", SPI_MODE_0, 1000000, 8);
    int i = 20;
        int a2dVal = 0;
    int a2dChannel = 0;
        unsigned char data[3];
    while(i > 0)
    {
        data[0] = 1;  //  first byte transmitted -> start bit
        data[1] = 0b10000000 |( ((a2dChannel & 7) << 4)); // second byte transmitted -> (SGL/DIF = 1, D2=D1=D0=0)
        data[2] = 0; // third byte transmitted....don't care
        a2d.spiWriteRead(data, sizeof(data) );
        a2dVal = 0;
                a2dVal = (data[1]<< 8) & 0b1100000000; //merge data[1] & data[2] to get result
                a2dVal |=  (data[2] & 0xff);
        sleep(1);
        cout << "The Result is: " << a2dVal << endl;
        i--;
    }
    return 0;
}

About The Author

Ibrar Ayyub

I am an experienced technical writer holding a Master's degree in computer science from BZU Multan, Pakistan University. With a background spanning various industries, particularly in home automation and engineering, I have honed my skills in crafting clear and concise content. Proficient in leveraging infographics and diagrams, I strive to simplify complex concepts for readers. My strength lies in thorough research and presenting information in a structured and logical format.

Follow Us:
LinkedinTwitter

Leave a Comment

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

Scroll to Top