There are a number of question that exhibit confusion in making the SPI interface work with the MCP3008 ADC chip using spidev.h and ioctl in C. For example viewtopic.php?t=115971. The bottom-line is you are responsible for pulling the SPI CE pin low for the device. Otherwise, all aspects of your code will pass all validation tests you include, but the adc values you receive from the transfer will be all 0 (zero). Why?
The problem is, as almost always, one of a lack of documentation combined with the limited parts of the Linux kernel spi/spidev made available. (see the spidev.h header and https://www.kernel.org/doc/html/v4.14/d ... i/spi.html) The only structure made available is:
The problem is nowhere do you specify which SPI device the structure is associated with. The confusion is made worse by the cs_change member which is described "to deselect device before starting the next transfer." (e.g. return the CE GPIO pin for the SPI device high, for GPIO8, 7 for SPI0, SPI1) This suggests that the making the ioctl call with SPI_IOC_MESSAGE(n) will pull the CE pin low on its own since there is a special cs_change to leave the pin low on completion of the transfer. However, that is not the case.
I stumbled into this issue comparing the operation of the BCM2835 library version of code to read the MCP3008 with the spidev/ioctl version. This nugget was found after much frustration, by adding a simple LED to the CE pin to determine when it was pulled low for the transfer. The bcm2835 version pulled the pin low, the spidev/ioctl version made no change to the CE pin (GPIO 8 for SPI0).
To make the bus timings comply with the MCP3008 datasheet, the key was to manually pull the CE pin low before the ioctl transfer and then restore it afterwards. The ADC transfer using spidev/ioctl immediately worked perfectly.
A modified version of the oft-seen mcp3008.c source modified to use the tinyGPIO library http://abyz.me.uk/rpi/pigpio/examples.html to set the mode and CE pin value is:
With voltages on channels 0, 4 and 6 of the MCP3008 and the remaining inputs floating, reading the values 3 times each results in:
Which matches exactly the output from the bcm2835 library version.
Hopefully this helps someone else looking at all 0 output from the MCP3008. The tiny gpio library was chosen because it does not require running as root (which is the whole purpose of not using the bcm2835 library to begin with).
The problem is, as almost always, one of a lack of documentation combined with the limited parts of the Linux kernel spi/spidev made available. (see the spidev.h header and https://www.kernel.org/doc/html/v4.14/d ... i/spi.html) The only structure made available is:
Code:
struct spi_ioc_transfer {__u64tx_buf;__u64rx_buf;__u32len;__u32speed_hz;__u16delay_usecs;__u8bits_per_word;__u8cs_change;__u8tx_nbits;__u8rx_nbits;__u8word_delay_usecs;__u8pad;/* If the contents of 'struct spi_ioc_transfer' ever change * incompatibly, then the ioctl number (currently 0) must change; * ioctls with constant size fields get a bit more in the way of * error checking than ones (like this) where that field varies. * * NOTE: struct layout is the same in 64bit and 32bit userspace. */};
I stumbled into this issue comparing the operation of the BCM2835 library version of code to read the MCP3008 with the spidev/ioctl version. This nugget was found after much frustration, by adding a simple LED to the CE pin to determine when it was pulled low for the transfer. The bcm2835 version pulled the pin low, the spidev/ioctl version made no change to the CE pin (GPIO 8 for SPI0).
To make the bus timings comply with the MCP3008 datasheet, the key was to manually pull the CE pin low before the ioctl transfer and then restore it afterwards. The ADC transfer using spidev/ioctl immediately worked perfectly.
A modified version of the oft-seen mcp3008.c source modified to use the tinyGPIO library http://abyz.me.uk/rpi/pigpio/examples.html to set the mode and CE pin value is:
Code:
#include <stdint.h>#include <unistd.h>#include <stdio.h>#include <stdlib.h>#include <fcntl.h>#include <sys/ioctl.h>#include <linux/types.h>#include <linux/spi/spidev.h>#include <tinygpio.h>#define ARRAY_SIZE(array) sizeof(array) / sizeof(array[0])static const char *DEVICE = "/dev/spidev0.0";static uint8_t MODE = SPI_MODE_0;static uint8_t BITS = 8;static uint32_t CLOCK = 1350000;static uint16_t DELAY = 5;/* * Ensure all settings are correct for the ADC */static int prepare (int fd){ if (ioctl (fd, SPI_IOC_WR_MODE, &MODE) == -1) { perror ("Can't set MODE"); return -1; } if (ioctl (fd, SPI_IOC_WR_BITS_PER_WORD, &BITS) == -1) { perror ("Can't set number of BITS"); return -1; } if (ioctl (fd, SPI_IOC_WR_MAX_SPEED_HZ, &CLOCK) == -1) { perror ("Can't set write CLOCK"); return -1; } if (ioctl (fd, SPI_IOC_RD_MAX_SPEED_HZ, &CLOCK) == -1) { perror ("Can't set read CLOCK"); return -1; } return 0;}/* * (SGL/DIF = 0, D2=D1=D0=0) */uint8_t control_bits_differential (uint8_t channel){ return (channel & 7) << 4;}/* * (SGL/DIF = 1, D2=D1=D0=0) */uint8_t control_bits (uint8_t channel){ return (0x8 | channel) << 4;}/* * Given a prep'd descriptor, and an ADC channel, fetch the * raw ADC value for the given channel. */int readadc (int fd, uint8_t channel){ uint8_t tx[] = {1, control_bits (channel), 0}; uint8_t rx[3] = {0}; struct spi_ioc_transfer tr = { .tx_buf = (unsigned long)tx, .rx_buf = (unsigned long)rx, .len = ARRAY_SIZE(tx), .delay_usecs = DELAY, .speed_hz = CLOCK, .bits_per_word = BITS, }; if (ioctl (fd, SPI_IOC_MESSAGE(1), &tr) == -1) { perror("IO Error"); abort(); } return ((rx[1] << 8) & 0x300) | (rx[2] & 0xFF);}int main (void) { unsigned CE_SPI_PIN = 8; int fd = open (DEVICE, O_RDWR); if (fd <= 0) { printf ("Device %s not found\n", DEVICE); return -1; } if (prepare(fd) == -1) { return -1; } printf ("opened SPI device: %s on file descriptor: %d\n" " mode : %d\n" " bits : %hhu\n" " clock : %u\n" " delay : %hu\n", DEVICE, fd, MODE, BITS, CLOCK, DELAY); if (gpioInitialise() < 0) { fputs ("error: gpioInitialize() failed\n", stderr); close (fd); return 1; } gpioSetMode (CE_SPI_PIN, PI_OUTPUT); /* set CE pin to output */ gpioWrite (CE_SPI_PIN, 0); /* pull CE pin low */ /* do ADC read */ for (uint8_t i = 0; i < 8; i++) { printf ("\nchannel: %d\n", i); for (uint8_t j = 0; j < 3; j++) { int val = readadc (fd, i); float res = (float)val * 3.3f / 1023.f; printf (" v : %.2f\n", res); } } gpioWrite (CE_SPI_PIN, 1); /* restore CE pin high */ gpioSetMode (CE_SPI_PIN, PI_ALT0); /* reset MODE (optional) */ close(fd); return 0;}
Code:
$ ./mcp3008minopened SPI device: /dev/spidev0.0 on file descriptor: 3 mode : 0 bits : 8 clock : 1350000 delay : 5channel: 0 v : 1.62 v : 1.62 v : 1.62channel: 1 v : 0.00 v : 0.01 v : 0.01channel: 2 v : 0.00 v : 0.00 v : 0.01channel: 3 v : 0.00 v : 0.01 v : 0.01channel: 4 v : 1.61 v : 1.63 v : 1.61channel: 5 v : 0.00 v : 0.01 v : 0.02channel: 6 v : 3.30 v : 3.30 v : 3.30channel: 7 v : 0.04 v : 0.02 v : 0.01
Hopefully this helps someone else looking at all 0 output from the MCP3008. The tiny gpio library was chosen because it does not require running as root (which is the whole purpose of not using the bcm2835 library to begin with).
Statistics: Posted by drankinatty — Tue Apr 09, 2024 1:37 am — Replies 0 — Views 20