ARTICLE DETAIL

资讯详情

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

STM32C5 I2C驱动IIS3DWB高带宽加速度计:时序分析与振动数据读取实战

STM32C5 I2C驱动IIS3DWB高带宽加速度计:时序分析与振动数据读取实战 前一篇我们把IIS3DWB10IS接到STM32C5的评估板上用最基础的点灯流程确认了复位和供电都没问题。这篇直接进正题讲怎么用IICI2C把震动计的数据真正读出来。IIS3DWB10IS不是手机里那种普通加速度计它是意法半导体面向振动监测推出的高带宽三轴加速度计带宽最大能到6kHz噪声特性也比普通传感器好很多STM32C5则是Cortex-M33内核的新一代MCUI2C外设配置很现代两者配合做状态监测非常顺手。这篇内容适合手里正好有C5样片、想把IIS3DWB跑起来的开发者也适合从G4或其他系列迁I2C代码时参考因为C5的I2C底层几乎可以平移过来用。1. 为什么用STM32C5驱动IIS3DWB10IS1.1 震动传感器的定位和选型逻辑IIS3DWB10IS这类传感器和消费级加速度计最大的区别就是带宽。普通加速度计比如常见的ADXL345带宽大概300到400HzLIS2DW12也差不多做倾斜、计步、跌落检测够了但拿来看旋转机械的振动远远不够。轴承故障产生的特征频率往往在几百赫兹到几千赫兹齿轮啮合频率更是轻松到5kHz以上如果传感器带宽不够信号直接衰减后面算法再猛也白搭。IIS3DWB把带宽做到了6kHz噪声密度标称值也压得比较低这才有资格做设备健康监测。选型的时候还有一个因素它同时提供I2C和SPI接口。使用I2C做配置和低速数据读取非常方便但真要把6kHz带宽吃满最终还是要走SPI加FIFO这一点后面我会专门讲。不过对于“先把传感器调通、看看数据是否合理”这个阶段I2C是成本最低、排查最快的方式。另外一个选型逻辑是功耗和温度稳定性。工业现场的设备往往长期挂着传感器采集I2C只比SPI多两根线放在靠近轴承的位置时布线压力小。虽然高带宽传感器本身功耗不算特别低但在电池供电的巡检设备上降低主控唤醒频率、用好FIFO整体还是能接受。1.2 STM32C5的I2C外设和G4对比差在哪STM32C5是意法半导体后面推出的主流低功耗系列内核是Cortex-M33和STM32G4的M4内核不一样但外设风格却很接近尤其是I2C。G4上的I2C已经是很新的版本支持多主机、SMBus、PMBus时序寄存器TIMINGR由硬件自动计算完全没有老F1系列那种需要不停查EV5、EV6标志的手工状态机。C5上的I2C模块基本延续了这套设计因此从G4把I2C驱动代码迁到C5是比较流畅的改的主要是时钟使能、引脚复用和中断向量名。我实测的体感差异有三点。第一C5的复位和时钟架构做了调整I2C外设挂在不同总线域上__HAL_RCC_I2C1_CLK_ENABLE()底层对应的寄存器位和G4不一样但HAL封装之后代码相同直接迁移没问题。第二C5的低功耗模式更丰富可以把I2C配置成从机唤醒源这在低功耗巡检设备里比G4的普通停机模式更好用。第三C5的主频和内部时钟树需要重新配置I2C的TIMINGR计算依赖PCLK如果直接用G4工程里的TIMINGR值实际波特率会偏务必用CubeMX重新生成。对比项STM32C5STM32G4内核Cortex-M33Cortex-M4I2C模块新IP核可编程时序新IP核可编程时序迁移难度低主要是时钟/引脚参照基准低功耗能力更强支持I2C唤醒侧重高性能模拟外设所以如果团队里已经有人在G4上写过IIS3DWB驱动换到C5时没必要重写只要对照CubeMX重新生成一遍I2C初始化然后再确认GPIO复用号即可。真正会坑人的还是传感器那边的配置和I2C总线设计。2. 硬件连接与IIC总线设计要点2.1 引脚分配、上拉电阻与电平匹配我用的STM32C5评估板I2C1默认放在PB8和PB9上。不同板子的默认引脚可能不一样C5的GPIO复用表和G4不完全相同直接搬之前G4工程里的GPIO_InitStruct.Alternate值要查一下C5的数据手册。最简单的办法是在CubeMX里直接勾选I2C1它会自动给你匹配可用引脚。传感器这边IIS3DWB10IS的I2C地址由SDO引脚决定SDO接GND时7位地址是0x18接VDD时是0x19。我习惯把SDO拉低固定用0x18这样万一SDA和SCL接反了判断起来更容易。然后是上拉电阻。I2C总线本身是开漏结构SCL和SDA必须有外部上拉才能输出高电平。上拉电阻取多大很多人直接照抄4.7k但这是分场合的。I2C快速模式400kHz要求上升时间小于300ns而上升时间近似等于0.8473乘以电阻和总线电容的乘积。假设短线连接时总线电容大概50pF4.7k对应上升时间约199ns没问题但如果线稍微长一点分布式电容到了100pF4.7k对应的上升时间就是398ns已经超了。这种场景建议换成2.2k100pF时上升时间约186ns还能留有裕量。低电平那边的约束也要提一句。上拉电阻太小比如低于1k总线灌电流过大从设备的输出级不一定扛得住太大会导致上升沿过缓时序违规。我自己的习惯是100kHz模式用4.7k400kHz模式用2.2k线缆超过20cm时优先选用2.2k甚至1.8k。内部上拉一般不开GPIO的弱上拉有几十k欧对外部上拉几乎没有帮助还会增加计算的不确定性不开更干净。关于“IIC为什么不能用推挽”这个问题顺便说清楚。推挽输出的低阻特性会让两个节点同时输出不同电平的时候产生短路电流I2C多主机仲裁机制完全依赖开漏加“线与”逻辑谁先拉低谁就赢了高电平靠上拉电阻决定。单主机场景虽然不需要仲裁但从设备会拉低SCL做时钟拉伸也会拉低SDA做应答所以主机的SCL和SDA引脚也必须配置成开漏。供电电平方面也要注意。IIS3DWB的I2C电平取决于VDD_IO如果传感器模块单独供电1.8V而STM32C5这边是3.3V那么I2C上拉电压必须按1.8V来并且确认C5引脚的容忍电压。最简单的方案是让传感器VDD_IO和MCU都用3.3V上拉电阻统一接到3.3V省掉电平转换电路。高带宽加速度计对电源纹波比较敏感我习惯在传感器的电源引脚前面串一个磁珠再加一个10uF和一个100nF的电容实测数据稳定很多。2.2 时钟占空比与时序计算I2C时钟占空比不是随便设的也不是必须50%。400kHz快速模式对时序的要求是SCL高电平时长至少0.6us低电平时长至少1.3us加起来2.5us正好是400kHz的周期所以理想占空比是24%比52%加边沿而不是各一半。现代I2C外设不使用固定占空比发生器而是通过TIMINGR寄存器里的SCLH和SCLL分别控制高低电平计数。CubeMX把PCLK频率和目标波特率填进去会自动算出TIMINGR不需要手工算。但手工理解一遍有好处。假设PCLK是80MHz周期12.5ns快速模式高电平0.6us对应需要48个计数低电平1.3us对应104个计数。SCLH和SCLL还得分别加上信号上升沿的补偿值CubeMX在这里会根据你填的上升时间自动微调。所以同一个I2C波特率在不同主频下TIMINGR完全不一样这也是为什么从G4迁到C5不能直接复制过去的原因。时序相关还有一个重要概念叫时钟拉伸。低速从设备没准备好时会在SCL拉低期间继续把SCL拉低让主机等待。C5的I2C硬件支持这个机制但是如果从设备因为某种原因持续拉低SCL主机一直等可能出现超时。因此HAL库的所有I2C函数都有超时参数我统一设成100ms看起来对I2C这种近距离通信很宽裕实际处理总线异常时很有用。3. 核心代码实现IIC读取震动数据3.1 底层IIC读写函数封装先用HAL库实现最底层的I2C读写。ST的传感器驱动通常会回调platform_write和platform_read两个函数我们可以在工程里把它们实现成基于HAL_I2C的封装。#define IIS3DWB_I2C_ADDR 0x18U #define IIS3DWB_I2C_ADDR_HAL (IIS3DWB_I2C_ADDR 1) /* HAL需要8位地址 */ int32_t platform_write(void *handle, uint8_t reg, const uint8_t *bufp, uint16_t len) { HAL_StatusTypeDef status HAL_I2C_Mem_Write( (I2C_HandleTypeDef *)handle, IIS3DWB_I2C_ADDR_HAL, reg, I2C_MEMADD_SIZE_8BIT, (uint8_t *)bufp, len, 100); return (status HAL_OK) ? 0 : -1; } int32_t platform_read(void *handle, uint8_t reg, uint8_t *bufp, uint16_t len) { HAL_StatusTypeDef status HAL_I2C_Mem_Read( (I2C_HandleTypeDef *)handle, IIS3DWB_I2C_ADDR_HAL, reg, I2C_MEMADD_SIZE_8BIT, bufp, len, 100); return (status HAL_OK) ? 0 : -1; }注意I2C_MEMADD_SIZE_8BIT必须写清楚因为寄存器地址是8位。如果不指定默认按16位寄存器地址发送两个字节地址传感器会完全不响应。HAL库的HAL_I2C_Mem_Read会自己处理“先写寄存器地址、再发起始位、再读数据”的完整时序不需要手动调用Transmit和Receive组合。3.2 配置传感器和确认通信连接建立后第一步不是配置寄存器而是读WHO_AM_I确认I2C链路真的通。IIS3DWB的WHO_AM_I寄存器地址是0x0F每个ST传感器都有一个固定ID我这片样片读回来是0x44不同批次以数据手册为准。把它做成宏避免在初始化代码里写魔法数字。#define IIS3DWB_WHO_AM_I 0x0FU #define IIS3DWB_EXPECTED_ID 0x44U uint8_t id 0; platform_read(hi2c1, IIS3DWB_WHO_AM_I, id, 1); if (id ! IIS3DWB_EXPECTED_ID) { // 通信异常检查地址、上拉、供电 Error_Handler(); }WHO_AM_I正常之后做传感器复位再设置量程、输出数据速率和带宽。ST官方驱动包里已经把这些配置封装成函数可读性比自己填寄存器好很多。下面代码是我在C5工程里的实际调用驱动包名称是iis3dwb_reg.c函数参数以SDK头文件里的枚举为准。iis3dwb_reg_t dev {0}; dev.write_reg platform_write; dev.read_reg platform_read; dev.handle hi2c1; uint8_t rst 1; platform_write(hi2c1, 0x20, rst, 1); // CTRL1复位位实际以手册为准 // 配置量程和ODR iis3dwb_fullscale_set(dev, IIS3DWB_2g); iis3dwb_odr_set(dev, IIS3DWB_ODR_1kHz); iis3dwb_xl_bw_set(dev, IIS3DWB_BW_1kHz);这里我先把ODR配置成1kHz而不是传感器最高的26.7kHz原因是I2C在400kHz下面连续读三轴原始数据最多也就跑到几千Hz采样率把ODR拉太高没有意义。先用1kHz调通后面真正做振动分析再上SPI加FIFO。3.3 读取加速度数据并转换为物理量IIS3DWB的数据寄存器从0x28开始依次是X低字节、X高字节、Y低字节、Y高字节、Z低字节、Z高字节。读的时候利用寄存器地址自动递增一次连续读6个字节效率更高也避免多次发起I2C事务造成数据不同步。#define IIS3DWB_OUT_X_L 0x28U void read_accel_raw(int16_t *ax, int16_t *ay, int16_t *az) { uint8_t raw[6]; platform_read(hi2c1, IIS3DWB_OUT_X_L, raw, 6); *ax (int16_t)((uint16_t)raw[0] | ((uint16_t)raw[1] 8)); *ay (int16_t)((uint16_t)raw[2] | ((uint16_t)raw[3] 8)); *az (int16_t)((uint16_t)raw[4] | ((uint16_t)raw[5] 8)); }转换为重力加速度g时和量程对应。我用的是±2g16位输出参考灵敏度是0.061mg/LSB换算公式就是原始值乘以0.061再除以1000。下面代码放在主循环里每2ms读一次对应500Hz的轮询频率验证数据已经足够。#define SENSITIVITY_MG 0.061f float ax_g, ay_g, az_g; int16_t ax_raw, ay_raw, az_raw; read_accel_raw(ax_raw, ay_raw, az_raw); ax_g (float)ax_raw * SENSITIVITY_MG / 1000.0f; ay_g (float)ay_raw * SENSITIVITY_MG / 1000.0f; az_g (float)az_raw * SENSITIVITY_MG / 1000.0f;如果初始化里选的量程不是±2g灵敏度必须同步修改。比如±4g时灵敏度会变成0.122mg/LSB不换的话算出来的加速度会差一倍。这类细节最容易踩坑建议大家把量程和灵敏度定义成宏放在同一个头文件里避免修改时漏掉。4. 实测过程与常见问题排查4.1 总线卡死和时钟拉伸最常见的两个坑实际调试中我遇到最多的问题不是寄存器配置而是I2C总线卡死。现象是第一次通信正常第二次HAL_I2C_Mem_Read返回HAL_BUSY用示波器看SDA被拉低不放。这种问题多半是上电时序或者从设备复位造成的。传感器还在复位时主机就开始发数据从设备没来得及处理总线状态就拧巴了。最简单有效的恢复方法是把SCL引脚临时配置成普通开漏输出手动翻转9个时钟周期。9个时钟能把卡在中间状态的总线状态机复位掉。注意翻转时钟时SDA也要保持高电平避免产生意外的起始或停止条件。void recover_i2c_bus(void) { GPIO_InitTypeDef gpio {0}; gpio.Pin SCL_PIN; gpio.Mode GPIO_MODE_OUTPUT_OD; gpio.Pull GPIO_PULLUP; HAL_GPIO_Init(SCL_PORT, gpio); for (int i 0; i 9; i) { HAL_GPIO_WritePin(SCL_PORT, SCL_PIN, GPIO_PIN_RESET); HAL_Delay(1); HAL_GPIO_WritePin(SCL_PORT, SCL_PIN, GPIO_PIN_SET); HAL_Delay(1); } // 恢复成I2C复用功能 gpio.Mode GPIO_MODE_AF_OD; gpio.Pull GPIO_PULLUP; gpio.Alternate GPIO_AF4_I2C1; HAL_GPIO_Init(SCL_PORT, gpio); }时钟拉伸导致的超时更容易被忽略。C5的I2C硬件会实时监测SCL电平从设备拉低SCL时主设备内部的传输计数器暂停所以慢速从设备不会造成数据错乱只会让单次传输时间变长。如果你的代码在中断里做I2C读取建议不要用阻塞式HAL API改成中断模式否则一个慢从设备会卡住整个中断响应。还有一个经验是I2C地址错了并不一定表现为NACK也可能是读回来的数据全是0xFF或者0x00。因为错误地址可能恰好被总线上另一个设备响应。排查时先用最简单的方式扫描总线向0x01到0x7F轮流发WHO_AM_I读请求看哪个地址有ACK。4.2 用示波器看时序别猜直接看在I2C这种有明确电平状态的协议上示波器是最好的排查工具。我调试时至少会抓两组波形一组是上电后第一次通信的完整时序另一组是出问题时的波形。第一组波形要看的是START条件SCL在高电平时SDA从高跳低。然后是地址字节我的传感器地址0x18左移一位后变成0x30发送方向是写。第8个时钟后SDA在第9个时钟被从设备拉低这是ACK。如果看不到ACK说明地址、上拉或传感器供电有问题。读操作时主机发送完寄存器地址后会再产生一个重复起始条件然后主机把方向改成读忽略一次应答之后连续收6个数据字节最后一个字节主机回NACK并产生STOP。第二组波形一般是总线卡死时的。SDA一直为低SCL还能翻转这是典型的从设备应答后没释放SDA。此时手动翻转SCL时钟同时盯着示波器看到SDA在第几个时钟恢复高电平就能大概判断是哪个状态机卡住了。要注意示波器探头的等效电容。便宜的探头本身可能有10多pF电容并联到总线上会让上升沿变慢原本临界满足的400kHz时序可能就变成超限了。调试时可以临时把速率降到100kHz排除探头负载的影响。4.3 I2C读取的带宽局限FIFO和中断怎么配合这篇文章标题是IIC获取震动数据但我要泼一盆冷水。IIS3DWB虽然支持6kHz带宽但I2C在400kHz下连续读6字节单次事务至少需要大约150到200us换算下来最多也就5kSPS左右吃不满传感器的高带宽。做振动监测时采样率至少要达到信号最高频率的两倍以上如果目标是6kHz带宽I2C物理层就不够用。合理的做法是开启传感器内置FIFO。IIS3DWB可以把多组采样数据暂存在FIFO里等攒够一定数量MCU再通过I2C一次性突发读取。这样I2C只需要在FIFO快满的时候介入一次其余时间传感器自己按高ODR采集。配合中断引脚FIFO水位到达阈值时触发MCUMCU进入中断把整块FIFO搬走能大幅降低总线占用和MCU唤醒次数。使用FIFO时尤其要注意读出长度必须和FIFO水位匹配。如果读到一半传感器还在往FIFO里写新数据数据帧边界就容易错位。我建议配置成FIFO满或者接近满时停止写入先让MCU把数据稳定搬走再恢复采集。这样会丢一部分数据但对连续监测场景来说比错位数据更好处理。4.4 避坑清单不同HAL版本、不同驱动包坑的位置其实很固定我整理了一份自己反复对照的清单。现象可能原因解决办法读WHO_AM_I返回全0xFFSDA上拉丢失或电压不匹配检查上拉电阻VDD_IO和MCU电平是否一致读WHO_AM_I返回全0x00地址错误或传感器没供电检查SDO引脚扫描I2C总线第一个字节正常后面全部超时持续读多字节时地址自增没生效确认寄存器地址使用连续读不要按单字节地址递增400kHz下波形上升沿很平总线电容过大或上拉电阻偏大换成2.2k缩短杜邦线偶尔NACK上位机数据跳变连接接触不良或电源波动改用焊接线传感器电源加滤波电容数值波动离谱低频噪声大传感器供电纹波或周围强磁场干扰电源加磁珠PCB布局远离电机驱动线还有一个容易忽略的点传感器中断引脚输出开漏如果要接到MCU的EXTI输入外部必须加上拉。我最早一次接中断引脚时忘了这个细节中断状态一直不对查了半天才发现是引脚浮空。最后说一个实际操作的体会我踩得最多的坑不是寄存器配置而是I2C的物理层。第一次用杜邦线飞线调试总线电容太大400kHz死活不稳后来降到100kHz才通。等到换成短焊接线再切回400kHz整个链路就非常稳定了。如果你也打算用STM32C5来读IIS3DWB建议开局直接检查三样东西SDO地址、上拉电阻、传感器供电滤波。这三样没问题剩下的就是寄存器配置和调试工具问题。目前这套I2C代码在样机上已经连续跑了两个星期低速读取和FIFO读取都验证过了下一步我准备把SPI那套加上真正把高带宽吃满。
返回列表