ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

STM32C5驱动LSM6D3TR-C六轴IMU:I2C轮询读取陀螺仪与加速度计实战

STM32C5驱动LSM6D3TR-C六轴IMU:I2C轮询读取陀螺仪与加速度计实战 1. 为什么选LSM6D3TR-C和STM32C5这套组合做惯性测量类的嵌入式项目选传感器和主控从来不是拍脑袋的事。我手头这块STM32C5开发板是最近才拿到的样片ST的C系列主打的是低功耗和性价比Cortex-M33内核主频能跑到250MHz资源不算夸张但很均衡。既然要熟悉这颗MCU顺手把IMU也换成ST自家新一代的LSM6D3TR-C一套流程走下来既能摸清MCU的HAL库和时钟树又能把加速度计和陀螺仪的外设驱动练一遍。先说LSM6D3TR-C。这颗芯片是LSM6DS3的升级版六轴IMU三轴加速度计加三轴陀螺仪封装是2.5mm x 3mm的LGA体积很小。它的陀螺仪满量程可选125/250/500/1000/2000dps加速度计满量程可选2/4/8/16g。通信接口支持I2C和SPII2C地址可以通过SA0引脚切换默认是0x6A。这颗芯片还有一个比较实用的特性是内置了智能FIFO和不少运动识别功能比如计步器、倾角检测、敲击检测功耗控制做得不错正常测量模式下陀螺仪加加速度计全开也就零点几毫安的电流。再回头看STM32C5这颗MCU。它属于STM32C5系列ARM Cortex-M33内核带FPU和DSP指令主频250MHz片上Flash最高512KBSRAM 96KB。这个配置在ST的产品线里属于中端偏入门的位置但它有个很吸引人的点是价格。对于成本敏感的量产项目比如智能穿戴、工业手持设备、简单的运动监测模块用这个组合能把BOM成本压下来不少。Cortex-M33内核跑HAL库完全没压力甚至上RTOS也绰绰有余。之所以第一期先做轮询方式读陀螺仪而不是直接上中断或者DMA原因是先要把最基础的外设链路打通。轮询方式逻辑最简单不容易出错适合验证硬件连接、I2C通信和寄存器读写是否正确。等这套基础跑稳了后面再上FIFO、中断、DMP之类的功能出问题时也容易定位是硬件问题还是软件逻辑问题。在实际项目里轮询读传感器确实不是最优方案因为CPU会被占用无法在读取期间做别的事而且数据读取频率受限于主循环周期。但作为入门阶段的第一步或者作为快速验证硬件是否正常的临时方案轮询是最可靠的。这篇就把整个流程拆开讲一遍从硬件连接、工程配置、寄存器初始化到具体的轮询读取代码和数据处理一步不落。2. 硬件连接与工程搭建的几个容易踩的细节2.1 引脚分配与接线方式我使用的是STM32C5系列的标准开发板具体型号是NUCLEO-C5系列板载ST-LINK调试和供电都很方便。LSM6D3TR-C模块我这里用的是常见的六轴IMU小模块淘宝上很多板子已经把I2C上拉电阻和去耦电容布好了直接飞线接就行。接线方式如下LSM6D3TR-C模块引脚STM32C5引脚说明VCC3.3V模块供电注意不要接5V芯片绝对最大额定电压只有3.6VGNDGND共地SCLPB8I2C1_SCL时钟线SDAPB9I2C1_SDA数据线SA0GND拉低I2C地址为0x6A这里有两个细节需要提醒。第一SA0引脚决定I2C地址。SA0接GND时地址是0x6A7位地址接VCC时地址是0x6B。如果读出来的设备ID不对第一个要检查的就是这个引脚的接法。第二模块上的上拉电阻。市面上部分IMU模块没有板载上拉电阻需要自己在SCL和SDA上各加一个4.7kΩ的上拉电阻到3.3V。我用的这个模块自带10kΩ上拉实测I2C波形OK但如果你的模块上没有记得补上否则通信时好时坏非常诡异。2.2 STM32CubeMX工程配置要点STM32C5系列的开发工具链和老的F1/G0系列基本一致用STM32CubeMX生成初始化代码IDE用Keil MDK或者IAR都行我这里用的是STM32CubeIDE集成了编译调试和代码生成不用来回切换工具。CubeMX里需要配置的部分如下。RCC配置选择HSE外部高速晶振作为时钟源HCLK设为250MHz。STM32C5内部的HSI精度一般做IMU这类对时序精度有要求的应用建议优先使用外部晶振保证I2C通信时钟准确。I2C1配置I2C1模式选择I2C基本参数保持默认I2C速度模式选Fast Mode时钟设为400kHz关闭I2C的Analog Noise Filter保持默认就行如果通信不稳定再调整GPIO配置PB8和PB9会自动被配置为I2C1的复用功能一般不需要手动改如果CubeMX没有自动识别出复用功能手动把PB8、PB9设置为AF4模式USART2配置用于调试信息输出异步模式115200-8-N-1。通过ST-LINK的虚拟串口打印传感器数据方便在PC端用串口助手查看。配置完成后直接生成代码。注意CubeMX生成的是初始化代码I2C的读写函数在stm32c5xx_hal_i2c.c里驱动层代码需要自己写。2.3 工程结构规划为了后面加功能方便我建议把传感器驱动单独拆成一个模块。这次的工程结构大致是这样的Core/ Inc/ main.h lsm6d3tr.h // 传感器寄存器定义和函数声明 Src/ main.c lsm6d3tr.c // 传感器驱动实现 Drivers/ STM32C5xx_HAL_Driver/ // HAL库驱动模块和业务逻辑分开后面如果要切换到DMA或者中断模式只需要改驱动内部实现主函数的调用逻辑不用大改。这个习惯从第一版代码就要养成否则项目一大文件间的依赖关系会直接变成意大利面条。3. LSM6D3TR-C的寄存器地图从芯片手册到代码的翻译过程3.1 芯片手册怎么看拿到一颗新传感器第一件事不是写代码而是把datasheet里的寄存器地图过一遍。LSM6D3TR-C的寄存器不算多总共两三百个字节的空间但实际用到的就那么几个。我习惯把关键寄存器的地址和功能抄到自己的笔记里写代码时随手能翻到不用每次打开PDF。这颗芯片的寄存器寻址方式是8位寄存器地址加8位数据。I2C的写入格式是先发送设备地址7位地址加R/W位然后发送寄存器地址再发送要写入的数据。读取格式是先发送设备地址加写位发送寄存器地址然后重新发送设备地址加读位再连续读取数据。HAL库的HAL_I2C_Mem_Write和HAL_I2C_Mem_Read函数封装的就是这个过程寄存器地址作为Mem_Address参数传入。3.2 设备ID确认0x6A还是别的值第一步永远是读WHO_AM_I寄存器地址是0x0F。LSM6D3TR-C的WHO_AM_I默认值是0x69。如果读出来是这个值说明I2C通信正常芯片正常工作。如果不是一个可能是接线错误或者I2C地址不对另一个可能是芯片进入了低功耗模式或者硬件出了问题。这里要注意WHO_AM_I寄存器在芯片处于睡眠模式时也能读取。所以如果连这个寄存器的值都读不对问题几乎可以确定出在硬件连接或者I2C配置上而不是传感器配置上。3.3 陀螺仪和加速度计的量程配置加速度计的量程配置在CTRL1_XL寄存器0x10陀螺仪的量程配置在CTRL2_G寄存器0x11。这两个寄存器的低4位是ODR输出数据速率配置高几位是量程配置。CTRL1_XL寄存器位定义位名称功能7ODR_XL[3]加速度计输出数据速率高四位6-4FS_XL[2:0]加速度计量程选择000±2g001±16g010±4g011±8g3-0ODR_XL[3:0]加速度计输出数据速率低四位详细映射见手册Table 22CTRL2_G寄存器位定义位名称功能7-4ODR_G[3:0]陀螺仪输出数据速率3-2FS_G[1:0]陀螺仪满量程00±250dps01±500dps10±1000dps11±2000dps10保留00保留我这次常用的配置是加速度计±2g陀螺仪±2000dpsODR都是104Hz。这个组合适合一般的运动监测场景量程够大不容易饱和。ODR选择104Hz而不是更高的208Hz或者416Hz是因为样机验证阶段不需要太高的采样率数据量小一点串口打印和上位机分析都方便。ODR配置有一个概念容易被忽略陀螺仪和加速度计的ODR是独立配置的可以不一样。比如加速度计用416Hz做震动检测陀螺仪用52Hz做姿态变化监测在低功耗场景里很常见。后面二期讲到中断和FIFO时这个特性会很关键。CTRL1_XL和CTRL2_G的配置值分别是// 加速度计ODR104Hz±2g // ODR_XL[3:0]0100FS_XL[2:0]000 #define LSM6D3TR_CTRL1_XL_ODR_104HZ_FS_2G 0x40 // 陀螺仪ODR104Hz±2000dps // ODR_G[3:0]0100FS_G[1:0]11 #define LSM6D3TR_CTRL2_G_ODR_104HZ_FS_2000DPS 0x4C这两个值可以直接通过查手册里的表格得到比如陀螺仪ODR 104Hz对应二进制的0100满量程2000dps在FS_G[1:0]位里是11组合起来就是0x4C。3.4 关闭内置数字滤波的坑LSM6D3TR-C内置了一套数字滤波链路包括高通滤波、低通滤波还有一组看似方便的嵌入式功能。开启后CPU负担小但调试时容易让人抓狂——你不知道当前读到的数据是原始值还是经过滤波后的值。我的建议是第一版代码里先关闭所有数字滤波拿到原始的ADC值再说。这些滤波功能放在CTRL3_C寄存器0x12里其中BDU位Bit 6要特别提一下。BDUBlock Data Update位设为1时加速度计和陀螺仪的高字节和低字节数据会被锁定直到两个字节都读完才更新。如果不设置这一位在高字节和低字节的两次读取之间数据可能被新值覆盖导致算出来的结果完全错误。这个位对连续读取多字节数据的场景特别重要建议从一开始就设为1。CTRL3_C寄存器还有一个IF_INC位Bit 2默认是1表示多字节读取时地址自动递增。做连续读取时这个位必须保持为1否则每次读一个字节都要重新指定寄存器地址效率会低很多。初始化配置代码如下// CTRL3_CBDU1IF_INC1 // 0x04是IF_INC0x40是BDU uint8_t ctrl3_c 0x44; lsm6d3tr_write_reg(LSM6D3TR_CTRL3_C, ctrl3_c);这里还要提一下CTRL4_C寄存器里的I2C禁用位。如果将来要用SPI接口和这颗芯片通信需要把CTRL4_C里的I2C_DISABLE位置1来关闭I2C接口。反过来如果用I2C这个位必须保持默认0。曾经有人不小心配置了这个位结果I2C通信完全无响应排查了半天才发现是寄存器配置的问题。3.5 软复位与启动等待CTRL3_C的Bit 0是SW_RESET写入1会触发软件复位所有寄存器恢复到默认值。这个功能很重要尤其是代码经历了异常复位后传感器可能处于一个未知的配置状态。初始化流程里应该先做一次软复位等一段时间然后再写入实际配置。软复位后要等待多久手册上没有明确写但根据经验等待50ms比较安全。这是我的初始化代码里加延时函数的由来。如果不加这个延时复位还没完成就写入配置配置可能会被复位覆盖掉导致初始化不生效。4. 轮询读取的完整实现数据手册上的公式怎么落到代码里4.1 初始化函数实现初始化流程的代码实现如下uint8_t LSM6D3TR_Init(void) { uint8_t who_am_i 0; // 1. 读取WHO_AM_I确认设备 if (LSM6D3TR_ReadReg(LSM6D3TR_WHO_AM_I, who_am_i) ! HAL_OK) { return 1; } if (who_am_i ! LSM6D3TR_WHO_AM_I_VALUE) // 0x69 { return 2; } // 2. 软复位 uint8_t ctrl3_c 0x01; LSM6D3TR_WriteReg(LSM6D3TR_CTRL3_C, ctrl3_c); HAL_Delay(50); // 3. 设置BDU和IF_INC ctrl3_c 0x44; LSM6D3TR_WriteReg(LSM6D3TR_CTRL3_C, ctrl3_c); // 4. 配置加速度计和陀螺仪 LSM6D3TR_WriteReg(LSM6D3TR_CTRL1_XL, 0x40); // ODR104Hz, ±2g LSM6D3TR_WriteReg(LSM6D3TR_CTRL2_G, 0x4C); // ODR104Hz, ±2000dps HAL_Delay(20); // 等待传感器稳定 return 0; }注意WHO_AM_I的读取放在复位之前。这是因为WHO_AM_I寄存器在任何模式下都能读先确认通信链路是否正常再做后续配置逻辑上是通的排查问题也方便。4.2 轮询读取的实现细节LSM6D3TR-C的加速度计数据寄存器从0x28开始陀螺仪数据寄存器从0x22开始。每个轴的数据占两个字节低字节在前高字节在后。所以连续读取陀螺仪X、Y、Z轴的原始数据就是从0x22开始连续读6个字节。具体的读取函数如下int16_t gyro_x_raw, gyro_y_raw, gyro_z_raw; uint8_t data[6]; HAL_I2C_Mem_Read(hi2c1, LSM6D3TR_I2C_ADDR, LSM6D3TR_OUTX_L_G, I2C_MEMADD_SIZE_8BIT, data, 6, 100); gyro_x_raw (int16_t)((data[1] 8) | data[0]); gyro_y_raw (int16_t)((data[3] 8) | data[2]); gyro_z_raw (int16_t)((data[5] 8) | data[4]);实现这个读取逻辑时有一个关键点是组合两个字节。因为寄存器里的数据是补码格式而C语言里的int16_t恰好也是补码表示所以直接左移或运算再强转成int16_t就能得到有符号的原始ADC值。有些人的代码里会先转成uint16_t再强制转换这其实也可以但要注意不要用在普通的int上。因为int在32位MCU上是32位的左移8位后符号扩展行为不一样容易算出错误结果。下面这个宏定义是我用来做字节拼装的#define BYTE_TO_INT16(low, high) ((int16_t)(((uint16_t)(high) 8) | (uint16_t)(low)))4.3 原始值到实际物理量的转换读出来的原始ADC值怎么转成角速度数据手册给了一个公式实际角速度 原始值 × 灵敏度灵敏度取决于满量程配置满量程配置灵敏度mdps/LSB换算系数dps/LSB±250dps8.750.00875±500dps17.500.01750±1000dps35.000.03500±2000dps70.000.07000按照我配置的±2000dps转换公式是float gyro_x_dps (float)gyro_x_raw * 0.070f; float gyro_y_dps (float)gyro_y_raw * 0.070f; float gyro_z_dps (float)gyro_z_raw * 0.070f;同理加速度计的灵敏度满量程配置灵敏度mg/LSB换算系数g/LSB±2g0.0610.000061±4g0.1220.000122±8g0.2440.000244±16g0.4880.000488如果用±2g加速度转换公式就是原始值乘以0.000061得到g值。浮点数运算在现代MCU上不是什么问题STM32C5有FPU单精度浮点运算是一个时钟周期的事。但如果用的是不带FPU的低端MCU建议用定点数运算来代替浮点或者用q15_t之类的定点格式省下大量CPU时间。4.4 主循环里的轮询逻辑主函数里最核心的部分是主循环中的轮询逻辑int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_I2C1_Init(); MX_USART2_UART_Init(); if (LSM6D3TR_Init() ! 0) { printf(LSM6D3TR init failed\r\n); while(1); } printf(LSM6D3TR init OK\r\n); while (1) { LSM6D3TR_ReadGyro(gyro_x_raw, gyro_y_raw, gyro_z_raw); float gyro_x_dps gyro_x_raw * 0.070f; float gyro_y_dps gyro_y_raw * 0.070f; float gyro_z_dps gyro_z_raw * 0.070f; printf(G_X:%.2f G_Y:%.2f G_Z:%.2f dps\r\n, gyro_x_dps, gyro_y_dps, gyro_z_dps); HAL_Delay(10); // 控制轮询频率 } }这里的HAL_Delay(10)其实就是把轮询频率限制在100Hz左右和数据手册里配置的104Hz ODR基本对应。如果去掉这个延时不控制节奏读取频率可能会超过ODR导致连续两次读取到相同的数据性能没有提升反而浪费CPU。4.5 轮询还是中断什么时候该切换轮询方式的优点可以用一句话概括简单直接代码量小逻辑清晰。但它最大的问题是阻塞——主循环在读取和打印数据的这段时间里做不了任何其他任务。如果主程序还需要处理按键、通信、显示等事务轮询就会成为整个系统的瓶颈。一般我的选择经验是这样的如果传感器的采样率要求不超过100Hz并且主循环的工作负载不大轮询完全够用如果采样率要求高比如高频振动监测需要1kHz以上或者系统里还要跑实时性要求高的通信任务建议换中断方式INT引脚触发或者DMA方式配合硬件FIFO如果做低功耗产品CPU大部分时间要睡觉那就必须用中断唤醒轮询模式会直接把电池耗干后面第二期我会写中断方式的实现那才是真正可用于产品开发的姿势。5. 调试中的意外情况与实际经验5.1 串口输出的浮点数显示异常用printf打印浮点数据时如果连接上串口但显示的值全是0或者输出乱码先检查一下编译器是否启用了浮点数打印支持。Keil MDK默认把printf的浮点格式精简掉了需要在Options for Target的Linker页面里勾选Use MicroLIB或者在C/C页面把优化级别改成不优化浮点。STM32CubeIDE也有类似的设置需要在工程属性里把--specsfloat_printf加入链接器选项否则printf无法正确处理%f格式。5.2 I2C读回来全是0xFF这是I2C通信最经典的故障表现。寄存器读回来全是0xFF说明I2C总线上没有设备应答。排查顺序如下量一下SCL和SDA引脚电压正常空闲状态应该是3.3V如果有一个是低电平说明上拉有问题或者总线被拉死检查设备地址。我用的是0x6A 1还是0x6AHAL库的I2C地址参数需要7位地址左移一位变成8位地址。很多人在这里混淆。HAL库的HAL_I2C_Mem_Read函数第二个参数是DevAddress这个参数需要左移一位。如果直接传0x6A实际上发送的是0xD4作为设备地址总线上的设备不会应答确认SA0引脚电平状态如果以上都排查了还是0xFF用示波器抓一下I2C波形看时序是否满足400kHz的要求地址这个坑我印象太深了不止一次看到群里有人卡在这里。HAL库的函数加不加左移很多人各执一词实际上ST的标准HAL库对I2C地址的处理方式一直都是传入7位地址后HAL内部会自动左移一位。但具体到某个库版本可能有差异最稳妥的办法是直接看库源码里I2C_TransferConfig函数的实现。5.3 陀螺仪数据噪声偏大静态放置传感器时读出来的角速度并不是0而是在某个值附近跳动。这个现象完全正常陀螺仪本身固有的零偏和噪声特性决定的。对于零偏可以在初始化时采集100组静态数据做平均得到一个零点偏移值然后在运行时把每次读到的原始值减去这个偏移量。对于随机噪声如果不是特别严重可以用滑动平均或者低通滤波处理。一个快速判断数据是否可信的方法是看Z轴的输出。把传感器平放在桌面上Z轴朝上此时Z轴的角速度应该接近0X轴和Y轴也应该接近0。然后手动绕Z轴旋转传感器Z轴的读数应该有明显变化这样基本能确定传感器工作正常。5.4 数据更新的时序问题轮询读取时偶尔会遇到连续两次读取到相同值的情况。这不是芯片有问题而是读取频率高于ODR导致的。比如你配置ODR为104Hz但主循环里的读取频率达到了200Hz在两次采样间隔之间读取自然拿到的是上一次的采样结果。上一节提到的HAL_Delay本质上就是在做频率匹配。更优雅的解法是读STATUS寄存器0x1E的XLDA和GDA位这两个位分别表示加速度计和陀螺仪是否有新数据。先读STATUS寄存器如果GDA位为1再读陀螺仪数据保证每次读到的都是新数据不浪费I2C带宽。这个做法其实就是中断的轮询版本只是省掉了中断引脚和外部中断配置。uint8_t status; HAL_I2C_Mem_Read(hi2c1, LSM6D3TR_I2C_ADDR, LSM6D3TR_STATUS_REG, I2C_MEMADD_SIZE_8BIT, status, 1, 100); if (status 0x02) // GDA位 { HAL_I2C_Mem_Read(hi2c1, LSM6D3TR_I2C_ADDR, LSM6D3TR_OUTX_L_G, I2C_MEMADD_SIZE_8BIT, data, 6, 100); // 处理数据 }注意STATUS寄存器里GDA位是Bit 1XLDA位是Bit 0。想同时检查两个轴数据是否都更新了用(status 0x03) 0x03这种写法。5.5 CubeMX生成的时钟树导致I2C通信异常这个坑可能很少有人遇到但我确实踩过CubeMX默认生成时钟配置如果开启HSE失败会回退到HSI导致HCLK降低I2C的时序计算也会变化。在调试初期如果I2C通信不稳定可以把I2C的时钟模式暂时降到Standard Mode 100kHz试试排除高速模式下的信号质量问题。还有一个和时钟相关的注意点就是在不用的外设上保持禁用状态。CubeMX生成代码时会把没用到的外设时钟自动关闭这是正常优化。但有时候GPIO的某个引脚一开始被配置成了其他功能后来又改成I2C了CubeMX可能没有完全清理掉之前的配置导致引脚复用不正确。遇到奇怪问题的时候重新生成一次工程有时候就自己好了。6. 原始数据可视化与半成品分析6.1 用串口数据简单画波形调试IMU光看串口数字是不够直观的尤其是数据量大之后眼睛根本看不过来。这里分享一个最简单的方法用ST的免费工具Unicleo-GUI它可以直接从虚拟串口读取数据并绘制实时波形。不过Unicleo-GUI对数据格式有要求需要按照它的格式输出文本比如[GYRO_X] 0.12 [GYRO_Y] -0.03 [GYRO_Z] 0.01。不想折腾上位机的话也可以用Python脚本读取串口数据用matplotlib画图效果一样好。Python的pyserial库几行代码就能搞定import serial import matplotlib.pyplot as plt ser serial.Serial(COM3, 115200) xs, ys, zs [], [], [] for i in range(500): line ser.readline().decode().strip() # 解析数据 if G_X: in line: parts line.split() x float(parts[1].replace(G_X:, ).replace(dps, )) y float(parts[3].replace(G_Y:, ).replace(dps, )) z float(parts[5].replace(G_Z:, ).replace(dps, )) xs.append(x); ys.append(y); zs.append(z) plt.plot(xs, labelX) plt.plot(ys, labelY) plt.plot(zs, labelZ) plt.legend() plt.show()调试IMU时图形的价值远超数字。数据波形可以直观地反映出传感器是否正常比如静止时输出是否平稳拿起来绕某个轴旋转时对应轴的曲线是否有明显变化这些用肉眼观察波形远比盯着串口数字容易判断。6.2 从陀螺仪数据算角度轮询打通之后很多人第一件想做的事就是用陀螺仪积分算角度。原理不复杂角度等于角速度对时间的积分离散化之后就是不断累加角速度乘以采样周期。float angle_z 0.0f; float dt 0.01f; // 假设轮询周期10ms // 每次读到新数据 gyro_z_dps gyro_z_raw * 0.070f; angle_z gyro_z_dps * dt;算法很简单但实际用起来会发现一个问题积分漂移。陀螺仪的零偏误差虽然很小但积分是累积过程几秒钟可能看不出来一两分钟后角度已经明显偏离真实值。这就是为什么实际产品里陀螺仪数据不会单独用来算角度而是要和加速度计数据融合用互补滤波或者卡尔曼滤波。这个内容也会放到后面的文章里展开。6.3 数据冻结时的排查手段调试过程中如果发现数据完全不变像是“冻住”了一样先别急着怀疑传感器坏了。用调试器读一下传感器当前配置状态看看ODR是否配置正确。如果只有加速度计数据更新而陀螺仪不更新可能是CTRL2_G没配好陀螺仪还在睡眠模式。如果两者都不更新很可能传感器没退出睡眠模式——检查CTRL1_XL和CTRL2_G的ODR字段是否为0这是传感器默认的睡眠状态。另外如果使用了寄存器地址自增也要确认在连续读取时地址指针是否正确。IF_INC位如果意外被清零连续读出来的数据实际上都是第一个寄存器的值表现为XYZ三轴数据完全相同。7. 实际操作中形成的一个不算技巧的技巧整个调试下来最想分享的反而不是某个具体寄存器的配置而是一个习惯在调试IMU这类器件时把所有的寄存器读写都封装成带日志的版本一旦通信异常可以快速定位是哪个寄存器出了问题。我的做法是在调试阶段给驱动层加一个全局开关开启后每个寄存器读写都会打印寄存器地址和读写的数据等系统跑稳定后再把这个开关关掉。这个习惯帮我省下了大量排查时间尤其是做I2C这种底层通信时打印信息几乎等于逻辑分析仪。另外一个心得是认真读一遍芯片手册比在网上找十篇现成代码有用。LSM6D3TR-C的数据手册虽然比不过那些上千页的复杂SoC手册但里面每个寄存器的每一位定义、每个模式的时序要求都是工程师花了大量时间验证过的。照着手册写出来的代码出错概率远低于照搬网上的代码。第一期就先到这里。截止到现在LSM6D3TR-C的轮询读取已经验证通过硬件链路、I2C通信、寄存器配置和数据转换这套基础都没问题。接下来的二期计划做中断方式读取传感器数据顺便打通数据就绪信号和外部中断的配合三期考虑做FIFO的使用和DMA传输把CPU负载进一步降下来。考虑到这颗芯片还自带计步、倾角检测等嵌入式功能后续也可以单独开一篇讲讲这些特色功能的使用。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进