ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

STM32C5 硬件 IIC 驱动 IIS3DWB 振动传感器实战

STM32C5 硬件 IIC 驱动 IIS3DWB 振动传感器实战 1. 项目背景与整体设计思路STM32C5 是意法半导体新推出的一条产品线定位介于主流型 G 系列与高性能 H 系列之间主打高性价比与低功耗的平衡。我拿到这块芯片的第一时间就想拿它跑一颗工业级的振动传感器验证一下它在真实数据采集场景下的表现。选型的传感器是 IIS3DWB这是一颗超宽带宽、低噪声的三轴数字振动传感器常用于工业设备的状态监测与预测性维护。它支持 SPI 和 IIC 两种数字接口上一篇文章里我用 SPI 打通了数据链路这一篇专门讲怎么用 IIC 把振动数据读出来。为什么还要单独折腾 IIC因为在实际项目里SPI 虽然速率高但占用的引脚多尤其是多传感器组网的时候片选线会迅速吃掉宝贵的 IO 资源。IIC 只用两根线就能挂载多个设备布线简单对于中低速的振动采样场景完全够用。IIS3DWB 在 IIC 模式下最高支持 400kHz 的快速模式配合它内部 26.667kHz 的 ODR读取三轴加速度数据绰绰有余。这篇文章适合已经上手过 STM32CubeMX、对 IIC 协议有基本概念、想快速把 IIS3DWB 跑起来的嵌入式开发者。我会从 CubeMX 配置开始一步步讲到寄存器读写、数据解析最后给出完整的可复现代码和踩坑记录。整个方案的核心思路很直接用 STM32C5 的硬件 IIC 外设做主控通过中断或轮询方式读取 IIS3DWB 的状态寄存器和数据寄存器把原始的 16 位加速度值转换成物理量。相比软件模拟 IIC硬件 IIC 的优势在于时序精准、CPU 占用低尤其是在高 ODR 下不会因为时序抖动丢数据。但硬件 IIC 也有它的脾气时钟占空比、上拉电阻、总线空闲时间这些细节没处理好很容易卡在某个状态出不来。下面我把整个设计和实操过程拆开来讲。2. 硬件连接与 IIC 基础要点2.1 IIS3DWB 的 IIC 引脚与地址配置IIS3DWB 的 IIC 接口复用 SPI 的引脚具体来说CS 引脚在 IIC 模式下充当地址选择位。这颗传感器的 7 位从机地址由 CS 引脚的电平决定CS 接高电平时地址为 0x6B7 位CS 接低电平时地址为 0x6A。注意这里说的是 7 位地址实际在总线上传输时要左移一位加上读写位组成 8 位。我一般习惯把 CS 直接拉高用 0x6B 这个地址这样和官方数据手册的默认示例一致调试的时候不容易搞混。SDO/SA0 引脚在 IIC 模式下没有作用可以悬空或者接地都行。SCL 和 SDA 分别接 STM32C5 的 IIC 时钟线和数据线我选的是 PB6 和 PB7这是 STM32 系列上最常用的 IIC1 引脚组合CubeMX 里直接选 I2C1 就能自动分配。供电方面 IIS3DWB 支持 1.71V 到 3.6V我直接用 3.3V 供电和 STM32C5 的 IO 电平匹配不需要额外的电平转换。注意IIS3DWB 的 IIC 模式下CS 引脚必须在每次通信前保持稳定电平不能在通信过程中翻转否则传感器会误判地址。我在第一次调试时就因为 CS 引脚被其他外设复用导致地址错乱读出来的全是 0xFF。2.2 上拉电阻取值与总线空闲时间IIC 总线的上拉电阻取值是个老生常谈的问题但每次都会有人栽在这里。标准模式下100kHz上拉电阻一般取 4.7kΩ快速模式下400kHz取 2.2kΩ 到 4.7kΩ 都可以。我实测下来STM32C5 的 IIC 引脚内部有弱上拉但驱动力很弱外部必须加上拉电阻。我用了两个 4.7kΩ 的电阻分别拉到 3.3V在 400kHz 下波形上升沿大概 300ns完全满足 IIS3DWB 的时序要求。上拉电阻的取值其实和总线电容有关。总线电容越大上升沿越慢上拉电阻就要越小。公式是 R t_r / (0.8473 × C)其中 t_r 是上升时间C 是总线电容。假设总线电容 100pF快速模式要求上升时间小于 300ns算下来 R 要小于 3.5kΩ。所以如果你挂了很多设备或者走线很长4.7kΩ 可能就不够了得降到 2.2kΩ 甚至 1.5kΩ。但电阻太小又会导致功耗增加低电平灌电流变大所以要在波形和功耗之间找平衡。总线空闲时间也是 IIC 协议里容易被忽略的点。IIS3DWB 在检测到总线空闲SCL 和 SDA 同时为高后才会响应新的起始条件。STM32C5 的硬件 IIC 外设会自动处理这个时序但如果你用软件模拟 IIC就必须在每次传输前确保总线空闲足够长的时间。我在用逻辑分析仪抓波形时发现如果两次传输间隔太短IIS3DWB 偶尔会不响应后来在两次读写之间加了 10us 的延时才稳定下来。2.3 STM32C5 与 G4 的 IIC 外设差异既然热搜里有人问 STM32C5 和 G4 的外设对比我顺便说一下 IIC 部分的差异。STM32C5 的 IIC 外设基本沿用了 G4 的架构都支持标准模式、快速模式、快速模式以及 SMBus 和 PMBus。但 C5 在时钟树上做了一些优化IIC 的时钟源可以选择 HSI、SYSCLK 或者 PCLK分频系数也更灵活。我在配置 400kHz 时G4 上需要仔细算 CCR 和 TRISE 的值C5 的 CubeMX 会自动帮你算好但底层逻辑是一样的。另一个差异是 C5 的 IIC 支持可编程的数字噪声滤波器可以滤掉总线上的毛刺。G4 上这个功能也有但 C5 的滤波器深度更大对于工业环境下的振动传感器场景很有用。我实测在电机旁边跑 IIC开启噪声滤波器后误码率明显下降。不过滤波器会引入额外的延迟如果 ODR 很高要权衡一下。3. STM32CubeMX 配置与代码生成3.1 新建工程与时钟树配置打开 STM32CubeMX选择 STM32C5 对应的芯片型号。我用的具体型号是 STM32C542RET6封装是 LQFP64。新建工程后第一件事是配置时钟树。STM32C5 最高主频可以跑到 144MHz我为了兼顾功耗和性能把 SYSCLK 设到 120MHzAHB 不分频APB1 分频系数设为 2这样 PCLK1 是 60MHzIIC1 挂载在 APB1 上时钟源就是 60MHz。时钟树配置有个小技巧CubeMX 里直接输入目标频率它会自动帮你算 PLL 参数。但自动算出来的参数不一定最优比如 PLL 的 M、N、P 值可能不是整数分频导致实际频率有偏差。我习惯手动核对一下确保 SYSCLK 是精确的 120MHz。IIC 的时钟频率对时序影响很大如果 PCLK1 有偏差CCR 算出来的实际 SCL 频率也会偏可能导致通信不稳定。3.2 IIC1 参数配置详解在 Connectivity 里选 I2C1Mode 选 I2CSpeed Mode 选 Fast ModeClock Speed 填 400000。下面几个参数需要特别注意Clock No Stretch Mode默认 Disabled表示允许时钟拉伸。IIS3DWB 在数据处理不过来时会拉低 SCL 来暂停传输所以这个必须保持 Disabled否则会丢数据。Primary Address Length选 7-bit因为 IIS3DWB 用的是 7 位地址。Dual Address AcknowledgedDisabled我们只用一个地址。General CallDisabled用不到广播呼叫。No-Stretch Mode和上面的 Clock No Stretch 是一回事保持 Disabled。配置完后CubeMX 会自动算出 Timing 寄存器的值。我用的 60MHz PCLK1400kHz SCL算出来的 TIMINGR 是 0x10805D3D。这个值我后来用逻辑分析仪验证过实际 SCL 频率是 398.7kHz误差在 0.3% 以内完全满足 IIS3DWB 的要求。3.3 USART1 配置与调试输出调试阶段离不开串口打印我在 CubeMX 里把 USART1 配成异步模式波特率 1152008 位数据位1 位停止位无校验。引脚是 PA9 和 PA10这是 STM32 上最经典的串口引脚。配置完后生成代码在 main 函数里重定向 printf 到 USART1就可以用 printf 打印调试信息了。重定向的代码很简单在 main.c 里加上#include stdio.h int __io_putchar(int ch) { HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, HAL_MAX_DELAY); return ch; }然后在 Keil 或 STM32CubeIDE 的工程设置里勾选 Use MicroLIB就可以直接用 printf 了。我习惯在初始化完成后打印一行 IIS3DWB IIC Test Start确认串口通了再往下调。3.4 生成工程与工程结构说明点击 Project Manager设置工程名称和路径Toolchain 选 MDK-ARM 或者 STM32CubeIDE取决于你用什么开发环境。我用的 Keil MDK所以选 MDK-ARM V5。Code Generator 里勾选 Generate peripheral initialization as a pair of .c/.h files这样每个外设的初始化代码会单独放在 i2c.c、usart.c 里工程结构更清晰。生成代码后工程目录下会有 Core/Src 和 Core/Inc 两个文件夹main.c、i2c.c、usart.c 都在里面。我一般会新建一个 iis3dwb.c 和 iis3dwb.h把传感器的驱动代码单独放方便以后移植到其他项目。这个习惯在多个传感器共存的项目里特别重要不然 main.c 会变得又长又乱。4. IIS3DWB 寄存器操作与数据读取4.1 关键寄存器地址与功能IIS3DWB 的寄存器地址是 8 位的但 IIC 传输时需要先发寄存器地址再发数据。几个核心寄存器如下寄存器名称地址功能说明WHO_AM_I0x0F设备 ID固定值 0x6BCTRL1_XL0x10加速度计控制寄存器 1设置 ODR 和量程CTRL3_C0x12控制寄存器 3设置 BDU、IF_INC 等CTRL6_C0x15控制寄存器 6设置带宽和滤波STATUS_REG0x1E状态寄存器判断数据是否就绪OUTX_L_A0x28X 轴加速度低字节OUTX_H_A0x29X 轴加速度高字节OUTY_L_A0x2AY 轴加速度低字节OUTY_H_A0x2BY 轴加速度高字节OUTZ_L_A0x2CZ 轴加速度低字节OUTZ_H_A0x2DZ 轴加速度高字节WHO_AM_I 是第一个要读的寄存器用来确认 IIC 通信是否正常。如果读出来不是 0x6B说明地址错了或者硬件连接有问题。CTRL1_XL 用来设置 ODRIIS3DWB 支持从 26.667kHz 到 1.6Hz 的多种速率振动监测一般用 26.667kHz 或者 6.667kHz。CTRL3_C 里的 BDU 位要置 1这样读取数据时高低字节不会在读取过程中被更新避免数据撕裂。4.2 IIC 读写函数封装HAL 库提供了 HAL_I2C_Mem_Read 和 HAL_I2C_Mem_Write 两个函数可以直接读写传感器的寄存器。封装成两个简单的函数#define IIS3DWB_ADDR (0x6B 1) uint8_t IIS3DWB_ReadReg(uint8_t reg) { uint8_t data 0; HAL_I2C_Mem_Read(hi2c1, IIS3DWB_ADDR, reg, I2C_MEMADD_SIZE_8BIT, data, 1, 100); return data; } void IIS3DWB_WriteReg(uint8_t reg, uint8_t data) { HAL_I2C_Mem_Write(hi2c1, IIS3DWB_ADDR, reg, I2C_MEMADD_SIZE_8BIT, data, 1, 100); }注意地址要左移一位因为 HAL 库的地址参数是 8 位的最低位是读写位。IIS3DWB_ADDR 定义为 0x6B 1也就是 0xD6。写操作时 HAL 库会自动把最低位置 0读操作时置 1不需要手动处理。读多字节数据时可以用 HAL_I2C_Mem_Read 一次读 6 个字节把三轴数据全部读出来。IIS3DWB 支持地址自动递增前提是 CTRL3_C 里的 IF_INC 位要置 1。我一般初始化时就把它打开这样读 OUTX_L_A 开始连续 6 个字节就能拿到 X、Y、Z 三轴的低高字节。4.3 初始化流程与参数计算初始化 IIS3DWB 的步骤如下延时 10ms等待传感器上电稳定。读 WHO_AM_I确认返回 0x6B。写 CTRL3_C设置 BDU1IF_INC1。写 CTRL1_XL设置 ODR 和量程。写 CTRL6_C设置带宽。延时 100ms等待传感器稳定输出。CTRL1_XL 的配置需要算一下。IIS3DWB 的 ODR 设置位是 bit7 到 bit4量程设置位是 bit3 到 bit2。我要 26.667kHz 的 ODR对应值是 0xA0量程选 ±16g对应值是 0x0C。所以 CTRL1_XL 写 0xAC。这里注意IIS3DWB 的量程和普通加速度计不一样它的满量程是 ±16g 固定但输出数据是 16 位的灵敏度是 0.488mg/LSB。也就是说读出来的原始值乘以 0.488 就是 mg 为单位的加速度。CTRL6_C 里设置带宽IIS3DWB 的带宽和 ODR 有关。26.667kHz 的 ODR 下带宽可以设到 6.6kHz 或者更窄。我设的是 0x00对应最宽带宽保留完整的振动信号。如果只关心低频振动可以设窄一点降低噪声。4.4 数据读取与物理量转换数据读取我用的是轮询 STATUS_REG 的方式。STATUS_REG 的 bit0 是 XLDA表示加速度数据就绪。轮询到 XLDA 为 1 后连续读 6 个字节uint8_t buf[6]; int16_t raw_x, raw_y, raw_z; float acc_x, acc_y, acc_z; if (IIS3DWB_ReadReg(0x1E) 0x01) { HAL_I2C_Mem_Read(hi2c1, IIS3DWB_ADDR, 0x28, I2C_MEMADD_SIZE_8BIT, buf, 6, 100); raw_x (int16_t)((buf[1] 8) | buf[0]); raw_y (int16_t)((buf[3] 8) | buf[2]); raw_z (int16_t)((buf[5] 8) | buf[4]); acc_x raw_x * 0.488f; acc_y raw_y * 0.488f; acc_z raw_z * 0.488f; }这里有个细节IIS3DWB 的输出是二进制补码格式直接强转成 int16_t 就能得到带符号的原始值。转换系数 0.488 是数据手册里给的单位是 mg/LSB。如果你想要 m/s²再乘以 0.00981 就行。注意读取数据时一定要先读低字节再读高字节因为 IIS3DWB 在读取低字节时会锁存高字节保证数据一致性。如果你先读高字节可能会读到旧数据。HAL_I2C_Mem_Read 是连续读的顺序由硬件保证所以没问题。5. 实操调试与问题排查5.1 逻辑分析仪抓波形验证时序调试 IIC 最有效的手段就是逻辑分析仪。我把 SCL 和 SDA 接到逻辑分析仪上抓了一次完整的读寄存器波形。起始条件、地址帧、寄存器地址、重复起始、读数据、停止条件整个时序和 IIS3DWB 数据手册里的图完全一致。SCL 高电平时间约 1.2us低电平时间约 1.3us占空比接近 50%符合快速模式的要求。这里顺便说一下 IIC 时钟占空比的问题。STM32 的硬件 IIC 在快速模式下SCL 的占空比不是严格的 50%而是根据 TIMINGR 寄存器的设置来的。我的配置下占空比是 48.7%在允许范围内。如果你发现占空比偏差太大可以调整 TIMINGR 里的 SCLL 和 SCLH 值。CubeMX 算出来的值一般没问题但如果你手动改了 PCLK1 频率就要重新算。5.2 常见问题速查表问题现象可能原因解决方法读 WHO_AM_I 返回 0x00地址错误或硬件未连接检查 CS 引脚电平确认地址是 0x6B 还是 0x6A读 WHO_AM_I 返回 0xFF总线被拉低或上拉电阻缺失检查 SDA/SCL 上拉电阻确认总线空闲时为高HAL_I2C_Mem_Read 返回 HAL_ERROR总线忙或从机不响应增加超时时间检查从机地址和电源数据偶尔跳变BDU 未开启或读取顺序错误设置 CTRL3_C 的 BDU 位先读低字节高 ODR 下丢数据轮询太慢或 IIC 速率不够改用中断方式或提高 IIC 速率到 400kHz波形上升沿太慢上拉电阻太大减小上拉电阻到 2.2kΩ5.3 踩坑记录与独家经验第一个坑是 CS 引脚。我一开始把 CS 接到 GPIO 上初始化时拉高但后来这个 GPIO 被其他功能复用了导致 CS 电平在通信过程中变化IIS3DWB 的地址在 0x6B 和 0x6A 之间跳变读出来的数据全是乱的。后来我把 CS 直接焊到 3.3V 上问题解决。所以如果你不需要切换地址CS 直接拉高最省事。第二个坑是 IIC 总线的死锁。有一次我在调试时热插拔了传感器STM32 的 IIC 外设卡在 BUSY 状态怎么复位都不行。后来查资料发现IIC 总线死锁时需要手动发送 9 个时钟脉冲来解锁。我在初始化代码里加了一个总线恢复函数检测到 BUSY 时先发 9 个 SCL 脉冲再发停止条件就能恢复正常。第三个坑是数据对齐。IIS3DWB 的输出是 16 位的但 IIC 传输是 8 位一个字节。我一开始把高低字节搞反了读出来的数据一直在跳。后来用逻辑分析仪抓了波形发现低字节在前高字节在后调整代码后就稳定了。这个顺序在数据手册里写了但很容易看漏。5.4 性能实测与优化建议我用 26.667kHz 的 ODR 连续跑了 10 分钟通过串口打印数据统计下来平均每秒读到 26650 个样本丢包率低于 0.1%。CPU 占用率大概 15%因为用的是轮询方式如果改成中断或者 DMACPU 占用可以降到 5% 以下。IIC 的 400kHz 速率下读 6 个字节加上状态寄存器的开销一次传输大概 200us理论上每秒可以读 5000 次远高于 ODR所以瓶颈不在 IIC 速率而在轮询的频率。如果你要更高的采样率建议用 SPI 接口IIS3DWB 的 SPI 可以跑到 10MHz读数据快得多。但 IIC 的优势是布线简单对于大多数振动监测场景26.667kHz 的 ODR 已经足够覆盖轴承故障的特征频率。我在实际项目里用 IIC 版本跑了三个月数据一直很稳定没有出现过丢包或者死机。最后分享一个小技巧IIS3DWB 的 FIFO 可以缓存数据如果你不想频繁轮询可以开启 FIFO 的水位中断等 FIFO 里攒够一定数量的样本再一次性读出来。这样 CPU 效率更高但要注意 FIFO 的深度有限IIS3DWB 的 FIFO 是 512 字节三轴数据的话大概能存 85 个样本。对于 26.667kHz 的 ODR85 个样本只有 3.2ms 的缓冲所以中断频率还是很高。如果 ODR 降到 6.667kHz缓冲时间就有 12.8ms轮询压力小很多。
RELATED READING

延伸阅读

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