拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

STM32C5通过I2C读取IIS3DWB振动传感器数据实战教程

STM32C5通过I2C读取IIS3DWB振动传感器数据实战教程

这篇是IIS3DWB振动计系列的第二篇。上一期我们把STM32C5的开发环境、时钟树和最小系统工程跑通了,这块板子的串口也输出了妥妥的“hello”。这期干一件正经事:通过IIC(准确说就是I2C)把IIS3DWB10IS这颗三轴震动传感器接进STM32C5,读取真实的振动数据。之所以选IIC而不是SPI,一来是省引脚,二来是想把I2C时序、上拉电阻、时钟占空比这些工程问题一起讲透。如果你正准备做工业振动监测、电机状态监测,或者只是想熟悉STM32C5的新外设,这篇可以直接抄作业。


1. 项目概述与整体思路

1.1 IIS3DWB10IS在项目里扮演什么角色

IIS3DWB10IS是意法半导体面向工业振动监测推出的一颗三轴数字加速度计。和普通6轴惯性模块里的加速度计不太一样,它主打的是宽带宽和低噪声。像电机轴承、风机、泵这类旋转设备,振动频率往往不是几十赫兹,而是几百赫兹甚至到几千赫兹,普通消费级加速度计带宽只有几百赫兹,根本抓不到高频特征,而IIS3DWB能覆盖到6kHz左右的带宽,正好卡在工业振动分析的常用区间。

这颗传感器的输出是数字量,内部已经做好了滤波、量程切换、温度补偿,MCU这边只需要通过通信接口去取数。它支持SPI和I2C两种接口,但在标题里我们用的是IIC,也就是I2C。IIC这个名字在中文社区里叫得特别顺口,ST官方文档一般写成I2C,后面我就按习惯两个混着说,大家知道是同一个东西就行。

1.2 为什么优先用IIC而不是SPI

很多人一看到高速传感器就默认上SPI,觉得I2C慢。这里要分场景。IIS3DWB数据输出最高可能到几十kHz的ODR,但每个数据帧也就6个字节左右(X/Y/Z各16位),就算再加上状态字节,按400kHz快速模式跑,完全喂得饱。I2C只占两根线,SCL和SDA,省下来的引脚可以给外部Flash、屏幕、调试接口用,对PCB布线也更友好。

SPI的优势是速率上限更高,一主一从全双工,而且没有地址寻址的额外开销,但需要四根线,而且如果后面想挂在一条多设备总线上,SPI还得每个设备单独拉片选。IIS3DWB这颗传感器本身是单设备场景居多,I2C反而更简洁。还有一个现实原因:I2C是开漏结构,抗干扰性在短距离板上通信里足够,工业现场大多数传感器节点和主控之间的线缆不会太长,I2C完全够用。


2. 硬件准备与关键选型细节

2.1 我手头的物料清单

先交代一下我在用的东西,方便你复现。主控是STM32C5系列,目前C5芯片在授权代理商那边基本能拿到样片,等货周期略久,手头的品牌开发板也完全没问题,关键是C5的I2C外设和CubeMX支持已经成熟。传感器用的是IIS3DWB10IS模组,某宝上搜“IIS3DWB 模块”也能找到现成的小板子,引脚一般引出了SCL、SDA、SA0、中断INT1/INT2,还有VDD和GND。

另外准备了几样东西:4.7kΩ贴片电阻两个、面包板或者转接板一块、杜邦线若干、一个逻辑分析仪(调试I2C时序必备,强烈建议备一个,后面你就知道它能省多少时间)。电压我统一用3.3V,STM32C5的IO和传感器模组都吃3.3V,千万别去接5V,除非你确认传感器模组带电平转换。

2.2 IIC上拉电阻怎么选,为什么不能用推挽

这是I2C总线最核心也最容易踩坑的地方。I2C的SCL和SDA都是开漏输出,也就是说设备只能主动把线拉低,不能主动拉高。如果代码里配置成推挽输出,多个设备同时操作总线时就有可能一个拉高一个拉低,直接短路,轻则数据错乱,重则烧IO。所以I2C引脚必须配成开漏模式,同时在总线外部接上拉电阻。

那上拉电阻取多大?我实测下来,3.3V供电、400kHz快速模式、板内走线十几厘米、总线上挂CPU和传感器两个设备,4.7kΩ非常稳。如果你用的线比较长、设备多,或者总线电容偏大,就换成2.2kΩ,代价是静态功耗大一点。100kHz标准模式下用10kΩ也常见,但既然传感器需要高数据率,一般不会把总线降到100kHz。

粗略估算可以用公式:上升时间 t_rise ≈ 0.8473 × R_pullup × C_bus。I2C规范要求快速模式上升时间不超过300ns,假设总线电容C_bus为100pF(约等于两块IC加上几厘米走线),R取4.7kΩ时,t_rise约400ns,稍微超一点;换2.2kΩ就是约186ns,余量很足。所以板内通信我更推荐2.2kΩ,除非你想极限降功耗。

2.3 STM32C5和G4外设的对比感想

既然热词里有人提到“STM32C5和G4外设对比”,我顺手说下我的感受。C5内核是Cortex-M33,主频做得比G4那批Cortex-M4更高,整体定位偏性价比和连接型应用。I2C外设这块,C5的寄存器结构和G4非常接近,都是ST近几年统一的设计风格,支持标准、快速、快速+模式,也支持可编程时序。差别主要体现在时钟树和中断映射上,C5挂在不同的APB总线上,CubeMX里配置时注意I2C外设时钟源选择和中断优先级分组,其它差别不大。

如果你之前写过G4的I2C驱动,迁移到C5几乎就是改一下引脚和时钟源的事。反过来,网上搜到的大部分G4 I2C例程也能参考,但要注意HAL库版本差异,老的例程里HAL_I2C_Mem_Read的接口没变,变的是一些时序配置的枚举项,在新CubeMX里直接用图形界面点就行,没那么玄乎。


3. IIC通信原理与关键细节

3.1 从时序图上理解一次完整的数据传输

I2C是一主多从的同步串行总线,这里STM32C5是主机,IIS3DWB10IS是从机。一次完整操作大概是这个流程:主机先拉低SDA,再拉低SCL,产生一个起始条件;接着发送7位从机地址加一个读写位;从机地址匹配后会拉低SDA发ACK;然后是寄存器地址字节、数据字节;每收一个字节,接收方都要回ACK;最后主机产生停止条件,SDA在SCL高电平期间由低变高。

关键点在于:SDA上的数据变化必须发生在SCL低电平期间,SCL高电平期间SDA必须保持稳定。这就是I2C时序图最核心的规矩。很多刚接触I2C的人被时序图吓到,其实把它想象成“SCL低电平的时候才能摆数据,SCL高电平的时候读取当前值”就对了。起始和停止条件则是例外,它们偏偏要在SCL高电平时翻转SDA,用来表示“开始”和“结束”。这个例外特别容易搞混,逻辑分析仪抓一次就全明白了。

3.2 时钟占空比、速率与CubeMX配置

热词里有“IIC 时钟占空比”,这里要澄清一个误区:I2C并不要求SCL一定是50%占空比。它要求的是SCL高低电平的时间满足每一段的最小值,比如400kHz快速模式下,SCL低电平时间最小约1.3μs,高电平时间最小约0.6μs,加起来一个周期,剩下的时间就是“松弛”。所以占空比其实是外设自动算出来的,你不用去刻意配成50%。

在STM32C5的CubeMX里,I2C配置页有一个“Timing”参数,里面是PRESC、SCLDEL、SDADEL、SCLH、SCLL这几个字段的组合。新手最容易偷懒填0,结果总线波形上升沿不够、通信偶发失败。我的做法是:直接选Fast Mode、目标速率400kHz,然后看数据手册里给出的总线电容估算值,让CubeMX自动计算时序。C5的HAL库底层会填好I2C_InitStruct.Timing,实测比手算准得多。

还有个经验:如果I2C总线上还接了其它从机,速率不要太激进。曾经为了榨速率把C5的I2C配成1MHz快速+模式,结果传感器数据读得飞起,但和另一个慢速从机通信就开始超时。工业现场稳字当头,400kHz是我的默认选择。

3.3 IIS3DWB的7位从机地址怎么确定

I2C通信第一步就是发对从机地址,地址错了整个总线上的设备都不会理你。IIS3DWB的7位地址由SA0引脚的电平决定。通常模组会把SA0引出来,接地时地址是0x6B,接VDD时地址是0x6D。这两个数在数据手册里会写,但这颗传感器也有可能是0x6A/0x68这类地址,所以上电第一步先读WHO_AM_I寄存器验证,读到预期值再往下走。

这里必须注意HAL库的一个习惯:HAL_I2C_Mem_Read的第一个参数是设备地址,它内部会再左移一位变成8位地址。所以你在代码里填地址时,直接填7位地址0x6B,不要自己手动左移成0xD6,否则HAL会再移一次,总线上的地址就变0xD6了,从机永远不响应。这个坑我见过太多次,包括我自己第一次写也中招。


4. 实操:STM32C5初始化与IIC读取振动数据

4.1 CubeMX工程里把I2C配置出来

在CubeMX里打开上一期建好的工程,先选中STM32C5对应的引脚映射。把PA9和PA10(或者你板子上的SDA/SCL引脚)配置为I2C1_SCL和I2C1_SDA。注意别把引脚复用错了,C5的引脚功能表在CubeMX里会直接显示,选I2C1_SCL之类的复用功能即可。

进入I2C1配置页:

  • Mode:I2C,勾上Fast Mode;
  • Timing参数让CubeMX自动计算,目标设400kHz;
  • 如果没有额外需求,关闭I2C1 interrupt或DMA,先用轮询方式把功能跑通再说。轮询简单、可控、好调试,后面要跑多任务再换中断或DMA。

时钟树里确认I2C1的时钟源频率不要跑偏,比如给到64MHz或48MHz都行,关键是必须符合CubeMX里Timing计算时用的那个频率,别改完时钟树忘了回I2C页面重新生成Timing。

4.2 IIC读写封装与初始化流程

生成代码后,在工程里加一个iis3dwb.c。先写两个基础函数,后面所有操作都基于它们:

#include "main.h" #include "iis3dwb.h" #define IIS3DWB_ADDR 0x6B // SA0接地时的7位从机地址 // 写单个寄存器 static HAL_StatusTypeDef IIS3DWB_WriteReg(uint8_t reg, uint8_t data) { return HAL_I2C_Mem_Write(&hi2c1, (uint16_t)IIS3DWB_ADDR, reg, I2C_MEMADD_SIZE_8BIT, &data, 1, 100); } // 连续读多个寄存器 static HAL_StatusTypeDef IIS3DWB_ReadRegs(uint8_t reg, uint8_t *buf, uint16_t len) { return HAL_I2C_Mem_Read(&hi2c1, (uint16_t)IIS3DWB_ADDR, reg, I2C_MEMADD_SIZE_8BIT, buf, len, 100); }

注意HAL_I2C_Mem_Write和HAL_I2C_Mem_Read的第三个参数是寄存器地址长度,这里用8位。从机地址用的是7位值0x6B,HAL内部会处理成写地址0xD6/读地址0xD7,别自己左移。

初始化流程分三步:验证ID、配置量程和数据率、然后开始循环读取。

uint8_t whoid = 0; IIS3DWB_ReadRegs(0x0F, &whoid, 1); // WHO_AM_I寄存器,地址以手册为准 if (whoid != 0x7F) { // 地址错误或接线问题,打印出来排查 }

寄存器地址这里我习惯用0x0F、0x20这组ST加速度计常见的命名,但每个型号都有一点差异化,拿到实物后一定要对着数据手册核对一遍,别指望所有ST加速度计寄存器100%通用。

接下来配置控制寄存器,比如把量程设成±16g、开启正常模式、关掉低功耗。具体位域在数据手册里非常清楚,用IIS3DWB_WriteReg(0x20, 0x90)这种写法,后面加注释说明每一bit的含义,方便自己过几天还能读懂。

4.3 读取并解析X/Y/Z轴振动数据

配置完成后,就可以不停地读数据。IIS3DWB的输出寄存器通常按X_L、X_H、Y_L、Y_H、Z_L、Z_H连续排布,一次性连续读6个字节,比挨个寄存器读效率高,也能保证X/Y/Z是同一时刻的快照。

uint8_t data[6]; int16_t x_raw, y_raw, z_raw; if (IIS3DWB_ReadRegs(0x28, data, 6) == HAL_OK) { // 起始寄存器地址以手册为准 x_raw = (int16_t)((data[1] << 8) | data[0]); y_raw = (int16_t)((data[3] << 8) | data[2]); z_raw = (int16_t)((data[5] << 8) | data[4]); }

为什么用int16_t?因为加速度数据是16位二补数,最高位是符号位。如果你不小心用了uint16_t,负值会被认成几万,换算出来的加速度完全离谱。

换算成g值的公式更简单:

float x_g = x_raw * sensitivity / 1000.0f;

这里的sensitivity取决于你配置的量程。±2g时大约1mg/LSB,也就是16384 LSB/g;±16g时就变成约4mg/LSB。我用±16g是因为振动监测场景里峰值加速度可能很大,量程太小会削顶,宁可分辨率牺牲一点,也要先保证数据不饱和。

把换算后的值通过串口打印出来,接上一个小振动源(比如手机振动马达、小风扇),能看到数据在0g附近波动,就说明链路已经通了。


5. 常见问题与排查技巧实录

5.1 读WHO_AM_I全是0xFF或全0

这是I2C调试最经典的问题。全0xFF通常意味着从机根本没响应,总线上的数据线被上拉电阻拉高,读出来全是空。全0则可能是SDA一直被拉低,或者从机地址错误导致一直收到NACK。

排查顺序我建议是:先用逻辑分析仪或示波器抓SCL和SDA,看波形上有没有起始条件、地址字节、ACK位。如果没有起始条件,说明代码根本没执行到I2C读写;如果有起始但没有ACK,说明地址不对或者SA0电平不对;如果有ACK但WHO_AM_I值不对,那就是读错了寄存器地址,或者读的字节被字节序搞混。

另外一个隐藏坑是引脚复用没生效。CubeMX生成的GPIO配置里,I2C引脚默认是开漏复用,但如果你手工改过GPIO模式,比如设成了推挽输出,总线就废了。我都是在MX_GPIO_Init里检查一下那几个引脚的Mode是不是GPIO_MODE_AF_OD。

5.2 IIC总线忙/SDA被拉死,怎么救

I2C最让人头大的故障是SDA一直低电平,整个总线瘫痪。这通常是因为通信中途断了电或者某个从机锁死了状态机。MCU主机倒是可以复位,但如果从机死在一个半截的状态机里,SCL再怎么切换它也不理你。

解决思路有两个。第一个是在上电初始化时对I2C总线做一次“软复位”:手动把SCL拉高拉低9个脉冲,让从机完成一次释放。第二个是在每次通信超时后,先把GPIO切回普通开漏模式,手动给SCL几个脉冲再切回复用模式,实测能救回大部分锁死状态。

我在项目里的做法是在MX_I2C1_Init()之前加一个I2C_Bus_Reset()函数,效果非常明显。具体代码网上有很多成熟模板,核心就是那9个SCL脉冲,不需要把SCL和SDA同时操作,只需要在SDA已经为高的时候让SCL多翻转几次。

5.3 拿到的振动数据全是毛刺,不像正常波形

数据读出来了,但画成波形一看全是毛刺,先别急着怪传感器。第一步检查供电,传感器供电纹波太大,输出数据噪声必然大,给VDD并一个0.1μF陶瓷电容加一个4.7μF电解电容,基本操作。第二步检查I2C通信有没有CRC错误或丢字节,如果主机速度太快、总线上干扰大,读出来的数据错位也会呈现为毛刺。用逻辑分析仪看一段时间的数据,能明显看出有没有偶发的ACK失败。

最后才是传感器配置问题。IIS3DWB带宽可以到几kHz,如果你用较低的ODR采样高频振动,混叠效应会把你看到的数据弄得乱七八糟。这其实不是一个“坏”波形,而是采样定律问题。经验是先把量程和ODR配对:设备振动频率范围不确定时,先用最高ODR采样,观察频谱结构,再决定要不要降低ODR节省功耗。


6. 最后聊两句踩坑体会

我在把这个传感器接到STM32C5上的过程中,最大的体会是I2C这东西看着简单,真正查起总线问题来特别费时间。硬件上先保证上拉电阻、开漏配置、供电滤波这老三样不出错,软件上再看从机地址、寄存器地址、时序这三关,八成问题都能定位。

另外一个容易被忽略的点是“地”的质量。工业振动采集不是纯数字电路问题,传感器和MCU之间的地如果不能等电位,I2C这种开漏总线抗干扰能力会大打折扣,远端设备建议用屏蔽双绞线,并且只在主控端单点接地。

如果你准备把这个方案拿去量产,我还建议传感器数据不要直接在应用层逐位处理,而是封装一层iis3dwb_update(),把连续读取和信号质量判断放到一个独立任务里,主循环只消费最新结果。这样后面加滤波算法、加阈值报警都会顺手很多。I2C这条路跑通之后,下一步就可以考虑把采样率进一步提升、加滑动平均滤波,然后真正去做频域分析了。

返回列表