资讯详情

STM32C5驱动IIS3DWB:I2C通信与寄存器配置实战解析

📅 2026/9/30 1:26:55 | 华诺云谱 👁 阅读
STM32C5驱动IIS3DWB:I2C通信与寄存器配置实战解析
STM32C5这颗新片子上篇已经吐槽过开发环境了——选型号、配时钟、点灯跑通都是板上钉钉的事。但芯片能跑起来真不算什么能把外部器件的数据实打实交换回来才算把硬件口子打通。这篇就来玩 IIS3DWBST 专为工业振动监测出的那颗宽带宽三轴加速度计用 IIC 把它里面的加速度原始值读出来。就像标题写的这是系列第2篇目标很明确先回读 WHO_AM_I 确认 I2C 链路通不通再把量程、输出速率几个寄存器配好最后把 X/Y/Z 六个字节的原始数据换算成能用的 g 值或者 m/s²。过程中你会踩到不少 I2C 的经典坑比如上拉电阻该取多大、时钟占空比为什么不是 50%、SCL 被拉死怎么复位这些我都会挨个说清楚。适合手里有 STM32C5 板子加 IIS3DWB 模块、想从零把 IIC 通信跑通的朋友也适合只想搞懂寄存器型传感器怎么跟 MCU 打交道的人。1. 硬件准备与连接1.1 板子与传感器模块STM32C5 是 ST 新一代主流 MCUCortex-M33 内核主频最高 250MHz带 FPU 和 DSP 指令外设设计上从 G4 继承了不少东西。我手头这块 C5 开发板已经调通了串口和点灯这篇直接在同一个工程上继续。IIS3DWB 是 LGA-12 封装的小芯片焊盘间距很密我这边用的是手焊转接小板把引脚引出来方便飞线官方的适配小板也能用接法大同小异。开发板上电前先检查一件事IIS3DWB 的供电电压范围是 1.8V 到 3.6V数字接口电压 VDDIO 也是独立引脚。很多朋友直接把 VDD 接 3.3VVDDIO 悬空结果寄存器读不出来其实就是芯片内部 IO 没有供电总线根本没工作。这块传感器模块我 VDD 和 VDDIO 都接 3.3V跟 MCU 逻辑电平保持一致最省事。1.2 I2C 接线与上拉电阻取值IIS3DWB 的 I2C 接口只要四根线SCL、SDA、VDD、GND剩下一个 SA0 引脚决定器件地址最低位。SA0 接 GND 时 7 位地址是 0x69接 VDD 时是 0x6B模块上一般有默认下拉或跳线我这里让它接地。接线对应关系如下IIS3DWBSTM32C5说明VDD3.3V模拟与数字电源VDDIO3.3VIO 电源务必接GNDGND共地SCLPB8I2C1_SCLSDAPB9I2C1_SDASA0GNDI2C 地址 LSB 置 0如果模块上不自带上拉SCL 和 SDA 各需要接一个上拉电阻到 3.3V。取值大小直接决定通信能不能在高速率下稳定。I2C 是开漏结构上拉电阻太小会让灌电流过大太大则上升沿变得太缓超过时序规范要求。工程上常用这样一组经验值标准模式 100kHz 选 4.7k 到 10k快速模式 400kHz 选 2.2k 到 4.7k快速模式 1MHz 选 1k 到 2.2k。我实测过短线连接下 4.7k 也能跑 400kHz但一旦杜邦线拉长到十几厘米示波器上 SCL 上升沿就明显变圆偶尔会读到错误字节换 2.2k 立刻恢复正常。严格算的话上拉电阻上限可以由上升时间公式估算R_max t_rise / (0.8473 × C_bus)。400kHz 要求上升时间不超过 300ns如果总线电容按 100pF 算R_max 约 3.5k所以 4.7k 本来就在临界边缘2.2k 更稳妥。这个公式不用背但心里要有概念排查问题时会很有用。1.3 电平匹配的坑IIS3DWB 的 VDDIO 可以接 1.8V如果你是低功耗系统MCU 这侧也是 1.8V IO其实更合适。但 C5 的 GPIO 基本是 3.3V 电平VDDIO 就不建议设成 1.8V否则 SDA 上拉到 3.3V 后芯片的 IO 不一定承受得住或者内部保护二极管导通导致波形异常。我自己在这上面吃过亏最初把 VDDIO 接 1.8V结果通信时灵时不灵原因是 I2C 总线被上拉到 3.3V电平超出了芯片 IO 的耐压范围换回 3.3V 后问题消失。所以这类传感器与 MCU 混接时第一原则是让 VDDIO 和总线参考电压保持一致别想当然各自为政。2. IIS3DWB 传感器基本功2.1 这颗传感器到底强在哪里IIS3DWB 不是手机里那种普通加速度计它带宽能做到 6kHz输出数据速率最高能配到约 26.7kHz专业方向就是工业振动监测——电机、泵、风机这些旋转设备的状态检测。普通加速度计带宽普遍只有几百赫兹看高频振动信号根本不够用。IIS3DWB 数字接口支持 I2C 和 SPI16 位数据输出量程可选 ±2g、±4g、±8g、±16g。它最大的价值就是这个宽带宽所以业内一般叫它“振动传感器”而不是简单叫加速度计。咱们这篇文章用 I2C 读它更多是为了先把通信链路搞明白如果想真正发挥 6kHz 带宽优势后面多半要切 SPI这个账我在第 5 章会细算。2.2 I2C 地址与寄存器地图跟寄存器型外设打交道本质就是读写它内部寄存器所以关键寄存器的地址必须心里有数。IIS3DWB 的几个核心位置如下寄存器地址作用WHO_AM_I0x0F固定回 0x52验证通信CTRL10x20主配置高 4 位设 ODRCTRL30x22bit2 是 IF_ADD_INC连续读自动递增CTRL40x23bit6 是 BDUbit[5:4] 设量程STATUS0x27bit0 是 DRDY数据就绪标志OUT_X_L0x28输出起始连续 6 字节为 X/Y/Z 低高字节WHO_AM_I 固定返回 0x52这是最常用的通信自检手段。写配置前先读它一次如果返回 0x52说明器件地址、引脚、上拉、时序全都对了后续配置才有意义。IF_ADD_INC 这个小位很多人容易忽略它的作用是多字节读时寄存器地址自动加一。不开启的话一次连续读 6 字节每一字节读到的都还是 OUT_X_L数据全错。2.3 量程、灵敏度与输出格式IIS3DWB 输出的是 16 位二进制补码满量程 ±32768。不同量程下每个 LSB 代表的加速度不一样常用灵敏度如下量程灵敏度mg/LSB1g 对应的原始值±2g0.061约 16384±4g0.122约 8192±8g0.244约 4096±16g0.488约 2048注意单位是 mg/LSB不是 g/LSB。换算成 m/s² 时要先把原始值乘上灵敏度得到“毫 g 数”再除以 1000 变成 g最后乘 9.80665。我在代码里为了省事直接把系数合在一起比如 ±2g 下每 LSB 对应 0.061 × 9.80665 / 1000 ≈ 0.000598 m/s²。这样打印的时候直接乘一个常量就行。3. STM32C5 的 I2C 外设配置3.1 C5 与 G4 的 I2C 外设对比先说个结论C5 的 I2C 外设在寄存器层面和 G4 基本是同宗同源都靠 TIMINGR 寄存器量化 SCL 时序都支持 Fast-mode Plus 的 1MHz支持 SMBus/PMBus支持模拟滤波和数字滤波。差别主要在外设时钟树C5 的 I2C kernel clock 可以有多个来源比如 PCLK1 或其他独立时钟配置 TIMINGR 之前得先搞清楚当前 I2C 模块用的是哪个时钟源。如果你从 G4 项目迁移代码过来I2C 部分几乎可以原样搬只要在 CubeMX 里重新确认一下时钟配置就行。对使用 HAL 库的人来说这个差异体感不明显但自己写寄存器时就会碰到所以先有个印象。3.2 CubeMX 里的 I2C 配置CubeMX 这边的操作很简单选择 I2C1I2C 模式选 I2CSpeed Mode 选 Fast Mode400kHz时钟源按默认 PCLK1 就行CubeMX 会自动生成 TIMINGR 配置。引脚选 PB8/PB9让它们复用为 I2C1_SCL 和 I2C1_SDA。生成代码后 HAL_I2C_Mem_Read / HAL_I2C_Mem_Write 就是两颗核心 API它们内部自动处理“发设备地址 发寄存器地址 读数据”这套事务比手动拼 START、STOP 省太多事。这里有个细节HAL_I2C_Mem_Read 的 DevAddress 参数要传入 8 位格式的设备地址也就是 7 位地址 0x69 左移一位变成 0xD2。如果直接传 0x69主机会发送错误的地址字节从机不回应函数直接返回超时。这个错误非常多见轮询代码里第一处检查点就在这。3.3 时钟占空比到底怎么算I2C 时序里“占空比”是个容易被忽略的点。I2C 规范里 SCL 高、低电平的最低时间是明确写死的标准模式 100kHz 要求高电平至少 4us、低电平至少 4.7us快速模式 400kHz 要求高电平至少 0.6us、低电平至少 1.3us快速模式 1MHz 要求高电平至少 0.26us、低电平至少 0.5us。也就是说低电平时间必须长于高电平占空比低于 50%这和 PWM 那种对半开的直觉不一样。原因在于 I2C 的低电平期间允许从机拉低 SCL 做时钟拉伸主机必须预留充足的低电平窗口否则从机还没来得及拉低主机就把时钟拉上去了。CubeMX 生成的 TIMINGR 已经把这些算好了SCLH 和 SCLL 两个字段分别代表高、低电平用多少个 I2C kernel clock 周期计数。真要手动算思路是用目标高/低电平时间减掉模拟滤波和数字滤波引入的延迟再除以 I2C 时钟周期最后对 SCLH、SCLL 取整并保证低电平计数不小于规范最小值。实际项目里我一般只在高速异常时才打开 CubeMX 生成的代码注释看 TIMINGR 值平时不手算。3.4 为什么 I2C 用开漏而不是推挽这是新手最容易绕不过去的问题。I2C 总线是“线与”结构SDA 上可以挂一大堆设备任何一个设备主动拉低整条总线就变低。如果大家都用推挽输出两个设备一个想拉高一个想拉低直接就是对拼短路总线电平被强驱动仲裁机制、时钟同步全部失效。开漏输出只能拉低释放后靠外部上拉电阻把电平恢复高天然实现了“谁都能拉低、没人拉低就自动回高”的效果。HAL 库初始化 I2C 时已经把引脚配成开漏复用模式不用操心但如果哪天你自己写寄存器初始化或者把引脚改成普通 GPIO 模拟 I2C千万别把 SCL/SDA 配成推挽这是模拟 I2C 的大忌。4. 代码实现从 WHO_AM_I 到加速度4.1 底层读写函数封装代码很直接重点是先把设备地址和寄存器地址定义清楚。我习惯把所有关键寄存器宏定义放到头文件顶部方便后面排查#define IIS3DWB_I2C_ADDR (0x69 1) // 7位地址0x69HAL需要8位格式 #define IIS3DWB_REG_WHO_AM_I 0x0F #define IIS3DWB_REG_CTRL1 0x20 #define IIS3DWB_REG_CTRL3 0x22 #define IIS3DWB_REG_CTRL4 0x23 #define IIS3DWB_REG_STATUS 0x27 #define IIS3DWB_REG_OUT_X_L 0x28 static uint8_t iis3dwb_reg_read(uint8_t reg) { uint8_t data 0; if (HAL_I2C_Mem_Read(hi2c1, IIS3DWB_I2C_ADDR, reg, I2C_MEMADD_SIZE_8BIT, data, 1, 100) ! HAL_OK) { return 0xFF; } return data; } static void iis3dwb_reg_write(uint8_t reg, uint8_t val) { HAL_I2C_Mem_Write(hi2c1, IIS3DWB_I2C_ADDR, reg, I2C_MEMADD_SIZE_8BIT, val, 1, 100); }HAL_I2C_Mem_Read 的第一个参数 hi2c1 是 CubeMX 生成的实例名。需要特别说明超时参数我给了 100ms正常情况下单字节读写几个周期就完成了但如果从机在拉低时钟拉伸主机就会在这里等待。超时时间给太短反而会在调试时误报失败给 100ms 比较稳。4.2 校准通信WHO_AM_I 验证上电后第一件事别急着配寄存器先读一次 WHO_AM_Iuint8_t who iis3dwb_reg_read(IIS3DWB_REG_WHO_AM_I); printf(WHO_AM_I 0x%02X\r\n, who); // 如果打印 0x52说明 I2C 通信链路已经通了这一步的价值在于把“通信问题”和“配置问题”彻底隔开。如果打印出来就是 0x52说明器件地址、引脚、上拉、时序全部正确后面配错只是寄存器数值的问题好排查得多。如果读出来不是 0x52先别怀疑传感器先用逻辑分析仪看波形再查地址和上拉效率最高。我见过有人上来就配置流配完发现数据全是 0xFF回头才发现地址从 0x69 开始就传错了白折腾半天。4.3 初始化配置CTRL1 到 CTRL4WHO_AM_I 通过后按顺序配置三个寄存器。我的初始化流程是void iis3dwb_init(void) { // 开启寄存器地址自增连续读6字节时地址自动递增 iis3dwb_reg_write(IIS3DWB_REG_CTRL3, 0x04); // BDU使能量程选 ±2g iis3dwb_reg_write(IIS3DWB_REG_CTRL4, 0x40); // ODR 设为 853.3Hz高4位对应数值0x0A iis3dwb_reg_write(IIS3DWB_REG_CTRL1, 0xA0); HAL_Delay(50); }CTRL3 写入 0x04是把 bit2 的 IF_ADD_INC 置 1。没有它连续读多字节时只会反复读同一个寄存器。CTRL4 写入 0x40bit6 是 BDUBlock Data Update开启后输出寄存器在高字节被读取之前不会刷新避免出现低字节是旧数据、高字节是新数据的错位量程 FS 位为 00对应 ±2g这样灵敏度最高静止时分辨率最好。CTRL1 写入 0xA0高四位 0x0A 对应 ODR 约 853.3Hz。这个速率不算高适合先把链路调通。配置完后延时 50ms让传感器内部完成启动和首次数据产出否则马上读数据很可能拿不到有效样本。4.4 读取 XYZ 并换算成 g 值读数据用一次 Mem_Read 连续读 6 个字节因为已经开了 IF_ADD_INC寄存器地址会自动从 0x28 递增到 0x2D一次事务拿回 X/Y/Z 的完整数据void iis3dwb_read_xyz(float *x, float *y, float *z) { uint8_t buf[6]; if (HAL_I2C_Mem_Read(hi2c1, IIS3DWB_I2C_ADDR, IIS3DWB_REG_OUT_X_L, I2C_MEMADD_SIZE_8BIT, buf, 6, 100) ! HAL_OK) { *x *y *z 0; return; } int16_t rx (int16_t)(uint16_t)((buf[1] 8) | buf[0]); int16_t ry (int16_t)(uint16_t)((buf[3] 8) | buf[2]); int16_t rz (int16_t)(uint16_t)((buf[5] 8) | buf[4]); const float sens 0.061f; // mg/LSB ±2g *x (float)rx * sens * 0.001f * 9.80665f; *y (float)ry * sens * 0.001f * 9.80665f; *z (float)rz * sens * 0.001f * 9.80665f; }这里有个非常典型的符号扩展坑。buf[1] 是 uint8_t左移 8 位后是 int 类型如果直接和 buf[0] 做或操作再强转 int16_t在某些编译器里可能因为符号位处理不当而出错。稳妥写法是先拼成 uint16_t再强转 int16_t这样符号扩展是确定的。我代码里专门套了一层 uint16_t 转换就是为了防这个。换算公式里 sens 的单位是 mg/LSB先乘 0.001 变成 g再乘 9.80665 变成 m/s²。4.5 轮询读取主循环主循环里最简单的读法是直接调用 read_xyz 然后打印但 I2C 读操作本身需要时间如果 ODR 较高读得太慢会拿到重复数据。我习惯先看一下 STATUS 里的 DRDY 位等数据就绪再读while (1) { if (iis3dwb_reg_read(IIS3DWB_REG_STATUS) 0x01) { float x, y, z; iis3dwb_read_xyz(x, y, z); printf(X%7.3f Y%7.3f Z%7.3f m/s^2\r\n, x, y, z); } HAL_Delay(10); }如果 ODR 很低DRDY 位还没置位就去读读到的可能是上一次的值这在调试看起来像“数据不更新”其实只是没等新数据。加了 DRDY 判断后逻辑上更严谨也为后面高 ODR 采集打基础。5. 实测逻辑分析仪与数据验证5.1 抓取 I2C 波形看时序细节调通代码后一定要把逻辑分析仪挂到 SCL/SDA 上抓一次波形。读 WHO_AM_I 时波形应该是这样的顺序起始位 SDA 在 SCL 高电平期间拉低然后发送写地址 0xD2从机回 ACK接着寄存器地址 0x0F从机再 ACK随后是重复起始位发送读地址 0xD3从机 ACK最后数据字节 0x52主机回 NACK发送停止位。看到 0x52 出现在数据字节位置这套时序就是完全正确的。抓波形时重点看两点。一是 ACK/NACK 的位置如果地址阶段之后没有 ACK说明器件地址不对或者模块没上电如果寄存器地址阶段没有 ACK可能是地址自增或寄存器配置有问题。二是上升沿形状波形上升沿如果很缓从上拉到高电平花了好几百纳秒赶紧检查上拉电阻和总线长度。我调试时遇到过一块模块插了杜邦线又并联了另一个传感器390ns 的上升沿直接把 400kHz 时序搞坏降到 100kHz 就正常后来换 2.2k 上拉并缩短走线才恢复。5.2 静止与翻转验证数据正确性数据读回来后最直接的验证方式就是把模块放平。正常情况下Z 轴垂直指向地心打印出来应该是 X≈0、Y≈0、Z≈9.8单位是 m/s²。把模块翻转 180 度Z 会变成约 -9.8。如果 X/Y/Z 的符号或数值对不上先检查拼字节的顺序OUT_X_L 在前、OUT_X_H 在后容易搞反。这一步还有个容易被忽略的细节传感器在静止时输出并不是完美零而是围绕零点有小幅度波动因为芯片本身有噪声。±2g 量程下静止噪声通常在几个 mg 量级折算成加速度就是几十 mm/s² 以内只要不是出现几十上百倍的跳动都属于正常。如果打印值一直乱跳检查 BDU 有没有开、DRDY 有没有判断再查电源有没有纹波。我遇到过用劣质 USB 供电时数据噪声明显变大换独立稳压供电后立刻稳定下来。5.3 I2C 带宽到底够不够用IIS3DWB 标称带宽最高 6kHzODR 最高能配到约 26.7kHz但用 I2C 连续读数据前务必算一笔账。一次读 6 字节的事务包含起始位、设备地址、寄存器地址、重复起始、设备地址、6 字节数据和应答位总共大概 83 个 bit。在 400kHz 下一次事务约 207us换算下来每秒最多能读约 4800 次即使升到 1MHz每秒也就 12k 次左右。这意味着 ODR 超过 3.4kHz 或 6.8kHz 这个级别I2C 就会被数据流占满更别提 26.7kHz 了。所以高频振动场景最终还是要靠 SPI或者用传感器内部的 FIFO 把数据攒一批再读。I2C 更适合演示、校准和低速监测这篇先把 I2C 这条路走通后面真正跑高频数据时再切成 SPI 就很顺理成章。6. 常见问题与排查技巧6.1 WHO_AM_I 读不到或读到 0xFF先看设备地址SA0 接地是 0x69接 VDD 就是 0x6BHAL 里传参要左移一位。接着查模块供电VDDIO 没接或电压不对芯片 IO 不工作读回 0xFF 很正常。再查上拉和接线SCL/SDA 接反、地线没共地都会导致通信失败。最后把 I2C 速率降到 100kHz 试试排除时序余量不足的问题。这四步挨个排查基本能定位 90% 的通信故障。如果读回的是 0x00方向就反了一般是总线被什么东西强制拉低或者地址写错导致从机重复响应。这时候逻辑分析仪比万用表好使直接看 ACK 位到底是不是低电平一眼就能分辨器件有没有应答。6.2 数据全是固定值或明显不对如果 WHO_AM_I 读到 0x52但加速度数据始终是同一个数优先检查 IF_ADD_INC 有没有开。没开的话连续读 6 字节只会反复读 OUT_X_L你会发现 Y、Z 和 X 的值完全一样。再检查寄存器顺序输出是 X_L、X_H、Y_L、Y_H、Z_L、Z_H拼字节时别把低高顺序搞反。数据如果溢出到满量程附近多是量程配置和灵敏度换算不一致比如寄存器配了 ±16g代码里却按 ±2g 的 0.061mg/LSB 去换算结果自然是错的。6.3 I2C 总线卡死SCL 或 SDA 一直为低总线卡死是 I2C 最恼人的问题。现象是 SCL 一直低或者 SDA 一直被拉低。原因多半是主机或从机的状态机进入了错误状态。软件恢复方法很经典手动把 SCL 翻转 9 个时钟周期从机内部状态机被强制复位总线就能释放。代码如下void i2c_bus_recover(void) { GPIO_InitTypeDef gpio {0}; gpio.Pin I2C1_SCL_Pin; gpio.Mode GPIO_MODE_OUTPUT_OD; gpio.Pull GPIO_PULLUP; HAL_GPIO_Init(I2C1_SCL_GPIO_Port, gpio); HAL_GPIO_WritePin(I2C1_SCL_GPIO_Port, I2C1_SCL_Pin, GPIO_PIN_SET); for (int i 0; i 9; i) { HAL_GPIO_WritePin(I2C1_SCL_GPIO_Port, I2C1_SCL_Pin, GPIO_PIN_RESET); HAL_Delay(1); HAL_GPIO_WritePin(I2C1_SCL_GPIO_Port, I2C1_SCL_Pin, GPIO_PIN_SET); HAL_Delay(1); } HAL_GPIO_Init(I2C1_SCL_GPIO_Port, (GPIO_InitTypeDef){ .Pin I2C1_SCL_Pin, .Mode GPIO_MODE_AF_OD, .Pull GPIO_PULLUP, .Alternate GPIO_AF4_I2C1 }); }注意补充 GP 初始化代码要适配实际工程。如果 9 个时钟仍然无效就直接给传感器断电重新上电C5 这边重新初始化 I2C。这个问题的根因很多时候是通信过程中主机异常中断或从机在时钟拉伸时主机提前释放状态机卡住。所以轮询读取时每次 HAL 函数返回 HAL_OK 再继续中途报错就要做恢复处理不能假装没看见。6.4 数据跳动离谱静止时数据跳得厉害先分清是原始值跳动还是换算后跳动。换算后跳动要检查浮点运算和打印精度原始值稳定的话多半是代码公式错误。原始值本身跳一步步查BDU 有没有开没开会读到高低字节错位的数据DRDY 有没有判断没判断会把旧数据当新数据读电源纹波大不大劣质 USB 供电在传感器高 ODR 时影响尤其明显。最后提一句I2C 总线如果走线太长、上拉电阻又太大波形畸变会直接导致寄存器读写出错数据自然乱七八糟这也是我反复强调先抓波形的原因。我自己实际玩 IIS3DWB 的时候最初有个错觉觉得 I2C 这套时序没啥好研究的。真踩过一轮坑之后才明白从设备地址到上拉电阻再到时钟低电平占比每一环都有讲究任何一个细节松懈数据就给你颜色看。下一步我准备把 FIFO 和 DRDY 中断打开让 C5 配合 DMA 去接高频振动数据再甩给串口看波形这个方向咱们下一篇继续聊。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑