最近在做一套旋转设备状态监测的方案主控选了STM32C5传感器用了ST的IIS3DWB一个Cortex-M33的新平台加一颗宽带振动计双新组合确实折腾了不少时间。这篇是系列的第二篇主要把IIC读取IIS3DWB震动数据的完整过程聊透从硬件连接、上拉电阻取值、IIC时序配置到寄存器初始化、数据读取流程和常见坑位全部是基于实际调试记录整理的适合正在用STM32C5或者类似新平台开发IIS3DWB的工程师参考想用这颗传感器做振动采集、故障诊断、状态监测的朋友也能直接抄作业。1. 项目概述与方案选型1.1 这套组合能解决什么问题IIS3DWB不是普通的加速度计ST给它定位是“宽带振动传感器”最高输出数据率26.7kHz平坦带宽能到6kHz左右。这个指标在MEMS加速度计里相当能打普通六轴IMU一般也就1kHz~4kHz的输出率想看轴承故障、齿轮啮合、电机异响这类高频振动特征基本无能为力。IIS3DWB正好补上这个空缺特别适合旋转机械的状态监测和预测性维护。STM32C5这边是ST新推出的主流MCU系列Arm Cortex-M33内核主频能跑到250MHz外设丰富度比老的F系列高不少。选它做数据采集端一是算力富余可以在MCU上直接跑一些简单的FFT和特征提取二是它跟G4/H5的外设风格接近IIC、SPI、DMA这些用起来比F1顺手很多。振动监测这类场景MCU端往往要同时采集多路传感器数据还要做实时特征计算STM32C5这个定位是合理的。1.2 为什么先用IIC而不是SPI或I3CIIS3DWB同时支持I2C和I3C两种接口CS引脚拉高就进入I2C/I3C模式拉低走SPI。我在原型阶段选了IIC理由很直接接线少两根线就能拉通逻辑分析仪夹上去就能看时序调试成本最低。I3C虽然速率更高但STM32C5这颗料本身对I3C控制器的支持还比较新工具链和驱动都不够成熟没必要在跑通数据链路的阶段就给自己上难度。SPI当然能跑更快但SPI会多占好几根引脚对于我这个板子上的IO资源分配不划算。IIC模式下IIS3DWB最高能跑到1MHzFast Mode Plus配合STM32C5的I2C外设是没问题的。但这里有个隐含前提就是你得把数据率算清楚400k的时候能不能扛得住26.7kHz输出率后面第5节我会专门算这笔账结论可能会让你意外。1.3 数据链路怎么规划整体数据链路是这样的IIS3DWB把振动信号转成16位数字量通过IIC总线进STM32C5的I2C外设再用DMA搬到内存环形缓冲区应用层从环形缓冲区取数据做滤波、特征提取和异常判断。高速采集场景下DMA几乎是必须的如果靠CPU在中断里一个字节一个字节搬数据250MHz的M33也扛不住毕竟中断开销和总线等待时间摆在那里。这里要提一个容易被忽略的点IIS3DWB内部有FIFO不要把每个采样点都当成一次独立的IIC事务去读否则总线开销会把有效带宽吃光。正确做法是让传感器以固定ODR连续采样FIFO攒够一批数据后通过中断通知MCUMCU一次突发读走一批数据这样IIC总线的利用率会高很多。2. 硬件准备与电路连接2.1 IIS3DWB引脚功能速览IIS3DWB的封装是ST常见的12引脚LGA体积很小手工焊接有点费劲。核心引脚如下VDD_IO数字接口电源通常跟MCU的IO电平保持一致我用3.3VSCL/SDAIIC时钟和数据线SA0IIC地址选择引脚拉低和拉高对应两个不同的7位地址CS接口模式选择IIC模式下必须拉高INT1/INT2中断输出可以配置成数据就绪、FIFO阈值等事件RES复位引脚低有效我这边板子上的实际接法是SA0直接接地CS上拉到VDD_IOINT1接到STM32C5的一个GPIO用于FIFO中断唤醒。需要注意的是CS引脚如果不接浮动状态可能导致传感器误入SPI模式IIC怎么都拉不通这是第一个要检查的硬件点。2.2 IIC上拉电阻到底取多大IIC是开漏总线SCL和SDA必须有上拉电阻才能产生高电平。这个电阻取值有讲究太小了低电平灌电流太大太大了上升沿太慢高速模式下波形直接变形。计算上拉电阻有两个边界条件。最小值由低电平输出能力决定Rp_min (VDD_min - VOL_max) / IOL_max。3.3V供电时VOL_max一般按0.4V算IOL_max对IIS3DWB这类传感器大约3mA算出来是(3.3 - 0.4) / 0.003 ≈ 966Ω。最大值由上升沿时间要求决定Rp_max tr_max / (0.8473 × C_bus)C_bus是总线寄生电容包括芯片引脚、PCB走线和MCU引脚一般按50pF~150pF估算。实际工程里400kHz快速模式我常用4.7kΩ如果总线链路比较长或者挂的器件多换成2.2kΩ。1MHz快速模式要用更小的电阻2.2kΩ或者1.8kΩ比较稳。我这次STM32C5和IIS3DWB几乎是同一块PCB上直连走线很短最后选了2.2kΩ实测1MHz波形上升沿干净利落。2.3 STM32C5引脚分配与GPIO配置STM32C5的I2C引脚是有复用功能的我用的是I2C2SCL和SDA分别映射到PB10和PB11具体以你手上的板子原理图为准。在CubeMX里配置起来很简单选好I2C2后它会自动把复用功能分配好GPIO模式会自动设为开漏输出。开漏输出这点务必注意很多人自己写GPIO初始化时习惯性配成推挽输出结果总线根本拉不低。另外外部上拉已经接了GPIO内部上拉就没必要开开了反而影响阻值计算这点不痛不痒但既然做工程就做干净。还有一个电平匹配问题。STM32C5的IO是3.3VIIS3DWB的VDD_IO也接3.3V两边电平一致不需要电平转换。如果VDD_IO接1.8VSCL/SDA就得注意MCU那边能不能接受1.8V高电平通常需要加电平转换芯片别硬连。3. I2C时序配置和参数细节3.1 时钟树与I2CTIMINGR配置STM32C5的I2C时序配置和G4/H5是一个套路通过TIMINGR寄存器控制时序而不是像F1那样直接设置高低电平时间。CubeMX生成代码时会根据你选的I2C速率自动算好TIMINGR但我强烈建议在生成后自己核对一遍。TIMINGR里面有四段参数PRESC、SCLL、SCLH、SDADEL、SCLDEL。其中SCLL和SCLH决定了SCL高低电平的持续时间这两段直接决定时钟占空比。我在STM32C5上实测发现CubeMX默认生成的值高低电平大致接近1:1也就是接近50%占空比。这在标准模式下问题不大但在快速模式下部分从设备会要求低电平时间长一些如果占空比太平均反而容易触发时序违规。我的做法是在CubeMX基础上手动微调SCLL和SCLH。IIS3DWB数据手册里对tLOW、tHIGH有明确的最低要求算好APB时钟周期后把SCLL设大一点让低电平时间比高电平多30%左右这样从设备识别更稳。3.2 总线空闲时间与连续读间隔IIC协议里有个容易被忽略的参数总线空闲时间tBUF也就是两次传输之间的最小间隔。快速模式下要求至少1.3μs快速模式要求至少0.5μs。如果连续发起两次IIC事务间隔短于这个时间总线可能还没完全释放第二次事务的STOP/START条件会出问题。我在调试时遇到过一种诡异现象连续读FIFO数据偶尔会丢一个字节逻辑分析仪看波形发现STOP和START之间几乎没空隙。后来在每次HAL_I2C_Mem_Read之间加了一个几微秒的小延时问题就消失了。这个延时不需要太长我用的1μs~3μs高频采集时这点开销完全可接受。3.3 STM32C5和G4外设的差异从G4迁移到STM32C5I2C外设的基本操作逻辑是一致的都是通过HAL_I2C_Mem_Read这类接口操作中断事件回调也是同一种风格。相比老F1的I2C它们少了很多麻烦事比如不需要手动处理EV/ERR事件不会动不动总线锁死。但C5毕竟是新平台有几处和G4不同的地方要注意。一是时钟树变了APB总线的频率来源和分频系数跟G4不一样初始化时先把RCC时钟配置理清楚。二是DMA请求映射有差异配置I2C的DMA通道时别直接照抄G4的工程。三是部分外设的引脚复用表有调整同是PB10在G4上可能是I2C2_SCL在C5上可能变成了别的功能生成代码前一定以CubeMX的引脚视图为准。还有个供应链方面的实际情况STM32C5这颗料目前渠道还不算特别畅通市场现货少价格也偏高。我这边是找代理商申请了几颗样片先跑验证等方案定下来再走正式备货流程。如果你也在评估这颗料建议先确认供货周期再决定项目节奏。4. IIS3DWB寄存器配置与数据读取4.1 第一步永远是读WHO_AM_I不管用什么接口拿到一颗新传感器我做的第一件事都是读WHO_AM_I寄存器确认IIC通路、地址、寄存器读写逻辑全都没问题。IIS3DWB的WHO_AM_I地址是0x0F复位值我实测是0x7C。IIC读取单寄存器的标准流程是主机先发送从机地址加写位再发送要读取的寄存器地址然后重新发起起始条件发送从机地址加读位最后读取数据。在STM32C5的HAL库下这个流程被封装成HAL_I2C_Mem_Read一行代码搞定。uint8_t who_am_i 0; HAL_I2C_Mem_Read(hi2c2, IIS3DWB_ADDR, 0x0F, I2C_MEMADD_SIZE_8BIT, who_am_i, 1, 100); if (who_am_i ! 0x7C) { /* 芯片没找到打印错误或者点个灯 */ }这里IIS3DWB_ADDR的取值要看SA0引脚。SA0接地时7位地址是0x53SA0拉高时是0x51具体以你手上的数据手册为准。HAL库的地址参数需要8位格式也就是左移一位我用宏定义写得清楚一点#define IIS3DWB_ADDR_7BIT 0x53 #define IIS3DWB_ADDR (IIS3DWB_ADDR_7BIT 1)如果读WHO_AM_I读不到先别急着查寄存器手册多半是硬件问题常见的我在第6节会逐条列出来。4.2 控制寄存器配置要点IIS3DWB的关键控制寄存器是CTRL10x20、CTRL20x21、CTRL30x22。这三个寄存器覆盖了采样率、带宽、量程、数据就绪和中断配置配置顺序也有讲究。CTRL1负责输出数据率和带宽选择。IIS3DWB的ODR分成几档最高26.7kHz带宽一般跟ODR绑定。做旋转机械振动监测时如果目标是电机轴故障6kHz带宽能覆盖大部分特征频率目标是齿轮箱的话可能需要用到13.3kHz甚至26.7kHz的ODR。我这里先用26.7kHz档验证传感器性能后面做整机功耗优化时再降到6.66kHz。CTRL2主要配置量程和高通滤波。IIS3DWB提供±2g、±4g、±8g、±16g四档量程。振动监测通常选±16g因为机械振动在冲击瞬间会有很大的加速度峰值量程选小了直接削顶。高通滤波可以隔掉直流分量和低频晃动让输出集中在振动频段这个对数据整体偏移修正很有用。CTRL3要开BDU块数据更新和配置中断。BDU这个功能特别关键它保证数据寄存器的高低位字节在读取过程中不会被新数据更新避免出现“高字节是新数据、低字节是旧数据”这种错位情况。高速采集时数据更新很快不开启BDU读到错误数据的概率会明显增加。配置完寄存器后建议读一次寄存器值回读校验防止写入失败。我习惯在初始化函数末尾加一个校验函数写什么读什么对不上就报错。4.3 获取原始加速度数据数据寄存器从0x28开始依次是X轴低字节、X轴高字节、Y轴低字节、Y轴高字节、Z轴低字节、Z轴高字节。每个轴16位有符号数补码格式。我通过HAL_I2C_Mem_Read连续读6个字节一次调用把所有轴数据都读完。uint8_t raw_data[6]; int16_t acc_x, acc_y, acc_z; HAL_I2C_Mem_Read(hi2c2, IIS3DWB_ADDR, 0x28, I2C_MEMADD_SIZE_8BIT, raw_data, 6, 100); acc_x (int16_t)((raw_data[1] 8) | raw_data[0]); acc_y (int16_t)((raw_data[3] 8) | raw_data[2]); acc_z (int16_t)((raw_data[5] 8) | raw_data[4]);把裸数据转成物理量也很简单。16位输出和量程的关系是物理量(mg) 原始值 × 满量程(mg) / 32768。比如量程±16g满量程相当于±16000mg那么1LSB约等于0.488mg。还有个实际工程上的细节就算量程选了±16g正常运转的设备振动幅值通常也就几百mg原始值可能只有几百到几千LSB看起来“数值很小”。这很正常振动监测关心的不是静止时的绝对加速度而是信号的AC分量和频谱特征不要被小数值吓到。5. 代码实现从初始化到持续读取5.1 初始化流程整理整个IIS3DWB初始化可以拆成三步IIC外设初始化、传感器配置、中断和DMA准备。IIC外设初始化由CubeMX生成传感器配置我封装成一个函数。void IIS3DWB_Init(void) { uint8_t ctrl1, ctrl2, ctrl3; /* 1. 确认芯片在线 */ uint8_t who_am_i 0; HAL_I2C_Mem_Read(hi2c2, IIS3DWB_ADDR, 0x0F, I2C_MEMADD_SIZE_8BIT, who_am_i, 1, 100); if (who_am_i ! 0x7C) { error_handler(); } /* 2. 配置CTRL1: 输出数据率和带宽ODR档位根据手册对应表填入 */ ctrl1 0x00; /* ... 将ODR位段设为26.7kHz档带宽对应6kHz ... */ HAL_I2C_Mem_Write(hi2c2, IIS3DWB_ADDR, 0x20, I2C_MEMADD_SIZE_8BIT, ctrl1, 1, 100); /* 3. 配置CTRL2: ±16g量程关闭高通 */ ctrl2 0x00; /* ... 设置FS位段为±16g ... */ HAL_I2C_Mem_Write(hi2c2, IIS3DWB_ADDR, 0x21, I2C_MEMADD_SIZE_8BIT, ctrl2, 1, 100); /* 4. 配置CTRL3: 开启BDU数据就绪中断输出到INT1 */ ctrl3 0x00; /* ... 置位BDU使能DRDY中断 ... */ HAL_I2C_Mem_Write(hi2c2, IIS3DWB_ADDR, 0x22, I2C_MEMADD_SIZE_8BIT, ctrl3, 1, 100); }上面故意用注释代替具体的十六进制数值原因是IIS3DWB的ODR和FS位段编码在不同版本手册中可能会有差异直接给一组数字反而容易误导。你把数据手册翻到CTRL1、CTRL2、CTRL3的寄存器说明页对着位定义填进去就好十分钟就能搞定。5.2 单次读取和状态寄存器判断传感器配置好后最简单的读取模式就是轮询状态寄存器等数据就绪再读。IIS3DWB的状态寄存器里有一个数据就绪标志位每当新的采样数据产生时置位读完后自动清除。uint8_t status 0; HAL_I2C_Mem_Read(hi2c2, IIS3DWB_ADDR, 0x27, I2C_MEMADD_SIZE_8BIT, status, 1, 100); if (status 0x01) /* 数据就绪标志 */ { HAL_I2C_Mem_Read(hi2c2, IIS3DWB_ADDR, 0x28, I2C_MEMADD_SIZE_8BIT, raw_data, 6, 100); acc_x (int16_t)((raw_data[1] 8) | raw_data[0]); acc_y (int16_t)((raw_data[3] 8) | raw_data[2]); acc_z (int16_t)((raw_data[5] 8) | raw_data[4]); }轮询模式适合低速采样比如ODR在1.66kHz以下。一旦ODR超过几kHz轮询的代码结构就可能出问题主循环处理其他任务的时间稍微长一点就会错过好几个采样点。5.3 高频采集时的带宽账要算清楚26.7kHz的ODR每个采样点要读6字节坐标数据算一下IIC总线的负载就知道轮询模式行不行。每笔IIC读事务至少包含起始位、7位地址、读写位、应答位、寄存器地址字节、应答位、重复起始、地址、数据字节、应答位、停止位。读6字节数据总线上实际要跑超过70个时钟位。26.7kHz × 70位 ≈ 1.87Mbps这已经超过1MHz的IIC硬上限了。也就是说在最高ODR下逐点读取所有三轴数据哪怕用Fm模式也扛不住。实际工程中一般有三种解法。第一是牺牲轴数只读X轴两个字节数据量降到三分之一勉强能在1MHz总线下跟上26.7kHz的节奏但信号频谱只覆盖单方向。第二是降低ODR用13.3kHz甚至6.66kHz六字节整读就没问题了。第三是开FIFO批量读取让FIFO攒够N个样本后一次性读出来把事务开销摊薄这个方案在低频ODR下效果尤其明显。我的实测建议是做原型验证时先降到6.66kHz全轴读取先把FFT波形和频谱看顺了再根据实际需要决定要不要上FIFO或者降轴数。5.4 IIC总线卡死后的恢复处理IIC总线最让人头疼的故障就是总线卡死SCL能正常翻转SDA一直被某个从设备拉低主机发什么都被认为NACK。IIS3DWB在异常掉电或主从状态机跑飞时偶尔会出现这种情况。恢复手段有几种按优先级排列。先用软件复位I2C外设很多情况下外设忙标志卡住复位寄存器就能清掉。如果外设复位不管用就把SCL和SDA两个引脚临时切成普通GPIO输出手动翻转9个SCL时钟脉冲帮总线上的从设备跳出内部状态机。最后一步是给传感器硬件复位拉低RES引脚再释放相当于让它重新上电初始化。我建议把这些恢复逻辑封装成一个函数在每次IIC通信返回错误时调用尤其在无人值守的设备上这比看门狗复位整个MCU温和得多不会造成采集数据的大段空洞。6. 常见问题与排查实录6.1 读WHO_AM_I返回0xFF或者0x00这是最高频的问题而且绝大多数情况下不是代码问题。返回0xFF先测SCL和SDA是否有波形如果SDA一直为高大概率是CS引脚没拉高传感器跑到了SPI模式。如果SDA一直为低重点查SA0地址是不是选错了以及总线是否被某个设备拉死。返回0x00也一样先排除上拉电阻问题。如果上拉电阻太大或者根本没接SDA在开漏模式下根本拉不高读回自然全是0。用示波器量一下SDA线上的高电平是不是接近VDD_IO如果不是检查上拉电阻到VDD_IO的路径有没有断路。我踩过的一个坑是SA0引脚虚焊传感器地址在两个值之间跳变时好时坏。后来用放大镜检查焊点才发现问题。新平台打样时引脚间距很小焊接后务必逐脚检查。6.2 数据读出来了但数值乱跳如果WHO_AM_I正常但加速度数据看起来完全没规律先想想是不是没开BDU。高速采样时两个轴的高低字节可能分别来自相邻的两个采样周期数值当然对不上。把CTRL3的BDU打开问题一般直接消失。还有一个原因是读取频率和ODR不匹配。比如ODR是6.66kHz你每1ms读一次每次读的时候数据已经更新了6次读到的都是中间某次的快照频谱分析时会出现混叠。解决思路是严格按数据就绪标志或者FIFO中断来触发读取而不是固定延时。6.3 NACK和总线挂死出现的时机NACK出现位置很重要。如果是在发送从机地址之后立即NACK说明地址不对或者SA0配置错了。如果是在读数据过程中NACK通常是SCL时序太快或者占空比不对从设备来不及准备数据。我在1MHz模式下遇到过开始通信正常跑了几分钟后偶发总线挂死的情况排查下来是总线空闲时间不足。两个IIC事务间隔太短上一笔的停止位还没稳定下一笔起始位就来了从设备状态机被打乱。解决办法一是每笔传输之间保证至少1μs间隔二是给I2C外设的TIMINGR设置更保守的参数三是关闭DMA的连续请求模式避免突发传输背靠背触发。6.4 上拉电阻和电平匹配的实际教训曾经在另一个项目上把IIS3DWB和STM32接在一条1米长的排线上用4.7k上拉400kHz跑起来波形已经严重畸变SDA上升沿快到只剩一个圆角。换成1k上拉之后波形干净很多这是因为线缆寄生电容普遍偏大上拉电阻必须相应减小。另外如果MCU供电是3.3V但传感器VDD_IO接了1.8V那么MCU的3.3V高电平对传感器来说可能超出其IO耐压范围反过来传感器1.8V的高电平对MCU来说又可能不够高。这种场景还是老老实实加电平转换芯片不要指望开漏总线能自动匹配。6.5 个人实测小结我实际用STM32C5在1MHz IIC下读取IIS3DWB6.66kHz ODR全轴读取非常稳定挂机跑了一整天没有一次总线错误。13.3kHz ODR全轴读取开始出现偶发NACK后来把时序的SCLL调大、增加事务间隔后也稳住了。26.7kHz ODR只能单轴读取或者靠FIFO批量读这个档位更多用于短时突发采集不太适合长时间连续记录。最后分享一个实用小技巧调试IIC通信时不要用仿真器看变量直接用逻辑分析仪挂在SCL和SDA上看波形。很多问题在数据层面表现为“读错值”但实际原因是时序层面的上升沿太慢、毛刺误触发、或者应答位没对齐这些靠逻辑分析仪一眼就能定位。低成本的双通道逻辑分析仪配合开源上位机软件就够用逻辑分析仪的触发条件设置为SCL下降沿采个几十K的采样率IIS3DWB的IIC通信过程就能看得明明白白。这套IIC通路的完整调试过程到这里就基本跑通了。接下来我在做的是把数据经过DMA环形缓冲灌进FFT计算模块用STM32C5内置的DSP指令跑实时频谱分析再配合IIS3DWB的FIFO中断把功耗进一步降下来。等这一版验证完我会把FFT实现和频域特征提取的代码整理出来作为这个系列的第三篇继续分享。