Skill Level: Intermediate
Table Of Contents
- Introduction
- What Is Needed
- Background Information
- Building The Circuit
- Installing The Libraries
- Writing The Program
- Running And Testing The System
- Additional Resources
- Summary
Introduction
This tutorial will teach you how to add analog inputs to your Raspberry Pi. This will be accomplished by interfacing an MCP3008 analog-to-digital converter (ADC) to the Raspberry Pi and using CircuitPython to access the ADC’s analog channels.
A basic understanding of electronics and programming is expected along with some familiarity with the Raspberry Pi platform. If you are new to Raspberry Pi or CircuitPython, or would just like to refresh your knowledge, please see our Blink: Making An LED Blink On A Raspberry Pi and Getting Started With CircuitPython On Raspberry Pi With Blinka tutorials before proceeding with this one. In addition, this tutorial will use a solderless breadboard to build a circuit from a schematic diagram. The All About Circuit’s Understanding Schematics, SparkFun’s How to Read a Schematic, Core Electronics’ How to Use Breadboards, and Science Buddies’ How to Use a Breadboard guides are good resources for learning how to translate a schematic to a breadboard.
The resources created for this tutorial are available on GitHub for your reference.
What Is Needed
- Raspberry Pi Running Raspberry Pi OS Or Similar Linux Based OS (available on Raspberry Pi Foundation and Adafruit)
- Optional Raspberry Pi GPIO Breakout Board (available on Adafruit and CanaKit)
- Solderless Breadboard (available on Adafruit and SparkFun)
- Preformed Breadboard Jumper Wire Kit (available on SparkFun and CanaKit)
- 6 x Male/Female Jumper Wires (available on Adafruit and Arrow)
- MCP3008 8-Channel 10-Bit ADC With SPI Interface (available on Adafruit and Arrow)
- 2 x 50 KΩ Linear Potentiometers (any value between 1 KΩ and 100 KΩ should work, available on Adafruit and Sparkfun)
Background Information
An analog voltage signal is a continuous voltage waveform that varies over time. That is opposed to a digital voltage signal that only has discrete voltage levels, or values. Many sensors that measure a physical quantity (such as light, sound, temperature, pressure, etc.) provide analog voltage signals. There are also various circuits that produce analog signals where we may want to measure the voltage across a particular component. Some examples of these might be a function generator, the output of a potentiometer, or the voltage level of a battery.
The Raspberry Pi is an amazing piece of hardware. It, along with the Arduino, are the de facto standard development boards used among the maker community. The Raspberry Pi can do a lot of things that are not possible on the Arduino, but there is one popular feature that is available on the Arduino that the Raspberry Pi does not have, that is analog inputs. However, we can add this capability to the Raspberry Pi by interfacing an external analog-to-digital converter (ADC) chip.
An ADC is an electronic component that converts analog voltage signals into digital values. These values can then be read by the host system. In this tutorial, we will use a stand-alone ADC integrated circuit, the MCP3008 by Microchip, and interface it to the Raspberry Pi via the SPI bus. The MCP3008 is an 8-channel 10-bit ADC, meaning it provides 8 individual analog inputs at 10-bit resolution. It also has the capability of running in a pseudo-differential mode where the single-ended inputs can be paired together to provide differential inputs, the voltage difference of the pairs. We will use both the single-ended and pseudo-differential modes in this tutorial.
My development board is the Raspberry Pi 3 Model B running the Raspberry Pi OS operating system. I am also using an optional GPIO breakout board to make the wiring between the Raspberry Pi and the breadboard a little easier. If you are using a different Raspberry Pi model or a different OS that is similar to Raspberry Pi OS, the vast majority of this tutorial should still apply, however, some minor changes may be necessary.
If you need assistance with your particular setup, post a question in the comments section below and I, or someone else, can try to help you.
Building The Circuit
Before connecting any circuitry to your Raspberry Pi, shutdown and disconnect it from power. This avoids accidental damage during wiring.
Place the components and wire up the circuit on a breadboard according to the schematic diagram shown below and then connect it to your Raspberry Pi.
The circuit should look similar to the one shown below once completed. Note, I am using a GPIO to breadboard interface board to make the wiring simpler, but this is not strictly necessary. I also used two 50 KΩ panel mountable potentiometers in my circuit because that is what I had available. Any trim or panel mountable potentiometers between 1 KΩ and 100 KΩ should work just fine. However, keep in mind, the lower the resistance values, the more power they will consume.
Once the circuit is built, connect power to your Raspberry Pi and boot it up.
Installing The Libraries
I assume you are already comfortable installing and using the Raspberry Pi OS so I won’t go into those details, but it is safe to say, you should be running a very recent and stable OS version.
We will now install the libraries required by our program. Since we will be creating a CircuitPython program, the first library we need to install/upgrade is Blinka itself. Open a terminal window on your Raspberry Pi and execute the following command.
$ pip3 install --upgrade Adafruit-Blinka
Fortunately, there is already a CircuitPython library available for use with the MCP3008 ADC, named Adafruit CircuitPython MCP3xxx. Install this library as well.
$ pip3 install adafruit-circuitpython-mcp3xxx
Writing The Program
Open your favorite text editor on the Raspberry Pi and create a CircuitPython program named mcp3008.py with the code shown below.
#!/usr/bin/env python3 from time import sleep import board import busio import digitalio import adafruit_mcp3xxx.mcp3008 as MCP3008 from adafruit_mcp3xxx.analog_in import AnalogIn import RPi.GPIO SPI = busio.SPI(clock=board.SCK, MISO=board.MISO, MOSI=board.MOSI) MCP3008_CS = digitalio.DigitalInOut(board.D22) MCP3008_SPI = MCP3008.MCP3008(SPI, MCP3008_CS) ADC_CH0 = AnalogIn(MCP3008_SPI, MCP3008.P0) ADC_CH1 = AnalogIn(MCP3008_SPI, MCP3008.P1) ADC_DIFF = AnalogIn(MCP3008_SPI, MCP3008.P1, MCP3008.P0) print("Press CTRL-C to exit.") try: while True: print(f"ADC CH0: {ADC_CH0.voltage:4.2f} V ({ADC_CH0.value:5d})") print(f"ADC CH1: {ADC_CH1.voltage:4.2f} V ({ADC_CH1.value:5d})") print(f"ADC DIFF: {ADC_DIFF.voltage:4.2f} V ({ADC_DIFF.value:5d})") print() sleep(1) except KeyboardInterrupt: RPi.GPIO.cleanup() print("\nCleaned up GPIO resources.")
Let’s take a look at some of the more interesting parts of the code.
Lines 7-8 import the relevant code needed from the adafruit-circuitpython-mcp3xxx library.
Lines 11-13 establish the SPI bus and enable the connection to the external MCP3008 chip.
Lines 14 and 15 create two single-ended analog input channels. Line 16 creates one differential input channel (CH1-CH0) using those same single-ended channels. These channels are then read later in the program.
Lines 21-23 print the channel values in both register (16-bit integer) and voltage representations.
If there is something that needs further explanation, please let me know in the comment section and I will try to answer your question.
Save your program when you are done editing.
Running And Testing The System
Now that our circuit is built and our software is written, it is time to run and test our program.
Run the following command to make the CircuitPython program an executable
$ chmod a+x mcp3008.py
and then run the program.
$ ./mcp3008.py
You should see the following printed to the screen.
Press CTRL-C to exit. ADC CH0: 1.00 V (19776) ADC CH1: 2.50 V (49600) ADC DIFF: 1.50 V (29760)
The analog values will be printed to the screen once every second. The numbers in parentheses are the 16-bit integer representations of the measured voltages.
Adjust the potentiometers and watch the reported analog values changing. The DIFF channel will display the difference between CH1 and CH0. Please note that the adafruit-circuitpython-mcp3xxx library does not register negative values, so if CH1 has a lower voltage than CH0, a value of 0.00 will be shown.
Press CTRL-C to exit the program when you are done.
Additional Resources
The following is a list of additional resources you may find helpful.
- Analog signal on Wikipedia (https://en.wikipedia.org/wiki/Analog_signal)
- Analog-to-digital converter on Wikipedia (https://en.wikipedia.org/wiki/Analog-to-digital_converter)
- Microchip MCP3008 Product Page (https://www.microchip.com/wwwproducts/en/MCP3008), including datasheet
- Adafruit MCP3008 Product Page (https://www.adafruit.com/product/856), including datasheet
- Adafruit MCP3008 – 8-Channel 10-Bit ADC With SPI Interface Learning Guide (https://learn.adafruit.com/mcp3008-spi-adc)
- Adafruit_CircuitPython_MCP3xxx Library Documentation (https://circuitpython.readthedocs.io/projects/mcp3xxx/)
- Adafruit_CircuitPython_MCP3xxx Library GitHub Repository (https://github.com/adafruit/Adafruit_CircuitPython_MCP3xxx)
Summary
In this tutorial, we learned how to add analog inputs to our Raspberry Pi by interfacing a stand-alone MCP3008 ADC using CircuitPython.
Specifically, we learned
- what an analog-to-digital converter is and why we may want to use it,
- how to construct a circuit with an MCP3008 ADC and connect it to the Raspberry Pi,
- how to install the appropriate libraries to enable MCP3008 support, and
- how to write and execute a CircuitPython program on the Raspberry Pi that accesses the connected MCP3008’s analog input channels.
The final source code and schematic used for this tutorial are available on GitHub. The GitHub version of the code is commented to include additional information, such as the program’s description, circuit connections, library references, code clarifications, and other details. The comments are also Sphinx compatible in case you want to generate the code documentation.
Thank you for joining me along this journey and I hope you enjoyed the experience. Please feel free to share your thoughts in the comments section below.
Many thanks John. I am an elderly Ham-Radio Operator and was looking to build my own Raspi-based Antenna Controller and was having trouble with the analog aspects. So many of the articles are either ancient or bug-ridden; but with yours it all came clear. It is working reliably now. Thanks again
Graham a.k.a. G4FUA
You are very welcome, glad I could help. Thank you for taking the time to share your experience.
Thanks for the guide. I’m curious if I’d be able to drive more than one MCP3008 to expand this to 16 or 24 channels (ultimately 64 channels as I’m working on a home panel meter). Would driving all of them off of the SPIO pins and then connecting each unit’s SHDN output to a DIO port and reading them accordingly work?
I believe that would work just fine. You can have many devices interfaced to a single SPI bus, you just need different select pins (SHDN) for each device.
I believe the MCP3008 will accept 5V, does the PI not provide 5V also? It seems better to use the 5V, since that will give a wider range of input voltages. Just wondering why that wasn’t done, because I am thinking of porting a project from Arduino to the PI, and it’s reference is 5V which would mean re-doing some calculations for the 24V sampling I am doing.
The supply voltage (Vdd) of the MCP3008 is connected to 3.3 V in order for its SPI pins to communicate directly, using the same voltage, with the Raspberry Pi. According to the MCP3008 datasheet, the reference voltage (Vref) pin can not be supplied a larger voltage than Vdd. If you want to use a reference voltage of 5 volts, I suggest you use a level shifter for the SPI pins.
Another option might be to use a voltage divider on the analog input to translate your original 24 V samples down to the 3.3 V currently used by the MCP3008 in this design. 1.6K and 10K resistors should work in this scenario.
The idea is vers simple , you can use for every o/p à transistor PNP and choose its base resistor to use the transistor in SATURATION mode , and connect the COLLECTOR voltage TO 5V , and I/P signal to its BASE with 3,3V.
Thank you for this tutorial. I still have my Timex/Sinclair 2068. Back in those days I put a Timex/Sinclair 1000 in a stage lighting controller, with A/D’s and D/A’s to communicate with the TS1000.
Wow, that is awesome! Thank you for sharing.