4、IMU数据采集:使用nRF SDK或Zephyr RTOS配置I2C/SPI接口,读取原始加速度和角速度数据
IMU(惯性测量单元)是动作识别系统的“感觉器官”,其数据采集质量直接决定了后续窗口特征提取和板端分类的准确性。在nRF52/nRF53系列平台上,开发者通常面临两种主流开发路径:基于nRF5 SDK的裸机或RTOS(如FreeRTOS)环境,以及基于Zephyr RTOS的设备树驱动环境。本章将深入讲解如何在这两种环境下配置I2C或SPI接口,完成对典型IMU传感器(如BMI160、LSM6DSO、ICM-42688-P)的寄存器初始化、数据读取与校验,并给出工程化的代码实现与排障思路。
在开始配置之前,必须明确一个核心原则:IMU数据采集不是简单的“读寄存器”操作,而是一个涉及时序、同步、功耗与数据完整性的系统工程。动作识别应用通常要求100Hz以上的采样率,且加速度和陀螺仪数据必须时间对齐。如果使用I2C接口,400kHz的时钟频率在读取6轴数据(12字节)时,理论耗时约240μs,加上寄存器寻址和应答开销,实际约300-400μs,这在100Hz采样周期(10ms)内是可行的。但若使用SPI,2MHz时钟下读取同样数据仅需约60μs,且支持DMA传输,CPU占用率更低。因此,对于电池供电且需要高采样率的可穿戴设备,SPI是更优选择;而I2C的优势在于引脚占用少(2线),适合引脚受限的设计。
4.1 核心原理:IMU寄存器映射与数据读取机制
所有数字IMU都遵循类似的寄存器访问模式。以BMI160为例,其加速度计和陀螺仪数据寄存器分别位于0x0A-0x0F(加速度X/Y/Z,每个轴16位,高字节在前)和0x10-0x15(角速度X/Y/Z)。读取时,主机发送寄存器起始地址,传感器连续输出6个字节(或12个字节同时读取加速度和陀螺仪)。关键点在于:必须一次性读取全部6个轴的数据,避免分次读取导致的时间错位。例如,如果先读加速度再读陀螺仪,两次读取间隔可能引入1-2ms的误差,在快速旋转时会导致加速度和角速度的“时间戳不一致”,进而影响姿态解算精度。
另一个核心机制是数据就绪中断。IMU内部有数据就绪(DRDY)引脚,当新数据写入输出寄存器时,该引脚产生脉冲或电平变化。MCU可以配置为GPIO外部中断,在中断服务函数中启动SPI/I2C读取。这种方式避免了轮询带来的CPU浪费,且能保证每个采样周期读取一次数据,不会漏采或重复采样。在Zephyr中,这对应sensor_trigger_set() API;在nRF5 SDK中,则需手动配置GPIOTE模块和传感器中断寄存器。
数据读取时序要点:
- SPI模式:通常使用模式0(CPOL=0, CPHA=0)或模式3,需查阅传感器数据手册。BMI160支持SPI模式0/3。
- I2C地址:BMI160的7位地址为0x68(SDO接地)或0x69(SDO接VDD)。LSM6DSO默认地址为0x6A。
- 多字节读取:I2C需在寄存器地址后连续读取,SPI需将CS拉低后连续发送时钟。
- 字节序:绝大多数IMU采用大端序(高字节在前),但部分传感器(如ICM-42688)可通过寄存器配置字节序。
4.2 判断方法:选择I2C还是SPI,以及Zephyr设备树配置
在工程决策中,可以从以下维度判断总线选型:
| 维度 | I2C | SPI | 决策建议 |
|---|---|---|---|
| 引脚占用 | 2(SCL/SDA) | 4(SCK/MOSI/MISO/CS) | 引脚紧张选I2C |
| 最大速率 | 400kHz(标准)/1MHz(快速) | 10MHz+ | 高采样率选SPI |
| CPU占用 | 较高(逐字节ACK) | 低(支持DMA) | 低功耗选SPI+DMA |
| 抗干扰 | 较弱(开漏) | 较强(推挽) | 恶劣环境选SPI |
| 多设备支持 | 地址区分,可挂多设备 | CS区分,需独立CS引脚 | 设备多时I2C更省引脚 |
在Zephyr RTOS中,配置IMU的推荐方式是使用设备树(Device Tree)。以nRF52840 + BMI160为例,在nrf52840dk_nrf52840.overlay中添加:
/ {
chosen {
zephyr,console = &uart0;
};
};
&spi2 {
compatible = "nordic,nrf-spim";
status = "okay";
cs-gpios = <&gpio0 28 GPIO_ACTIVE_LOW>;
bmi160: bmi160@0 {
compatible = "bosch,bmi160";
reg = <0>;
spi-max-frequency = <8000000>;
label = "BMI160";
int-gpios = <&gpio0 29 GPIO_ACTIVE_LOW>;
};
};
然后在prj.conf中启用SPI和传感器驱动:
CONFIG_SPI=y
CONFIG_BMI160=y
CONFIG_SENSOR=y
CONFIG_SENSOR_TRIGGER=y
对于I2C方案,只需将spi2替换为i2c0,并设置compatible = "bosch,bmi160",reg = <0x68>即可。Zephyr的sensor子系统会自动处理寄存器初始化,开发者只需调用sensor_sample_fetch()和sensor_channel_get()。
4.3 操作流程:nRF5 SDK手动配置与数据读取
在nRF5 SDK(如nRF5_SDK_17.1.0)中,没有现成的IMU驱动,需要开发者基于TWI(I2C)或SPI外设驱动编写。以下是一个基于SPI的BMI160读取流程:
- 初始化SPI外设:配置
nrf_drv_spi_config_t,设置SCK频率为4MHz,模式0,位序MSB优先。 - 配置GPIO:将CS引脚配置为输出,默认高电平;INT引脚配置为输入,上升沿触发中断。
- 传感器复位与配置:向0x7E寄存器写入0xB6(软复位),等待10ms;然后配置加速度计量程(0x41寄存器,±2g)和陀螺仪量程(0x43寄存器,±2000dps),设置输出数据速率(0x40寄存器,100Hz)。
- 使能数据就绪中断:向0x50寄存器(INT_EN_1)写入0x01,使能数据就绪中断。
- 中断服务函数中读取数据:在GPIOTE中断回调中,拉低CS,发送0x0A(加速度X高字节地址),然后连续读取12字节,存入缓冲区。
void bmi160_read_data(int16_t *accel, int16_t *gyro) {
uint8_t reg = 0x0A; // 起始地址
uint8_t data[12];
nrf_drv_spi_transfer(&spi, ®, 1, data, 12);
// 大端序转换
accel[0] = (int16_t)((data[0] << 8) | data[1]);
accel[1] = (int16_t)((data[2] << 8) | data[3]);
accel[2] = (int16_t)((data[4] << 8) | data[5]);
gyro[0] = (int16_t)((data[6] << 8) | data[7]);
gyro[1] = (int16_t)((data[8] << 8) | data[9]);
gyro[2] = (int16_t)((data[10] << 8) | data[11]);
}
注意:nrf_drv_spi_transfer是阻塞式的,在中断中调用会阻塞中断上下文。更优做法是在主循环中通过标志位触发读取,或使用SPI的DMA模式(nrf_drv_spi_transfer本身支持非阻塞回调)。
4.4 案例拆解:Zephyr下实现100Hz IMU数据采集与缓存
假设我们需要为一个手腕动作识别设备采集IMU数据,要求100Hz采样率,数据通过BLE实时传输。我们选择nRF52840 + LSM6DSO(SPI接口),使用Zephyr RTOS。
背景与约束:设备电池容量为200mAh,目标续航24小时;MCU主频64MHz;BLE连接间隔为7.5ms(用于低延迟传输)。
初始现象:使用I2C接口时,CPU占用率高达35%,导致BLE协议栈处理延迟,出现数据丢包。改用SPI后,CPU占用率降至12%,但发现数据偶尔出现“跳变”——加速度值突然变为0或满量程。
分析过程:通过逻辑分析仪抓取SPI波形,发现CS信号在读取过程中出现毛刺,导致传感器输出无效数据。进一步检查发现,CS引脚配置为推挽输出,但未使能内部上拉,在SPI时钟空闲时CS电平不稳定。
解决方案:在设备树中为CS引脚添加GPIO_PULL_UP标志,并增加10ms延时等待传感器稳定。修改后的设备树片段:
cs-gpios = <&gpio0 28 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>;
结果对比:修复后,连续运行12小时,未再出现数据跳变。CPU占用率稳定在12%,BLE传输正常,数据包丢失率为0。
风险与代价:SPI总线频率从8MHz降至4MHz,以进一步降低辐射干扰;代价是单次读取时间从30μs增至60μs,但100Hz采样下仍远低于10ms周期。
经验复盘:IMU数据采集的“玄学”问题,90%以上是电气连接或时序问题。建议在调试初期使用逻辑分析仪(如Saleae)同时抓取SCK、MOSI、MISO、CS四路信号,确认时序符合传感器数据手册要求。另外,务必检查IMU的VDDIO引脚电压是否与MCU的IO电压匹配(nRF52840为3.3V,部分IMU支持1.8V)。
4.5 常见误区与总结复盘
在IMU采集的工程实践中,以下误区最为常见:
- 误区一:忽略传感器上电稳定时间。BMI160上电后需要至少10ms才能访问寄存器,LSM6DSO需要约15ms。若上电后立即读取,可能返回0xFF或随机值。
- 误区二:中断引脚未配置为输入。在nRF5 SDK中,若忘记将INT引脚配置为输入,GPIOTE中断将无法触发,导致数据永远不更新。
- 误区三:SPI时钟极性/相位错误。BMI160支持SPI模式0和3,但LSM6DSO仅支持模式0。若配置错误,读取的数据将全部为0或乱码。
- 误区四:在中断服务函数中执行耗时操作。SPI/I2C读取本身耗时,若在中断中直接调用阻塞式读取,会阻塞其他中断(如BLE协议栈的RADIO中断),导致系统崩溃。
- 误区五:忽略FIFO功能。许多IMU内置FIFO缓冲区(如BMI160的1024字节),可以缓存多组数据。在低功耗场景下,可以让IMU以较高ODR写入FIFO,MCU定期唤醒批量读取,从而降低平均功耗。
总结:IMU数据采集的核心在于“可靠地、低延迟地、时间一致地”获取六轴原始数据。在nRF平台上,Zephyr RTOS提供了标准化的sensor驱动,适合快速原型开发;nRF5 SDK则提供了更大的灵活性,适合深度优化。无论选择哪种路径,都必须关注电气时序、中断处理和功耗平衡。建议在项目初期就使用SPI+DMA+中断的方案,为后续高采样率(>200Hz)和复杂算法预留性能余量。同时,务必在硬件设计阶段预留测试点(TP),方便调试时连接逻辑分析仪或示波器。
风险提示:当使用Zephyr的sensor驱动时,默认的sensor_sample_fetch()是阻塞式的,会等待数据就绪。在100Hz采样下,这意味着每10ms阻塞一次,若传感器无响应,可能导致线程挂起。建议设置传感器触发模式(trigger mode),配合K_NO_WAIT超时参数,避免无限等待。