ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

基于STM32C5的LSM6D3TR-C陀螺仪轮询读取实战

基于STM32C5的LSM6D3TR-C陀螺仪轮询读取实战 最近在STM32C5上调试LSM6D3TR-C把轮询读取陀螺仪这套流程完整走了一遍从CubeMX配置到寄存器读写再到串口打印数据整个过程踩了不少坑。这篇先写第一篇轮询获取陀螺仪数据。后面如果大家有兴趣我再把中断方式、FIFO批量读取、以及用加速度计做姿态解算的内容补上毕竟LSM6D3TR-C这颗六轴传感器在可穿戴、机器人、云台稳定这类项目里实在太常见了。这篇内容适合谁参考刚接触STM32C5或者想用Cortex-M33内核做传感器采集的开发者以及手头有LSM6D3TR-C模块但不知道从哪里下手的同学。我会把完整的初始化流程、寄存器配置、轮询逻辑、数据换算和调试经验都写清楚理工科的朋友可以直接照抄然后根据自己的实际场景去调整。1. 项目核心思路与硬件选型1.1 STM32C5为什么值得选STM32C5是意法半导体新推出的入门级系列内核升级到了Arm Cortex-M33带FPU和DSP扩展主频最高可以跑到250MHz。相比以前用F103或者G0系列做传感器读取C5在算力上明显上了一个台阶。陀螺仪数据读取本身并不复杂但如果你后面要做姿态融合、Kalman滤波甚至跑一些简单的神经网络手势识别C5的浮点能力和DSP指令集就有用了。我手头这颗具体型号是STM32C502系列跑在250MHzFlash 128KB起步实际用下来跑I2C轮询加串口打印非常轻松CPU占用率几乎可以忽略。这个系列在价格上比较友好从G0/F0迁移过来的代码改动也不大GPIO、DMA、定时器这类外设的HAL库风格基本一致。另外它支持TrustZone做安全启动和数据保护的时候也能派上用场。1.2 LSM6D3TR-C传感器能干什么LSM6D3TR-C是一款系统级封装的六轴惯性测量单元里面同时集成了三轴陀螺仪和三轴加速度计。陀螺仪用来测量角速度单位是dps度每秒加速度计用来测量比力单位是g。两者配合就可以做姿态检测、运动识别、振动监测等应用。这颗芯片的接口很灵活支持I2C和SPI两种方式我这次用的是I2C。陀螺仪的满量程档位可以从±125dps一直切到±2000dps加速度计从±2g到±16g都可以配置用户可以根据实际场景选择灵敏度和量程的平衡。它内部还有一个3KB的FIFO可以直接缓存大量数据适合省电或者主控繁忙时不丢数据的场景。不过本篇先用最基础的轮询方式打通链路把数据先读到再说。1.3 材料清单与接线方案本次测试用到的材料非常少STM32C5开发板一块具体型号以C502/C503为主接口兼容LSM6D3TR-C模块市面上很多带稳压和电平转换的成品小板买回来直接用杜邦线若干USB转TTL串口线用来打印调试信息ST-Link或J-Link调试器接线方案很简单LSM6D3TR-C用I2C接口接两根数据线加电源地线即可。模块上的SA0引脚如果接地I2C设备地址是0x6A如果接高电平地址就变成0x6B。传感器引脚开发板引脚说明VCC3.3V供电模块一般支持1.71V到3.6VGNDGND共地SCLPB8I2C1_SCLSDAPB9I2C1_SDA注意如果模块上没有集成上拉电阻I2C的SCL和SDA需要外接4.7kΩ上拉到3.3V。我以前用某些裸芯片模块时忘了加上拉结果I2C总线时钟拉不起来通信一直超时排查了半天。成品模块一般自带上拉但最好确认一下原理图。2. STM32CubeMX工程配置与初始化2.1 新建工程与I2C外设配置STM32CubeMX版本需要更新到支持C5系列的版本并安装对应的固件包。打开CubeMX选择芯片型号时直接搜STM32C5系列选好自己板子对应的具体型号然后开始配置。调试接口这里我选了Serial Wire也就是SWD四线模式方便用ST-Link下载和调试。系统时钟直接让CubeMX自动配置到最高主频如果需要自己切时钟注意PLL的配置参数C5系列最高可以跑到250MHz确认电压档位选对即可。接着配置I2C1Mode选择I2CTiming Configuration里面我选了Fast Mode400kHz地址长度为7位在GPIO设置里I2C_SCL和I2C_SDA会自动映射到PB8和PB9默认推挽输出开漏模式在CubeMX中会自行处理无需手动改。对于轮询读取传感器这种应用400kHz已经足够快6字节数据的读取耗时在微秒级别完全不会成为瓶颈。生成代码后打开工程检查一下MX_I2C1_Init函数确认I2C时钟频率已经正确配置。如果后续发现I2C通信不稳定再将时钟降到100kHz试一下这是最简单的排查手段。2.2 串口配置与printf重定向数据读出来以后要打印到电脑上看所以还配了一个串口。我使用的是USART2波特率115200引脚默认PA2和PA3。在CubeMX中使能USART2后需要手动开启中断或者DMA吗都不需要轮询发送就够了。printf重定向做一个简单的fputc函数即可。#include stdio.h int fputc(int ch, FILE *f) { HAL_UART_Transmit(huart2, (uint8_t *)ch, 1, 0xFFFF); return ch; }这样就实现了printf输出到串口。注意在STM32CubeIDE或MDK中有些工程需要勾选微库MicroLib才能让printf正常工作不然可能进HardFault或者不输出内容。我用的是STM32CubeIDE因为自带的调试器查看变量比较方便所以我同时用了SWV方式观察数据UART则用来输出最终换算后的角速度值。3. LSM6D3TR-C寄存器配置与轮询读取驱动3.1 关键寄存器一览LSM6D3TR-C的寄存器映射跟LSM6DS系列比较接近用到的核心寄存器有下面几个寄存器地址作用WHO_AM_I0x0F芯片ID应为0x69CTRL1_XL0x10加速度计输出速率和满量程配置CTRL2_G0x11陀螺仪输出速率和满量程配置CTRL3_C0x12软件复位、块更新、自动递增等关键开关STATUS_REG0x1E数据就绪状态位OUTX_L_G0x22陀螺仪X轴低字节OUTX_H_G0x23陀螺仪X轴高字节OUTY_L_G0x24陀螺仪Y轴低字节OUTY_H_G0x25陀螺仪Y轴高字节OUTZ_L_G0x26陀螺仪Z轴低字节OUTZ_H_G0x27陀螺仪Z轴高字节CTRL3_C这个寄存器值得单独说一下bit0是软件复位位写1后芯片自动复位并恢复默认寄存器值复位完成后该位自动清零。bit6是块更新Block Data UpdateBDU使能位强烈建议置1。BDU开启后输出数据寄存器的高低字节会保持同步更新防止你在读取过程中刚好遇到数据刷新导致高低字节来自不同时刻从而产生跳变毛刺。bit2是自动递增位置1后连续读取多个寄存器时地址会自动加一这样我可以用一个I2C读操作连读6个陀螺仪数据字节非常方便。3.2 初始化流程与代码初始化流程分为三步软件复位、等待复位完成、配置量程和输出速率。#define LSM6D3TR_C_ADDR 0x6A 1 // SA0接地7位地址0x6A左移一位 #define REG_CTRL3_C 0x12 #define REG_CTRL1_XL 0x10 #define REG_CTRL2_G 0x11 #define REG_STATUS_REG 0x1E #define REG_OUTX_L_G 0x22 void LSM6D3TR_C_Init(void) { uint8_t data 0; // 软件复位 data 0x01; HAL_I2C_Mem_Write(hi2c1, LSM6D3TR_C_ADDR, REG_CTRL3_C, I2C_MEMADD_SIZE_8BIT, data, 1, 100); HAL_Delay(100); // 等待复位完成 // 开启BDU和自动递增 data 0x44; // bit6 BDU1, bit2 IF_INC1 HAL_I2C_Mem_Write(hi2c1, LSM6D3TR_C_ADDR, REG_CTRL3_C, I2C_MEMADD_SIZE_8BIT, data, 1, 100); // 加速度计ODR416Hz, FS±2g data 0x60; HAL_I2C_Mem_Write(hi2c1, LSM6D3TR_C_ADDR, REG_CTRL1_XL, I2C_MEMADD_SIZE_8BIT, data, 1, 100); // 陀螺仪ODR416Hz, FS±250dps data 0x60; HAL_I2C_Mem_Write(hi2c1, LSM6D3TR_C_ADDR, REG_CTRL2_G, I2C_MEMADD_SIZE_8BIT, data, 1, 100); }ODR_F设定为416Hz意味着每2.4ms左右产生一组新的陀螺仪数据。满量程选±250dps这个档位的精度最高适合静止时噪声评估和慢速运动场景。如果你的设备旋转速度很快比如机器人云台或航模那就要把满量程扩大到±500dps或±2000dps否则数据会被截断。输出速率和满量程的具体配置值我习惯列一个表方便查。CTRL2_G寄存器中ODR_G占bit7到bit4FS_G占bit3到bit1ODR_G[3:0]输出速率0000关闭000112.5 Hz001026 Hz001152 Hz0100104 Hz0101208 Hz0110416 Hz0111833 Hz10001.66 kHzFS_G为000时对应±250dps001对应±500dps010对应±1000dps011对应±2000dps。3.3 状态寄存器轮询与数据拼接初始化完成后就可以在主循环中轮询STATUS_REG寄存器的GDA位。STATUS_REG的bit1是GDA如果为1表示陀螺仪已经有新的数据写入输出寄存器。轮询读取的代码框架如下void LSM6D3TR_C_Read_Gyro(float *gx, float *gy, float *gz) { uint8_t status 0; uint8_t buf[6] {0}; int16_t raw_x, raw_y, raw_z; // 读取状态寄存器 HAL_I2C_Mem_Read(hi2c1, LSM6D3TR_C_ADDR, REG_STATUS_REG, I2C_MEMADD_SIZE_8BIT, status, 1, 100); // 检查GDA位 if (status 0x02) { // 自动递增模式从OUTX_L_G开始连读6字节 HAL_I2C_Mem_Read(hi2c1, LSM6D3TR_C_ADDR, REG_OUTX_L_G, I2C_MEMADD_SIZE_8BIT, buf, 6, 100); 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 raw_x * 0.00875f; *gy raw_y * 0.00875f; *gz raw_z * 0.00875f; } }为什么用HAL_I2C_Mem_Read而不是先Mem_Write改地址再Master_Receive因为HAL_I2C_Mem_Read在内部已经处理了先写寄存器地址再读数据的完整时序对于I2C传感器来说最直接。而手动分两步时容易出现总线竞争或者时序不对的问题特别是当系统里还有其他I2C设备时。数据拼接这里有个细节寄存器里是低字节在前高字节在后。所以buf[0]是低8位buf[1]是高8位组合时需要先左移8位再按位或。组合完的类型必须是int16_t因为陀螺仪的原始值是二进制补码形式的有符号数如果声明成uint16_t后续转换成float时符号就错了。0.00875这个系数从哪来的±250dps满量程对应16位输出即满量程范围覆盖-32768到32767每个LSB对应的角速度为250除以32768约等于0.00763dps。不过LSM6D3TR-C手册给出的官方灵敏度是8.75mdps/LSB也就是0.00875dps/LSB这是芯片出厂校准后的典型值直接用它更准确。4. 数据换算与实测输出4.1 陀螺仪原始值如何转成角速度陀螺仪输出寄存器里存的是16位有符号整数范围从-32768到32767。这个原始值必须乘上灵敏度系数才能得到实际角速度。不同满量程档位对应不同的灵敏度满量程档位灵敏度(dps/LSB)±1250.004375±2500.00875±5000.01750±10000.03500±20000.07000所以你在配置CTRL2_G时选了什么满量程后面换算系数就要对应改。我这次用的是±250dps所以固定乘0.00875。如果满量程切到±2000dps系数就要改成0.07否则读出来的角速度会大8倍这个很容易踩坑。加速度计的换算逻辑完全一样CTRL1_XL配置±2g时灵敏度是0.061mg/LSB换算是乘0.000061得到单位为g的值。4.2 实测波形与结果分析烧录程序后打开串口助手波特率115200把开发板平放在桌面上输出大概是这样的gx0.03 dps, gy-0.02 dps, gz0.05 dps gx0.02 dps, gy-0.01 dps, gz0.04 dps gx-0.01 dps, gy0.03 dps, gz0.06 dps gx0.04 dps, gy-0.02 dps, gz0.03 dps静止状态下三个轴的角速度应该在0附近波动波动幅度和芯片噪声以及供电质量有关。LSM6D3TR-C在±250dps档位下的噪声密度大约在4mdps/√Hz左右所以看到±0.1dps左右的噪声是正常的。用手拿着板子绕Z轴快速旋转此时gz的值会明显变大比如转得很快时能读到100到300dps的数值这是正常现象。实测中我发现如果用手捏着板子轻微晃动X轴和Y轴的数值也会有响应说明陀螺仪每个轴都工作正常。为了验证正方向我特意查了手册的坐标定义用右手定则判断方向避免后面做姿态解算时方向搞反。5. 轮询读取的坑与排查心得5.1 WHO_AM_I都读不到怎么办我建议调试任何I2C传感器时第一步都去读WHO_AM_I寄存器先确认通信链路通不通再谈配置和数据读取。LSM6D3TR-C的WHO_AM_I值应该是0x69。如果读出来的值不对优先检查I2C地址。注意HAL库函数的地址参数需要是8位设备地址也就是7位地址左移一位。如果SA0接地7位地址是0x6A传给HAL_I2C_Mem_Read的地址参数就是0x6A左移一位即0xD4。这个左移操作最容易漏。其次是供电问题。有些模块板载了稳压芯片可以接受5V供电有些则只能3.3V。输入电压过高可能导致芯片进入保护状态I2C无响应。C5开发板的3.3V引脚供电能力有限如果模块比较多最好用独立的3.3V LDO供电避免电压跌落。还有一个容易忽略的坑是I2C地址引脚SA0。如果模块上的SA0没有明确连接悬空时读取到的地址可能不稳定。最好在模块上飞线把SA0明确接地或接VCC保证地址固定。5.2 数据全零和毛刺问题初始化代码写完读出来的陀螺仪数据全部为0这种问题我遇到过一次。排查思路是这样的先看寄存器配置是否生效。我读回CTRL2_G寄存器发现值变成了0x00说明配置根本没写进去。继续追踪发现是I2C通信偶发超时Mem_Write函数返回HAL_ERROR但代码没做错误处理所以表面上程序继续跑实际上寄存器没写成功芯片处于掉电模式所有输出为0。解决方法是每次Mem_Write之后检查返回值如果超时就重试。养成习惯后很多奇怪的问题都能提前暴露。毛刺问题就是前面提到的BDU没开启。默认状态下BDU0输出寄存器更新是异步的读取过程中如果刚好发生数据更新那么高低字节可能来自两个不同时刻的样本拼接出来的数值就会出现一个明显的跳变尖峰。在姿态解算里这种毛刺如果不滤掉积分后会变成很大的角度漂移。所以CTRL3_C里BDU一定要置1代价几乎为零。另外电源纹波也会导致陀螺仪数据出现周期性波动。传感器供电最好用LDO单独供不要在数字电路开关频繁的3.3V上直接取电。5.3 轮询与数据重复/丢失轮询方式本身有一个绕不开的问题如果主循环轮询速度比数据更新速度快GDA位在两次轮询之间可能一直保持为1这时同一组数据会被重复读取多次。反过来如果你在循环里做了大量其他处理或者调用了耗时较长的延时轮询间隔超过了ODR周期那新数据产生时旧数据还没读走新数据就会覆盖旧数据导致丢样本。对于416Hz的ODR数据周期是2.4ms。如果主循环里除了读传感器还有别的任务比如LCD刷新或复杂的浮点运算就很容易超过这个时间。解决办法有几个把主循环跑快一点每次只做轻量操作保证循环周期远小于数据周期把ODR降低到104Hz或208Hz匹配实际的循环速度使用FIFO让芯片数据先缓存起来主控有空时批量读取我在这个项目里把主循环用HAL_Delay(1)控制节奏实测发现循环周期大概1.1ms左右在416Hz下不会丢数据。但如果你改了其他配置最好通过计数器统计一下每秒实际读取的数据组数和ODR对比就能看出是否丢数据。5.4 轮询读取的性能权衡与后续扩展轮询方式是所有读取方式里最简单、最容易调试的适合刚上手时确认硬件和驱动没有问题。但它不适合低功耗场景因为主控需要一直跑着去查询状态位无法进入睡眠模式也不适合高速率场景ODR超过1.66kHz时轮询循环的锯齿状读取模式会浪费大量CPU时间。如果你后面想把STM32C5的潜力发挥出来建议优先研究中断方式让LSM6D3TR-C的INT1引脚在数据就绪时拉高触发MCU的外部中断在中断服务函数里读取数据。这样主循环可以做别的事数据也不会漏。再往后就是FIFO模式芯片内部缓存一批数据主控通过DMA一次性搬走效率更高适合数据量大、需要离线分析的应用。STM32C5的DMA配合I2C来搬运传感器数据其实很顺滑带宽充足基本感知不到I2C的占用。我准备在下一篇里把中断方式加进去再把FIFO超时事件利用起来做一个不丢数的可穿戴姿态采集方案到时候欢迎继续看。这次调试对我个人来说最大的体会是先把最简单的轮询跑通比盲目追求高级特性重要得多。你在轮询过程中遇到的问题——地址写错、寄存器没配进去、拼接字节顺序反了——到了中断和FIFO阶段依然是同一个底层问题底层不扎实上层再花哨也白搭。LSM6D3TR-C本身是一颗相当成熟的传感器数据手册的寄存器结构也很规整只要耐心一步一步验证很快就能跑出稳定的数据流。
返回列表