news 2026/9/16 7:19:53

STM32中断方式读取LSM6DSOW陀螺仪:从I2C配置到DRDY中断实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32中断方式读取LSM6DSOW陀螺仪:从I2C配置到DRDY中断实战

陀螺仪数据能不能稳定、及时地拿到,往往是 IMU 项目里最容易翻车的地方。最近我在 STM32C5 上调试 LSM6DSOW,把传感器数据就绪(DRDY)中断接到 MCU 的外部中断上,用中断方式读取陀螺仪数据。和简单的轮询相比,这套方式不需要在主循环里反复查询状态寄存器,CPU 占用低很多,采样节拍也更准,不会因为主循环被其他任务拖住而漏掉数据。

这篇文章会把从硬件连接到寄存器配置、从 EXTI 设置到中断服务函数的完整过程记录下来,过程中踩过的坑也一并说清楚。如果你正在做运动检测、姿态解算,或者想优化 IMU 读取方式,可以照着抄。

1. 为什么采用中断方式读取LSM6DSOW陀螺仪数据

1.1 轮询方式的痛点到底在哪

很多人第一次点陀螺仪,都会在主循环里写一个“定时读状态寄存器”的逻辑,比如这样:每 5ms 去读一下 STATUS_REG,发现陀螺仪数据就绪位变成 1,就把 OUTX_L_G、OUTX_H_G 这几个寄存器读回来。

这套逻辑在最简单的 Demo 里跑得通,但一旦主循环里有屏幕刷新、电机控制、通信协议处理,事情就变了。某次循环多跑了 2ms,下一次去读状态寄存器时,数据就绪脉冲可能已经被传感器发完并清掉了。结果就是丢数,姿态解算出来的角度就会出现跳变。轮询看起来“代码简单”,但本质上是用 CPU 的时间和主循环的确定性去赌传感器不丢数据,赌赢了没事,赌输了很难查。

中断方式解决的是“谁先主动”的问题。LSM6DSOW 一旦完成一次陀螺仪采样,会通过 INT1 引脚主动告诉 MCU:有新数据了,你赶紧来拿。MCU 的 EXTI 模块收到这个脉冲后,会触发中断服务函数。这样就算主循环很忙,数据就绪事件也不会被遗漏,只需要在中断里置一个标志,等主循环空闲时再去读。

1.2 LSM6DSOW的DRDY中断机制

LSM6DSOW 内部其实有两套“中断系统”,很多人容易搞混。

第一套是传感器内部事件中断,比如陀螺仪数据就绪、加速度计数据就绪、FIFO 水位达到阈值、唤醒事件检测等。这些事件会映射到芯片的两个物理引脚 INT1 和 INT2,由寄存器 INT1_CTRL、INT2_CTRL 控制。

第二套才是 MCU 侧的外部中断。STM32C5 的某个 GPIO 引脚配成 EXTI 模式后,收到 INT1 引脚上的脉冲就会跳进自己家的中断服务函数。

我这里做的事情就是把两颗芯片连接起来:LSM6DSOW 的陀螺仪数据就绪事件映射到 INT1 引脚,INT1 引脚再接到 STM32C5 的一个可外部中断的 GPIO 上。这样传感器每次采样完,MCU 就会被“踢一脚”,在中断回调里记录一下“该取数了”。

LSM6DSOW 的数据就绪位在 STATUS_REG 的 bit1,也就是 GDA(Gyro Data Available)。当这个位为 1 时,说明陀螺仪新的 X/Y/Z 三轴数据已经写入输出寄存器。把这个事件映射到 INT1 引脚,需要把 INT1_CTRL 寄存器的 INT1_DRDY_G 位置 1,对应写入值就是 0x02。

1.3 什么时候不建议用中断

虽然中断比轮询好,但不是所有场景都必须上中断。

如果你的 IMU 采样率很低,比如每秒 10 次,而且主循环空得很,轮询完全够用。反过来,如果你把陀螺仪 ODR 开到 3.3kHz 甚至 6.6kHz,每个周期只有几百微秒,中断函数本身在频繁触发,CPU 大量时间都在进中断和出中断,这时候单纯用 DRDY 中断反而拖累系统。

我自己的经验是:低频率采样用轮询,中频率采样式 100Hz 到 1kHz 用 DRDY 中断,高频率采样优先考虑 FIFO,让传感器先把数据存起来,MCU 每隔一段时间批量取一次。后面会有 FIFO 的方案说明。

2. 硬件连接与CubeMX工程配置

2.1 引脚规划与硬件连接表

STM32C5 的 I2C 外设比较丰富,我这次用 I2C1 和 LSM6DSOW 通信。在 CubeMX 里把 I2C1 的 SCL、SDA 分别分配到 PB8、PB9 两个引脚,然后把 LSM6DSOW 的 INT1 接到 PA2,PA2 同时启用了 EXTI2 外部中断,当作传感数据就绪信号输入。

LSM6DSOW 这边除了 I2C 通信线,还要处理好电源和地址引脚。

LSM6DSOW引脚接到 STM32C5 或外部说明
VDD3.3V数字电源
GNDGND
SCLPB8I2C 时钟
SDAPB9I2C 数据
INT1PA2中断输出,接 MCU 外部中断
SDO/SA0可选,我接的 GND决定 I2C 地址
CS接 3.3V不使用 SPI 时固定拉高

SDO/SA0 引脚接 GND 时,LSM6DSOW 的 7 位 I2C 地址是 0x6A,接 VDD 时是 0x6B。具体到代码里,写寄存器时要记得把 7 位地址左移一位变成 8 位地址,也就是 HAL 库里的0x6A << 1。很多人第一次 I2C 通信失败,就是卡在这个地址换算上。

I2C 的 SCL 和 SDA 需要外部上拉电阻,一般 4.7kΩ 配 3.3V 供电比较稳。如果板子已经自带传感器模组,通常不用再加大电阻。

2.2 CubeMX 配置步骤与容易忽略的细节

打开 STM32CubeMX,选择 STM32C5 型号后,第一件事是配 RCC 时钟源和调试下载口。如果用的是 SWD 调试,SYS 里 Debug 要选 Serial Wire,不然下载几次后可能把调试口占了。

接着配置 I2C1,打开对应引脚后把速度选到 400kHz。400kHz 是 LSM6DSOW 和 STM32C5 都支持的标准 fast mode,读取 6 字节陀螺仪数据大概不到 150 微秒,对中断处理来说足够快。

配置 PA2 外部中断时,GPIO 模式选择 External Interrupt Mode with Rising edge trigger detection,也就是外部中断上升沿触发。这里要特别留意,LSM6DSOW 的 DRDY 是短脉冲,选上升沿才能在脉冲跳变的那一刻捕获到。如果选成下降沿,那只能在脉冲回落时触发,虽然偶尔也能用,但时序上容易出问题。

PA2 内部可以开启上拉电阻,这样 INT1 在空闲状态下不会悬空。虽然 LSM6DSOW 的 INT1 默认是推挽输出,但加上拉不会坏事,反而能避免因为接线松动带来的电平抖动。

最后在 NVIC 设置里,确认 EXTI2 中断没有被禁止。CubeMX 默认会生成能打开的 IRQHandler,但如果你手动关过,就要去查一下。

2.3 初始化LSM6DSOW的关键寄存器

代码层面,初始化传感器建议按这个顺序走:

  1. 读取 WHO_AM_I,确认 I2C 通没通。
  2. 软复位传感器,让内部寄存器回到默认值。
  3. 设置 BDU 和地址自增。
  4. 配置陀螺仪 ODR 和满量程。
  5. 把数据就绪中断映射到 INT1。

WHO_AM_I 寄存器地址是 0x0F,LSM6DSOW 的固定值一般是 0x6C。如果读出来不对,后面就别继续了,优先查接线和地址。

软复位写 CTRL3_C 的 SW_RESET 位,等待传感器内部完成复位,保险起见加 20ms 延时,然后再写 BDU 和 IF_INC。BDU 是 Block Data Update,意思是高字节和低字节的输出寄存器在做切换时,会被锁存到同一份采样值,避免读取过程中高低字节来自不同时刻的数据。IF_INC 叫 Register Address Increment,开启后可以连续读多个寄存器,读 6 字节陀螺仪数据会非常方便。

陀螺仪配置写到 CTRL2_G,寄存器地址 0x11。我用的 ODR 是 104Hz,满量程 2000dps,配置值写成 0x46。这里的 0x46 含义是:高四位 ODR_G 为 0x04 表示 104Hz,中间的 FS_G 位为 0x03 并且放到 bit3 到 bit1 的位置,最后 bit0 的 FS_125 不置位。如果是其它系列的 ST 传感器,位定义可能有差异,建议以 LSM6DSOW 数据手册的 CTRL2_G 位定义为准。

中断映射寄存器 INT1_CTRL 地址是 0x0D,写入 0x02,把 INT1_DRDY_G 使能。这样每次陀螺仪有一个新采样准备好,INT1 引脚就会输出一个脉冲。

初始化代码:

uint8_t id = 0; lsm6dsow_read_regs(0x0F, &id, 1); if (id != 0x6C) { // 通信失败或地址不对,停在错误处理里 } lsm6dsow_write_reg(0x12, 0x02); // SW_RESET HAL_Delay(20); lsm6dsow_write_reg(0x12, 0x44); // BDU | IF_INC lsm6dsow_write_reg(0x11, 0x46); // 104Hz, 2000dps lsm6dsow_write_reg(0x0D, 0x02); // INT1_DRDY_G

2.4 I2C读写的底层函数

写寄存器用 HAL 的HAL_I2C_Mem_Write,读寄存器用HAL_I2C_Mem_Read。这两个函数针对的是“带子地址的 I2C 设备”,正好适用于 LSM6DSOW。

#define LSM6DSOW_ADDR 0x6A #define LSM6DSOW_REG_WHO_AM_I 0x0F #define LSM6DSOW_REG_CTRL3_C 0x12 #define LSM6DSOW_REG_CTRL2_G 0x11 #define LSM6DSOW_REG_INT1_CTRL 0x0D #define LSM6DSOW_REG_STATUS 0x1E #define LSM6DSOW_REG_OUTX_L_G 0x22 static void lsm6dsow_write_reg(uint8_t reg, uint8_t val) { HAL_I2C_Mem_Write(&hi2c1, LSM6DSOW_ADDR << 1, reg, I2C_MEMADD_SIZE_8BIT, &val, 1, 10); } static void lsm6dsow_read_regs(uint8_t reg, uint8_t *buf, uint16_t len) { HAL_I2C_Mem_Read(&hi2c1, LSM6DSOW_ADDR << 1, reg, I2C_MEMADD_SIZE_8BIT, buf, len, 10); }

这一段是基础,后面所有读取都建立在它上面。

3. 中断读取实现的完整流程与代码解析

3.1 中断回调里该放什么,不该放什么

STM32 的标准 HAL 库会在 EXTI2_IRQHandler 里调用HAL_GPIO_EXTI_IRQHandler,最终再调用弱定义的HAL_GPIO_EXTI_Callback。我们只需要覆盖这个回调函数就行。

一开始我图省事,直接在回调函数里用阻塞式 I2C 去读陀螺仪,也就是在 EXTI 中断里面调用HAL_I2C_Mem_Read。低 ODR 时好像没啥问题,一旦 ODR 调到 416Hz 以上,问题就来了:I2C 阻塞传输本身要花时间,如果中断优先级不够高,还可能被其他中断打断。更严重的是,如果主循环里正好还在用 I2C 读其他寄存器,两边的传输会互相干扰,出现 I2C 总线超时。

所以中断回调里最稳的做法是只置一个标志位,把真正耗时的读取操作放到主循环里。这也是中断处理的最基本原则:中断服务函数要短,不在中断里做耗时的外围设备访问。

volatile uint8_t gyro_ready = 0; void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == LSM_INT1_Pin) { gyro_ready = 1; } }

这个变量加上 volatile,是因为它会在中断里被写入,在主循环里被读取,编译器如果不加 volatile,可能会把主循环里的读取优化成只读一次,导致标志变了主循环也不知道。

3.2 主循环里面怎么读陀螺仪数据

主循环里判断gyro_ready,如果变成 1,就清标志并调用读取函数。读取函数不是直接读输出寄存器,而是先看看 STATUS_REG 的 bit1 是否为 1,确认数据确实准备好了,再一口气读 6 个字节。

这里有个细节:LSM6DSOW 的陀螺仪输出寄存器从 OUTX_L_G 开始,依次是 X 轴低位、X 轴高位、Y 轴低位、Y 轴高位、Z 轴低位、Z 轴高位。因为前面开了 IF_INC,I2C 多字节读可以自动地址递增,一次读完 6 个字节。

float gx_dps, gy_dps, gz_dps; void read_gyro_data(void) { uint8_t status = 0; uint8_t buf[6]; int16_t raw_x, raw_y, raw_z; lsm6dsow_read_regs(0x1E, &status, 1); if ((status & 0x02) == 0) { return; } lsm6dsow_read_regs(0x22, buf, 6); raw_x = (int16_t)((buf[1] << 8) | buf[0]); raw_y = (int16_t)((buf[3] << 8) | buf[2]); raw_z = (int16_t)((buf[5] << 8) | buf[4]); gx_dps = raw_x * 70.0f / 1000.0f; gy_dps = raw_y * 70.0f / 1000.0f; gz_dps = raw_z * 70.0f / 1000.0f; }

主循环里的逻辑就很简洁:

while (1) { if (gyro_ready) { gyro_ready = 0; read_gyro_data(); printf("gx=%.2f gy=%.2f gz=%.2f\r\n", gx_dps, gy_dps, gz_dps); } }

3.3 原始值换算成角速度

LSM6DSOW 陀螺仪输出是 16 位有符号整数,不经过换算直接看原始值没有意义。换算成角速度需要一个灵敏度系数,这个系数由满量程决定。

满量程 (dps)灵敏度 (mdps/LSB)原始值 32768 对应的角速度
1254.375约 143 dps
2508.75约 286 dps
50017.5约 573 dps
100035约 1146 dps
200070约 2294 dps

上面的代码里用的是 2000dps 满量程,所以灵敏度取 70 mdps/LSB,原理解就是每个 LSB 代表 0.07 dps。把原始值乘以 70 再除以 1000,就是实际的每秒旋转度数。如果你的满量程不一样,灵敏度系数也要跟着换。

这套换算在 125dps 档位下需要特别注意,因为 4.375 mdps/LSB 意味着相同角速度下原始值更大,精度更高,但也更容易溢出。选择量程的原则是:尽量选接近实际运动范围的最小量程,反正代码改一个灵敏度系数就行。

3.4 进阶优化:用FIFO扛住高采样率

当你把 ODR 拉到 833Hz 甚至 3.3kHz 时,每次采样都触发一次 EXTI,中断次数会非常多。以 3.3kHz 为例,大概每 303 微秒就触发一次。主循环哪怕只是进中断再退出,都会占用不少 CPU。

LSM6DSOW 内部有 FIFO,可以不断把陀螺仪数据存进去,MCU 不用每个采样都去响应,而是等 FIFO 里的数据攒够一批再一次性读出来。这样 EXTI 中断频率就从 3.3kHz 降到了比如 100Hz,调用一次 I2C 批量读取就能取回几十组数据。

FIFO 的使用逻辑大致是:

  1. 配置 FIFO 工作模式,让它持续存储陀螺仪数据。
  2. 设置 FIFO 阈值,比如存到 32 组数据时触发一次中断。
  3. 把 FIFO 阈值中断映射到 INT1。
  4. 在中断回调里置标志,主循环根据 FIFO_STATUS 寄存器读回样本数,再批量读取。

我自己在工程里用的就是这个思路,ODR 设在 833Hz,FIFO 攒一部分数据再取,CPU 占用比单纯 DRDY 中断低很多。不过 FIFO 涉及到的寄存器比较多,建议先把单字节中断读通,再迁移到 FIFO,排错会更容易。

4. 常见问题、排查技巧与方案升级

4.1 INT1没有脉冲,中断一直不触发

这是最常见的问题。我建议排查时先用示波器或逻辑分析仪看 INT1 引脚,手动缓慢转动板子,观察是否有脉冲输出。如果没有脉冲,问题基本在传感器侧;如果有脉冲但 MCU 不进中断,问题在 EXTI 配置侧。

传感器侧先回读 INT1_CTRL,确认写入的 0x02 是生效的,很多情况下是因为 I2C 写顺序不对,后面又被别的初始化覆盖了。另外检查传感器是否真的进入了正常工作状态,如果 CTRL2_G 的 ODR_G 还是 0,陀螺仪不采样,自然不会有数据就绪中断。

还有一个非常容易忽略的点:GPIO 外部中断优先级。如果项目里其他外设中断比较多,而且 NVIC 里 EXT2 的优先级被设置得非常低,它可能一直被其他中断抢占。虽然标志位最后还是会置起来,但延迟可能已经很大,表现为数据总是“晚到”。

4.2 I2C通信失败,WHO_AM_I读回不对

排查顺序如下:

  • 确认 SDO/SA0 引脚电平,地址是 0x6A 还是 0x6B。
  • 代码里有没有把 7 位地址左移一位传给 HAL 函数。
  • I2C 总线上有没有上拉电阻,上拉电阻是不是太大或者太小。4.7kΩ 是一个通用起点。
  • 尝试把 I2C 速度降到 100kHz,排除时序问题。
  • 检查 LSM6DSOW 的供电,VDD 是否稳定在 1.71V 到 3.6V 之间,GND 是否共地。

我在调试时遇到过一种情况,SCL 和 SDA 两根线被模块板和主控板的长排线一接,线长超过 20cm,400kHz 下时不时读错。把速度降到 100kHz 后稳定了,但最后还是缩短线距才彻底解决。

4.3 陀螺仪数据偶尔突变,静止时不为零

这个现象通常不是中断问题,而是读数本身不稳定。

如果你没有开 BDU,高字节和低字节可能在两次独立 I2C 传输之间被传感器更新,读出个“缝合怪”数据,看起来就是偶尔跳一下。这个用示波器很难抓,但把 CTRL3_C 的 BDU 打开后,跳变基本消失。

静止时陀螺仪输出不为零非常正常,这是零偏。每个芯片出厂都不一样,温度变化也会影响。工程上要做零偏校准:上电稳定后,把板子放平静止,连续采集 1000 组数据,求平均值,作为零偏。之后每次读到的数据减去这个零偏值再参与计算,姿态角就不会漂得那么快了。

电源噪声也会影响陀螺仪读数,尤其是 VDD 纹波大时,输出会有周期性波动。在 VDD 和 GND 之间加一个 100nF 陶瓷电容,离传感器引脚越近越好,能明显改善。

4.4 不要在主循环里同时发起多个I2C传输

从 STM32C5 的 HAL 库角度讲,HAL_I2C_Mem_Read是阻塞式调用。如果主循环里有两个任务都在用同一个 I2C1,它们必须串行执行。一旦有人在中断里插了一脚,I2C 总线的起始条件就可能被打乱,导致总线锁死或读回 0xFF。

我踩过一次坑:一个任务在读气压计,另一个任务在读陀螺仪,两个任务还不在同一个优先级里。结果 I2C 总线偶尔卡死,必须复位传感器才能恢复。后来把所有 I2C 访问都放到同一个线程里,另外加了互斥保护,问题才消失。

如果你必须在外围设备上做类似的事情,建议用一个 I2C 访问锁,或者统一走一个“外设读取队列”,不要让两个任务同时碰 I2C。

4.5 如何验证中断读取的数据真的准确

验证分为三步。

第一步,静止测试。板子放平不动时,打印出来的角速度应该在 0 附近小范围波动,波动幅度和你设置的 ODR、量程有关。如果出现几百 dps 的数值,检查是不是单位换算搞错了。

第二步,方向测试。绕 Z 轴转动板子,观察 gz 的数值是否随转动方向和快慢变化,x、y 轴应该接近 0。用手比划一个右手坐标系,转哪个轴就看哪个轴的数据,确保坐标轴方向和你预期一致。

第三步,与轮询对比。在同一个工程里,先跑 5 分钟轮询采集,再跑 5 分钟中断采集,对比数据有没有明显丢帧。中断方式下gyro_ready置位的次数理论上应该等于传感器产生的采样点数,如果差距很大,说明中断回调里处理太慢,或者 EXTI 丢失了脉冲。

我个人在实际项目里的体会是:中断读陀螺仪最大的收益不是“快”,而是“稳”。它让采样节奏和传感器自己保持一致,不用去猜主循环什么时候轮到 IMU 任务。如果你现在还在用轮询,先改成 DRDY 中断试试,感受会很直接;等 ODR 上去了,再考虑 FIFO。这个小改动能让后续的姿态解算少很多莫名其妙的跳变。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 7:19:47

移相全桥DSP数字控制开关电源设计实战:从ZVS计算到波形验证

简介&#xff1a;面向电力电子与嵌入式软件工程师的移相全桥DSP数字控制开关电源设计资料包&#xff0c;系统覆盖从硬件参数计算、原理图设计到DSP数字环路控制与调试的全流程&#xff0c;适合有一定电源开发基础的中高级工程师及相关专业学生作为项目参考或课题框架。包内共40…

作者头像 李华
网站建设 2026/9/16 7:19:42

Colibri开发板实战:ESP32-S3离线语音交互与低功耗设计全解析

拿到这块Colibri开发板的时候&#xff0c;我第一反应是这名字起得真贴切——蜂鸟。板子比一张名片还小一圈&#xff0c;但上面塞下了完整的音频采集、音频编解码、无线通信和AI加速能力。过去大半年我一直在拿它做离线语音交互相关的原型验证&#xff0c;从最开始的录音回放、到…

作者头像 李华
网站建设 2026/9/16 7:19:24

RoboMaster硬件调试实战手册:GD32H7电源与CAN故障排查指南

1. 这份讲义到底在讲什么&#xff1a;不是教材&#xff0c;是硬件工程师的“现场作业手册”“Robomaster硬件基础讲义V0.2.1”——光看标题&#xff0c;很多人第一反应是“哦&#xff0c;又是那种PPT式教学材料”&#xff0c;翻两页就搁下了。但我在哈工大电控组带过三届RoboMa…

作者头像 李华
网站建设 2026/9/16 7:18:43

Android Media3音乐播放器2.0:适配Android 12+的后台播放与Scoped Storage实战

简介&#xff1a;本资源是面向Android初学者的音乐播放器开发实战项目&#xff0c;聚焦移动应用开发核心技能训练&#xff0c;特别适合课程设计、大作业及自学实践。作为1.0版本的深度升级版&#xff0c;2.0版本新增上一首/下一首功能、采用个性化按钮UI设计&#xff0c;并为全…

作者头像 李华
网站建设 2026/9/16 7:18:26

AI智能体选型四维标尺:意图锚定、知识活化、行动闭环与进化韧性

1. 这不是选“AI工具”&#xff0c;是在选你的数字分身&#xff1a;为什么2026年智能体选择突然变得致命&#xff1f;“AI智能体到底怎么选&#xff1f;”——这句话在2026年已经不是技术圈内部的讨论&#xff0c;而是销售总监晨会的第一议题、自由职业者接单前的必查清单、甚至…

作者头像 李华