# MCP3004 Fails when attempting to use more than one channel

#### bxdobs

Joined May 11, 2014
29

The original sketch was built to use an NTC Thermistor to monitor a remote mountaintop shack's ambient temperature ... this has been in operation for over 3 years ... we recently added some batteries with a Victron BP65 over/under voltage device ... because Victron doesn't provide an API for Bluetooth to allow monitoring of the Battery Voltage on the Pi, we were thinking we would just add a simple voltage divider to monitor the voltage using one of the unused Channels on the existing ADC. HOWEVER, for some unknown reason, the moment voltage is applied to the Vbat pin (top of the 100k pot) both Channel 0 and Channel 1 read 0V even though actual voltages of 1.2V and 2.2V respectively are being applied to the inputs of these channels ... the code listed below shows that these channels are being configured for single-ended use so completely stumped as to why this simple addition not only fails to provide any readings on Channel 1 but ALSO affects Channel 0.

Can anyone explain what I might be missing here?

The only change made was to add the 100k Pot which was pre-adjusted with a 20V input so the wiper reads 3.3V to ground ... this allows for the Pi to estimate the vBat as (ADC Val) / 1024 * 20 where ADC Val would be (Vchn1 / 3.3 * 1024) ... ADC read function unchanged

Example:
ch1 ip => 2.2V -> expected ADC reading => 683 -> estimated VBat => 13.4V

Instead, both Chn0 and 1 are returning readings of 0 until the VBat voltage is removed from the 100k pot

NOTE: pg and pgl referenced in the code are defined as follows: pg is a CLI process that allows a user to manually request current status ... pgl is a continuous loop process running in the background with a watchdog used to monitor and provide instant email notifications when readings hit or exceed set limits. An unused GPIO pin on the Pi is being used as a hardware Flag to ensure a CLI request doesn't collide with a Loop Request

Python Code:
#----------------- readADC( Channel# ) ---------------------------------------
# Microchip ADC MCP300X family has 3 variations; 2, 4 or 8 Channel 10bit A/D
# all 3 use a Serial Data Pin to Clock in Commands & Clock out Digital Data
# representing the Analog values
#

# ensure both pgl and pg don't attempt to read adc at the same time
bArf = GPIO.input(ARF[_PIN]) # is there a Read in Progress
while bArf == 1:
bArf = GPIO.input(ARF[_PIN])
# wend

GPIO.output(ARF[_PIN], GPIO.HIGH) # set Read in Progress

# Datasheet says chip select must be pulled high between conversions
GPIO.output(CS[_PIN],  GPIO.HIGH)

# Start the read with clock high and chip select low
GPIO.output(CS[_PIN],  GPIO.LOW)
GPIO.output(CLK[_PIN], GPIO.HIGH)

# ADC commands are 5 bits long:
#   start bit = 1
#   single-ended comparison = 1 (vs. pseudo-differential = 0)
#   channel num bit 2 (MSB)     (for MCP3004 this is always 0)
#   channel num bit 1
#   channel num bit 0 (LSB)
# ie for single-ended channel 0 this command should be 1 1 0 0 0 or 0x18
iAdcCmd = 0x18  # base command for Channel 0

for iBit in range(5):

# output MSB bit: HI or LO
if iAdcCmd & 0x10: # is MSB HI?
GPIO.output(MOSI[_PIN], GPIO.HIGH)
else:
GPIO.output(MOSI[_PIN], GPIO.LOW)
# endif

# Shift next bit to MSB

# Send a clock pulse HI then immediately LO
GPIO.output(CLK[_PIN], GPIO.HIGH)
GPIO.output(CLK[_PIN], GPIO.LOW)

# next

for iBit in range(12):

# Pulse clock pin
GPIO.output(CLK[_PIN], GPIO.HIGH)
GPIO.output(CLK[_PIN], GPIO.LOW)

# Read 1 data bit in
if GPIO.input(MISO[_PIN]):
# endif

# Shift in next bit

# next

# Divide by two to drop the stop bit

# Set chip select high to end the read
GPIO.output(CS[_PIN], GPIO.HIGH)
GPIO.output(CS[_PIN], GPIO.LOW)

GPIO.output(ARF[_PIN], GPIO.LOW) # clear Read in Progress

# end readACD

Last edited:

#### bxdobs

Joined May 11, 2014
29
Solved

Based on my review/analysis of the original code (which was copied, as is, from examples on github) VS the Microchip MCP3004 datasheet, suggests to me that: the CLK signal was coded incorrectly ... basically, if the clock edges are shifted to the left, this results in the Command being misinterpreted as a DIFFERENTIAL request (which could/would explain this failure ... attaching an unmatched voltage to ch 1 will wipe out both ch 0 and 1 readings)

The following analysis shows that the Command 11000 (0x18) is actually being read as 10000 (0x10) because the first/start bit is being lost due to incorrect clocking.

Code analysis:
1) CS is initially set low followed by CLK being set high
a) if CLK was low this rising edge is expected to be ignored because the start of MSB hasn't been given
b) if CLK was high then the Chip is still waiting for a falling edge to indicate the start of MSB
2) GPIO pin used for Din is set to the First BIT then the Command is Shifted Left (tossing the Start Bit)
3) CLK is set high (which is the state it is already at) so nothing changes
4) CLK is set low ... falling edge is now indicating to the chip that this is the Start of MSB
5) NEXT throws us back up to the top of the FOR loop
6) GPIO pin used for Din is NOW set to the Second BIT and Command is Shifted Left
7) CLK is set high resulting in a Rising Edge which finally Clocks in it's FIRST BIT (which is it's start bit)

As you can see the START bit has been lost ... the second bit is being interpreted as the first or Start bit ... and for Ch 0 the remainder of the Command bits will be 0

This Command puts Ch 0 and 1 in Differential mode ... as long as there is nothing (no voltage) on Ch 1 it won't cancel the value on Ch 0 but the moment an unmatched voltage is applied to ch 1 the differential mode will undoubtedly result in unexpected values

Corrected Code:
def readAdc(iChn): # MCP3004 has 4 Channels: 0 to 3

# ensure both pgl and pg don't attempt to read the ADC at the same time
# if CS is low then an ADC read is in progress
bArf = 0
while bArf == 0:
bArf = GPIO.input(CS[_PIN])
# end while

# Start the read with clock high and chip select low
GPIO.output(CLK[_PIN], GPIO.HIGH)
GPIO.output(CS[_PIN],  GPIO.LOW)

# ADC commands are 5 bits long:
#   start bit = 1
#   single-ended comparison = 1 (vs. pseudo-differential = 0)
#   channel num bit 2 (MSB)     (for MCP3004 this is always 0)
#   channel num bit 1
#   channel num bit 0 (LSB)
# ie for single-ended channel 0 this command should be 1 1 0 0 0 or 0x18
iAdcCmd = 0x18  # base command for Channel 0

# Write Command
for iBit in range(5):

# Write out 1 bit of data to Pi GPIO pin connected to Din
# output MSB bit: HI or LO
if iAdcCmd & 0x10: # is MSB HI?
GPIO.output(MOSI[_PIN], GPIO.HIGH)
else:
GPIO.output(MOSI[_PIN], GPIO.LOW)
# endif

# Shift next bit to MSB

# Falling Clock Edge to move to next bit
GPIO.output(CLK[_PIN], GPIO.LOW)
# Rising Clock Edge to clock in the data waiting on Din
GPIO.output(CLK[_PIN], GPIO.HIGH)

# next

for iBit in range(12):

# Falling Clock Edge to move to next bit
GPIO.output(CLK[_PIN], GPIO.LOW)
# Rising Clock Edge to clock out the data from Dout
GPIO.output(CLK[_PIN], GPIO.HIGH)

# Read in 1 data bit from Dout
if GPIO.input(MISO[_PIN]):
# endif

# Shift in next bit

# next

# Set chip select high to end the read
GPIO.output(CS[_PIN], GPIO.HIGH)

# Divide by two to drop the stop bit (there is no stop bit!)
# leave this code in place to provide a buffer between reads

# end readACD

Last edited:

#### twohats

Joined Oct 28, 2015
362