Analog to Digital Conversion

The Raspberry Pi computer does not have a way to read analog inputs. It’s a digital-only computer. Analog inputs are handy because many sensors are analog outputs, so we need a way to read that inputs. For that we are going to use an analog-to-digital converter, in our case the chip MCP3008.

An analog-to-digital converter (ADC, A/D, or A to D) is a device that converts a continuous physical quantity (usually voltage) to a digital number that represents the quantity’s amplitude. Typically the digital output will be a two’s complement binary number that is proportional to the input.

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, so it can handle up to 8 ADC conversions. The MCP3008 is a 10-bit ADC so its output will vary from 0 to 1023. The pinout of the MCP3008 is the following:

MCP3008 SDI ADC Pinout
MCP3008 SDI ADC Pinout — Tony DiCola [CC BY 3.0]

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 $\frac{3.3\text{V}}{1024}= 3.22\text{mV}$ 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.

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.

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

  • Master in slave out (DIN). Data is moved from slave to master on this wire.
  • Master out slave in (DOUT). Data is moved from master to slave on this wire.

Serial clock (CLK). 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 (CS) for its respective device. Only one slave device can have its chip select signal asserted by the master controller at a time.

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 DIN and DOUT 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.

ADC using SPI from a Raspberry Pi

In order to read analog data we need to use the following pins: VDD (power), DGND (digital ground) to power the MCP3008 chip. We also need four ‘SPI’ data pins: DOUT (Data Out from MCP3008), CLK (Clock pin), DIN (Data In from Raspberry Pi), and /CS (Chip Select). Finally of course, a source of analog data, we’ll be using the basic 10k trim pot. A trimpot is a three-terminal resistor with a sliding or rotating contact that forms an adjustable voltage divider:

10k Ohm Trimpot
10k Ohm Trimpot

The MCP3008 has a few more pins we need to connect: AGND (analog ground, used sometimes in precision circuitry, which this is not) connects to GND, and VREF (analog voltage reference, used for changing the ‘scale’ - we want the full scale so tie it to 3.3V)

Below is a wiring diagram:

ADC circuit
ADC circuit

The connections of the MCP3008 chip is the following:

MCP 3008 Pin RPi Pin
VDD 3.3V (red)
VREF 3.3V (red)
AGND GND (black)
CLK GPIO17 (orange)
DOUT GPIO27 (yellow)
DIN GPIO22 (blue)
CS GPIO10 (purple)
DGND GND (black)

Next connect up the potentiometer. Pin #3 (left) connects to 3.3V (red), #2 (middle) connects to MCP3008 CH0 (analog input #0) with a pink wire, and #1 (right) goes to GND (black).

The following python program read the value of the potentiometer and print it value to the screen:

import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)

CLK  = 17
DOUT = 27
DIN  = 22
CS   = 10

GPIO.setup(CLK,  GPIO.OUT)
GPIO.setup(DOUT, GPIO.IN)
GPIO.setup(DIN,  GPIO.OUT)
GPIO.setup(CS,   GPIO.OUT)

# MCP3008 chip input channel
potentiometer = 0

# read SPI data from MCP3008 chip, 8 possible adc's  
def readadc(num, clk, dout, din, cs):
    if ((num > 7) or (num < 0)):
        return -1

    GPIO.output(cs , 1) # Stopping any previous transitions
    GPIO.output(clk, 0) # start clock
    GPIO.output(cs , 0) # Selecting slave to start transition

    command = num 
    command |= 0x18     # Puting 2 ones at front of the number
    command <<= 3       # Moving the number to the first 5 digits

    for i in range(5):
        if (command & 0x80):
            GPIO.output(din, 1)
        else:
            GPIO.output(din, 0)

        command <<= 1
        GPIO.output(clk, 1) # clock pulse to shift
        GPIO.output(clk, 0)

    out = 0
    
    # read in one empty bit, 10 ADC bits and one 'null' bit at the end
    for i in range(12):
        GPIO.output(clk, 1) # clock pulse to shift
        GPIO.output(clk, 0)
        out <<=1
        out |= GPIO.input(dout)

    GPIO.output(cs , 1) # Deselecting slave to stop transmition
    out >>= 1 
    return out	 

try:
    while True:
        value = readadc(potentiometer, CLK, DOUT, DIN, CS)
        print("Reading {:4} of 1023 ".format(value))
        print("({:7.2%})".format(value/1023.0))
        time.sleep(1)
except KeyboardInterrupt:
    GPIO.cleanup()

Exercise

Add another potentiometer to the circuit and extend the above program to print both values.

Last updated February 26, 2024 Fixed Indentation error and adding new information (a633b8f)