做旋转机械状态监测的朋友应该都有体会,振动信号里藏着轴承磨损、转子不平衡、齿轮断齿这些故障的第一手证据。我最近在STM32C5平台上把ST的IIS3DWB宽频加速度计跑通了,用IIC接口读取三轴振动数据,整个过程不算复杂,但细节非常多,尤其是地址位、上拉电阻、寄存器配置这些环节,稍不注意就卡一整天。这篇文章把从选型、硬件连接、寄存器初始化到IIC读取的完整实现路径记录下来,包含我实际调试中的踩坑过程,给后面要做同类项目的同行一个可以直接参考的底稿。
1. IIS3DWB在振动监测里的定位:为什么普通加速度计不够用
1.1 振动监测的带宽需求,先算一笔账
机械设备故障诊断里,振动信号的频率范围跨度很大。以最常见的旋转机械为例,转子不平衡、轴线不对中这些故障的特征频率通常就在1倍频到3倍频,几十赫兹左右;但轴承外圈、内圈的故障特征频率往往在几百赫兹到几千赫兹,齿轮啮合频率更容易上到几千赫兹。要捕捉这些高频成分,传感器带宽必须足够。
我之前用过不少ST的通用加速度计,比如LIS3DH、LIS2DH12这类,它们的输出数据率最高也就是几百赫兹到1kHz上下,带宽被限制在几百赫兹以内,测测姿态、计步没问题,一旦拿到电机轴承上采集振动,高频成分直接衰减没了。IIS3DWB这颗芯片不一样,它把-3dB带宽做到了6kHz以上,输出数据率最高可以到20多kHz,量程支持±2g到±16g,本身定位就是工业振动监测、预测性维护这些场景。
这颗芯片另一个特点是噪声密度很低,低噪声模式下能达到低频微弱信号的采集要求。对机械故障诊断来说,很多时候早期故障信号非常微弱,如果传感器自身噪声太大,后面做FFT也救不回来。所以我选了IIS3DWB,而不是拿通用MEMS加速度计硬上。
1.2 接口选择:IIC还是SPI,先看数据量
IIS3DWB同时支持SPI和IIC,这个项目标题是IIC,但我在选型时确实认真对比过两种接口。很简单,算一下数据量就能判断:IIC快速模式400kHz,一个完整的事务要传输地址帧、寄存器地址、应答位、数据帧,三轴六个字节大约要消耗80到90个bit。如果输出数据率跑到6.6kHz,每秒大约需要570kbit,IIC已经吃满了,扛不住。
所以我的结论是:IIC方案适合输出数据率在2kHz以下的场景,也就是关注频率在几百赫兹以内的故障特征,比如转子不平衡、不对中、基础松动这类中低频问题。如果你的目标是齿轮箱、轴承早期故障这类高频信号,数据率要拉到6.6kHz以上,那就老老实实上SPI,别在IIC这个接口上硬撑。
STM32C5的I2C外设和G4系比,寄存器结构和HAL API基本是延续的,我这次用CubeMX配置完后直接套用了以前G4的经验,没有踩到外设差异的坑。C5的时钟树和G4不一样,I2C的PCLK来源、分频系数配置要按C5的芯片手册来,后面我会专门说到时钟占空比配置。
2. 硬件连接:上拉电阻、地址选择这些决定成败的细节
2.1 IIC为什么必须用开漏加外部上拉,不能用推挽
这是IIC初学者最容易问的问题,也是IIC协议能不能稳定跑起来的关键。IIC总线是多主多从架构,允许挂多个设备,通信靠SDA线拉低来表示0,靠外部上拉电阻拉高来表示1。也就是说,任何设备在总线上只有两种合法动作:释放总线(让上拉电阻把电平拉高)和主动拉低总线。这种"线与"结构保证多个设备同时操作总线时不冲突。
如果用推挽输出,一个单片机引脚输出低电平,另一个单片机引脚输出高电平,两个引脚之间直接形成短路通路,轻则数据错乱,重则烧毁引脚。所以我看到不少新手把SCL、SDA配置成推挽输出然后卡在总线上,就是这个原因。STM32CubeMX配置I2C引脚时,正确做法是把引脚复用为开漏输出,外部挂上拉电阻。
2.2 上拉电阻取多大,直接给结论和计算思路
IIS3DWB的SCL和SDA需要外接上拉电阻,芯片内部没有集成可用的上拉。这个电阻取值不是随便选的,太小了电流大、功耗高,而且拉低时可能超过芯片的灌电流能力;太大了上升沿变缓,高速模式下直接通信失败。
常用的经验区间是2.2k到4.7k,3.3V供电、总线不长的情况下,我用过2.2k和4.7k都能稳定跑400kHz。如果想算得更精确,可以用IIC协议给出的上升时间要求倒推:快速模式400kHz要求上升沿不超过300ns,高速模式1MHz要求不超过120ns。上升沿时间由RC决定,充电从0到高电平阈值大约需要0.85倍的RC时间。于是R_max约等于上升时间除以0.85倍总线上电容,总线电容包括引脚电容、走线电容和上拉电阻寄生电容,一般估算在50pF到150pF之间。
按100pF算,400kHz模式下R_max约等于3.5k,所以我走线稍长或者挂了多个设备时,优先选2.2k而不是4.7k。最小值方面,下拉时要把电压拉到V_OL允许值0.4V以下,3.3V供电下灌电流能力通常3mA,算下来R_min大概1k。综合下来,单设备短走线用4.7k没问题,稳妥起见用2.2k。
2.3 IIS3DWB引脚与供电的关键连接
IIS3DWB有两个独立的供电域:VDD和VDD_IO。VDD是模拟和数字核心供电,VDD_IO是I/O接口供电,决定了SCL、SDA、中断脚的电平。我这个项目直接用3.3V同时供两边,跟STM32C5电平匹配,没有做额外转换。
需要注意的是VDD_IO如果和MCU电平不一致,比如传感器用1.8V、MCU用3.3V,就必须处理电平匹配问题,否则IIC通信时序电压阈值对不上。另外,VDD和VDD_IO引脚旁边都要放100nF去耦电容,尽量靠近芯片引脚,我还会再并一个1uF到10uF的钽电容应对瞬态电流,振动采集设备工况往往比较恶劣,电源干净与否直接影响数据质量。
模式选择上,IIS3DWB的CS引脚要接高电平,芯片工作在IIC模式;CS拉低则进入SPI模式。SA0引脚用来选择IIC地址,接地时7位地址是0x38,接高电平时7位地址是0x39。很多同学习惯用8位地址表示法,SA0接地时8位地址就是0x71。这个地址后面代码里要重点核对,因为HAL库的地址参数格式和手册写法经常让人绕晕。
3. 传感器初始化流程:先把寄存器地图搞明白再动手
3.1 几个关键寄存器的作用
IIS3DWB的寄存器不算多,但每个位都有讲究。我这里只挑初始化必须碰的寄存器说,详细定义以ST官方数据手册为准,我项目里的配置值可以作为参考底稿。
首先一定要读WHO_AM_I寄存器,地址0x0F。IIS3DWB的WHO_AM_I值是0x7A。上电第一件事就是读这个寄存器,读到的值不对,后面所有工作都是白费,先排查通信链路。
然后是CTRL1寄存器,主要配置输出数据率和低噪声模式。IIS3DWB的ODR范围很宽,从几十赫兹到二十几千赫兹都有对应档位。我这次IIC方案把ODR设在约1.6kHz,既能覆盖几百赫兹以内的故障特征频率,又不会让IIC总线处于极限状态。
CTRL2寄存器负责模拟滤波和带宽配置,IIS3DWB内置了抗混叠滤波器,这个一定得按实际ODR配置好,否则采样后高频信号混叠到低频段,频谱分析直接没法看。
CTRL3寄存器用来配置中断源和中断输出脚,我这次没用中断,但把软件复位功能确认了一下,避免调试过程中反复断电。
CTRL4寄存器配置量程和BDU位。量程根据振动幅度来,我的应用场景是电机壳体振动,峰值通常在1g以内,所以选了±2g,分辨率最高。BDU位是Block Data Update,这个务必打开,否则读取过程中如果新数据更新了寄存器,高字节和低字节可能来自两次不同的采样,数据就撕裂了。
3.2 初始化序列与代码
初始化顺序我总结成五步:读WHO_AM_I、软件复位、配置CTRL1、配置CTRL2、配置CTRL4。软件复位之后要等一下,让芯片完成内部重启,这个延迟建议不小于1ms,实际我给了5ms,稳妥。
基于HAL库的初始化代码大概长这样:
uint8_t who_am_i = 0; uint8_t ctrl1 = 0x00; uint8_t ctrl2 = 0x00; uint8_t ctrl4 = 0x00; /* 1. 读取WHO_AM_I */ HAL_I2C_Mem_Read(&hi2c1, IIS3DWB_ADDR, IIS3DWB_WHO_AM_I_REG, I2C_MEMADD_SIZE_8BIT, &who_am_i, 1, 100); if (who_am_i != 0x7A) { /* 通信异常,处理错误 */ } /* 2. 软件复位(具体复位位看手册) */ HAL_I2C_Mem_Write(&hi2c1, IIS3DWB_ADDR, IIS3DWB_CTRL3_REG, I2C_MEMADD_SIZE_8BIT, &(uint8_t){0x01}, 1, 100); HAL_Delay(5); /* 3. 配置ODR和低噪声模式,这里以约1.6kHz为例,具体值对照手册 */ ctrl1 = 0x00; HAL_I2C_Mem_Write(&hi2c1, IIS3DWB_ADDR, IIS3DWB_CTRL1_REG, I2C_MEMADD_SIZE_8BIT, &ctrl1, 1, 100); /* 4. 配置抗混叠滤波器 */ ctrl2 = 0x00; HAL_I2C_Mem_Write(&hi2c1, IIS3DWB_ADDR, IIS3DWB_CTRL2_REG, I2C_MEMADD_SIZE_8BIT, &ctrl2, 1, 100); /* 5. 配置量程±2g,同时打开BDU */ ctrl4 = 0x00; HAL_I2C_Mem_Write(&hi2c1, IIS3DWB_ADDR, IIS3DWB_CTRL4_REG, I2C_MEMADD_SIZE_8BIT, &ctrl4, 1, 100);这里要插一句关于IIS3DWB地址的细节,也是我调试时踩过的一个坑。STM32HAL库的I2C接口,DevAddress参数要求的是8位地址格式,也就是7位地址左移一位。IIS3DWB的7位地址是0x38,那么传给HAL的DevAddress应该是0x38左移一位,得到0x70。ST官方驱动里经常直接写#define IIS3DWB_I2C_ADD_L 0x71,这个0x71实际上是8位读地址,HAL_Mem_Read传入0x71会把最低位当作地址位的一部分处理,结果地址就错位了,读出来的数据自然不对。
4. IIC读取振动数据的代码实现
4.1 基于HAL库的IIC读写封装
初始化跑通后,读取就简单了。IIS3DWB的加速度输出寄存器地址从0x28开始,依次是OUT_X_L、OUT_X_H、OUT_Y_L、OUT_Y_H、OUT_Z_L、OUT_Z_H。支持连续读,一次事务把六个字节全读完,省去多次发起通信的开销。
我封装了一个读取函数:
#define IIS3DWB_ADDR (0x38 << 1) /* 8位地址格式 */ #define IIS3DWB_STATUS_REG 0x38 #define IIS3DWB_OUT_X_L_REG 0x28 int8_t iis3dwb_read_accel(int16_t *acc_x, int16_t *acc_y, int16_t *acc_z) { uint8_t buf[6] = {0}; uint8_t status = 0; /* 等待数据就绪 */ if (HAL_I2C_Mem_Read(&hi2c1, IIS3DWB_ADDR, IIS3DWB_STATUS_REG, I2C_MEMADD_SIZE_8BIT, &status, 1, 50) != HAL_OK) { return -1; } if (!(status & 0x01)) { /* DRDY位未置位 */ return 0; } /* 一次读取六个字节 */ if (HAL_I2C_Mem_Read(&hi2c1, IIS3DWB_ADDR, IIS3DWB_OUT_X_L_REG, I2C_MEMADD_SIZE_8BIT, buf, 6, 100) != HAL_OK) { return -1; } *acc_x = (int16_t)((uint16_t)buf[1] << 8 | buf[0]); *acc_y = (int16_t)((uint16_t)buf[3] << 8 | buf[2]); *acc_z = (int16_t)((uint16_t)buf[5] << 8 | buf[4]); return 1; }这个函数的思路很简单:先读STATUS寄存器,看DRDY位是否置位,置位了才去读数据,这样可以保证每次读到的都是新采样值。如果不判断DRDY直接读,可能读到的是上次的老数据,在固定采样率采集时会造成数据错位,后续FFT频谱会出现莫名其妙的杂散谱线。
4.2 关于采样节奏和一些边界情况
IIC读取振动数据,最关键的是保持数据的时间同步性。我实际测试下来,用轮询DRDY位配合HAL_Delay控制采样周期,在1.6kHz ODR下工作得很好。但要注意HAL_I2C_Mem_Read本身是阻塞函数,如果总线上有其他设备占用,或者上拉电阻太大导致时序变差,一次读取可能超过采样周期,造成丢样。
如果需要更高的采样率,建议换成中断方式或者DMA方式:把DRDY引脚接到STM32C5的EXTI引脚,在中断里触发I2C DMA传输,这样可以解放CPU让它做FFT或者其他运算。不过中断里做I2C要特别小心嵌套,我的做法是中断里只置一个标志位,主循环里检测到标志再启动DMA读取。
另一个边界情况是读到的原始数据是int16_t,IIS3DWB输出的数据格式按量程换算成重力加速度。比如±2g量程,换算公式是:
加速度(g) = 原始值 * 2.0 / 32768.0也就是一个LSB对应约0.061mg。这个分辨率在振动监测里已经够用了。我通常会把原始值放大1000倍,用mg为单位处理,避免浮点运算拖慢速度。
5. 把原始数据变成有用的振动信息
5.1 去直流分量:振动数据的第一个关键处理
加速度计测出来的值里,静态分量是重力加速度和安装倾斜带来的偏差,动态分量才是真正的振动信号。比如电机壳体上安装传感器,Z轴方向通常有约1g的重力分量,如果不把它去掉,后面算RMS振动总值时这个直流偏移会严重干扰结果。
我做了一个简单的高通滤波,截止频率大约在几赫兹,把直流和极低频的漂移滤掉。最基础的做法是滑动平均:
static int32_t dc_buf[3] = {0}; static int32_t dc_sum = 0; static int16_t last_raw = 0; float highpass_filter(int16_t raw, float alpha) { dc_buf[0] = dc_buf[0] + alpha * (raw - dc_buf[0]); return (float)raw - (float)dc_buf[0]; }alpha取多大要看采样率,我的是1.6kHz采样,alpha取0.005左右,对应的截止频率大约在1.3Hz附近,能有效去掉直流又不伤害振动信号。
5.2 实测波形与频谱验证
把传感器贴在小型电机壳体上,用喷嘴胶固定,连好STM32C5,我用串口把去直流后的数据发到PC上分析。电机转速约1500转每分钟,也就是25Hz旋转频率。时域波形上能明显看到周期性冲击,把1024点数据做FFT之后,频谱图在25Hz处有一个明显峰值,后面还有二倍频、三倍频的谐波。这说明IIC链路传输的数据是真实有效的,传感器安装方向对的话,不同轴的频谱特征也符合预期。
这一步验证很重要,因为它能证明整个链路不只是"读到数据"而已,而是数据在时间上对齐、幅度上正确。我见过有人读到数据但发现波形完全随机,最后定位是I2C读到的字节顺序反了,高低字节拼反了导致数据完全失真。所以测振动数据一定要做FFT验证,看看频点对不对得上设备转速。
5.3 简单RMS计算的做法
工业现场最常用的振动指标是宽带RMS值,对应振动速度或者振动加速度的总有效值。我采集一定长度的数据,去直流后做RMS:
float compute_rms(int16_t *buf, uint32_t len) { uint64_t sum = 0; for (uint32_t i = 0; i < len; i++) { sum += (uint32_t)((int32_t)buf[i] * buf[i] >> 2); } return sqrtf((float)sum / (float)len); }这个RMS值可以跟ISO振动标准做比对,判断设备当前振动水平是否超标。配合频谱分析,就能定位振动来源到底是转子动平衡还是轴承早期损伤。
6. 调试中绕不开的几个坑:完整排查链路
6.1 WHO_AM_I一直读不到,先查地址还是先查电平
这个问题几乎每个人都会遇到。我的排查顺序是先用示波器或逻辑分析仪看SCL和SDA线有没有波形。如果SCL没有时钟,那就不是传感器的问题,是STM32C5的I2C外设没正常启动,检查CubeMX配置、时钟使能、引脚复用是否冲突。
如果有波形但SDA一直保持高,看看是否忘记接上拉电阻。我之前一次调试就是觉得STM32C5的I2C引脚内部有上拉,结果没接外部电阻,高速模式下波形上升沿极其缓慢,通信完全不稳定。
如果波形都正常,那十有八九是地址问题。IIS3DWB的SA0引脚电平要和代码里的地址匹配,SA0接地用7位地址0x38,SA0接高用0x39。特别注意HAL库的参数要左移一位,这是很多坑的根源。还有一个容易忽略的点是CS引脚,如果它被意外拉低,芯片会进入SPI模式,IIC总线上怎么调都调不通。
6.2 能读到数据但数值乱跳,BDU和字节序哪个优先查
数据乱跳有两种常见原因:一是BDU没打开,高字节和低字节来自不同样本,我前面已经强调过CTRL4里的BDU位要置1,这是低速读取和中断读取场景下必须做的第一件事。
第二种是字节序处理错误。IIS3DWB的输出是小端模式,低字节在前,所以拼装时要把低字节作为低8位。我在代码里写的是(uint16_t)buf[1] << 8 | buf[0],如果写反了,数据看起来也在变化,但量级和物理意义完全不对。排查这个可以用一个简单办法:把传感器静止放在桌面上,读Z轴原始值,正常应该在1g附近,换算系数算一下就知道对不对。如果静止读数在大幅跳动,先加平均滤波再看是不是字节序问题。
6.3 IIC时钟占空比异常导致的偶发失败
IIC协议对时钟占空比没有特别严格的要求,但STM32的I2C外设在快速模式下对SCL高电平宽度有约束。我遇到过一种偶发问题:大部分时间通信正常,个别时候第一批数据读出来全是0xFF,后面又自己恢复。最后用逻辑分析仪抓波形,发现SCL上升沿偶尔过缓,导致某些位被从设备误判。
根因是I2C时钟配置时计算的占空比参数不对,具体是TIMINGR寄存器里的SCLDEL和SDADEL没有按I2CCLK频率算准。STM32C5的I2C时钟树和G4不同,我最开始用G4的配置直接搬到C5上,结果就是时序边缘情况。解决方法是回CubeMX重新生成配置,填好I2C速度频率,让工具自动计算TIMINGR参数。如果手动配置,一定要先确认I2C内核时钟是多少。调试的时候我把上拉电阻从4.7k降到2.2k,上升沿明显变陡,这之后偶发失败再没出现过。
6.4 采样率上不去,IIC数据吞吐到了极限
如果你的应用要把ODR拉到6.6kHz甚至更高,IIC方案大概率撑不住。我用逻辑分析仪实测过,一个完整的三轴读取事务在400kHz下需要约200多微秒,理论上最多只能跑到每秒4到5k个样本,这还不算协议开销和CPU处理时间。
所以最终建议:IIC适合1kHz到2kHz量级的振动采集,做中低频故障诊断完全够用;高频振动采集,直接改用SPI。IIS3DWB的SPI模式最高可以跑到10MHz,配合FIFO或者DMA,支撑6.6kHz以上的数据率没有压力。我这次项目里IIC方案稳定运行在1.6kHz采样率,满足了对中低频故障特征的覆盖需求。
回到文章开头那个问题,IIS3DWB配STM32C5做振动监测,IIC这条路线走通之后最大的体会是:硬件连接和地址格式是最大的拦路虎,寄存器配置反而是其次。调试时备一台逻辑分析仪、先把WHO_AM_I跑通、开BDU、确认字节序,这套排查顺序能帮你省下大量时间。如果后面有机会,我再把SPI模式读同一颗芯片的对比数据整理出来,毕竟高频场景还是得靠SPI,IIC适合中低频、少引脚的场景,这也是一个信号采集项目里很实用的一条经验。