在Linux嵌入式开发中,尤其是在进行高性能SoC上的信号采集时,IIO (Industrial I/O) 子系统提供了强大的框架。为了实现连续、同步且高效的数据采集,掌握其高级特性至关重要,其中Triggered Buffer(触发缓冲)、DMA(直接内存访问)以及高精度时间戳是三个核心模块。
Triggered Buffer(触发缓冲区):同步采集的基石
IIO子系统的Triggered Buffer是实现多通道同步、连续数据采集的核心机制。它确保在触发事件发生时,所有使能的通道在同一时刻被采样,并将数据高效地传输至用户空间。
其工作流程可概括为:
- 触发事件:由外部硬件中断、高精度定时器或软件指令发起。
- 触发器分发:IIO核心层接收信号,并调用所有关联此触发器的驱动回调函数。
- 数据读取:驱动的中断处理函数被唤醒,从硬件寄存器读取所有使能通道的数据。
- 推送至缓冲区:数据被打包后,推送到内核的环形缓冲区(Buffer)。
- 用户态读取:用户空间通过
poll或读取/dev/iio:deviceX字符设备获取数据。
IIO Buffer 是一个内核环形队列,数据按扫描顺序(Scan Order)排列。例如,启用通道0、2及时间戳后,Buffer中的一帧数据可能为:[Ch0][Ch2][填充][Timestamp]。内核利用kfifo等机制管理并发读写,防止数据丢失。
IIO Scan Mask(扫描掩码) 决定了哪些通道的数据会被放入Buffer。用户可通过Sysfs接口(如scan_elements/in_voltageX_en)动态选择通道,驱动则根据active_scan_mask读取相应的硬件寄存器。
常用触发器类型
- iio-trig-interrupt:基于物理中断触发,例如ADC完成采样后通过DRDY引脚产生中断。
- iio-trig-hrtimer:基于内核高精度定时器,适用于由SoC主控设定固定采样率(如1kHz)的场景。
- iio-trig-sysfs:软件触发,通过向特定sysfs文件写入指令来触发单次采样,主要用于调试。
驱动层实现详解
第一步:配置扫描元素
在iio_chan_spec结构体中定义通道的scan_index和scan_type,这决定了数据在Buffer中的布局。
static const struct iio_chan_spec ads1256_channels[] = {
{
.type = IIO_VOLTAGE,
.indexed = 1,
.channel = 0,
.scan_index = 0, // 在Buffer中的排列顺序
.scan_type = {
.sign = 's', // 有符号
.realbits = 24, // 真实有效位
.storagebits = 32, // 存储占用位(4字节对齐)
.endianness = IIO_BE, // 大端(SPI常用)
},
},
IIO_CHAN_SOFT_TIMESTAMP(1), // 添加时间戳通道,scan_index通常设为最高
};
第二步:实现触发器处理函数
这是采集的核心回调函数,在触发事件发生时被调用。
static irqreturn_t ads1256_trigger_handler(int irq, void *p)
{
struct iio_poll_func *pf = p;
struct iio_dev *indio_dev = pf->indio_dev;
struct ads1256_state *st = iio_priv(indio_dev);
u8 data[MAX_CHANNELS * 4 + 8]; // 数据缓冲区
// 1. 根据active_scan_mask,读取所有使能通道的数据
ads1256_read_active_channels(st, indio_dev->active_scan_mask, data);
// 2. 将数据与高精度时间戳一同推入IIO Buffer
iio_push_to_buffers_with_timestamp(indio_dev, data, iio_get_time_ns(indio_dev));
// 3. 通知触发器本次处理完成
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
}
第三步:初始化并关联Buffer
在驱动的probe函数中,调用API完成Buffer设置。
// 设置触发缓冲
ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
NULL, // top half 中断处理(可选)
ads1256_trigger_handler, // bottom half 处理函数
NULL);
进阶:DMA与零拷贝优化
当采样率极高或数据量巨大时,CPU频繁参与SPI/I2C读取并拷贝至内核Buffer会成为瓶颈。此时,可以借助DMA(直接内存访问)来实现零拷贝(Zero-copy),让数据直接从外设(如ADC)通过DMA传输到IIO Buffer所在的物理内存中。
实现思路:
- 为IIO Buffer分配DMA可访问的内存区域(通常使用
dma_alloc_coherent)。
- 将这片内存区域注册为IIO Buffer的后端存储。
- 配置ADC的DMA控制器,使其完成转换后自动将数据搬移到指定内存地址。
- 触发处理函数中,仅需获取数据地址并添加时间戳,无需实际的数据拷贝操作。
这大幅降低了CPU占用率,提升了系统整体性能和实时性,是Linux驱动开发中进行高性能数据采集的关键优化手段。
总结
掌握IIO的Triggered Buffer机制是实现可靠同步采集的基础,而结合DMA的零拷贝技术则是迈向高性能嵌入式数据采集系统的进阶步骤。通过合理设计扫描掩码、选择触发源并优化数据通路,开发者可以充分挖掘硬件潜力,满足严苛的连续信号采集需求。