0

I am currently working on a project that requires me to read analog voltages for 16 different sensors and convert them to angle readings. To accomplish this I decided to utilize the MCP3008 ADC chip along with a rpi and python as that was something that I had done before for other projects that required a similar process. unlike my previous project however, I have to use 2 MCP3008 chips as they can only read 8 channels each. I know that this should be possible based on the way that spi communication works and the way that the MCP3008 chips work but I am running in to some problems getting both chips to work simultaneously. To preface, I am using adafruits mcp3xxx library to work with the chips. The first thing that I tried was putting both chips on the same spi bus using different chip select. the code that I used for that looked something like this

import busio
import digitalio
import board
import time
import adafruit_mcp3xxx.mcp3008 as MCP
from adafruit_mcp3xxx.analog_in import AnalogIn
# this function is used to calculate angle of displacement
def Angle(Vmin,Vmax,R,Vt):
    return ((Vt-Vmin)/(Vmax-Vmin))*R
# create the spi bus
spi = busio.SPI(clock=board.SCK, MISO=board.MISO, MOSI=board.MOSI)

#create the chip select(s)
cs0 = digitalio.DigitalInOut(board.CE0)
cs1 = digitalio.DigitalInOut(board.CE1)



#create the mcp object(s)
mcp0 = MCP.MCP3008(spi,cs0)
mcp1 = MCP.MCP3008(spi,cs1)


#create the analog channels
chan0_0 = AnalogIn(mcp0, MCP.P0)
chan1_0 = AnalogIn(mcp1, MCP.P0)


R_elbow = 180;
from CalibratedVoltages import*


while True:
#     A_elbow = Angle(VminElbow,VmaxElbow,R_elbow,chan0_0.voltage)
#     time.sleep(1)
#     A_knee = Angle(VminKnee, VmaxKnee, R_knee,chan1_0.voltage)
#     print('elbow angle:', A_elbow,'degrees')
     print('0',chan0_0.voltage)
     print('1',chan1_0.voltage)
     time.sleep(2)
     print('update')

With this setup however, any readings taken on the mcp1 object were dependent on the readings taken on the mcp0 object for some reason. For example, if you were to take a reading on channel 0 of mcp1 instead of ranging from 0 to 3.3 volts as it should have it ranged from 0 to whatever was being read on channel 0 of the mcp0 object. This was the same for all of the channels on the mcp1 object.

Unable to figure out why this was happening, I decided to try to simply put each MCP3008 chip on a separate spi bus to prevent interference due to a shared bus which is what I assumed was the causing the error. I did a dtoverlay to enable the spi1 bus and then changed my code to something more like this.

import busio
import digitalio
import board
import time
import adafruit_mcp3xxx.mcp3008 as MCP
from adafruit_mcp3xxx.analog_in import AnalogIn
# this function is used to calculate angle of displacement
def Angle(Vmin,Vmax,R,Vt):
    return ((Vt-Vmin)/(Vmax-Vmin))*R
# create the spi bus
spi0 = busio.SPI(clock=board.SCK, MISO=board.MISO, MOSI=board.MOSI)
spi1 = busio.SPI(clock=21, MISO=19, MOSI=20)

#create the chip select(s)
cs0 = digitalio.DigitalInOut(board.D5)
cs1 = digitalio.DigitalInOut(board.D9)



#create the mcp object(s)
mcp0 = MCP.MCP3008(spi0,cs0)
mcp1 = MCP.MCP3008(spi1,cs1)


#create the analog channels
chan1_0 = AnalogIn(mcp1, MCP.P0)
chan0_0 = AnalogIn(mcp0, MCP.P0)
#chan0_1 = AnalogIn(mcp0, MCP.P1)
#chan0_2 = AnalogIn(mcp0, MCP.P2)
#chan0_3 = AnalogIn(mcp0, MCP.P3)
#chan0_4 = AnalogIn(mcp0, MCP.P4)
#chan0_5 = AnalogIn(mcp0, MCP.P5)
#chan0_6 = AnalogIn(mcp0, MCP.P6)


R_elbow = 180;
from CalibratedVoltages import*


while True:
#     A_elbow = Angle(VminElbow,VmaxElbow,R_elbow,chan0_0.voltage)
#     time.sleep(1)
#     A_knee = Angle(VminKnee, VmaxKnee, R_knee,chan1_0.voltage)
#     print('elbow angle:', A_elbow,'degrees')
     print('0',chan0_0.voltage)
     print('1',chan1_0.voltage)
#     print('2',chan0_2.voltage)
#     print('3',chan0_3.voltage)
#     print('4',chan0_4.voltage)
#     print('5',chan0_5.voltage)
#     print('6',chan0_6.voltage)
     time.sleep(2)
     print('update')

With this set up, only the MCP chip on the spi1 bus works and the other chip returns 0.0 on all channels regardless of what voltage is attached to it. Even if you change the code back to only utilizing the spi0 bus, the chip on the spi0 bus continues to return all 0.0 readings until and unless I reboot the rpi and run the code for just spi0 bus readings without ever running any of the lines that set up anything for the spi1 bus. At this point I am fairly lost and can only assume that it has something to do with one of the library's that I am using and I chose the MCP3008 chips because I knew there was an easy to use library available from adafruit and I wanted to avoid having to put a lot of effort in to this part of the project specifically. If anyone has had this same kind of issue and knows how to solve it or even just take a guess it would be very appreciated. A solution to either of the problems that were described would allow me to finish the project.

2 Answers 2

0

I did the same thing, and got the same results.

The best tool for debugging this is called 'piscope' and can be found here: http://abyz.me.uk/rpi/pigpio/piscope.html

Here is the answer - DO NOT USE CE0.

Explanation: For some reason when using the adafruit libraries, whenever an spi call happens CE0 goes low - no matter which device/chip select is configured.

This can be seen in piscope by watching the gpio pins. The correct cs pin goes low - but then a little bit later, CE0 also goes low, making both spi devices active and creating a collision situation.

I followed the typical logic when laying out my pcb, and made my two devices use cs CE0 and CE1, as logic would say to, because these are the defaults.

I don't know if it is the adafruit library or the actual pi kernel that creates the collision situation, but to fix this, I lifted pin D8 from my chip, and connected to D20 - an unused pin.

Then I set the cs configurations in python as expected: cs1 = digitalio.DigitalInOut(board.D7) cs2 = digitalio.DigitalInOut(board.D20)

and everything worked perfectly - 16 channels of independent voltage readings.

Watching in piscope showed what I expected: for every spi call, CE0 goes low.

Solution: Do not use CE0 in multiple spi device designs.

I also tested using 2 additional software configured only devices, for a total of 4 separate spi calls with 4 gpio pins, without D8. Each call caused CE0 to be pulled low, but when not connecting CE0 in hardware, the spi calls acted normal, showing (tentatively) that any number of devices can be attached to the spi bus with the adafruit libraries.

Sign up to request clarification or add additional context in comments.

1 Comment

hey fry, thank you for this help, one thing I was going to try was to simply manually set gpio pins high or low instead of using the built in cs function but given that this works it seems like a much better option. That being said, i'm a little bit confused about how the particle stuff is working in your code. CE0 and CE1 correspond to GPIO7 and GPIO8 not D7 and D8, and D20 seems to simply not exist, am I missing something extremely basic about how you have yours functioning ? pic related. link
0

Setting the gpio manually will not work.

I tried manual manipulation of the GPIO, but CE0 always overrides even manual gpio conditions. The CE0 override is somewhere in the adafruit library or possibly the pi kernel for spi driver.

There are several ways to address pins, and I never use the particle method. I read somewhere the particle identifiers are for very high level processor type people, which I am not. I use the adafruit example level code

import RPi.GPIO as GPIO
# create the spi bus
spi = busio.SPI(clock=board.SCK, MISO=board.MISO, MOSI=board.MOSI)

# create the cs (chip select)
cs1 = digitalio.DigitalInOut(board.D7)
cs2 = digitalio.DigitalInOut(board.D20)

# create the mcp object
mcp1 = MCP.MCP3008(spi, cs2)
mcp2 = MCP.MCP3008(spi, cs2)

To use the GPIO designations, just change the GPIO part of GPIOxx to Dxx. For example, D20 is pin 38, or D15/A6 as a particle. D7 is GPIO7, pin 26, and D8 (not used) would be GPIO8 or pin 24.

Here is the full code for my thermistor test:

import busio
import math
import digitalio
from decimal import Decimal
import board
import adafruit_mcp3xxx.mcp3008 as MCP
from adafruit_mcp3xxx.analog_in import AnalogIn
import time
from time import sleep
from gpiozero import CPUTemperature
import RPi.GPIO as GPIO
GPIO.setwarnings(False)


# create the spi bus
spi = busio.SPI(clock=board.SCK, MISO=board.MISO, MOSI=board.MOSI)

# create the cs (chip select)
cs1 = digitalio.DigitalInOut(board.D7)
cs2 = digitalio.DigitalInOut(board.D20)

# create the mcp object
mcp1 = MCP.MCP3008(spi, cs1)
mcp2 = MCP.MCP3008(spi, cs2)


while True:
    
    print(mcp1)
    print("Channel 1")
    c1 = 0
    for i in range (1, 101):
        chan=AnalogIn(mcp1, MCP.P0)
        c1=c1+chan.value
    print (c1/100/64)
    sleep(.2)
    
    c1 = 0
    for i in range (1, 101):
        chan=AnalogIn(mcp1, MCP.P1)
        c1=c1+chan.value
    print (c1/100/64)
    sleep(.2)
    
    c1 = 0
    for i in range (1, 101):
        chan=AnalogIn(mcp1, MCP.P2)
        c1=c1+chan.value
    print (c1/100/64)
    sleep(.2)
    
    c1 = 0
    for i in range (1, 101):
        chan=AnalogIn(mcp1, MCP.P3)
        c1=c1+chan.value
    print (c1/100/64)
    sleep(.2)
    
    c1 = 0
    for i in range (1, 101):
        chan=AnalogIn(mcp1, MCP.P4)
        c1=c1+chan.value
    print (c1/100/64)
    sleep(.2)
    
    c1 = 0
    for i in range (1, 101):
        chan=AnalogIn(mcp1, MCP.P5)
        c1=c1+chan.value
    print (c1/100/64)
    sleep(.2)

    c1 = 0
    for i in range (1, 101):
        chan=AnalogIn(mcp1, MCP.P6)
        c1=c1+chan.value
    print (c1/100/64)
    sleep(.2)
    

    c1 = 0
    for i in range (1, 101):
        chan=AnalogIn(mcp1, MCP.P7)
        c1=c1+chan.value
    print (c1/100/64)
    
    print (" ")
    
    sleep(3)

    print(mcp2)
    
    print("Channel 2")
    
    c1 = 0
    for i in range (1, 101):
        chan=AnalogIn(mcp2, MCP.P0)
        c1=c1+chan.value
    print (c1/100/64)
  
    
    c1 = 0
    for i in range (1, 101):
        chan=AnalogIn(mcp2, MCP.P1)
        c1=c1+chan.value
    print (c1/100/64)

    c1 = 0
    for i in range (1, 101):
        chan=AnalogIn(mcp2, MCP.P2)
        c1=c1+chan.value
    print (c1/100/64)

    c1 = 0
    for i in range (1, 101):
        chan=AnalogIn(mcp2, MCP.P3)
        c1=c1+chan.value
    print (c1/100/64)

    c1 = 0
    for i in range (1, 101):
        chan=AnalogIn(mcp2, MCP.P4)
        c1=c1+chan.value
    print (c1/100/64)

    c1 = 0
    for i in range (1, 101):
        chan=AnalogIn(mcp2, MCP.P5)
        c1=c1+chan.value
    print (c1/100/64)

    c1 = 0
    for i in range (1, 101):
        chan=AnalogIn(mcp2, MCP.P6)
        c1=c1+chan.value
    print (c1/100/64)

    c1 = 0
    for i in range (1, 101):
        chan=AnalogIn(mcp2, MCP.P7)
        c1=c1+chan.value
    print (c1/100/64)
    sleep(3)
    
    
    print(" ")
    
    sleep(3)
    

I found the mcp3008 to relatively noisy, even when the input signal was clean, so I use 100 samples to average the final result. Slows things down a bit, but the results look nicer.

2 Comments

ah ok makes sense, I was just confused as to what had to be done for board.D7 to refer to GPIO 7 instead of particle D7. the way my code was set up when I used board.D9 and board.D5 it automatically was referring to particle D9 (pin 12) and particle D5 (pin 31) . Am I correct in my assumption that your code works differently due to the "import RPi.GPIO as GPIO" line? Rather unimportant, what really matters is that I don't use CE0 (pin 24 / GPIO8) as a chip select as it will automatically go low even if that is not the designated chip select line to go low, was just wondering.
The RPi.GPIO is a library that allows addressing pins by gpio designations - and I find it to be the most intuitive. It matches a lot of other device nomenclature, such as arduino, where all of the io is structured to match gpio numbering.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.