MCP3008/3201/3208 ADC Programming Notes - Part 1
https://electronics.stackexchange.com/questions/515225/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.
- 2Time for a real scope ... – Tom L. Aug 6 at 22:17
- 4Decoupling caps? – DKNguyen Aug 6 at 22:20
- 1I have caps on all the inputs, same as on the breadboard. – boileau Aug 6 at 22:26
- 1@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. – boileau Aug 6 at 22:28
- 1@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? – DKNguyen Aug 6 at 23:10
- 1do you have components available to go back to solderless breadboard? ... if you do have the components, then go back to a working version and switch to external power supply – jsotola Aug 7 at 0:14
- 1@DKNguyen if an entire byte is missing, then bits are necessarily missing (8 to be precise). – boileau Aug 7 at 6:40
- @jsotola I don't because I managed to short 24V through the first mcu. That would have been my next step otherwise. – boileau Aug 7 at 11:51
- @boileau They are different problems. Failing to assert an entire random bytes is different than failing to assert random individual bits. – DKNguyen Aug 7 at 13:09
MCP3008 and
MCP3208 Programming Notes - Update 2020aug17hkt2136
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.
(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.
(5.1) MCP3201 Testing Program V1.1 tlfong01 2020aug12hkt1612
(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.
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
(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
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 ***
- @boileau, How nice to hear that you have found the solution. By the way, I appreciate very much the way you present the problem concisely but still with all the necessary helpful details. It has been a pleasure learning problem solving with you. Have a great project. Cheers. – tlfong01 Aug 7 at 8:19
- 1Thanks for the feedback. I'm hoping the shared ground bit helps someone else. My own googling didn't lead me to anything useful. – boileau Aug 7 at 11:53
- Yes, even your question with the detailed troubleshooting history help newbies a lot. BTW, I am finding more and more things that I didn't know that I didn't know, eg. how to by pass the reference voltage. So I am adding more troubleshooting tips from time to time. Have a great project. Cheers. – tlfong01 Aug 10 at 8:52
End of answer