2026/9/14 14:15:22

STM32C5驱动LSM6DSV16X:轮询读取陀螺仪数据实战

STM32C5驱动LSM6DSV16X:轮询读取陀螺仪数据实战 刚拿到LSM6DSV16X这颗六轴传感器时我第一反应是赶紧让它把陀螺仪数据吐出来。放在STM32C5上跑轮询是最直接也最能验证硬件通路的方式主控循环去查传感器“数据好了没”好了就一口气把三轴原始值读回来没好了就继续等。这个流程听起来简单里面却藏着不少门道——SPI时序怎么配、寄存器先写谁后写谁、读到的16位补码如何换算成角速度、为什么非要看状态寄存器搞不清楚这些读出来的数据就是一堆“看起来在变、实际不知道对不对”的数字。这篇文章我真的用STM32C5和LSM6DSV16X把路走了一遍工程基于STM32CubeMX和HAL库通信走SPI。全文覆盖接线方式、CubeMX配置、寄存器原理、完整代码和踩坑记录适合刚上手LSM6DSV16X、或者想从MPU6050迁移过来的朋友。把这篇文章跑通了你不仅能得到一个可以正常输出角速度的轮询工程还能把“传感器驱动”这件事里最基础的一套逻辑理顺。1. 轮询获取陀螺仪数据先把方案想清楚1.1 这个项目究竟在干什么题目“STM32C5开发LSM6DSV16X(1)----轮询获取陀螺仪数据”拆开看是三件事主控是STM32C5传感器是LSM6DSV16X通信方式是轮询。STM32C5是ST新推出的入门级产品线内核从Cortex-M0升级到了Cortex-M33主频更高外设也更丰富工程上最典型的用法是替代老F0系列。LSM6DSV16X则是ST的六轴惯性传感器内部同时集成三轴加速度计和三轴陀螺仪它最大的卖点是超低功耗以及一颗相当能打的机器学习核MLC和可编程状态机FSM。不过在系列第一篇里我们不急着碰高级功能先把陀螺仪数据通过轮询稳定读出来这是所有后续工作的地基。轮询方式说白了就是MCU主动、反复地去问传感器“你的陀螺仪数据更新了吗”LSM6DSV16X内部有个状态寄存器专门记录各路数据的“新鲜程度”。陀螺仪数据更新完毕对应标志位会置1MCU看到标志位之后再去取数拿到的就是一组完整的、没有读到一半的原始数据。这套逻辑看起来机械但它有两个好处代码简单、行为确定。对初学阶段或者数据量不大的应用轮询完全够用而且出问题的时候容易排查。1.2 为什么轮询方式在这里够用不少人在接触STM32F4、F1时习惯了用EXTI中断去触发传感器读取觉得这样“实时性高”。这句话没有错但它不是放之四海皆准的真理。轮询和中断本质区别在于“谁来发起读操作”。轮询是MCU主动中断是传感器主动。LSM6DSV16X的GDA陀螺仪数据可用标志位本质上就是一根“软中断线”它告诉你数据已经就绪接下来的读操作不会白等。轮询的适用场景很明确数据率不高比如250Hz以下主循环本身还有别的周期性任务读陀螺仪只是插在某一小段里对延时不太敏感多等几十微秒完全无所谓想最快速度打通硬件通路用最简单的方式验证SPI/I2C通信和寄存器配置。如果你后面要做高速运动捕捉比如1kHz以上的数据率或者希望CPU尽量不占着等数据那再去考虑中断DMA。FSM/MLC这些边缘计算功能更是把“读数据”这件事下沉到传感器内部MCU只在最终结果变化时醒来。这些是后续系列文章可以展开的本篇先把轮询吃透。1.3 LSM6DSV16X是什么不止是一颗陀螺仪先说个容易踩的小坑LSM6DSV16X不是“纯陀螺仪”。它内部既有陀螺仪又有加速度计。标题里写“轮询获取陀螺仪数据”我们重点用陀螺仪但寄存器配置时如果完全不管加速度计也是可以的——因为加速度计和陀螺仪在LSM6DSV16X里是两套相对独立的信号链各有各的使能开关和ODR输出数据率配置。所以这一篇我们能只开陀螺仪不动加速度计。LSM6DSV16X支持SPI、I2C和I3C三种通信接口。我这次用SPI原因也很实际SPI的速率上限比I2C高但接线多两根而I2C只需要两根线但涉及器件地址和寄存器地址调试时多一层概念。对于传感器驱动这种高频读数的设备SPI是更“硬核”的选择大部分ST官方评估板默认也是SPI。和常用的MPU6050对比两者都是六轴但LSM6DSV16X的优势在于功耗低很多适合电池供电产品内置MLC和FSM可以在传感器内部完成活动识别、异常检测支持传感器融合库可以输出四元数封装更小适合可穿戴设备。对应地它的寄存器比MPU6050多很多数据手册有几百页所以一开始一定要抓住主线电源和复位、陀螺仪配置、状态寄存器、数据寄存器把这四个模块搞清楚驱动就通了。2. 硬件准备与CubeMX工程搭建2.1 物料清单与接线我用的是一块STM32C5系列探索板外加一个LSM6DSV16X的贴片转接板。如果你手里是其他型号的STM32只要带SPI外设代码逻辑几乎可以直接复用HAL库API是通用的。接线表如下SPI方式LSM6DSV16X引脚方向STM32C5引脚说明VDD电源输入3V3传感器供电LSM6DSV16X支持1.71~3.6V通常直接接3.3VGND地GND共地SCK输入PA5SPI1_SCKSPI时钟SDIMOSI输入PA7SPI1_MOSI主出从入SDO/SA0MISO)输出PA6SPI1_MISO主入从出CS输入PA4任意GPIO片选软件控制SDO/SA0——在SPI模式下同时承担MISO功能不用额外处理如果你没有转接板而是自己手工焊的传感器记得在VDD和GND之间加一个100nF去耦电容这个电容离芯片越近越好。电源纹波会直接影响陀螺仪输出的稳定性这个后面排查“数据跳动”时会详细说。2.2 CubeMX的引脚规划打开STM32CubeMX先选择你的STM32C5型号。如果你是第一次用这个系列需要先在CubeMX的“嵌入式软件包管理器”里下载STM32Cube FW_C5支持包否则器件列表里根本找不到芯片。选择芯片后按下面步骤配置在Pinout视图中找到PA5、PA6、PA7确认它们被分配到SPI1。如果引脚冲突先手动解除。将PA4配置为GPIO_Output用于CS片选信号。在左侧Categories里找到SPI1Mode设成Full-Duplex Master。在Configuration里把Parameter Settings按下一节参数设置。生成工程Toolchain选MDK-ARM或者STM32CubeIDE都行。这里有个细节STM32C5的PA4默认可能被分配为别的功能比如ADC或者定时器CubeMX里直接点击引脚在GPIO Output那一项选择即可。CS这个片选信号在SPI协议里虽然叫“片选”但很多传感器的CS时序比较复杂需要软件手动控制电平所以一定不要把它硬绑定到SPI的NSS硬件管理上否则初始化时序会出问题。2.3 SPI初始化参数怎么填SPI参数我建议这样设置参数值理由ModeFull-Duplex Master传感器读操作需要双向通信Data Size8 BitLSM6DSV16X寄存器都是8位Clock Polarity (CPOL)LowSPI Mode 0与绝大多数ST传感器示例一致Clock Phase (CPHA)1 Edge同上对应Mode 0Prescaler32或64初始阶段把速率压低稳定后再提高BaudRate约为1~2MHz杜邦线场景下太高速率容易出问题NSSSoftwareCS由GPIO控制不用硬件NSS关于SPI ModeLSM6DSV16X数据手册明确支持Mode 0和Mode 3我习惯用Mode 0也就是CPOL0、CPHA0。两种模式都能跑但整个工程里必须保持一致混用会导致数据错位。SPI速率也是新手常栽的地方。LSM6DSV16X的SPI最高能到10MHz但那是理想PCB条件下的指标。如果你用杜邦线插面包板5MHz以上就可能出现数据错位、丢字节的情况。我的建议是先把分频系数调大跑通之后再慢慢提速不要一上来就追求极限。2.4 新建工程时最容易忽略的细节CubeMX生成了工程之后有两点我吃了不少亏第一注意时基源。STM32C5的HAL库默认把SysTick作为HAL_Delay的时基如果你后面要用FreeRTOS记得在System Core的SYS里把Timebase Source改成其他定时器否则任务调度会和HAL_Delay打架。这一篇裸机跑可以不改但这个习惯早点养成。第二串口打印尽早配好。调试传感器数据时光靠点灯和Debug看变量效率太低。强烈建议在CubeMX里顺手配一个UART比如LPUART1重定向printf到串口这样轮询读数时可以直接用串口助手看角速度。重定向代码很标准#ifdef __GNUC__ int _write(int fd, char *pBuffer, int size) { for (int i 0; i size; i) { while ((USART1-ISR USART_ISR_TXE) 0); USART1-TDR *pBuffer; } return size; } #else #endif当然寄存器操作方式取决于你用的编译器。MDK下用fputc重定向也常见这里不展开。总而言之串口是这个项目的“眼睛”有了它确认数据对不对就轻松多了。3. LSM6DSV16X寄存器操作原理3.1 第一次通信先认亲WHO_AM_I写驱动第一步不是配置陀螺仪而是读WHO_AM_I寄存器。它就像传感器自报家门“你好我是0x6B”。LSM6DSV16X的WHO_AM_I寄存器地址是0x0F复位默认值0x6B。这么做的原因很实际SPI通信使能后硬件通路是否正常、接线是否接对、SPI参数是否正确在你写入任何配置之前可以用一次读操作全部验证出来。如果读WHO_AM_I返回的不是0x6B后面做再多配置都是白费。我在调试时一般会把WHO_AM_I的结果直接打印到串口或者在Debug里看这个变量。排查顺序也很死板先看WHO_AM_I对不对再谈别的。需要特别说明的是SPI读寄存器和写寄存器的指令格式不一样。LSM6DSV16X的寄存器地址是7位的SPI传输时第一个字节的高7位是地址最低位是读写标志写操作发送地址字节bit00读操作发送地址字节bit01比如读0x0F就要发送0x8F写0x11则发送0x11。这个“地址左移一位再拼读写位”的操作不同厂家传感器略有差异ST这颗很多人在这一步翻车。后面读多字节也是同样原理起始地址带上0x80剩下的字节在IF_INC开启时自动递增。3.2 配置陀螺仪的关键寄存器CTRL2_G与CTRL3_CLSM6DSV16X的寄存器地址很有意思CTRL1_XL0x10管加速度计CTRL2_G0x11管陀螺仪CTRL3_C0x12管系统级配置。三个地址挨在一起很容易记混。我先说CTRL2_G这是本篇的核心配置寄存器。位名称说明bits[7:4]ODR_G陀螺仪输出数据率bits[3:1]FS_G陀螺仪满量程bit0保留写0ODR_G直接决定了陀螺仪每秒更新多少次数据。对于轮询方式ODR选得太低数据更新慢选得太高主循环需要频繁去查状态还没有中断配合的话CPU占用率会上去。我工程里用的208Hz这个频率下姿态解算基本够用功耗和CPU负载也比较均衡。想跑更高速率的场合等把轮询逻辑跑通后再改也不迟。FS_G决定量程也就是陀螺仪最大能测量多大的角速度。量程可选±125、±250、±500、±1000、±2000dps。量程和分辨率是一对矛盾量程越大单位数字量代表的角速度越大对微小转动的分辨能力越差。我选±250dps这个范围覆盖了日常手势、机器人姿态的大部分场景精度也相对较好。CTRL3_C则管一些系统级行为我们这里用到三个位SW_RESETbit0软件复位写1重启传感器BDUbit6块数据更新防止读取过程中数据更新导致高低字节不匹配IF_INCbit3寄存器地址自动递增多字节读强烈依赖它。BDU这个功能强烈建议打开。如果不开启BDU读数据时如果恰好碰到传感器更新可能低字节是旧数据、高字节是新数据拼出来的16位值直接崩掉。开了BDU传感器会等寄存器读完一组后才更新下一组数据这是轮询读数的“保命”开关。3.3 状态寄存器与数据输出寄存器状态寄存器STATUS_REG地址是0x1E它就像传感器的“工位公告栏”。我们关心以下几位bit5GDA陀螺仪数据可用标志bit4XLDA加速度计数据可用标志bit6TDA温度传感器数据可用标志。轮询时GDA从0变1代表陀螺仪X/Y/Z三轴数据已经全部更新完成此时去读数据寄存器拿到的一定是同一时刻的完整数据。这里有个细节读状态寄存器这个动作本身就是一次通信它不会清除GDA位GDA位只会在下一次内部数据更新时被清掉。所以不要担心“读了状态寄存器会不会把数据弄丢”。数据输出寄存器有六个OUTX_L_G0x22和OUTX_H_G0x23组成X轴16位数据OUTY_L_G0x24和OUTY_H_G0x25组成Y轴数据OUTZ_L_G0x26和OUTZ_H_G0x27组成Z轴数据。读的时候先读低字节再读高字节合并成一个int16_t。因为陀螺仪输出的是二进制补码格式中间值0实际上代表“没转动”正值例如0x1000代表正向旋转负值如0xF000代表反向旋转。在IF_INC开启的情况下可以一次性从0x22连续读6个字节不需要每次单独发地址效率和代码简洁度都会更好。3.4 轮询读取的核心思想轮询代码写起来很简单但判断“数据可读”的逻辑一定要正确。最稳妥的做法是发送读STATUS_REG指令拿到当前状态字检查GDA位是否为1如果为1执行从0x22开始的6字节读如果为0等一小会儿再重复。这也是一种“状态轮询”不是无脑地每隔固定时间去读数据而是“只要状态没准备好就一直等到准备好为止”。如果不看状态直接读可能出现三种情况读到旧数据上一次数据还没更新这次读到的是重复值读到高低字节不一致的拼接数据引发剧烈跳变读到传感器根本没有更新过的初值比如0让你误以为传感器坏了。我在实际使用中总是把“读状态寄存器判断GDA”这一步放在一个do-while循环里。数据率208Hz意味着每4.8ms更新一次即使读操作很快循环也会在数据未就绪时快速自旋。如果后续想降低CPU占用可以在循环体里加个HAL_Delay(0.1)或调用RTOS的延时函数牺牲一点点响应时间把总线占用降下来。4. 代码实现从初始化到输出实际角速度4.1 SPI底层读写封装封装底层函数前先定义CS控制。用HAL库写GPIO注意CS引脚要在每次通信开始前拉低结束后拉高。这一步写在单独的宏或函数里方便日后换引脚。#define LSM_CS_LOW() HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET) #define LSM_CS_HIGH() HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET)单字节写函数void LSM_WriteReg(uint8_t reg, uint8_t value) { uint8_t tx[2]; tx[0] reg 0x7F; // 写操作bit0清零 tx[1] value; LSM_CS_LOW(); HAL_SPI_Transmit(hspi1, tx, 2, HAL_MAX_DELAY); LSM_CS_HIGH(); }单字节读函数uint8_t LSM_ReadReg(uint8_t reg) { uint8_t tx[2]; uint8_t rx[2] {0, 0}; tx[0] reg | 0x80; // 读操作bit0置1 tx[1] 0x00; // 读时序中MOSI数据任意一般发0 LSM_CS_LOW(); HAL_SPI_TransmitReceive(hspi1, tx, rx, 2, HAL_MAX_DELAY); LSM_CS_HIGH(); return rx[1]; // 第二个字节才是寄存器值 }多字节连续读函数void LSM_ReadRegs(uint8_t reg, uint8_t *buf, uint8_t len) { uint8_t tx[len 1]; uint8_t rx[len 1]; tx[0] reg | 0x80; for (uint8_t i 0; i len; i) { tx[i 1] 0x00; } LSM_CS_LOW(); HAL_SPI_TransmitReceive(hspi1, tx, rx, len 1, HAL_MAX_DELAY); LSM_CS_HIGH(); for (uint8_t i 0; i len; i) { buf[i] rx[i 1]; } }这里有个关键点HAL_SPI_TransmitReceive在通信过程中是全双工收发的读多字节时主控要去读MISO线上的数据就必须同时在MOSI上发出时钟。向MOSI发什么数据不重要一般发0即可。4.2 传感器初始化流程初始化顺序有讲究我的流程是这样的软件复位让传感器恢复到默认状态等待复位完成配置CTRL3_C开启BDU和IF_INC配置陀螺仪的ODR和量程读取WHO_AM_I确认还在。软件复位的代码LSM_WriteReg(0x12, 0x01); // CTRL3_C, SW_RESET1 HAL_Delay(20);SW_RESET置位后传感器内部会进行一系列校准和重启20ms基本足够了。复位完成之后之前的所有配置都会丢失所以顺序上必须先复位、再配置。配置CTRL3_C时我推荐把BDU和IF_INC一起打开LSM_WriteReg(0x12, 0x44); // BDU1, IF_INC10x44对应二进制0100 0100正好是bit6和bit3置位。有些工程习惯在复位后马上写0x04只开IF_INCBDU后面再单独配这样也没问题但少了一次写操作总是好的。接着配置陀螺仪LSM_WriteReg(0x11, 0x50); // CTRL2_G: ODR208Hz, FS±250dps0x50对应二进制0101 0000其中ODR_G0101即208HzFS_G000即±250dps。如果想要±2000dps量程FS_G011则应该是0x56。读取WHO_AM_I确认uint8_t who LSM_ReadReg(0x0F); if (who ! 0x6B) { // 出错处理灯闪/串口打印 }如果WHO_AM_I不对先别急着往下跑去检查硬件常见原因后面统一讲。4.3 主循环轮询读数主循环的结构非常直接就三步查GDA、读六字节、拼数据。while (1) { uint8_t status 0; uint8_t data[6]; // 等待陀螺仪数据可用 do { status LSM_ReadReg(0x1E); // STATUS_REG } while ((status 0x20) 0); // GDA位 // 一口气读六字节 LSM_ReadRegs(0x22, data, 6); // 拼接成三轴原始值 int16_t gx (int16_t)((data[1] 8) | data[0]); int16_t gy (int16_t)((data[3] 8) | data[2]); int16_t gz (int16_t)((data[5] 8) | data[4]); // 这里可以打印、存储或者做进一步处理 printf(%d,%d,%d\r\n, gx, gy, gz); }注意拼接代码里的类型转换。data[0]是低字节data[1]是高字节。如果直接把两个uint8_t用“|”拼起来赋给int16_t编译器在运算时会先提升为int再把结果截断所以高低字节的符号位会通过整型提升被保留。实际操作中我习惯写成(int16_t)((data[1] 8) | data[0])确保结果是我们想要的补码值。4.4 原始值换算成dps寄存器里读到的原始值不是我们直觉中的“度每秒”它只是一个16位有符号整数拿满量程±250dps来说数值范围是-32768到32767对应-250dps到250dps。换算公式非常简单角速度dps 原始值 × 灵敏度Sensitivity不同满量程对应的灵敏度不同LSM6DSV16X手册里给的是mdps/LSB毫度每秒用的时候除以1000满量程灵敏度每LSB对应的角速度±125dps4.375 mdps/LSB0.004375 dps/LSB±250dps8.75 mdps/LSB0.00875 dps/LSB±500dps17.5 mdps/LSB0.0175 dps/LSB±1000dps35 mdps/LSB0.035 dps/LSB±2000dps70 mdps/LSB0.07 dps/LSB所以我们用±250dps量程时换算代码如下float dps_x gx * 0.00875f; float dps_y gy * 0.00875f; float dps_z gz * 0.00875f;当然这里算出来的角速度已经包含了零偏误差。传感器即使静止不动理论上输出应该为0但实际上会有几十甚至几百LSB的偏移换算成dps就是零点几度每秒的漂移。这属于正常现象后续做姿态解算时可以通过校准来消除这一篇我们先把原始值正确读出来。5. 实测中遇到过的坑与排查方法5.1 WHO_AM_I不对先查硬件还是先查配置读WHO_AM_I返回不对这算是所有传感器调试里最让人血压升高的问题之一。我遇到的情况分几类返回全0xFF通常是通信完全没建立。SPI的SCK或MOSI接反、CS没有拉低、供电没上、GND没共地任何一个问题都会导致返回0xFF。返回全0x00一样是通信链路问题但方向不同可能是MISO没接好或者SPI的MISO引脚配置成了别的功能。返回不确定值比如0x2A、0x5A之类多半是SPI Mode不对CPOL和CPHA组合错了数据在错误沿被采样。排查顺序先用万用表确认传感器VDD对GND电压在1.71~3.6V再检查SCK、MOSI、MISO、CS四条线是否和代码一致然后用示波器没有示波器就用逻辑分析仪观察CS拉低后主机是否发出了正确帧格式。如果波形没问题再去调整SPI Mode把CPOL/CPHA组合换个试试多数情况下问题就出在一个Mode上。5.2 陀螺仪一直输出0读到的数据全是0但WHO_AM_I正常说明通信没问题问题出在配置环节。最常见的两个原因第一CTRL2_G的ODR_G没有配置默认是0000也就是关闭状态陀螺仪不工作数据自然一直是0。必须写一个非零的ODR值。第二状态寄存器判断写反了。如果把while条件写成(status 0x20) 1在GDA为1时反而跳出循环在GDA为0时一直等待主循环直接卡死在里面自然看不到数据。GDA是bit5掩码0x20判断条件应该是(status 0x20) ! 0。5.3 静止数据跳动大传感器静止时输出应该在零附近波动通常±0.5dps以内换算成LSB就是几十个字。如果跳动非常剧烈比如几百度每秒来回跳首先怀疑BDU没有开启导致高低字节拼错数据出现瞬间毛刺。其次检查电源。LSM6DSV16X的内核模拟部分对电源噪声比较敏感特别是面包板供电的情况下VDD和GND之间必须加100nF去耦电容。我一开始图省事没加静止跳动在±300LSB左右加上电容后立刻降到±20LSB以内。最后检查SPI速率。杜邦线太长或者面包板寄生电容太大高速SPI下MISO波形边沿变形采回来的数据会偶发错误表现也是随机跳变。可以把分频系数调大一些试试。5.4 符号反向和数据错位三轴方向反了或者X和Y数据交换了这不是硬件问题是坐标系定义不同。LSM6DSV16X的轴方向和你的安装方向、模块布局有关。处理方式很简单判断是绕哪个轴反向就在换算时乘-1判断是X和Y交换就在代码里交换数组下标但注意如果只有某一轴的符号反了姿态解算时会得到错误的旋转方向不能靠简单地调整输出顺序来掩盖最好从机械安装上解决。5.5 问题排查速查表现象优先检查项解决方案WHO_AM_I0xFF供电、接线、CS确认3.3V供电共地CS拉低WHO_AM_I异常值SPI Mode调整CPOL/CPHA组合陀螺仪输出恒为0ODR_G、GDA判断写非零ODR检查掩码0x20静止跳动大BDU、电源、SPI速率开BDU、加去耦电容、降频轴方向或数据错位安装方向、数据拼接交换轴或乘-1检查高低字节初始化后一段时间才能读到数据复位等待时间不足HAL_Delay延长到30ms以上6. 轮询版还能怎么演进6.1 从轮询到中断轮询的缺点是MCU在主循环里一直被“数据是否可用”这件事吊着。虽然208Hz的数据率下问题不大但如果后续要处理显示屏刷新、通信协议栈、PID控制等多个任务中断方式会更合理。LSM6DSV16X的INT1引脚可以配置为“陀螺仪数据准备好”信号输出数据更新时引脚会拉高触发MCU的EXTI中断在中断服务函数里读数据。这样主循环就不需要死等状态寄存器MCU能腾出时间处理其他任务。从这篇的轮询代码改造到中断版本只需要在CTRL4_C里把INT1_DRDY_G使能位打开配置INT1引脚复用为EXTI在中断回调里执行原本主循环里的读数据逻辑。代码改动不大但实时性和CPU利用率都会更好。6.2 从“读出来”到“用起来”把陀螺仪原始值读出来只是万里长征第一步。后面你会发现原始数据里有零偏、有噪声、有时间漂移直接用来算角度是一团乱麻。对我个人而言这个系列第一篇最重要的意义是建立起一条完整可靠的传感器数据通路。接下来可以做的事很多采集静止数据做零偏校准用一阶低通或互补滤波对数据进行平滑把陀螺仪和加速度计数据融合算roll/pitch角再往后可以尝试LSM6DSV16X内置的MLC和FSM直接在传感器内部做姿态事件判断让MCU从繁重的数据流里彻底解放出来。传感器驱动这块一开始跑通的成就感很强但真正能拉开差距的往往是后面的数据处理和系统架构设计。在实际操作里我最大的体会是轮询读传感器不是“傻等”而是要对“数据何时有效”有清晰认知。每次读STATUS_REG、检查GDA位、再读数据寄存器这三步缺一不可。把所有模块都跑通后再回头优化性能、压低功耗、上中断才是稳妥的路径。这篇先分享到这儿。代码关键部分都贴出来了照着做一遍你也能让STM32C5稳稳地读出LSM6DSV16X的陀螺仪数据。接下来的文章中我会继续写中断方式、零偏校准和姿态解算以及这颗传感器最有意思的MLC功能。