正确做法是批量发送而非单字节循环:用spidev+ioctl配置模式和速率后,通过spi_ioc_transfer一次性提交多字节数据,避免write()单字节调用引发的内核调度开销、CS频繁切换及DMA超时等问题。

直接结论:用 spidev + ioctl 配置好模式和速率后,write() 或 spi_ioc_transfer 批量发,别用单字节循环。
为什么不能用 write() 单字节发高速数据
Linux 的 spidev 设备节点虽然支持 write(),但它会为每个字节触发一次内核 SPI 传输调度,带来严重开销:每次调用都经过 VFS 层、copy_from_user、DMA setup、中断等待等路径。实测在 10MHz SCLK 下,连续 write(fd, &b, 1) 十次,实际线上的时隙间隔可能高达 20–50 μs,远低于理论吞吐——这不是总线瓶颈,是系统调用拖慢的。
正确做法是把数据攒成块(如 64/256/1024 字节),一次性提交:
- 用
struct spi_ioc_transfer填充tx_buf、len、speed_hz等字段,再ioctl(fd, SPI_IOC_MESSAGE(1), &tr) - 或直接
write(fd, buf, len),但仅限于简单全双工发送(MOSI-only 场景下 MISO 会被丢弃) - 避免混用:不要在同一个 fd 上交替调用
write()和ioctl(SPI_IOC_MESSAGE),内核驱动对状态缓存不一致
spi_ioc_transfer 必须设对的三个参数
高频通信下,这三个字段错一个,数据就错位或丢字节:
立即学习“C++免费学习笔记(深入)”;
-
speed_hz:不是“建议值”,是实际 SCLK 频率上限。驱动会向下取整到最接近的可分频值(如请求 12.5MHz,硬件只支持 12/13.3/16MHz,则可能落到 12MHz)。用cat /sys/class/spi_master/spi0/device/spi0.0/spi_bus_speed(若存在)或查芯片手册确认分频器精度 -
bits_per_word:默认是 8,但有些从设备(如某些 ADC 或 LED 驱动)要求 16 位帧。设错会导致高位被截断或低位补零,且spidev不校验该值是否匹配硬件能力 -
mode:必须和从设备文档严格一致,例如SPI_MODE_0(CPOL=0, CPHA=0)和SPI_MODE_3(CPOL=1, CPHA=1)在上升沿采样时机差半个周期,高速下极易误读
CS(片选)电平与自动控制陷阱
spidev 默认在每次 SPI_IOC_MESSAGE 调用前后自动拉低/拉高 CS,看似省事,但问题明显:
- 多包连续传输时,CS 中断会导致从设备重置内部状态机(比如 MAX7219 会清空显示缓冲区)
- 某些 Flash 或传感器要求 CS 在整个命令序列中保持低电平(如写使能+页编程),此时必须关掉自动 CS:
ioctl(fd, SPI_IOC_WR_CS_HIGH, &val)(val=0表示 CS 低有效),再手动用 GPIO 控制 - 如果用
write()发送多个小包,CS 会逐包开关——这不是“协议允许”,而是驱动行为;想保持 CS 连续,只能合并进单个spi_ioc_transfer数组
真正卡住性能的其实是内存与缓存
高频发送(>5MHz)时,tx_buf 指针若指向栈或普通堆内存,可能因 cache line 未对齐或未预取导致 DMA 超时。常见表现是 ioctl 返回 -1 并报 EIO,dmesg 显示 “DMA transfer timeout”。
解决方式很具体:
- 用
posix_memalign(&buf, 4096, size)分配页对齐内存(不是malloc) - 发前加
__builtin___clear_cache(buf, buf + size)(GCC)或__builtin_arm_dcache_clean()(ARM Cortex-M)确保数据落物理内存 - 避免在中断上下文或实时线程里分配/释放该内存;最好初始化阶段一次配好,复用
这些细节不写进代码,跑在 AM335x 或 i.MX6 上可能前 100 次都正常,第 101 次突然失败——因为 cache 状态刚好凑巧不对。


















