Electronic – SPI slave randomly missing bits in response

adcraspberry pispi

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.

Best Answer

Question

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


Answer

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.


mcp3x08 config 1


mcp3x08 config 2


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.


dual mcp3201 test


dual mcp3201 test 3


(5.3) MCP3201 Testing Program V1.3 - tlfong01 2020aug15


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.


loop back test


repeat send two bytes

(5.1) MCP3201 Testing Program V1.1 tlfong01 2020aug12hkt1612


mcp3201 output


(5.2) MCP3201 Testing Program V1.2 tlfong01 2020aug12hkt1613


Update 2020aug12hkt1540

(5) MCP3201 Testing Program V1.0 tlfong01 2020aug12hkt1442

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.


mcp3201 test 10khz to 10MHz


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.


mcp3201 test 2


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.


mcp3xxx conversion summary


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.


mcp3201 test 1


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:


mcp3201 ref source by pass


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.


mcp3008/3208 schematic 1


(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:


mcp3008 sample hold cap


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

(1) MCP3004/3008 2.7V 4-Channel/8-Channel 10-Bit A/D Converters with SPI Serial Interface - MicroChip 2000feb

(2) MCP3008 for Rpi Tutorial - M Sklar, AdaFruit, 2019jul

(3) MCP3008 ADC readings not accurate Problem - tlfong01, rpi.stackexchange 2029may 22

(4) Bypass cap on reference voltage? - EE SE 2020aug09

(5) MCP3201 Testing Program V1.0 tlfong01 2020aug13hkt1442

(5.1) MCP3201 Testing Program V1.1 tlfong01 2020aug12hkt1612

(5.2) MCP3201 Testing Program V1.2 tlfong01 2020aug12hkt1613

(5.3) MCP3201 Testing Program V1.3 - tlfong01 2020aug15

(6) Raspberry Pi 4 Multiple SPIs - 2020jan26

(7) Add more than 2 SPI slaves - 2015, Viewed 23k times


Appendices

Appendix A - MCP3008 Operation

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:
#   adc_util01_v111.py tlfong01 2020aug12hkt1314

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

# *** 1. Program Config ***

programTitle = 'adcutil_01_v111'

# *** 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 ***

def testAdc(spiPortName, adcName, spiSpeedName):
    
    # 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 ***
    if adcName == 'MCP3208':
        pass
        # / to continue, ...        
        print('       ADC 12 Bit Results =', hex(adc12BitResults))
    if adcName == 'MCP3008':
        pass
        # / to continue, ...
    if adcName == 'MCP3201':
        print('       *** Read', adcName, 'Conversion Results ***')
        #spiPort = spiutil.spiPortDict[spiPortName]
        #recvArray = spiutil.spiSendRecvTwoBytes(spiPort, 0x00, 0x00)
        #adcResults = (((recvArray[0] & 0x3f) << 8) + recvArray[1]) >> 1
        adc12BitResults = readMcp3201(spiPortName)
        adcVolts   = (adc12BitResults / 0xfff) * 3.3
        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

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

def readMcp3201DecimalResults(spiPortName):
    adc12BitResults = readMcp3201(spiPortName)
    adcVoltResults = (adc12BitResults / 0xfff) * 3.3
    return adcDecimalResults

def repeatReadMcp3201(spiPortName, pauseSecondsName, repeatTimesName):
    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
        adc12BitResults = readMcp3201(spiPortName)
        sleep(pauseSeconds)
    return adc12BitResults

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']
    }

def testAdcMultipleSpiSpeedsMultipleTimes(spiPortName, adcName, refVoltName, speedNameListName, sampleSizeName):
    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)
      
      # *** Read ADC Multiple Times***
 
      # repeatTimes = 1
      
      binResultList =  [0.0 for x in range(sampleSize)]
      decResultList =  [0.0 for x in range(sampleSize)]
      
      for count in range(sampleSize): 
          if adcName == 'MCP3008':
              print('Not available')
          if adcName == 'MCP3208':
              print('Not available')
          if adcName == 'MCP3201':
              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 ***
'''
>>> %Run adc_util_01_v111.py
     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 ***

End of answer