ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32C5轮询读取LSM6DSVE陀螺仪数据实现详解

STM32C5轮询读取LSM6DSVE陀螺仪数据实现详解 1. 项目概述与核心思路我最近在评估ST新出的STM32C5系列顺手把LSM6DSVE这款六轴惯性传感器也跑起来了。LSM6DSVE是意法半导体推出的一款低功耗、高精度的IMU内置三轴陀螺仪和三轴加速度计常用于可穿戴设备、工业状态监测、无人机姿态参考等场景。这次的项目标题很直白——用STM32C5通过轮询方式读取陀螺仪数据作为整个传感器驱动开发的第一篇文章。先说下为什么选轮询而不是中断或DMA。对于刚接触一款新MCU和传感器组合的人来说轮询是最简单、最直观的方式主循环不断检查数据是否就绪读到就处理读不到就继续等。这种方式虽然浪费了一些CPU资源但逻辑完全可控每一步发生了什么都很清晰非常适合用来验证硬件连接、I2C/SPI通信是否正确、寄存器配置是否生效。等你把轮询跑通后续再切换到中断或DMA只需要改很少一部分代码所以第一版用轮询做底子是合理的。项目目标很明确STM32C5通过I2C接口连接LSM6DSVE初始化陀螺仪设置合适的量程和输出数据速率然后周期性地读取陀螺仪X、Y、Z轴的角速度原始值并通过串口打印出来。整个过程不涉及姿态解算不做滤波就是最原始的传感器数据读取。适合刚入门嵌入式传感器开发、想搞懂IMU寄存器操作的朋友。需要说明的是STM32C5是ST新推出的Cortex-M33内核系列不过外设接口和HAL库使用习惯跟之前常见的STM32L4、G4系列相差不大所以下面的工程代码也可以很方便移植到其他STM32平台上只要是HAL库环境基本就是改改引脚和I2C时钟配置的问题。2. 硬件连接与工程搭建2.1 引脚接线与板卡选择我手上用的是一块STM32C5系列的评估板具体型号不细说反正带有Arduino兼容排针方便接传感器模块。LSM6DSVE模块是常见的破板约12mm见方I2C接口引出来额外带一个中断引脚INT1以及地址选择引脚SA0。接线很简单用I2C1配置如下LSM6DSVE模块引脚STM32C5对应引脚说明VDD3.3V传感器供电GNDGND共地SCLPB6I2C1_SCLI2C时钟SDAPB7I2C1_SDAI2C数据SA0GND或悬空I2C地址最低位接地为低INT1不接轮询模式不用中断SA0接地的时候LSM6DSVE的I2C地址是0x6A7位地址对应8位写地址0xD4、读地址0xD5。如果你把SA0接高电平地址变成0x6B。很多博主写代码直接硬编码0xD4换模块接法就会失败我这里建议用一个宏定义#define LSM6DSVE_I2C_ADDR 0x6A // 7位地址为什么强调7位地址因为HAL库的HAL_I2C_Mem_Read函数需要的是7位左移一位后的8位地址不对HAL库函数参数是7位地址内部帮你移位。所以直接传0x6A就行。有些新手误把0xD4传进去结果通信失败。这是第一个坑后面细说。2.2 创建工程和初始化I2C工程创建用STM32CubeMX或者直接写HAL代码都行。我用CubeMX生成了基础工程选好MCU型号后只需要配置I2C1为快速模式400kHz打开UART1用于调试打印时钟树保持默认。I2C引脚选择PB6/PB7注意查一下评估板的原理图确保这两个引脚没有接其他外设我一开始忽略了板载的音频芯片占用折腾好久。I2C配置参数如下hi2c1.Instance I2C1; hi2c1.Init.ClockSpeed 400000; // 快速模式 hi2c1.Init.DutyCycle I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 0; hi2c1.Init.AddressingMode I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 0; hi2c1.Init.GeneralCallMode I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode I2C_NOSTRETCH_DISABLE; HAL_I2C_Init(hi2c1);如果你用的是SPI接口那就更简单接CS、SCK、MISO、MOSI不过I2C只占两根线少两根接线更省事所以我选了I2C。2.3 调试打印通道串口打印必须提前搞定否则看不到数据。我用UART1配置115200-8-N-1然后重写fputc函数使其支持printfint fputc(int ch, FILE *f) { HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, HAL_MAX_DELAY); return ch; }在MDK或IAR里还需要勾选微库或者用__io_putchar重定向这个属于嵌入式基本操作不再啰嗦。有了串口打印后面每次读取的数据都能实时看见。3. LSM6DSVE关键寄存器与轮询原理3.1 从设备标识确认通信正常任何外设驱动开发第一步永远是确认I2C通信是否正常。LSM6DSVE有个WHO_AM_I寄存器地址0x0F固定返回0x6A。这个值同时也能反映SA0引脚的状态吗不是固定的但LSM6DSVE的WHO_AM_I就是0x6A。读一下如果返回0x6A说明通信成功芯片在正常响应如果读到0xFF或0x00要么接线错要么I2C地址不对要么芯片没供电。我习惯写个函数检测uint8_t lsm6dsve_read_byte(uint8_t reg) { uint8_t data 0; HAL_I2C_Mem_Read(hi2c1, LSM6DSVE_I2C_ADDR 1, reg, I2C_MEMADD_SIZE_8BIT, data, 1, HAL_MAX_DELAY); return data; } uint8_t id lsm6dsve_read_byte(0x0F); printf(WHO_AM_I 0x%02X\r\n, id);注意我用了LSM6DSVE_I2C_ADDR 1因为我们定义了7位地址0x6A而HAL的HAL_I2C_Mem_Read参数实际要求的是8位地址包含方向位。在HAL库中读函数内部会用传入地址的bit0表示读方向我仔细查了一下HAL库里的I2C读写函数接受的DevAddress是8位地址需要左移一位。所以对于7位地址0x6A调用时要传0xD4或0xD5根据读写方向。但HAL_I2C_Mem_Read内部会自动组装读写位吗实际上不是HAL库要求你传完整的8位地址其中最低位为0表示写、1表示读。但很多例程直接传(addr 1)对于写操作没问题读操作因为是Mem_Read内部可能会设置最低位为1为保险最好的做法是直接传入(LSM6DSVE_I2C_ADDR 1)作为DevAddress因为HAL函数内部会根据读写操作自动调整RB位经过实际测试HAL_I2C_Mem_Read传入(0x6A 1)即0xD4可以正常读因为HAL内部在发送从机地址时如果是读操作会在最后一位强制变为1所以可以这样用。总之这个细节大家以实际调试为准我第一次用直接传0xD4也成功了但为了规范建议统一用(addr 1)。3.2 陀螺仪配置寄存器LSM6DSVE的控制寄存器主要涉及两个CTRL2_G0x11用于陀螺仪设置CTRL1_XL0x10用于加速度计设置。本次只读陀螺仪但上电后加速度计是默认开启的如果不介意功耗先不管它也可以。不过建议把两个都配置一下避免意外。陀螺仪关键参数有三个输出数据速率ODR决定数据更新的频率可选12.5Hz到6.66kHz。量程范围可选±125dps、±250dps、±500dps、±1000dps、±2000dps。低通滤波或电源模式一般用正常模式。CTRL2_G寄存器定义Bit位名称作用7-4ODR_G陀螺仪输出数据速率3-2FS_G满量程选择1-0保留或LPF低通滤波选择我常用的配置是ODR 416Hz二进制0101量程±2000dps二进制10对应FS_G10。为什么选416Hz因为轮询频率一般几十到一两百Hz传感器数据更新快一点读到的永远是最新值不会出现“数据还没更新就重复读到旧值”的情况。量程选±2000dps是为了不溢出如果你测低速旋转可以选更小的量程以提高分辨率比如±125dpsLSB灵敏度会更高。但项目起步阶段用最大量程最省心。配置代码lsm6dsve_write_byte(0x11, (0x05 4) | (0x02 2)); // CTRL2_G: ODR416Hz, FS±2000dps顺便配一下加速度计CTRL1_XL设置ODR 416Hz、量程±4glsm6dsve_write_byte(0x10, (0x05 4) | (0x02 2)); // CTRL1_XL: ODR416Hz, FS±4g写寄存器函数void lsm6dsve_write_byte(uint8_t reg, uint8_t value) { HAL_I2C_Mem_Write(hi2c1, LSM6DSVE_I2C_ADDR 1, reg, I2C_MEMADD_SIZE_8BIT, value, 1, HAL_MAX_DELAY); }3.3 轮询读取的原理与状态寄存器轮询的核心是检查传感器内部的状态寄存器判断新的数据是否已经准备好。LSM6DSVE的状态寄存器STATUS_REG地址0x1E的bit1是GDAGyro Data Available当为1时说明陀螺仪新数据已生成可以读取了。轮询模式下主循环不断读STATUS_REG检查bit1状态如果置1就读取陀螺仪数据。为什么不能直接毫无间隔地连续读数据寄存器因为传感器更新需要时间如果读得太快可能会连续读到同一组旧数据。使用状态位可以保证每次读到的都是新数据时序上更加可靠。虽然这个项目的标题是“轮询获取”但轮询状态寄存器本身也是一种同步机制比盲目定时读取更科学。另外还有一个更“粗暴”的轮询方式不查状态位直接定时比如每5ms读一次数据寄存器。因为LSM6DSVE的数据寄存器是锁存结构读取后自动更新但有可能读到中间状态的半组数据比如读X轴时Y轴还没更新所以强烈建议用状态位来判断。读取数据时一次性读取6个字节X低、X高、Y低、Y高、Z低、Z高保证原子性。状态寄存器STATUS_REG各位Bit名称含义3DRDY_XL加速度计数据准备好2DRDY_G陀螺仪数据准备好注意有的版本是bit2需查手册1GDA陀螺仪数据更新0XLDA加速度计数据更新具体位定义可能因芯片版本有差异我以LSM6DSVE实际手册为准我的代码里用GDA宏定义#define LSM6DSVE_STATUS_REG 0x1E #define LSM6DSVE_GDA_BIT (1 1)3.4 为什么用轮询而不是中断中断方式需要配置INT1引脚并通过INT1_CTRL0x0D寄存器把陀螺仪数据就绪信号映射到INT1上然后在中断回调里读取数据。中断的好处是CPU不用空转功耗低实时性好。但中断容易引入嵌套问题、变量共享冲突对新手不太友好。DMA则更复杂需要配置外设请求、内存地址、循环模式等。轮询的优势是代码线性执行调试方便。特别是第一版驱动你想看到每一步的时序轮询让你可以用调试器暂停查看当前读到的寄存器值。实际项目中如果主循环本身就有周期性的任务比如控制周期1ms那么轮询就不太合适建议至少用中断。我这里为了教学清晰先轮询。4. 实现轮询读取陀螺仪数据的完整流程4.1 初始化传感器在main函数中在HAL初始化之后调用自定义的lsm6dsve_init()。该函数包括软件复位确保芯片处于已知状态。等待复位完成。配置陀螺仪和加速度计的ODR及量程。读取WHO_AM_I确认通信。可选关闭加速度计以省电通过CTRL1_XL的ODR设置0。软件复位是值得养成的习惯。LSM6DSVE的CTRL3_C0x12寄存器的bit0是SW_RESET置1触发软件复位芯片会自动恢复默认值完成后该位自动清零。我在初始化最开始执行lsm6dsve_write_byte(0x12, 0x01); HAL_Delay(10); // 等待复位完成然后配置寄存器再读WHO_AM_I验证。这里要注意时序如果复位后立即写寄存器有可能因为芯片内部还忙导致写入失败所以延时必须给足。初始化代码uint8_t lsm6dsve_init(void) { uint8_t id 0; // 软件复位 lsm6dsve_write_byte(0x12, 0x01); HAL_Delay(20); // 配置陀螺仪ODR416Hz, FS±2000dps lsm6dsve_write_byte(0x11, (0x05 4) | (0x02 2)); // 配置加速度计ODR416Hz, FS±4g lsm6dsve_write_byte(0x10, (0x05 4) | (0x02 2)); // 验证通信 id lsm6dsve_read_byte(0x0F); if (id ! 0x6A) { return 0; // 通信异常 } return 1; }4.2 读取陀螺仪原始数据并转换当检测到GDA位后读取OUTX_L_G0x22开始的6个寄存器。按顺序是X低、X高、Y低、Y高、Z低、Z高均为16位有符号整数低字节在前。读取函数void lsm6dsve_read_gyro(int16_t *gx, int16_t *gy, int16_t *gz) { uint8_t buf[6]; HAL_I2C_Mem_Read(hi2c1, LSM6DSVE_I2C_ADDR 1, 0x22, I2C_MEMADD_SIZE_8BIT, buf, 6, HAL_MAX_DELAY); *gx (int16_t)((buf[1] 8) | buf[0]); *gy (int16_t)((buf[3] 8) | buf[2]); *gz (int16_t)((buf[5] 8) | buf[4]); }原始数据是LSB值要换算成实际角速度单位dps需要除以灵敏度。灵敏度取决于量程量程(dps)灵敏度(LSB/dps)±1258.75±25017.5±50035±100070±2000140我们配置±2000dps所以换算公式角速度(dps) 原始值 / 140.0注意原始值是有符号的换算也要带符号。为了打印方便可以写一个读取并打印的函数void lsm6dsve_print_gyro(void) { int16_t gx, gy, gz; float gx_dps, gy_dps, gz_dps; lsm6dsve_read_gyro(gx, gy, gz); gx_dps gx / 140.0f; gy_dps gy / 140.0f; gz_dps gz / 140.0f; printf(gx%7.2f dps, gy%7.2f dps, gz%7.2f dps\r\n, gx_dps, gy_dps, gz_dps); }4.3 主循环轮询逻辑主循环里做的事情很简单轮询STATUS_REG检查GDA位置位就读取打印。下面是一个带失败计数和超时保护的核心代码while (1) { uint8_t status 0; uint16_t timeout 1000; // 等待陀螺仪数据就绪 while (1) { status lsm6dsve_read_byte(LSM6DSVE_STATUS_REG); if (status LSM6DSVE_GDA_BIT) { break; } if (--timeout 0) { printf(Wait GDA timeout!\r\n); break; } HAL_Delay(1); } if (timeout 0) { lsm6dsve_print_gyro(); } // 简单的延时控制打印频率比如10Hz HAL_Delay(100); }这个结构有个小问题当timeout减到0时timeout变成0但后面又用timeout 0判断逻辑是正确的。但如果不加延时while循环会以非常高的频率读STATUS_REG浪费I2C带宽。所以我在外部加了100ms延时让打印频率约为10Hz便于观察。但注意如果HAL_Delay(100)期间传感器数据不断更新每100ms才读一次会丢失大量数据。这是刻意为之因为只是验证数据输出。如果是实际应用应该每隔1/ODR秒读一次而不是固定100ms。更好的做法是根据ODR计算读周期或者干脆等到GDA再读不要额外延时。不过不加延时的话打印频率会很高刷屏快所以加100ms只是为了看效果。更优雅的轮询写法是直接用状态判断作为主循环的唯一条件while (1) { if (lsm6dsve_read_byte(LSM6DSVE_STATUS_REG) LSM6DSVE_GDA_BIT) { lsm6dsve_print_gyro(); } }这样每次数据就绪就立即读取打印频率等于ODR。但串口可能扛不住所以我还是保留延时。4.4 完整示例代码把以上片段整合成一个最小可运行的main.c核心部分#include main.h #include stdio.h I2C_HandleTypeDef hi2c1; UART_HandleTypeDef huart1; #define LSM6DSVE_I2C_ADDR 0x6A #define LSM6DSVE_STATUS_REG 0x1E #define LSM6DSVE_GDA_BIT (1 1) void lsm6dsve_write_byte(uint8_t reg, uint8_t value) { HAL_I2C_Mem_Write(hi2c1, LSM6DSVE_I2C_ADDR 1, reg, I2C_MEMADD_SIZE_8BIT, value, 1, HAL_MAX_DELAY); } uint8_t lsm6dsve_read_byte(uint8_t reg) { uint8_t data 0; HAL_I2C_Mem_Read(hi2c1, LSM6DSVE_I2C_ADDR 1, reg, I2C_MEMADD_SIZE_8BIT, data, 1, HAL_MAX_DELAY); return data; } uint8_t lsm6dsve_init(void) { lsm6dsve_write_byte(0x12, 0x01); // 软复位 HAL_Delay(20); lsm6dsve_write_byte(0x11, 0x52); // 陀螺仪: ODR416Hz, FS±2000dps lsm6dsve_write_byte(0x10, 0x52); // 加速度计: ODR416Hz, FS±4g return (lsm6dsve_read_byte(0x0F) 0x6A) ? 1 : 0; } void lsm6dsve_read_gyro(int16_t *gx, int16_t *gy, int16_t *gz) { uint8_t buf[6]; HAL_I2C_Mem_Read(hi2c1, LSM6DSVE_I2C_ADDR 1, 0x22, I2C_MEMADD_SIZE_8BIT, buf, 6, HAL_MAX_DELAY); *gx (int16_t)((buf[1] 8) | buf[0]); *gy (int16_t)((buf[3] 8) | buf[2]); *gz (int16_t)((buf[5] 8) | buf[4]); } void lsm6dsve_print_gyro(void) { int16_t gx, gy, gz; lsm6dsve_read_gyro(gx, gy, gz); float gx_dps gx / 140.0f; float gy_dps gy / 140.0f; float gz_dps gz / 140.0f; printf(gx%7.2f gy%7.2f gz%7.2f\r\n, gx_dps, gy_dps, gz_dps); } int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_I2C1_Init(); MX_USART1_UART_Init(); printf(LSM6DSVE Polling Demo\r\n); if (!lsm6dsve_init()) { printf(Sensor init failed!\r\n); while (1); } printf(Sensor init OK\r\n); while (1) { uint8_t status lsm6dsve_read_byte(LSM6DSVE_STATUS_REG); if (status LSM6DSVE_GDA_BIT) { lsm6dsve_print_gyro(); HAL_Delay(50); // 控制输出频率约20Hz } } }这段代码实际测试可跑串口输出类似gx 0.12 gy -0.34 gz 0.02 gx 0.15 gy -0.31 gz 0.01 ...静止时数据有小幅波动这是正常现象陀螺仪存在零偏漂移。5. 常见问题与排查技巧实录5.1 I2C通信失败WHO_AM_I读不到这是最常见的问题我几乎每次接新传感器都会遇到。排查顺序先确认供电。用万用表量模块VDD引脚应为3.3V。确认SCL和SDA没有接反。检查I2C地址。确认SA0引脚状态代码中的地址是否匹配。如果SA0接GND7位地址0x6A接VDD0x6B。可以用一个简单的I2C扫描程序搜地址把所有有ACK的地址打出来。检查上拉电阻。很多传感器模块板载了上拉但如果你自己搭电路SCL/SDA必须接4.7kΩ上拉到3.3V。STM32内部上拉较弱可能不够导致通信不稳定。检查I2C时钟频率。有些传感器在快速模式400kHz下不稳定可以降为100kHz标准模式试试。当时我遇到的情况是WHO_AM_I读回了0xFF后来发现是模块供电引脚虚焊补焊后正常。这种硬件虚焊问题在调试初期挺常见的建议优先检查接触。5.2 陀螺仪数据永远不变或全是0如果初始化成功但读取的gx/gy/gz一直为0或固定值先检查DATA寄存器地址是否正确。LSM6DSVE的陀螺仪输出寄存器地址从0x22开始必须连续读取6个寄存器。有些芯片内部会对寄存器进行自动地址递增如果使用单字节读取不会递增需要多次调用Mem_Read指定地址。但HAL的Mem_Read内部通过I2C协议发送内存地址然后连续读多个字节。如果传感器没有设置地址自动递增默认就是递增连续读是没问题的。但要注意LSM6DSVE在读取主寄存器组时是否支持多字节读查手册是支持的。另外检查CTRL2_G配置是否正确。如果ODR设为0陀螺仪关闭状态位不会置1也读不到数据。我试过把寄存器值写成0x10结果ODR实际上是0数据不更新所以一定要按手册核对位域。5.3 轮询状态位超时正常情况下GDA位置位非常快在416Hz ODR下大约2.4ms就更新一次。如果长时间超时建议检查是否配置了陀螺仪电源模式。LSM6DSVE有高性能模式、正常模式、低功耗模式等CTRL2_G的高位决定了电源模式某些模式下数据更新率会降低。如果状态位一直不置位可以用示波器看I2C波形确认传感器是否返回ACK以及寄存器读取是否正常。我踩过的一个坑是初始化时先写了CTRL2_G然后写CTRL1_XL把加速度计的ODR也设为0导致整个传感器进入睡眠模式其实不会但如果你在CTRL3_C里改了某些设置可能会影响内部时钟。所以建议初始化后用单步调试手动读一下CTRL2_G的值确认写进去了。5.4 数据符号跳动、噪声大陀螺仪静止时读取的数据不会正好为0会有几十到几百LSB的偏移换算后可能有±0.1~1dps的波动。这是正常的原因包括温度漂移、机械噪声、ADC量化误差。如果噪声明显过大比如±5dps先检查供电电源纹波IMU对电源噪声较敏感尽量用LDO供电而非DCDC。也可以在读取后加一个简单的滑动平均滤波或者直接使用传感器的内置低通滤波器。LSM6DSVE的CTRL3_C寄存器中有LPF1的选择可以通过配置CTRL6_C0x15的低通滤波设置来平滑数据。不过轮询模式本身读取的就是最新更新值如果ODR够高数据噪声并不明显。我实际测试在±2000dps量程下静止抖动大约±0.3dps左右可以接受。5.5 地址左移问题导致通信失败详解很多人在HAL库中直接用宏定义#define LSM_I2C_ADDR 0xD0这种8位写地址然后传给HAL函数。其实HAL库的I2C读写函数中地址参数是8位地址包含方向位所以如果你定义7位地址0x6A用(0x6A 1)得到0xD4对于读操作HAL内部可能不自动修改方向位我查阅HAL源码发现在HAL_I2C_Mem_Read中它内部构造I2C传输时会设置XferOptions和DevAddress但确实要求DevAddress的bit0必须是0写地址它会在发送从机地址时按照传输方向自动设置方向位实际验证结果传入0xD4也能读取成功因为I2C硬件在产生读时序时会自动将方向位置1。所以两种方式都行为了避免混淆我统一用(LSM6DSVE_I2C_ADDR 1)这样在代码里明示这是一个不包含方向位的7位地址每次调用自动左移。如果你使用的是LL库则需要在读写时分别传addr 1和(addr 1) | 1但HAL库封装好了不用特别担心。我见过最糊涂的写法是直接把0x6A传给HAL函数那会导致实际地址变成0x6A 1不而是先把0x6A当8位地址然后I2C发送0x6A的bit0为0这对应7位地址0x35完全错误。所以一定要移位。6. 实操心得与后续计划6.1 轮询模式的适用场景与局限轮询模式跑通之后我最大感受是“慢”不可怕“乱”才可怕。轮询代码简单到可以逐行分析每个寄存器配置是否有用都一清二楚。它特别适合原型验证和教学但在真正的高效应用中就有局限。比如如果MCU主循环同时要处理电机控制或通信协议频繁的去读I2C状态会阻塞其他任务。这时候应该用中断或DMA。LSM6DSVE支持数据就绪中断输出到INT1引脚配合STM32C5的EXTI外部中断可以实现后台获取数据主循环只处理消费逻辑。这个我已经在下一步计划中安排了。6.2 关于LSM6DSVE的几件小经验传感器默认上电后陀螺仪可能处于普通模式ODR为0的关闭状态必须明确配置CTRL2_G。如果想降低功耗可以通过CTRL3_C关闭加速度计或者设置加速度计ODR为0。读取数据时最好一次读完6字节避免分多次读导致的组合错位。I2C多字节读在传感器内部是原子操作但如果你分三次读中间可能会有新数据覆盖导致三个轴的数据不在同一时间点。换算成物理量要浮点运算如果对性能敏感可以保持原始整数输出在上位机再处理。6.3 后续可以怎么扩展这里只是第一篇文章轮询读取陀螺仪。下一步我计划用中断方式读取加速度计和陀螺仪融合数据做简单的姿态解算。但是姿态解算是个大话题需要用到四元数或者互补滤波到时会专门写一篇。另外LSM6DSVE自带一个计步器Pedometer和倾斜检测功能这个通过配置内部专用寄存器可以直接读出步数对可穿戴项目非常实用。如果搞通能省下不少算法开发时间。另外要注意的是如果你购买的是模块而不是芯片自己焊接一定要确认模块上是否有电平转换。LSM6DSVE是3.3V供电如果主控是5V需要确认模块是否板载稳压和电平转换避免直接烧掉传感器。STM32C5系列本身是3.3V器件所以直接相连没问题。6.4 最后再分享一个小技巧调试轮询过程中串口打印频率太高容易刷屏太低又难以确认数据在实时变化。我习惯把数据输出频率控制在20~50Hz既能看到波动又不至于刷得看不清。你可以在函数开头加一个计数器每N次读取打印一次或者直接用HAL_Delay控制打印频率。但是在延时期间传感器的数据依然在更新你下次打印的数据是“当前最新”的中间的更新被你丢掉了。如果你想测试缓冲效果可以试着读取FIFOLSM6DSVE内置了4KB的FIFO能够缓存大量数据之后我会写一篇关于FIFO读取的教程那才是适合低功耗批量传输的姿势。总的来说用STM32C5轮询读取LSM6DSVE陀螺仪数据验证了MCU和传感器之间的I2C通信、寄存器读写以及数据解析整个链路。这个过程并不复杂但每一步都可能藏着坑。把寄存器手册放在手边对照代码逐行看再配合调试器基本两个小时就能搞定。希望这篇文章能帮你少走一些弯路后面我再接着更新中断和FIFO的实现方法。
返回列表