Not a Pi engineer, but I've been down this road recently and have simply decided to use to kernel interface through ioctl() for accessing peripherals. Works fine for SPI using sysfs directly (no issues with SPI clock at 1.3MHz). Same for GPIO. I recently posted a small example in the C++ forum viewtopic.php?t=369361#p2217802 You can do the same with SPI.
Below are the snippets for reading from an 8-channel ADC (MCP3008) over SPI set at 1.3MHz. The device configure and read functions are provided along with the device specific struct used:
The you can simply loop calling spi_read_adc() for each channel at whatever rate you like.
I've used the kernel interface for SPI, I2C, GPIO, PWM, etc.. and the benefit is you learn it once and can then use it with any device running Linux, you are not tied to any 3rd party library that will go away, and there is no root privileges required. That checked one of the primary boxes I had. Good luck with your coding.
Below are the snippets for reading from an 8-channel ADC (MCP3008) over SPI set at 1.3MHz. The device configure and read functions are provided along with the device specific struct used:
Code:
...#include <sys/ioctl.h>#include <linux/types.h>#include <linux/spi/spidev.h>typedef struct { /* SPI device struct for mcp3008 */ struct spi_ioc_transfer spi_xfer; uint8_t buf[6]; int fd; uint8_t mode, cepin;} spidev;/* SPI and mcp3008 constants */#define SPIDEVFS "/dev/spidev0.0"#define BITS 8#define CLOCK 1350000#define DELAY 5#define SPI_CE 8#define SPI_MISO 9#define SPI_MOSI 10#define SPI_CLK 11/** * @brief initialize the SPI system (e.g. open "/dev/spidev0.0") and configure * the device struct for reading from the mcp3008. * @param spidevfs filesystem device node for SPI. * @param dev pointer to struct holding mcp3008 configuration. * @param delay microsecond delay for mcp3008 conversion (sample switching). * @param clock SPI bus speed in Hz. * @param bits bits per-bytes (e.g. CHAR_BITS). * @param mode SPI_MODE_x (single-ended or pseudo-differential pairs). * @param cepin CS/CE GPIO pin number. * @return returns 0 on success, -1 otherwise. */int spi_device_init (const char *spidevfs, spidev *dev, uint16_t delay, uint32_t clock, uint8_t bits, uint8_t mode, uint8_t cepin){ /* initialize struct members */ dev->spi_xfer.tx_buf = (unsigned long)dev->buf; dev->spi_xfer.rx_buf = (unsigned long)(dev->buf + 3); dev->spi_xfer.len = 3; dev->spi_xfer.delay_usecs = delay; dev->spi_xfer.speed_hz = clock; dev->spi_xfer.bits_per_word = bits; dev->mode = mode; dev->cepin = cepin; /* open spi device in Linux sysfs */ if ((dev->fd = open (spidevfs, O_RDWR)) == -1) { perror ("open spidevfs"); return -1; } /* set device mode */ if (ioctl (dev->fd, SPI_IOC_WR_MODE, &dev->mode) == -1) { perror ("error init SPI_IOC_WR_MODE"); return -1; } /* set number of bits per word (byte) */ if (ioctl (dev->fd, SPI_IOC_WR_BITS_PER_WORD, &dev->spi_xfer.bits_per_word) == -1) { perror ("error init SPI_IOC_WR_BITS_PER_WORD"); return -1; } /* send requested SPI bus speed */ if (ioctl (dev->fd, SPI_IOC_WR_MAX_SPEED_HZ, &dev->spi_xfer.speed_hz) == -1) { perror ("error init SPI_IOC_WR_MAX_SPEED_HZ"); return -1; } /* read configured SPI bus speed */ if (ioctl (dev->fd, SPI_IOC_RD_MAX_SPEED_HZ, &dev->spi_xfer.speed_hz) == -1) { perror ("error init SPI_IOC_RD_MAX_SPEED_HZ"); return -1; } return 0;}/** * @brief set channel to psuedo-differential compare mode. * @param channel channel to set control bits on (SGL/DIF = 0, D2=D1=D0=0). * @return returns control bits set for channel. */uint8_t channel_cfg_differential (uint8_t channel){ return (channel & 7) << 4;}/** * @brief set channel to single-ended input mode. * @param channel channel to set control bits on (SGL/DIF = 1, D2=D1=D0=0). * @return returns control bits set for channel. */uint8_t channel_cfg_single (uint8_t channel){ return (0x8 | channel) << 4;}/** * @brief read sample from mcp3008 for channel. * @param dev pointer to mcp3008 device struct. * @param channel analog input channel to read. * @return returns raw 10-bit value for sample. */int spi_read_adc (spidev *dev, uint8_t channel){ dev->buf[0] = 1; dev->buf[1] = channel_cfg_single (channel); dev->buf[2] = 0; if (ioctl (dev->fd, SPI_IOC_MESSAGE(1), &dev->spi_xfer) == -1) { perror ("ioctl SPI_IOC_MESSAGE()"); abort(); } return ((dev->buf[4] & 0x03) << 8) | (dev->buf[5] & 0xff);}...
The you can simply loop calling spi_read_adc() for each channel at whatever rate you like.
I've used the kernel interface for SPI, I2C, GPIO, PWM, etc.. and the benefit is you learn it once and can then use it with any device running Linux, you are not tied to any 3rd party library that will go away, and there is no root privileges required. That checked one of the primary boxes I had. Good luck with your coding.
Statistics: Posted by drankinatty — Fri May 03, 2024 11:01 pm