在高频采样场景下,仅依赖CPU进行SPI字节读写极易导致系统响应迟滞甚至卡死。对于32位高精度模数转换器(ADC)的数据采集,要实现高吞吐、低延迟与零丢包的目标,必须在内核层面进行深度优化,核心在于利用SPI控制器的直接内存访问(DMA)功能与优化中断处理机制。
在常见的SoC(如RK3568)中,SPI控制器通常配备专用DMA通道。启用DMA后,数据搬运流程如下:
- FPGA的DRDY引脚电平跳变,触发SoC的GPIO中断。
- 内核驱动向DMA控制器提交传输描述符,指令内容为“从SPI接收寄存器搬运32字节数据至内存地址A”。
- DMA引擎直接在系统总线上完成数据搬移,此过程无需CPU介入。
- 传输完成后,DMA控制器产生中断,通知驱动数据已在内存中准备就绪。
核心优化策略
1. 批量读取(Batching / Burst Mode)
若每采集一帧数据(如32字节)就触发一次DMA传输,中断频率仍然很高(例如10kHz采样率对应每秒1万次中断)。优化方案是在FPGA侧构建一个深度FIFO缓冲区(例如缓存10帧数据),FPGA在攒够10帧数据后才触发一次DRDY中断。驱动程序则通过一次DMA传输读取320字节数据,从而将中断频率降低一个数量级,大幅减少系统上下文切换开销。
2. 内存对齐与缓存一致性(DMA Coherent Mapping)
32位ADC对数据完整性要求极高。在ARM64等架构中,必须妥善处理CPU缓存与DMA访问内存之间的一致性问题。在驱动开发中,应使用 dma_alloc_coherent() 分配缓冲区,或在定义缓冲区时使用 ____cacheline_aligned 属性进行标记。这确保了DMA写入内存的数据能被CPU立刻读取到最新值,而非过时的缓存副本。
3. 线程化中断(Threaded IRQs)优先级调优
在Linux内核的中断处理模型中,将耗时操作放在底半部(线程化中断)中是常见做法。为了进一步降低中断处理的延迟,可以将处理SPI数据的线程设置为实时(Real-time)调度策略与高优先级。
// 在驱动probe函数中,设置中断线程为SCHED_FIFO实时优先级
struct sched_param param = { .sched_priority = 90 };
sched_setscheduler(st->irq_thread, SCHED_FIFO, ¶m);
配置SoC设备树以开启DMA
在原厂设备树中,必须显式声明SPI控制器使用的DMA通道。
&spi0 {
status = "okay";
/* 指定使用的DMA控制器及请求线(通道) */
dmas = <&dmac0 0>, <&dmac0 1>;
dma-names = "tx", "rx";
/* 关键参数:调整接收FIFO阈值以匹配DMA传输 */
rx-sample-delay-ns = <10>;
adc_aggregator@0 {
compatible = "my_org,fpga-ltc2500-aggregator";
reg = <0>;
spi-max-frequency = <50000000>;
/* 连接GPIO中断 */
interrupt-parent = <&gpio3>;
interrupts = <RK_PC2 IRQ_TYPE_EDGE_FALLING>;
};
};
在IIO驱动中触发DMA传输
在IIO驱动的轮询函数(trigger handler)中,通过 spi_message 接口提交传输请求。当数据长度超过内核预设的DMA阈值且设备树已正确配置时,SPI控制器驱动会自动启用DMA模式。
static irqreturn_t fpga_adc_trigger_handler(int irq, void *p)
{
struct iio_poll_func *pf = p;
struct iio_dev *indio_dev = pf->indio_dev;
struct fpga_adc_state *st = iio_priv(indio_dev);
struct spi_transfer xfer = {
.rx_buf = st->rx_buf, // 必须为Cacheline对齐的DMA缓冲区
.len = 320, // 批量读取长度,例如10帧*32字节
.cs_change = 0,
};
/* spi_sync_transfer 会根据长度和配置自动选择PIO或DMA模式 */
spi_sync_transfer(st->spi, &xfer, 1);
iio_push_to_buffers_with_timestamp(indio_dev, st->rx_buf, pf->timestamp);
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
}
性能验证与调试
完成内核优化后,需通过系统工具验证效果:
- 查看中断负载:执行
cat /proc/interrupts。观察对应的SPI或GPIO中断计数增长是否符合预期频率(如批量后应为原始频率的1/10)。同时,通过 top 命令查看系统CPU使用率,若 si(软中断)或 sy(系统)占用率极低,表明DMA工作正常。
- 检查DMA引擎状态:通过
cat /sys/kernel/debug/dmaengine/summary 查看DMA控制器(如dw-axi-dmac)的活动传输记录。
- 分析延迟:使用
ftrace 或 trace-cmd 工具,跟踪从硬件中断触发到 spi_sync_transfer 函数完成的完整路径耗时,以确认优化后的延迟是否符合要求。
通过上述内核驱动与系统调优手段,可以显著提升基于SPI接口的高速ADC数据采集系统的稳定性和数据完整性。