MCP3008 SPI Addressing and 0x00 returned values



  • Hi,
    I've been having issues using an MCP3008 with the Omega2. I saw this post: Using a MCP3008 via SPI, and am also having issues. I'm trying to read channel 0 on the ADC.

    That post mentioned sending the command word 1100 or 11000000 to read from the device, however my attempts were unsuccessful. I used the following command:

    res = spi.readBytes(0xC0,1)
    

    I consistently get 0x00 as the returned value , when attempting a whole range of different addresses (not just 0xC or 0xC0).
    My specific questions are:

    • Am I addressing channel 0 correctly?
    • What should I check if I keep getting 0x00 as the readBytes returned value?

    Thanks!



  • I have not used SPI, but according to this post, it is not reliable.

    Current Defects



  • Actually, 0xC0 would be transmitted correctly in all cases. In my tests, byte values from 0x40 to inclusive 0xbf are wrong, and 0xC0 is just one above that. I'll get an Arduino, my Omega2 and a MCP3008 today, will attempt to read out the channel 0 with both devices, and compare SPI transfers with my logic anyalzer. After that, I will be able to tell you if the software is wrong or if the SPI failed. There are many factors involved, like hardware and sofware alike software (e.g. SPI configuration (speed, bit length of transfers, SPI mode 0 to 3,...)).



  • Update: I can successfully read the ADC's values with an Arduino Nano and my Omega2+. I ported https://github.com/adafruit/Adafruit_MCP3008 to C for the Omega.

    The interesting bit is the requesting the ADC value: You have to read the datasheet very carefully, there is section 6 about reading data using a MCU. Your command byte 0xC0 is correct, but you have to send two 0x00 bytes to read the 10-bit response.

    0_1516117467572_datasheet.png

    Anyways, here is the C code.

    MCP3008.c

    /* MCP3008.c
     *
     * This file contains functions for letting the Omega interact with an MCP3008,
     * a 8-channel 10 bit ADC.
     *
     * Ported by: Maximilian Gerhardt, January 2018.
     */
    #include "MCP3008.h"
    
    #include <stdio.h>
    #include <stdbool.h>
    #include <unistd.h> /* for slep() */
    #include <onion-spi.h> /* main SPI functionality */
    #include <onion-debug.h> /* debug output */
    
    /* Global SPI object. Can only have 1 user at a time. */
    static struct spiParams params;
    
    /* Depending on the given VDD voltage, the MCP3008 supports a different maximum SPI clock speeds. */
    #define MCP3008_SPI_MAX_5V  3600000
    #define MCP3008_SPI_MAX_3V  1350000
    #define MCP3008_SPI_MAX     MCP3008_SPI_MAX_3V /* Select the 3.3V version*/
    
    static int SPIxADC(int channel, bool differential);
    
    /* Initialize the SPI bus on the given GPIO pins.
     * On the MCP3008, these pins are called D_out, D_in, CLK, /CS/SHDN
     * */
    bool MCP3008_Startup(int miso, int mosi, int sclk, int cs) {
    
    	int err = 0;
    
    	//Start initializing the SPI bus.
    	spiParamInit(&params);
    	params.misoGpio = miso;
    	params.mosiGpio = mosi;
    	params.sckGpio = sclk;
    	params.csGpio = cs;
    	params.speedInHz = MCP3008_SPI_MAX;
    	params.modeBits = SPI_MODE_0;
    	params.busNum = 1; //this comes from my local machine. no idea if it works elsewhere.. (ls /dev/spi*)
    	params.deviceId = 32766;
    	params.delayInUs = 10;
    
    	//is our device already mapped?
    	err = spiCheckDevice(params.busNum, params.deviceId, ONION_SEVERITY_DEBUG);
    	if(err == EXIT_FAILURE) {
    		printf("[-] spiCheckDevice() failed.\n");
    		return false;
    	}
    
    	//register ourselves
    	err = spiRegisterDevice(&params);
    	if(err == EXIT_FAILURE)  {
    		printf("spiRegisterDevice() failed.\n");
    		return false;
    	}
    	printf("[+] SPI register device okay.\n");
    
    	err = spiSetupDevice(&params);
    	if(err == EXIT_FAILURE)  {
    		printf("spiSetupDevice() failed.\n");
    		return false;
    	}
    
    	return true;
    }
    
    int MCP3008_ReadChannel(int channel) {
    	if ((channel < 0) || (channel > 7)) return -1;
    	return SPIxADC(channel, false);
    }
    
    int MCP3008_ReadChannelDifferential(int differential) {
    	if ((differential < 0) || (differential > 7)) return -1;
    	return SPIxADC(differential, true);
    }
    
    static int SPIxADC(int channel, bool differential) {
    	uint8_t command, sgldiff;
    
    	if (differential) {
    		sgldiff = 0;
    	} else {
    		sgldiff = 1;
    	}
    
    	command = ((0x01 << 7) |               // start bit
    			(sgldiff << 6) |              // single or differential
    			((channel & 0x07) << 3) );    // channel number
    
    
    	uint8_t spiRequest[3] = { command, 0x00, 0x00};
    	uint8_t spiResponse[3];
    
    	spiTransfer(&params, spiRequest, spiResponse, sizeof(spiRequest));
    
    	//Reconstruct numerical 10-bit ADC value
    	uint8_t b0 = spiResponse[0], b1 = spiResponse[1], b2 = spiResponse[2];
    
    	return 0x3FF & ((b0 & 0x01) << 9 |
    			(b1 & 0xFF) << 1 |
    			(b2 & 0x80) >> 7 );
    }
    

    MCP3008.h

    #ifndef MCP3008_H_
    #define MCP3008_H_
    
    #include <stdint.h>
    #include <stdbool.h>
    #include <stddef.h>
    
    bool MCP3008_Startup(int miso, int mosi, int sclk, int cs);
    int MCP3008_ReadChannel(int channel);
    int MCP3008_ReadChannelDifferential(int differential);
    
    #endif /* MCP3008_H_ */
    

    main.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <stdbool.h>
    #include <unistd.h>
    #include <time.h>
    #include <sys/time.h>
    #include "MCP3008.h"
    
    int main() {
    
    	//MISO MOSI SCLK CS
    	if(!MCP3008_Startup(9,8,7,6)) {
    		printf("MCP3008 SPI initialization not successful.\n");
    		return -1;
    	}
    
    	/* Read channels a few times */
    	int nTimes = 100;
    	int delay = 300;
    
    	for(int i=0;i < nTimes; i++) {
    
    		for(int channel = 0; channel < 8; channel++) {
    			int adcValue = MCP3008_ReadChannel(channel);
    			//Print comma-separated, end with newline
    			printf("%d", adcValue);
    			if(channel != 7) printf(",");
    			else printf("\n");
    		}
    
    		usleep(delay * 1000);
    	}
    
    	printf("Goodbye.\n");
    
    	return 0;
    }
    

    Output:

    root@Omega-17FD:~# ./mcp3008_readADC
    > SPI device already available
    [+] SPI register device okay.
    > Initializing SPI parameters...
      > Set SPI mode:       0x0
      > Set bits per word:  0
      > Set max speed:      1350000 Hz (1350 KHz)
    1020,3,5,3,2,1,1,2
    1020,4,3,3,2,2,2,2
    1020,8,7,6,7,5,8,9
    1021,6,5,5,4,3,5,9
    

    A potentiometer was connected to channel 0. SPI GPIO pins per main.c. VDD and V_REF to 3.3V, DGND and AGND to Omega's GND.

    I'll try to do this in python and post the code here as well.



  • Done.

    #!/usr/bin/env python
    
    import onionSpi
    import time
    import struct
    
    def printSettings(obj):
    	print "SPI Device Settings:"
    	print "  bus:    %d"%(obj.bus)
    	print "  device: %d"%(obj.device)
    	print "  speed:    %d Hz (%d kHz)"%(obj.speed, obj.speed/1000)
    	print "  delay:    %d us"%(obj.delay)
    	print "  bpw:      %d"%(obj.bitsPerWord)
    	print "  mode:     %d (0x%02x)"%(obj.mode, obj.modeBits)
    	print "     3wire:    %d"%(obj.threewire)
    	print "     lsb:      %d"%(obj.lsbfirst)
    	print "     loop:     %d"%(obj.loop)
    	print "     no-cs:    %d"%(obj.noCs)
    	print "     cs-high:  %d"%(obj.csHigh)
    	print ""
    	print "GPIO Settings:"
    	print "  sck:      %d"%(obj.sck)
    	print "  mosi:     %d"%(obj.mosi)
    	print "  miso:     %d"%(obj.miso)
    	print "  cs:       %d"%(obj.cs)
    	print ""
    
    #Returns a correctly configured SPI device
    def initADC(miso, mosi, sclk, cs):
    	spiDev  = onionSpi.OnionSpi(1, 32766)
    	spiDev.delay = 10
    	spiDev.mode = 0
    	spiDev.sck = sclk
    	spiDev.mosi = mosi
    	spiDev.miso = miso
    	spiDev.cs = cs
    	spiDev.speed = 1350000 # for 3.3V supply
    
    	spiDev.setVerbosity(0)
    	# check the device
    	print 'Checking if device exists...'
    	ret = spiDev.checkDevice()
    	print '   Device does not exist: %d'%(ret)
    
    	# register the device
    	print 'Registering the device...'
    	ret = spiDev.registerDevice()
    	print '   registerDevice returned: %d'%(ret)
    
    	# initialize the device parameters
    	print 'Initializing the device parameters...'
    	ret = spiDev.setupDevice()
    	print '   setupDevice returned: %d'%(ret)
    
    	# check the device again
    	print '\nChecking if device exists...'
    	ret = spiDev.checkDevice()
    	print '   Device does not exist: %d'%(ret)
    	return spiDev
    	
    #reads an ADC channel 
    def readADC(spiDev, channel):
    	#sanity check
    	if channel < 0 or channel > 7:
    		return -1
    	return SPIxADC(spiDev, channel, False)
    
    def readADCDiff(spiDev, differential):
    	if differential < 0 or differential > 7:
    		return -1		
    	return SPIxADC(spiDev, differential, True);
    
    def SPIxADC(spiDev, channel, differential):
    	command = 0
    	sgldiff = 0
    
    	if differential:
    		sgldiff = 0
    	else:
    		sgldiff = 1
    	
    	# start bit + single or differential + channel number 
    	command = ((0x01 << 7) | (sgldiff << 6) | ((channel & 0x07) << 3) )
    	
    	# This function will transmit 3 bytes.
    	# TX buffer of 3 bytes is only populated with 1 byte (command)
    	# rest is filled with random data (due to malloc), but thankfully that doesn't matter.
    	# we just need some dummy bytes there.
    	# 3 bytes will be read into the RX buffer.
    	# see https://github.com/OnionIoT/spi-gpio-driver/blob/master/src/python/python-onion-spi.c#L131
    	# we CANNOT use 3 different .readBytes() calls because the delay between them would be 
    	# too big. Thus we'll only read 0xff.. 
    	spiResp = spiDev.readBytes(command, 3)
    
    	b0 = spiResp[0]
    	b1 = spiResp[1]
    	b2 = spiResp[2]
    	
    	#print(b0,b1,b2)
    	#reconstruct 10-bit ADC value
    	return 0x3FF & ((b0 & 0x01) << 9 | (b1 & 0xFF) << 1 | (b2 & 0x80) >> 7 )
    	
    	
    # MAIN PROGRAM
    
    spi = initADC(9,8,7,6)
    printSettings(spi)
    
    for i in range(30):
    
    	adcValues = []
    	for chan in range(0, 8):
    		adcValues.append(readADC(spi, chan))
    
    	line = ','.join(str(v) for v in adcValues)		
    	print(line)
    	
    	time.sleep(0.3)
    

    Output

    root@Omega-17FD:~# ./readADC.py
    Checking if device exists...
       Device does not exist: 0
    Registering the device...
    > SPI device already available
       registerDevice returned: 0
    Initializing the device parameters...
    > Initializing SPI parameters...
      > Set SPI mode:       0x500
      > Set bits per word:  0
      > Set max speed:      1350000 Hz (1350 KHz)
       setupDevice returned: 0
    
    Checking if device exists...
       Device does not exist: 0
    SPI Device Settings:
      bus:    1
      device: 32766
      speed:    1350000 Hz (1350 kHz)
      delay:    10 us
      bpw:      8
      mode:     0 (0x00)
         3wire:    0
         lsb:      0
         loop:     0
         no-cs:    0
         cs-high:  0
    
    GPIO Settings:
      sck:      7
      mosi:     8
      miso:     9
      cs:       6
    
    940,2,2,2,1,0,0,0
    940,3,4,3,2,1,2,3
    938,5,4,3,2,1,1,2
    938,3,3,2,1,2,0,1
    938,2,3,1,0,0,0,0
    ...
    


  • @Maximilian-Gerhardt Wow thank you so much! I totally missed the leading 0's part in the datasheet... I'll try it out tomorrow.



Looks like your connection to Community was lost, please wait while we try to reconnect.