linux下用spidev驱动读写spi设备最简路径是打开/dev/spidevx.y后通过ioctl()调用spi_ioc_message传输struct spi_ioc_transfer;需严格匹配设备树的总线号x与片选号y,分配物理连续内存作tx/rx_buf,校验返回长度并处理eagain/eio等错误。

Linux下用spidev驱动读写SPI设备最简路径
C++本身不提供SPI抽象,必须走系统接口;在嵌入式Linux上,spidev是唯一靠谱的用户态选择。别想绕过它自己mmap寄存器——没驱动支持、不可移植、且容易锁死SPI控制器。
实操就是打开/dev/spidevX.Y,用ioctl()发struct spi_ioc_transfer。注意X是总线号(如spidev0.0中的0),Y是片选号(CS0/CS1),不是随便写的数字,得和设备树或spi_board_info里注册的一致。
- 先确认设备存在:
ls /dev/spidev*,没有就说明内核没启用spidev模块或设备树没配对 -
open()要用O_RDWR,只读打开后调ioctl(fd, SPI_IOC_MESSAGE(1), &tr)会失败并报Invalid argument - 每次
ioctl最多传64个spi_ioc_transfer结构(取决于内核配置),单次传太大可能被截断,建议拆成≤16段
spi_ioc_transfer字段填什么才不丢数据
这个结构体看着简单,但tx_buf/rx_buf填错地址、len算错长度、bits_per_word没对齐,都会导致MISO全0或乱码。尤其注意:即使只发不收,也要分配rx_buf内存并设为非NULL,否则某些SoC(如RK3399)直接忽略本次传输。
-
speed_hz不能超过硬件上限(比如全志H3标称50MHz,但实际稳定在24MHz以下);设太高会静默降频,你以为发了8MHz,其实跑了4MHz -
delay_usecs是字节间延时,不是帧间;如果需要CS拉高后再等几微秒,得手动ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed)切速,再usleep(),不能指望它 -
cs_change = 1只在最后一个transfer设,否则每字节都抖CS,从机根本来不及响应
为什么std::vector<uint8_t></uint8_t>直接传tx_buf会崩溃
因为tx_buf要求是物理连续、可DMA的内存地址。std::vector在堆上分配,虽然逻辑连续,但内核无法直接拿它的虚拟地址做DMA;必须用malloc()或mmap()配MAP_LOCKED,否则ioctl返回-EFAULT。
立即学习“C++免费学习笔记(深入)”;
- 安全做法:用
uint8_t* buf = static_cast<uint8_t>(malloc(len))</uint8_t>,用完free(buf) - 别用
std::unique_ptr<uint8_t></uint8_t>配malloc——默认deleter调delete[],会崩 - 高频场景(如音频流)建议提前
mmap(/dev/mem)申请DMA buffer,但需要root权限和设备树预留内存
C++封装spidev时最容易漏掉的错误检查
很多人封装个SPIBus类,只检查open()是否成功,后面全靠assert(ioctl(...) >= 0),结果在产线遇到EAGAIN(忙等超时)或EIO(信号干扰导致CRC错)直接abort。这些错误必须显式处理。
-
ioctl()返回-1时,立刻查errno:EAGAIN要重试(最多3次),EIO大概率是线路接触不良,该报错日志而不是跳过 - 读写后必须校验
len字段是否等于期望值,有些驱动在缓冲区溢出时悄悄截断,但不报错 - 关闭fd前务必发一次空transfer(
len=0),否则下次打开可能遇到CS电平异常,尤其带EEPROM的设备会拒响应
SPI不是UART,没自动重传、没流量控制、没错误恢复协议。每一字节都得自己盯住状态、时序、电源噪声——连示波器都没接就跑通的SPI,多半只是运气好。










