'Trouble setting up multiple mcp3008 ADC chips on rpi0 using python. Any ideas as to what is wrong are appriciated

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.



Solution 1:[1]

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.

Solution 2:[2]

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.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 fry j
Solution 2