SPI slave randomly missing bits in response

I've connected an MCP3008 to a raspberry pi. First on a solderless breadboard, where everything worked correctly. Then on a prototyping board, where the only difference is that the chip is externally powered. The problem is now that the response from the slave is occasionally missing bits (the slave fails to pull the signal up for every bit that it should). Here is a series of readings taken at a 0.01 s interval:

[3.4, 3.39, 3.29, 0.0, 3.4, 3.4, 3.4, 2.55, 0.0, 2.98, 3.39, 0.0, 3.39, 3.3, 3.19, 3.4, 2.98, 3.4, 3.4, 0.58]

The readings are normalized for Vref = Vin = 3.4.

I've tried:

• Redoing the soldering,
• replacing the MCP3008,
• adjusting the SPI frequency (currently at 1.35 MHz),
• checking the connections and voltages using a multimeter
• using the piscope to examine the serial communication (tricky, because if I set the frequency low enough to get a good reading, the chip rarely returns any values at all)
• grounding unused inputs

The PCB has copper wire from the header to the chip for the SPI interface, about 5cm long. As far as I can see, that should be fine for SPI. SpiDev is set to use mode 0.

Any other ideas as to what might be going wrong would be hugely appreciated.

Update:

Connecting the Raspberry Pi ground to the external ground solved the issue, so it looks like the lack of common reference voltage caused the mcu to sometimes not pull high above the necessary threshold.

• Time for a real scope ... Aug 6, 2020 at 22:17
• Decoupling caps? Aug 6, 2020 at 22:20
• I have caps on all the inputs, same as on the breadboard. Aug 6, 2020 at 22:26
• @TomL. Yeah, I think I'll finally have to bite the bullet and pick up an oscilloscope. But in this case it will only rule out bad wiring, I think. Aug 6, 2020 at 22:28
• @boileau It could show a noise issue, connection instability, or brownout. You said in your post that you're missing bits, but your 3.4V readings show that you're missing entire bytes. Which is it? Aug 6, 2020 at 23:10

Question

The OP is testing MCP3008 with SPI speed 1.35MHz, and found results inaccurate. How come?

Update 2020aug17hkt2136

MCP3008 and MCP3208 Programming Notes

Now that all basic MCP3201 functions have been tested OK, it is time to move to MCP3008 and MCP3208, both of which are more complicated that MCP3201 (Note 1). Some differences are listed below. (1) MCP3201 only need to read two bytes to get results, no writing command to select which channel is to read. For MCP3008 and MCP3208, three write read bytes are need, as illustrated below.

Note 1 - The nice thing is that (a) MCP3008 MCP3208 have the same 16 pin DIP pinouts, (b) the config pins are identical (see Table 5.2 of both datasheets). In other words, there are little changes need to be made converting MCP3008 to MCP3208 and vice versa.

Update 2020aug17hkt1511

Now I have wired two MCP3201s for troubleshooting and cross/self calibration. I found that the accuracy is < 0.5%, max/min difference for 10 samples < 0.015V.

Next step is to test and calibrate MCP3208 and MCP3008.

Update 2020aug13hkt1601

Now I am using a scope to display the SPI signal waveforms of loopback two bytes 0x5b, 0x5c at 1 MHz. Reading or converting MCP3201 and MCP30008/MCP3208 is as simple as reading two/three bytes. The following screen capture shows the two bytes loopback testing. The MCP3201 conversion wiring is the same, except MOSI and MISO is not shorted, but all signals are connected to MCP3201.

Update 2020aug12hkt1540

The OP is using SPI speed 1.35MHz to do the testing. Now I am testing MCP3201 the accuracy of converting a voltage of 1.68V with the voltage reference also 1.68V (Vcc/2) without using any by pass capacitors at MCP3201 Vcc and voltage reference. I tested over a range of 10kHz to 10 MHz. I found that the readings at 1 MHz or over is unreliable. So from now on I will only test at 1Mhz or below.

Now I am testing again, this time only on 100kH, 400kHz, and 1MHz, and over 100 samples. I found there is not much accuracy advantage using lower 100kHz and 400kHz, so from now on I only focus on 1MHz.

Update 2020aug12hkt1148

The OP mentions that he is using more than one MCP3008 to do swap troubleshooting. I always do swapping testing and troubleshooting, but I usually use samples from two shops, because from time to time I find that the whole lot I buy from one shop is bad. The other thing is that eBay shops are not always good. I usually buy from manufacturer's authorized shop (eg MicroChip authorized shop at TaoBao).

Now I am writing just one python program to calibrate all three ADCs, MCP3008, MCP3201, and MCP3208. I can actually calibrate all three at the same time with different SPI ports, each with multiple readings, with max, min, mean, and error values. MCP3008, MCP3201, and MCP3208 have the same SAR ADC architecture, and the SPI commands are extremely simple and newbie friendly, there is no need to access any device register, making one conversion is as simple as reading three bytes for MCP3008, MCP3208, and only two bytes for MCP3201, as illustrated below.

Since MCP3201 need two bytes instead of MCP3008/MCP3201 three bytes. So sampling time is roughly 2 bytes / 3 bytes, or 33% shorter.

Update 2020aug11hkt2116

The OP has found that improper grounding causes inaccurate conversion results. I might also investigate the effect of using digital ground vs analog ground or Vcc ground etc.

But I think the most important factor of conversion accuracy is SPI speed. I know 4MHz might be the upper limit, but I am interested to know if 2MHz or 1MHz is optimum, or if I should use even lower, perhaps 400kHz and 100kHz for higher reliability.

My test plan now is to first try one channel MCP3201 because the wiring and software is very simple, and techniques acquired can easily scale up to 8 channel MCP3208 and MCP3008.

I am starting the prototype hardware without any by pass caps for both Vcc and Vref. If I find results bad, then I will add bypass caps to compare and contrast.

Update 2020aug10hkt1652

My incomplete answer has inspired the OP to find the solution himself, as explained in his edit.

I am learning more things that might cause noise problems and inaccurate measurements, eg how to use bypass caps to stabalise the reference voltage source (ref 4), as illustrated below:

Update 2020aug07hkt1219

Now I am searching my old lab log to hopefull find something useful to suggest the OP to troubleshoot. I read my old schematic and found two things the OP might consider.

(1) Use separate analog grounds and digital grounds, as shown in the schematic below.

(2) Use a digital voltage reference with small series resistance, to maximize sample and hold capacitor current, so to prevent the not enough time filling up at high frequencies.

(3) Use one differential input channel, instead of single ended, to avoid noise problems. The three other not used differential inputs should be grounded, also to prevent noise, (ESD, back EMF) surge/spike problems.

(4) The schematic also reminds me that MCP3008's Chip Select (CS) pin is also used to "Shut Down". The Op might not be aware that there shutting down might need some time to wake up, otherwise the next conversion might not be accurate. This is just brainstorming, and I have not looked into this minor details for these particular hip, but I do have annoying experiences of not being aware that some chips's initialization and waking up take a long time, in the order of milliseconds.

(5) One more thing is that the OP is powering MCP3208 with 5V. In this case Rpi 3V3 logic SPI signals should NOT directly drive 5V device. I almost always do not use directly Rpi's 3V3 GPIO/UART/I2C/SPI signals. I always shift up Rpi'3 3V3 signals to 5V (using TSX/TBX0102/4/6/8)

I forgot if there is any other precautions things I need to observe. So I searched my reading logs and found the following:

Update 2020aug07hkt1149

I made a careless mistake in reading the datasheet, resulting a wrong conclusion. Please ignore this wrong answer. I will try to improve my answer later. My apologies.

Part A - Datasheet spec summary

A.1 - max data rate = 20ksps (Errata - should read 200ksps).

A.2 - conversion time = 3 SPI byte transactions, or 24 cycles.

Part B - Circuit analysis

B.1 - Example Case 1 : SPI Frequency = 1MHz, => conversion time = 24us.

B.2 - Max sps = 1/24us ~= 40kHz.

B.3 - Conclusion: 1MHz means 40ksps is too high for MCP3008 to handle.

Errata - Conclusion is wrong. Max sps of 200ksps should imply max SPI frequency around 4MHz

Part C - Troubleshooting suggestions

C.1 - Suggest to lower SPI frequency from OP's 1.35MHz to 100kHz (Note 1) and test again.

Notes

N.1 - Spec says min frequency > 10kHz, or sample and hold cap leaks.

N.2 - Volt source Rs < 1k, or sample and hold cap input current too small to fill up in time.

N.3 - Using SPI frequency might have a same trouble as N.2 above: sample and hold cap does not have enough time to fill up.

N.4 - My always dodgy calculation is not proofread.

References

Appendices

Appendix A - MCP3008 Operation

Appendix B - The OP's missing bits in his MCP3008 ADC Conversion Results

I was curious which bits were missing. So I converted the decimals to binary to try to find a clue.

dec 3.40 = bin 11.01100110011001100110
dec 3.39 = bin 11.01100011110101110001
dec 3.30 = bin 11.01001100110011001101
dec 3.29 = bin 11.01001010001111010111
dec 3.19 = bin 11.00110000101000111101
dec 2.98 = bin 10.11111010111000010100
dec 2.55 = bin 10.10001100110011001101
dec 0.00 = bin 0.000000000000000000000


My thought for 15 seconds and jumped to the conclusion that the missing bits should be random, so I give up diving deeper.

Appendix C - MCP3201 Test Program V1.0 Listing

# Program:

from time     import sleep
import               spidev
import               inspect
from datetime import datetime
import spi_util_01_v108 as spiutil

# *** 1. Program Config ***

# *** 2. System Utilities ***

def printBeginProgram():
print('     Begin program ' + programTitle + '  tlfong01  ' + timeNowStr)
#print(' ')
return

def printEndProgram():
print('\n     End   program ' + programTitle + '  tlfong01  ' + timeNowStr)
return

def printTitle(title, indentFormat):
print((' ' * (indentFormat[0])), title.ljust(indentFormat[1]))
return

def printTitleNoNewLine(title, indentFormat):
print((' ' * (indentFormat[0])), title.ljust(indentFormat[1]), end = '')
return

def printTitleString(title, indentFormat, string):
printTitleNoNewLine(title, indentFormat)
print('=', string)
return

def printStarLine():
print('')
printTitle(('*' * 100), indentFormat480)
print('')
return

def printBeginExecFunction():
functionName = inspect.stack()[1][3]
title = 'Begin Execute Function ' + functionName + '  ' + timeNowStr
printStarLine()
printTitle(title, indentFormat480)
print('')
printTitleString('Function Name', indentFormat640, functionName)
return

def printEndExecFunction():
title = 'End   Execute Function ' + inspect.stack()[1][3] + '  ' + timeNowStr
print('')
printTitle(title, indentFormat480)
printStarLine()
return

def convertOneByteNumToFourCharStr(oneByteNum):
tempStr = ((hex(oneByteNum))[2:])
if (len(tempStr) != 2):
tempStr = '0' + tempStr
fourCharStr = '0x' + tempStr
return fourCharStr

def convertTwoByteNumToEightCharStr(twoByteNum): # new <<<<<<<<<<
tempStr = ((hex(twoByteNum))[2:])
tempStr = '0' * (4 - len(tempStr)) + tempStr
tenCharStr = '0x' + tempStr
return tenCharStr

# *** Time Now String ***

timeNowStr = str(datetime.now())[0:16]

# *** Format string for print functions ***

indentFormat480 = [4, 80]
indentFormat608 = [6,  8]
indentFormat610 = [6, 10]
indentFormat615 = [6, 15]
indentFormat630 = [6, 30]
indentFormat640 = [6, 40]

# *** Repeat Times and Pause Dict ***

repeatTimesDict = {
'1 times'         :        1,
'10 times'        :       10,
'100 times'       :      100,
'1000 times'      :     1000,
'10000 times'     :    10000,
'100000 times'    :   100000,
'1000000 times'   :  1000000,
'10000000 times'  : 10000000
}

sampleSizeDict = {
'1 sample'          :        1,
'10 samples'        :       10,
'100 samples'       :      100,
'1000 samples'      :     1000,
'10000 samples'     :    10000,
'100000 samples'    :   100000,
'1000000 samples'   :  1000000,
'10000000 samples'  : 10000000
}

pauseSecondsDict = {
'0.001 second'    : 0.001,
'0.002 second'    : 0.002,
'0.01 second'     : 0.01,
'0.1 second'      : 0.1,
'10 ms'           : 0.01
}

# *** Write/Read Device Register Functions ***

# 1. *** Set SPI speed ***
print('\n       # *** Set SPI Port Speed ***')
spiutil.setSpiPortSpeedBySpiPortNameList([spiPortName], spiSpeedName)
print('')

# 2. *** Test SPI loopback (for troubleshooting only) ***
#spiutil.testLoopbackTwoBytesSpiPortNameList(['SpiPort00'], '0x5b', '0x5c')

# 3. *** Test ADC ***
pass
# / to continue, ...
pass
# / to continue, ...
#spiPort = spiutil.spiPortDict[spiPortName]
#recvArray = spiutil.spiSendRecvTwoBytes(spiPort, 0x00, 0x00)
#adcResults = (((recvArray[0] & 0x3f) << 8) + recvArray[1]) >> 1
print('       MCP3201 Results in 12 bits binary (expect fff/2~= 0x0800) =', convertTwoByteNumToEightCharStr(adc12BitResults))
print('                       in V (expect 3.3V /2 ~= 1.65V)            =', "%.2f" % adcVolts, 'V')
return

spiPort = spiutil.spiPortDict[spiPortName]
recvArray = spiutil.spiSendRecvTwoBytes(spiPort, 0x00, 0x00)
adcBinaryResults = (((recvArray[0] & 0x3f) << 8) + recvArray[1]) >> 1

spiPort = spiutil.spiPortDict[spiPortName]
pauseSeconds = pauseSecondsDict[pauseSecondsName]
repeatTimes = repeatTimesDict[repeatTimesName]

for count in range(repeatTimes):
#recvArray = spiutil.spiSendRecvTwoBytes(spiPort, 0x00, 0x00)
#adcResults = (((recvArray[0] & 0x3f) << 8) + recvArray[1]) >> 1
sleep(pauseSeconds)

refVoltDict = \
{
'0.00V'        : 0.00,
'1.68V'        : 1.68,
'2.048V'       : 2.048,
'3.30V'        : 3.30,
'4.096V'       : 4.096
}

spiSpeedNameListDict = \
{
'All speeds name list' : ['10 kHz', '50 kHz', '100 kHz', '400 kHz', '1 MHz', '2 MHz', '4 MHz', '5 MHz', '6 MHz', '8 MHz', '10 MHz']
}

spiPort = spiutil.spiPortDict[spiPortName]

print('         Test Config: SPI Port =', spiPortName, ';', 'ADC =', adcName, ';', 'Speed List =',  speedNameListName, ';', 'Sample Size =', sampleSizeName)
print('\n        ', '----------'.rjust(10), '----------'.rjust(10), '----------'.rjust(10), '----------'.rjust(10),'----------'.rjust(10),
'----------'.rjust(10), '----------'.rjust(10),'----------'.rjust(10))
print('        ', 'SPI Port'.rjust(10), 'Speed'.rjust(10), 'Mean Raw'.rjust(10), 'Mean Volt'.rjust(10), 'Error (%)'.rjust(10), \
'Max Volt'.rjust(10), 'Min Volt'.rjust(10), 'MaxMin Dif'.rjust(10), end = '')
print('\n        ', '----------'.rjust(10), '----------'.rjust(10), '----------'.rjust(10), '----------'.rjust(10),'----------'.rjust(10),
'----------'.rjust(10), '----------'.rjust(10),'----------'.rjust(10))

refVolt = refVoltDict[refVoltName]
speedNameList = spiSpeedNameListDict[speedNameListName]
sampleSize = sampleSizeDict[sampleSizeName]

for speedName in speedNameList:
# *** Set SPI speed ***
spiutil.setSpiPortSpeedByName(spiPortName, speedName)

# repeatTimes = 1

binResultList =  [0.0 for x in range(sampleSize)]
decResultList =  [0.0 for x in range(sampleSize)]

for count in range(sampleSize):
print('Not available')
print('Not available')
recvArray = spiutil.spiSendRecvTwoBytes(spiPort, 0x00, 0x00)
binResult = (((recvArray[0] & 0x3f) << 8) + recvArray[1]) >> 1
decResult = (binResult / 0xfff) * 3.3
error      = ((refVolt - decResult) / refVolt) * 100
binResultList[count] = binResult
decResultList[count] = decResult

maxDecResult  = max(decResultList)
minDecResult  = min(decResultList)
meanDecResult = sum(decResultList) / sampleSize
maxMinDiff    = maxDecResult - minDecResult

print('        ', spiPortName.rjust(10), end = '')
print('', speedName.rjust(10), end = '')
print('', convertTwoByteNumToEightCharStr(binResult).rjust(10), end = '')
print('', ("%.3f" % decResult).rjust(10), end = '')
print('', ("%.1f" % error).rjust(10), end = '')
print('', ("%.3f" % maxDecResult).rjust(10), end = '')
print('', ("%.3f" % minDecResult).rjust(10), end = '')
print('', ("%.3f" % maxMinDiff).rjust(10), end = '')

print('\n        ', '----------'.rjust(10), '----------'.rjust(10), '----------'.rjust(10), '----------'.rjust(10),'----------'.rjust(10),
'----------'.rjust(10), '----------'.rjust(10),'----------'.rjust(10))

return

# *** Main ***

def main():
printBeginProgram()

# *** Read MCP3201 ADC result with list of SPI speds ***
print('\n       # *** Test MCP3201 ADC with Ref Voltage = 1.68V, and a range of SPI speeds ***')
testAdcMultipleSpiSpeedsMultipleTimes('SpiPort00', 'MCP3201', '1.68V', 'All speeds name list', '10 samples')

printEndProgram()
return

if __name__ == '__main__':
main()

# End of program

# *** Sample Output ***
'''
Begin program adcutil_01_v111  tlfong01  2020-08-12 13:40

# *** Test MCP3201 ADC with Ref Voltage = 1.68V, and a range of SPI speeds ***
Test Config: SPI Port = SpiPort00 ; ADC = MCP3201 ; Speed List = All speeds name list ; Sample Size = 10 samples

---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
SPI Port      Speed   Mean Raw  Mean Volt  Error (%)   Max Volt   Min Volt MaxMin Dif
---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
SpiPort00     10 kHz     0x07ff      1.650        1.8      1.652      1.646      0.006
---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
SpiPort00     50 kHz     0x0802      1.652        1.7      1.652      1.646      0.006
---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
SpiPort00    100 kHz     0x07ff      1.650        1.8      1.650      1.647      0.003
---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
SpiPort00    400 kHz     0x07fe      1.649        1.9      1.651      1.649      0.002
---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
SpiPort00      1 MHz     0x0803      1.653        1.6      1.655      1.650      0.005
---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
SpiPort00      2 MHz     0x07cc      1.608        4.3      1.608      1.604      0.005
---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
SpiPort00      4 MHz     0x06ce      1.404       16.4      1.406      1.404      0.002
---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
SpiPort00      5 MHz     0x0773      1.537        8.5      1.539      1.535      0.004
---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
SpiPort00      6 MHz     0x02ff      0.618       63.2      0.618      0.618      0.000
---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
SpiPort00      8 MHz     0x02ff      0.618       63.2      0.618      0.618      0.000
---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
SpiPort00     10 MHz     0x02e0      0.593       64.7      0.593      0.593      0.000
---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------

End   program adcutil_01_v111  tlfong01  2020-08-12 13:40
>>>
'''

# *** End ***