2012-09-05

Raspberry Pi hardware SPI analog inputs using the MCP3008

A hardware SPI remake of the bit-banged Adafruit project:  Analog Inputs for Raspberry Pi Using the MCP3008.


Take a look at the Adafruit project and particularly the datasheet for the MCP3008 - what we're making is a hardware volume control using a 10K potentiometer. Instead of using the GPIO pins and bit-banging the SPI protocol I'm using the proper SPI pins and the hardware driver.

Hardware


The connections from the cobbler to the MCP3008 are as follows:
  • MCP3008 VDD -> 3.3V (red)
  • MCP3008 VREF -> 3.3V (red)
  • MCP3008 AGND -> GND (orange)
  • MCP3008 CLK -> SCLK (yellow)
  • MCP3008 DOUT -> MISO (green)
  • MCP3008 DIN -> MOSI (yellow)
  • MCP3008 CS -> CE0 (red)
  • MCP3008 DGND -> GND (orange)

Operating system and packages

This project was built using Occidentalis v0.2 from Adafruit which takes the hassle out of fiddling with Linux. It comes with the hardware SPI driver ready to go. (It also has super-simple wifi setup if you have a dongle like the Edimax EW-7811UN). A couple of packages are required to complete this project. Firstly the mp3 player:
sudo apt-get install mpg321

and secondly the Python wrapper for SPI:
cd ~

git clone git://github.com/doceme/py-spidev

cd py-spidev/

sudo python setup.py install

Talking to the MCP3008

With the bit-banging code you're in control of the chip-select, clock, in and out pins and so you can effectively write and read a single bit at a time. When you use the hardware driver you talk using 8-bit words and it takes care of the lower level protocol for you. This changes the challenge slightly because the MCP3008 uses 10-bits for the values giving a range from 0 to 1023. Thankfully the Python wrapper is excellent and the datasheet has good documentation:

So from the diagram above you can see that to read the value on CH0 we need to send 24 bits:

.... ...s S210 xxxx xxxx xxxx
0000 0001 1000 0000 0000 0000

Let's say the current value is 742 which is 10 1110 0110 in 10-bit binary. This is what is returned in B9 to B0 in the diagram. The driver and Python wrapper returns this as 3 8-bit bytes as seen above, the mildly confusing thing is that you'll get some bits set where the question marks are in the diagram which you have to ignore (if you were sending a continuous stream these would be more useful). The 24 bits returned will be something like this:

???? ???? ???? ?n98 7654 3210
0110 0111 1000 0010 1110 0110

The python wrapper will give you 3 ints in a list:

[103,130,230]

So, we ignore the first int and then mask out all the bits apart from the last two of the second int by anding it with 3 (0000 0011). In this case you get 0000 0010. We then shift this left by 8 positions to give 10 0000 0000 and then add the third int to give 10 1110 0110 which is 742 decimal. Here's the Python for that:

# read SPI data from MCP3008 chip, 8 possible adc's (0 thru 7)
def readadc(adcnum):
        if ((adcnum > 7) or (adcnum < 0)):
                return -1
        r = spi.xfer2([1,(8+adcnum)<<4,0])
        adcout = ((r[1]&3) << 8) + r[2]
        return adcout
The rest of the code is pretty much the same as the Adafruit example. My version is available on git here: https://github.com/jerbly/Pi/blob/master/raspi-adc-pot.py

Run it

Start up mpg321 with your favourite mp3 in the background and then run the Python code:
mpg321 song.mp3 &

sudo python raspi-adc-pot.py
Turn the pot to adjust the volume.


Extra goodies

Combine this with the TextStar screen and the Python code from my previous blog entry: Raspberry Pi with TextStar serial LCD to have a nice bar graph for the pot position. Just use this method on one of the pages and in the on_tick handler so it updates every 0.1 seconds:

# Add this to the display class
    def capped_bar(self, length, percent):
        self.ser.write(ESC+'b'+chr(length)+chr(percent))

s = spidev.SpiDev()
s.open(0,0)

def get_val():
    r = s.xfer2([1,128,0])
    v = ((r[1]&3) << 8) + r[2]
    return v

def write_pots():
    display.position_cursor(1, 1)
    val = get_val()
    percent = int(val/10.23)
    display.ser.write(str(val).ljust(16))
    display.capped_bar(16, percent)


14 comments:

Adrian Blake said...

great work man!

Anonymous said...

Thanks for this excellent tutorial. Its just what I was looking for.

Unknown said...

AWESOME.

This is exactly what I needed to help me get to grips with SPI. After reading the datasheet of my ADC and making the necessary changes I have the example working - thanks to you :)

Anonymous said...

great man thankss...
how can i change the speed of spi?
byebye continue with the great job...

Michael Horne said...

Any idea why my reading would be stuck at 100%?

RasPi.TV said...

This works a treat - thank you Jeremy :) It would have taken me a long time to work through the datasheet (and I'm not completely sure I would have come out with a working result).

Mike - I'd put money on it being a wiring error - possibly the potentiometer wires?

Alex RasPi.TV

Maria Land said...

The install command returns a fatal error:

$ sudo python setup.py install
running install
running build
running build_ext
building 'spidev' extension
gcc -pthread -fno-strict-aliasing -DNDEBUG -g -fwrapv -O2 -Wall -Wstrict-prototypes -fPIC -I/usr/src/linux/include -I/usr/include/python2.7 -c spidev_module.c -o build/temp.linux-armv6l-2.7/spidev_module.o
spidev_module.c:20:20: fatal error: Python.h: No such file or directory
compilation terminated.
error: command 'gcc' failed with exit status 1


Any idea what I'm doing wrong?

Jeremy Blythe said...

Are you running the Adafruit Occidentalis v0.2 distro? I haven't tried this with anything else.

Maria Land said...

I use the standard distro. I see now what I need to do: the development tools are not installed. Doing this: sudo apt-get install python-dev solved the problem. Thanks for your help, and your excellent blog!

Maria Land said...

It is possible to set the mode and speed from Python?

Anonymous said...

Very nice job, thank you for sharing! I just realised that sometimes (happened 3 times in the last 3 days) using this SPI method to read out MCP3008, freezes the PI. In such cases I hae to unplug and restart :(

I read the values continuously in a loop. What can be the problem? Thanks!

Anonymous said...

Hi And thankyou for a nice description, this tutorial was very helpful. But i wounder Howe to set clock speed on SPI.

Anonymous said...

Thank you very much, you tutorial helped me alot! SPI puzzled me at first but now, it's clear.

m0xpd said...

Jeremy

Thanks for the inspiration!

I am trying to achieve higer sample rates - at the moment I'm limited to c. 7k samples per second. I know I am not limited by the SPI clock rate.

Any idea where the bottleneck might be?

My code and a fuller explanation are here