paulv
Posts: 564
Joined: Tue Jan 15, 2013 12:10 pm
Location: Netherlands

HOWTO: Add an analog input to the Pi

Thu Oct 22, 2015 3:21 pm

In this post I will show you how to add an analog input to the Pi, by using an Analog to Digital Convertor, or ADC.

I will show you how to measure the 5V supply voltage as an example application, but you can easily change this setup to do other things.

There have been so many issues reported with the 5V supply for the Pi that on the latest models the designers added a visual indication for the accepted voltage level. However, this has not really resulted in a much improved situation, judging from the many comments and related problems in the forums.

As we all (should) know by now, the 5V supply is rather critical for the Pi itself, but even more so when peripherals like WIFI adapters are added to the USB ports.
To properly and precisely measure the 5V supply by the Pi itself, additional hardware is needed, because the Pi lacks analog inputs and outputs.
There are several add-ons available that can be used to measure the 5V supply, but I will use this post to go to the basics with a minimum setup. In another set of posts, I will also explain how to add analog output capabilities to the Pi.

Here are these posts:
Output with PWM : viewtopic.php?f=37&t=124130
Output with DAC : viewtopic.php?f=37&t=124184

So, here we go.
I’ll try to make this solution as simple as possible, and I will not go into much details here. In any case, we need a true (there are other tricks) analog input, and we will use a readily available chip to implement that. The MCP3002 is such a device. It provides 10bit resolution on the conversion, has two input channels, and has an SPI interface to communicate with it.
Here is a link to the datasheet: http://ww1.microchip.com/downloads/en/D ... 21294E.pdf
This chip can even be ordered through Amazon for around $4,00 for the 8pin DIP version, which is easy to use with breadboards.
The supply voltage for the MCP3002 is also the reference voltage that is used for the conversion circuits. I won’t go in details here, but that is an important reference for the measurements it makes. In our case, we want to measure the 5V supply, so we can’t use that. It would distort the level and precision of the measurements we want to make because this supply is depending on the chargers or supplies we need to check. However, we also have the 3V3 supply of the Pi available to us, and that is much more stable and precise. We could also use another chip/method to provide a stable and precise reference voltage, but the 3V3 will do fine for this application. Unfortunately, with most ADC’s, they can only measure up to their reference voltage, so we need to apply a trick to measure 5V with a 3V3 reference/supply. The solution is rather simple, we can just use a resistor voltage divider to bring the input voltage below the 3V3 reference voltage.

Let’s examine the precision of what we will be able to measure, and how that works. With a 3V3 reference voltage, and a 10-bit resolution, we will get a precision of 3V3 divided by 10-bits or 1024 different values. 3V3 is 3300 mV, and 3300mV / 1024 gives a 3.22mV resolution for every bit, and that gives a theoretical measurement precision of 3.22mV / 3300mV = 0.097% This is more than enough.

If we use a voltage divider of 1:2, we just need to multiply the measured value by 2 to get the “real” input back. We will use two 10K resistors to create a 1:2 divider, and that allows us to measure up to 6V6 at the input.
You can use higher resistor values, but you may start to influence the conversion process. Let me explain this in simple terms. The ADC works by letting the input voltage charge a capacitor within a certain time period. If you start to limit the charging current by using high value resistors, the capacitor will not charge quickly enough, and you will get lower results than you actually supply because you are fooling the ADC chip.

To tell the chip what to do, and to read back the results, we’ll use the SPI interface. This only uses a few of the pins of the P1 connector, and are readily available. We’ll need a clock signal (SCLK) to send/receive the data and control information. We’ll need the data and control input and output pins (MOSI and MISO), and we need a way to select the proper device, because the Pi can accommodate two SPI devices. We’ll use the CE0 pin for that purpose.

The following schematic shows the connections of the MCP3002 to the P1 connector of the Pi, and also the voltage divider that connects the 5V to the ADC. If you use a breadboard to wire this up initially, I recommend you to use at least a 100nF by-pass capacitor on the reference/voltage pin of the ADC. Better even would be the addition of an electrolyte (C2). The value is not really important, it acts like a simple voltage buffer or reservoir for the chip.
5V Measurement.png
5V Measurement.png (35.11 KiB) Viewed 25915 times
OK, that’s it for the hardware. Now what and how do we tell the ADC what to do, and how do we get the results back and interpret those into meaningful measurement results?
This also is rather simple, once you have prepared the Pi, so we’ll go through all the steps.
I am now using the latest firmware (Jessie) as a reference because things changed since we moved to that version of the Debian OS. Keep that in mind.
My OS version is:
pi@raspberrypi ~ $ uname -a
Linux raspberrypi 4.1.7+ #815 PREEMPT Thu Sep 17 17:59:24 BST 2015 armv6l GNU/Linux
pi@raspberrypi ~ $
First we need to import the SPI drivers so we can communicate over the SPI bus, and some tools to install them.
Install spidev:

Code: Select all

sudo apt-get install python-setuptools
sudo apt-get install python-dev
sudo apt-get install git
git clone git://github.com/doceme/py-spidev
cd py-spidev/
sudo python setup.py install
To add the spi driver at boot time:

Code: Select all

sudo nano /boot/config.txt 
At the very end add:

Code: Select all

dtparam=spi=on
Then reboot the system to make it all effective:

Code: Select all

sudo reboot
Create a Python source file or copy this file into your editor:

Code: Select all

#!/usr/bin/python
#-------------------------------------------------------------------------------
# Name:        MCP3002 Measure 5V
# Purpose:     Measure the 5V Supply of the Raspberry Pi
#
# Author:      paulv
#
# Created:     22-10-2015
# Copyright:   (c) paulv 2015
# Licence:     <your licence>
#-------------------------------------------------------------------------------

import spidev # import the SPI driver
from time import sleep

DEBUG = False
vref = 3.3 * 1000 # V-Ref in mV (Vref = VDD for the MCP3002)
resolution = 2**10 # for 10 bits of resolution
calibration = 38 # in mV, to make up for the precision of the components

# MCP3002 Control bits
#
#   7   6   5   4   3   2   1   0
#   X   1   S   O   M   X   X   X
#
# bit 6 = Start Bit
# S = SGL or \DIFF SGL = 1 = Single Channel, 0 = \DIFF is pseudo differential
# O = ODD or \SIGN
# in Single Ended Mode (SGL = 1)
#   ODD 0 = CH0 = + GND = - (read CH0)
#       1 = CH1 = + GND = - (read CH1)
# in Pseudo Diff Mode (SGL = 0)
#   ODD 0 = CH0 = IN+, CH1 = IN-
#       1 = CH0 = IN-, CH1 = IN+
#
# M = MSBF
# MSBF = 1 = LSB first format
#        0 = MSB first format
# ------------------------------------------------------------------------------


# SPI setup
spi_max_speed = 1000000 # 1 MHz (1.2MHz = max for 2V7 ref/supply)
# reason is that the ADC input cap needs time to get charged to the input level.
CE = 0 # CE0 | CE1, selection of the SPI device

spi = spidev.SpiDev()
spi.open(0,CE) # Open up the communication to the device
spi.max_speed_hz = spi_max_speed

#
# create a function that sets the configuration parameters and gets the results
# from the MCP3002
#
def read_mcp3002(channel):
    # see datasheet for more information
    # 8 bit control :
    # X, Strt, SGL|!DIFF, ODD|!SIGN, MSBF, X, X, X
    # 0, 1,    1=SGL,     0 = CH0  , 0   , 0, 0, 0 = 96d
    # 0, 1,    1=SGL,     1 = CH1  , 0   , 0, 0, 0 = 112d
    if channel == 0:
        cmd = 0b01100000
    else:
        cmd = 0b01110000

    if DEBUG : print"cmd = ", cmd

    spi_data = spi.xfer2([cmd,0]) # send hi_byte, low_byte; receive hi_byte, low_byte

    if DEBUG : print("Raw ADC (hi-byte, low_byte) = {}".format(spi_data))

    # receive data range: 000..3FF (10 bits)
    # MSB first: (set control bit in cmd for LSB first)
    # spidata[0] =  X,  X,  X,  X,  X,  0, B9, B8
    # spidata[1] = B7, B6, B5, B4, B3, B2, B1, B0
    # LSB: mask all but B9 & B8, shift to left and add to the MSB
    adc_data = ((spi_data[0] & 3) << 8) + spi_data[1]
    return adc_data


try:
    print("MCP3002 Single Ended CH0 Read of the 5V Pi Supply")
    print("SPI max sampling speed = {}".format(spi_max_speed))
    print("V-Ref = {0}, Resolution = {1}".format(vref, resolution))
    print("SPI device = {0}".format(CE))
    print("-----------------------------\n")

    while True:
        # average three readings to get a more stable one
        channeldata_1 = read_mcp3002(0) # get CH0 input
        sleep(2)
        channeldata_2 = read_mcp3002(0) # get CH0 input
        sleep(2)
        channeldata_3 = read_mcp3002(0) # get CH0 input
        channeldata = (channeldata_1+channeldata_2+channeldata_3)/3
        #
        # Voltage = (CHX data * (V-ref [= 3300 mV] * 2 [= 1:2 input divider]) / 1024 [= 10bit resolution]
        #
        voltage = int(round(((channeldata * vref * 2) / resolution),0))+ calibration
        if DEBUG : print("Data (bin)    {0:010b}".format(channeldata))
        print("Voltage (mV): {}".format(voltage))
        if DEBUG : print("-----------------")


except KeyboardInterrupt: # Ctrl-C
    if DEBUG : print "Closing SPI channel"
    spi.close()

def main():
    pass

if __name__ == '__main__':
    main()
Note, at first sight, the code may look complex to you, but that is more because of the large amounts of comments and debug statements.
The datasheet of the ADC and the comments in the source should give you a good idea on how this works. If you want to use another ADC, this code should be relatively easy to adapt.
Once downloaded to your Pi, you start the program with:

Code: Select all

python Measure_5V.py
and this is the result:
MCP3002 Single Ended CH0 Read of the 5V Pi Supply
SPI max sampling speed = 1000000
V-Ref = 3300.0, Resolution = 1024
SPI device = 0
-----------------------------

Voltage (mV): 5052
Voltage (mV): 5072
Voltage (mV): 5078
Voltage (mV): 5065
Voltage (mV): 5059
Voltage (mV): 5065
Voltage (mV): 5059
Voltage (mV): 5072
Voltage (mV): 5072
Voltage (mV): 5072
Voltage (mV): 5078
Voltage (mV): 5072
Voltage (mV): 5065
Voltage (mV): 5072
I used a DMM to set the calibration constant in the code, and I use a very stable variable supply with 2.5Amps, set to 5V21 before it goes through a short and solid USB cable. The voltage drop is still about 150mV by the time you measure it at the P1 connector.
Because I use the "old" Pi, it also has the poly fuse in the circuit. Still, despite the solid supply, there is still a variation in the 5V as you can see. (I use my Pi headless with a WIFI adapter)

I hope this will help you to trace and monitor your supply problems, and that we will see a lot fewer issues with this, now that you have a simple way to measure what voltage actually reaches the Pi. At the same time, I hope you learned how easy it actually is to add analog inputs to the Pi. I hope to do the same in a while to add analog outputs as well, so stay tuned.

With this program as the basis, it should now be easy for you to add data logging, warnings and other bells and whistles, to keep an eye on that dreaded 5V supply, all to your harts delight.

Enjoy!
Last edited by paulv on Tue Apr 19, 2016 9:23 am, edited 3 times in total.

User avatar
davidcoton
Posts: 5875
Joined: Mon Sep 01, 2014 2:37 pm
Location: Cambridge, UK
Contact: Website

Re: HOWTO: Add an analog input to the Pi

Sun Oct 25, 2015 9:53 pm

Let’s examine the precision of what we will be able to measure, and how that works. With a 3V3 reference voltage, and a 10-bit resolution, we will get a precision of 3V3 divided by 10-bits or 1024 different values. 3V3 is 3300 mV, and 3300mV / 1024 gives a 3.22mV resolution for every bit, and that gives a measurement precision of 3.22mV / 3300mV = 0.097% This is more than enough.
Don't think you will get anywhere near 0.1% accuracy with a breadboard circuit. 1% accuracy (30mV) will be about the limit -- even if you can calibrate against a 0.1% accuracy lab voltmeter. Resolution (number of digits) does not equate to accuracy. You may still be able to resolve comparative changes of the order of say 10mV, but not better than that.
If you connect the CH0 input of the ADC directly to the 3V3 supply (with of without the resistor divider), you will see that this supply is very stable, with a minimum amount of variation. I will leave it up to you to change the program to also measure the 3V3 supply with the other ADC input, so you have a way to measure and report both voltages.
Of course it is. You are using the 3V3 supply as Vref, so any ripple will affect the measured voltage and the reference equally. Hence a rock steady reading, whatever is actually happening. The reservoir capacitors will not help in this respect, they will only remove some noise from the whole 3V3 line (not quite true, but near enough in this case). However, assuming that the 3V3 rail is stable, measurements of the 5V rail are valid and will show actual variations.
Location: 345th cell on the right of the 210th row of L2 cache

paulv
Posts: 564
Joined: Tue Jan 15, 2013 12:10 pm
Location: Netherlands

Re: HOWTO: Add an analog input to the Pi

Mon Oct 26, 2015 6:25 am

Hi David,

You are right in both cases, of course. To not mislead others, I added the word "theoretical" to the precision calculation.

I also deleted the paragraph about measuring the 3V3 supply with this setup. When I worked on this application, I used a seperate reference supply, which is why I could also measure the 3V3, but when writing the post I decided to simplify the circuit and reduce components. The focus for this post should be on measuring and tracking the 5V.

Thanks!

cw21
Posts: 1
Joined: Mon Apr 20, 2015 3:55 pm

Re: HOWTO: Add an analog input to the Pi

Mon May 09, 2016 2:30 pm

Hi,

Found these few tutorials to be very useful, both the pwm and the adc.

Just on the adc - I was trying to read of channel 1 but I am getting a zero reading back. Channel 0 is reading fine. I changed the channels in the code (from 0 to 1) but still not working.

Am I missing something in the coding? Any help would be appreciated.

Thanks

paulv
Posts: 564
Joined: Tue Jan 15, 2013 12:10 pm
Location: Netherlands

Re: HOWTO: Add an analog input to the Pi

Mon May 09, 2016 7:43 pm

Hi cw21,

All you should have to do to use channel 1 instead of channel 0 is to change these calls in the code :

Code: Select all

channeldata_1 = read_mcp3002(0)
into this:

Code: Select all

channeldata_1 = read_mcp3002(1)
And connect your analog input to the channel1 input of the ADC.

If you already did all that, the only other explanation I can offer is that of a hardware connection issue or a chip failure. If channel0 works, you can already eliminate all the other possible issues.

The good news is that there's no magic involved, it's actually all very simple so check everything carefully and give it another try.

Good luck!

HanyTalaatRamadan
Posts: 3
Joined: Fri Dec 22, 2017 11:01 pm

Re: HOWTO: Add an analog input to the Pi

Sat Dec 23, 2017 12:19 am

Hi ,

i'm new to Current monitor and analog sensor with Raspberry

i'm using SCT-013 with Raspberry Pi 3 and connected to MCP3008 to read analog input from SCT-013 to calculate the Current Amp , i followed datasheet to wire MCP3008 to Raspberry and SCT-013 connected to CH0 .

i tried Python code you provided but i got 1308 mV all the time while i'm connected to 100 Watt lamp and AC 240 V even if the sensor caliper is not connected to Elec. cable !

can you explain to me how to use

Return to “Automation, sensing and robotics”