在高速、高精度的ADC数据采集场景中,特别是需要多片ADC实现多通道同步采样时,通常有两种驱动架构方案可选。第一种是将多片ADC抽象为一个拥有多个通道的单一IIO设备。第二种,也是工业级仪器中更为常见的标准架构,是在SOC(核心板)与ADC之间加入一个FPGA(可编程逻辑阵列)。在这种SOC+FPGA的架构下,复杂的硬件时序同步逻辑交由FPGA完成,而运行在Linux驱动层的任务则变得更加纯粹和高效,主要负责数据搬运。
逻辑架构设计
FPGA侧职责:
- 负责多片ADC的并行时序控制、主时钟(MCLK)分发以及同步(SYNC)信号生成。
- 将多通道(假设为
n个)的24位或32位采样数据,拼接成一个“超大帧”(大小为 n × 4 字节)。
- 当一帧数据准备就绪后,FPGA向SOC端发出一个DRDY(数据就绪)中断信号。
SOC侧(Linux驱动)职责:
- 将FPGA识别为一个单一的SPI从设备。
- 通过IIO子系统管理这
n个逻辑通道。
- 利用SPI的DMA模式,一次性读取完整的
4n字节数据帧,实现高效传输。
自定义IIO驱动实现(以6通道为例)
我们需要编写一个专用的IIO驱动,将FPGA抽象为一个具有6个采集通道的ADC设备。
1. 设备树(DTS)配置
关键配置包括SPI通信速率(FPGA通常支持极高频率,建议设置在20MHz~50MHz)以及数据就绪中断引脚。
&spi0 {
status = "okay";
fpga_adc: adc-collector@0 {
compatible = "my_org,fpga-adc-aggregator";
reg = <0>;
spi-max-frequency = <50000000>; // 50MHz,保证读取速度
/* FPGA 的数据就绪中断信号 */
interrupt-parent = <&gpio3>;
interrupts = <RK_PC2 IRQ_TYPE_EDGE_FALLING>;
};
};
2. 驱动核心:通道定义 (iio_chan_spec)
这里需要定义6个通道,告知IIO内核每一帧数据中包含6个32位的电压值。
static const struct iio_chan_spec fpga_adc_channels[] = {
{
.type = IIO_VOLTAGE,
.indexed = 1,
.channel = 0,
.scan_index = 0,
.scan_type = {
.sign = 's',
.realbits = 32,
.storagebits = 32,
.endianness = IIO_BE, // FPGA拼帧通常采用大端字节序
},
},
/* ... 同样方式定义通道 1 到 5 ... */
IIO_CHAN_SOFT_TIMESTAMP(6), // 通道6用作软件时间戳
};
3. 驱动核心:触发处理函数 (Trigger Handler)
当FPGA发出中断时,此函数被调用,执行一次完整的SPI数据帧读取。
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 my_fpga_state *st = iio_priv(indio_dev);
// 24字节数据(6通道*4字节)+ 8字节对齐的时间戳
u8 rx_buf[32];
/* 使用SPI同步读取整个长帧 */
// 在底层,SOC的SPI控制器驱动会自动使用DMA(如果已配置)
int ret = spi_read(st->spi, rx_buf, 24);
if (ret)
goto out;
/* 将数据推送到IIO缓冲区 */
iio_push_to_buffers_with_timestamp(indio_dev, rx_buf, pf->timestamp);
out:
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
}
用户态数据读取
在应用层,我们依然可以方便地使用libiio库。FPGA方案的一大优势在于,开发者只需打开这一个IIO设备,从缓冲区中读取到的数据天然就是6个通道严格同步对齐的。
// 伪代码演示用户态读取
struct iio_buffer *buf = iio_device_create_buffer(adc_dev, 1024, false);
while (running) {
iio_buffer_refill(buf);
// 获取缓冲区起始地址
int32_t *data_ptr = (int32_t *)iio_buffer_first(buf, chan0);
// data_ptr[0] = 通道0, data_ptr[1] = 通道1 ... data_ptr[5] = 通道5
// 这6个采样点在硬件上是绝对同步的
process_synchronized_data(data_ptr);
}
FPGA方案的进阶优化
利用FPGA的可编程性,我们可以实现更高级的功能以提升系统性能:
- 硬件时间戳注入:让FPGA在数据帧末尾追加8字节时间戳。该时间戳由FPGA内部计数器生成,并由GPS的PPS信号清零,从而获得硬件级的高精度时间基准,彻底消除Linux软件中断引入的延迟。
- SPI块传输优化:当采样率极高(如超过50ksps)时,可在FPGA内部设计一个FIFO,缓存多帧数据(例如10帧,共240字节)后再触发一次中断。这能大幅降低SOC的中断频率,显著提升系统整体稳定性,是应对高并发数据流的有效策略。
- 状态监控:在SPI数据帧中预留若干字节作为“状态位”,用于指示FPGA检测到的外部同步异常、ADC溢出或电源波动等硬件状态,实现更完善的系统监控。