Raspberry Pi Pico project 2 - MCP3008
This post show you how to drive the MCP3008 8-channel ADC using a Raspberry Pi Pico. I'll start with a personally embarrassing and annoyingly relevant story. I'll also tell you about a minor 'gotcha' when using SPI on the Pico, and how you can avoid it. Finally, I've include the code and fritzing diagram I used to test the interface.
First, the story.
Back in 2012, when the Raspberry Pi was fresh and new, I was running a startup called Quick2Wire. We made add-on boards for the Pi. Just before Christmas we sent out our first batch of boards to a small army of beta-testers.
The beta boards didn't work.
Somehow a single trace on the pcb had got deleted and one of the GPIO pins was isolated. The boards were still usable and it wasn't too hard to solder in a jumper wire to replace the missing trace, but it was very embarrassing.
It wasn't just that we'd shipped boards with a defect. The really annoying thing was that I'd set up an automated loop-back test harness, but in a fit of over-confidence I just did a quick manual test on the board before we sent them out.
The automated test would have caught the problem which my quick manual test failed to spot.
I'll come back to loop-back testing below.
Back to the Pico
One of my goals for my Pico mini-projects was to verify that I could drive I2C and SPI chips using the Pico's MicroPython. I2C and SPI protocols are really useful.They let you drive all kinds of interesting sensors and actuators, and that means that they enable lots and lots of interesting applications.
There are an SPI example in the excellent Getting Started guide, and it gave me some useful ideas. There's a little more information in the Pico Python SDK documentation, and yet more clues in the Pico pinout diagram. I also found a CircuitPython driver for the MCP3008 on Adafruit's GitHub repository.
I reckoned that I now had enough information to wire up and drive an MCP3008 chip. The MCP3008 is an 8-channel ADC (analogue-to-digital) converter. The Pico has 3 ADC pins, but sometimes you need more. The 3008 has a simple, well documented SPI interface, and I had a few in my parts bin, so it was an obvious choice for the test.
I quickly wrote some code, and sure enough it worked. I posted it as a gist on GitHub and tweeted about it.
Some time later that day I got a comment from Matt Trentini, one of the members of a Facebook group that covers the Pico and other MicroPython controllers. He suggested a small refactoring to the code. It clearly made the code better, and I decided to implement it.
Loopback tests
SPI defaults on the PICO
The Pico has two hardware SPI busses, and you can access each buss from several different pins. An SPI chip needs three data inputs and one chip selection input. The data inputs are traditionally called SCK, MISO and MOSI. SCK carries a clock signal. MISO carries data from the chip to the Pico, and MSOI carries data from the Pico to the chip. Because the Pico can be a controller or be controlled by SPI, the Pico's pinout diagram calls MISO SPI RX and calls MOSI SPI TX.
""" MicroPython Library for MCP3008 8-channel ADC with SPI Datasheet for the MCP3008: https://www.microchip.com/datasheet/MCP3008 This code makes much use of Adafruit's CircuitPython code at https://github.com/adafruit/Adafruit_CircuitPython_MCP3xxx adapted for MicroPython. Tested on the Raspberry Pi Pico. Thanks, @Raspberry_Pi and @Adafruit, for all you've given us! """ import machine class MCP3008: def __init__(self, spi, cs, ref_voltage=3.3): """ Create MCP3008 instance Args: spi: configured SPI bus cs: pin to use for chip select ref_voltage: r """ self.cs = cs self.cs.value(1) # ncs on self._spi = spi self._out_buf = bytearray(3) self._out_buf[0] = 0x01 self._in_buf = bytearray(3) self._ref_voltage = ref_voltage def reference_voltage(self) -> float: """Returns the MCP3xxx's reference voltage as a float.""" return self._ref_voltage def read(self, pin, is_differential=False): """ read a voltage or voltage difference using the MCP3008. Args: pin: the pin to use is_differential: if true, return the potential difference between two pins, Returns: voltage in range [0, 1023] where 1023 = VREF (3V3) """ self.cs.value(0) # select self._out_buf[1] = ((not is_differential) << 7) | (pin << 4) self._spi.write_readinto(self._out_buf, self._in_buf) self.cs.value(1) # turn off return ((self._in_buf[1] & 0x03) << 8) | self._in_buf[2]
from machine import Pin, SPI from time import sleep, sleep_ms from mcp3008 import MCP3008 spi = SPI(0, sck=Pin(2),mosi=Pin(3),miso=Pin(4), baudrate=100000) cs = Pin(22, Pin.OUT) cs.value(1) # disable chip at start square = Pin(21, Pin.OUT) chip = MCP3008(spi, cs) while True: square.value(1) actual = chip.read(0) print(actual) sleep(0.5) square.value(0) actual = chip.read(0) print(actual) sleep(0.5)
Great to hear from you again. I'm still a Quick2Wire user and I'll give this a try.
ReplyDeleteTony Goodhew
I'm glad we're back in touch - and thanks again for the great instructable!
Delete