尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32C5与IIS3DWB的振动监测SPI采集实战:从选型到避坑

STM32C5与IIS3DWB的振动监测SPI采集实战:从选型到避坑 去年底接了一个旋转机械状态监测的项目要在现场搭一套能抓轴承故障特征频率的振动采集节点。选型时把几颗常用加速度计的参数翻了个遍最终落在了STM32C5加IIS3DWB这个组合上前者是ST新出的Cortex-M33平台后者是意法半导体那颗带宽能到6kHz的三轴数字加速度计两者通过SPI接口连接刚好能满足10kHz采样率下的振动原始数据采集需求。这篇文章就把整个开发过程里我觉得最有价值的东西整理出来从选型逻辑、SPI时序、CubeMX配置到最后的避坑经验给同样在做振动监测、故障诊断以及传感器采集的工程师一个可复现的参考。1. 先从选型说起这个组合为振动监测解决了什么问题1.1 IIS3DWB能满足哪些传统加速度计做不到的场景很多人选加速度计只看量程和灵敏度却忽略了带宽这个关键参数。常规的消费级加速度计比如ADXL345或者LIS3DH数字输出带宽往往只有几百赫兹做人体活动识别、倾角检测足够了但拿去采集电机轴承故障信号就会严重失真。轴承外圈故障的特征频率通常在几百赫兹到几千赫兹之间齿轮啮合频率更是能冲到几kHz传感器的幅频响应如果在这里已经开始衰减频谱分析的结果就没有意义了。IIS3DWB这颗器件很有意思它的-3dB带宽做到了DC到6kHz输出数据率最高10kHz量程固定在±16g。也就是说它能真实还原设备在6kHz以内的振动能量分布而不是给你一个被内部低通滤波磨平了棱角的信号。常规传感器在这个频段的响应早就滚降得一塌糊涂而IIS3DWB几乎平坦。做预测性维护的人拿到它基本等同于拿到了一个能直接怼到轴承座上的宽频振动传感器。1.2 STM32C5在其中的定位低成本Cortex-M33传感器选好了MCU也不能拖后腿。STM32C5是ST面向成本敏感型工业节点推出的Cortex-M33主控相比之前老一代Cortex-M0产品有明显算力提升能支撑振动数据的实时采集、简单的边缘侧FFT甚至频谱包络计算。它和STM32G4的外设IP有不少相似之处但产品定位更偏简化和低成本没有G4那么丰富的模拟外设资源。对于传感器采集节点来说其实不需要一堆运放和高级定时器C5这种“传感器接口齐全、主频够用、成本可控”的定位反而更舒服。实际对比下来我手上这块C5跑10kHz采样同时开SPI接收和DMA搬运CPU占用率并不高还能剩出大量算力做数据预处理。如果是老一代M0同样的事情做起来会紧绷很多。所以结论很明确IIS3DWB负责把宽频振动信号忠实采集上来STM32C5负责把这些数据按时读走并做初步处理二者在性能和成本上是匹配的。1.3 为什么坚持用SPI而不是I2CIIS3DWB其实同时支持I2C和SPI两种接口但我从一开始就没打算用I2C。算一笔账10kHz输出率下每个样本要读6个字节X/Y/Z各16位原始数据吞吐接近480kbps这已经超过了标准I2C 400kHz模式的理论上限更别提地址、寄存器等协议开销。I2C模式下总线会变成整个系统的瓶颈采样率稍微拉高一点就会丢帧。SPI这边完全不一样IIS3DWB支持的SPI时钟最高能到10MHz读6字节数据只需要几个微秒。同样一帧数据I2C可能要花0.3ms以上SPI在50us内就能搞定。对于采样周期只有100us10kHz的振动采集场景这个差距是决定性的。如果你的应用只需要低频振动监测I2C也不是不能用但要给内部总线和处理留足余量我个人的原则是带宽需求超过1kHz的传感器一律优先SPI。1.4 需要的硬件材料清单动手之前把东西备齐这套方案需要的东西并不复杂一块STM32C5系列最小系统板选择具体型号时注意留出至少一个空闲SPI外设和若干GPIOIIS3DWB传感器芯片或对应的评估板评估板的好处是已经把去耦电容和电平转换做好了杜邦线若干如果做原型验证完全够用如果要做正式样机建议直接画PCB逻辑分析仪或示波器调试SPI时序时几乎必备ST-Link下载器用于烧录和调试注意IIS3DWB是3.3V逻辑器件STM32C5的IO口也是3.3V两者电平可以直接对接不需要额外加电平转换。千万别在5V单片机平台上直接用会烧传感器。2. 硬件连接与SPI时序动手前先弄懂这三个细节2.1 引脚对照与CS控制方式的选择IIS3DWB的SPI接口是标准的四线制含SCLK、SDI、SDO和CS。接线其实很简单下面这张表以SPI1为例实际引脚可以按你的板子走线灵活调整CubeMX里分配了哪组引脚就用哪组IIS3DWB引脚STM32C5引脚说明VDD3.3V电源GNDGND共地SCLKPA5 (SPI1_SCK)SPI时钟SDIPA7 (SPI1_MOSI)主机发送到传感器的数据线SDOPA6 (SPI1_MISO)传感器返回给主机的数据线CSPA4普通GPIO片选信号低电平有效CS这块值得多说一句。STM32的SPI支持硬件NSS但我在这个项目里用的是普通GPIO做软件片选。原因很简单IIS3DWB的SPI事务要求CS在整个地址数据帧期间保持低电平帧结束后拉高。用硬件NSS时主模式下的NSS输出时序和字节边界配合起来需要仔细调稍不注意就会出现多拉半拍、少拉半拍的奇奇怪怪的时序问题排查起来很痛苦。软件片选无非就是GPIO置低、发数据、GPIO置高三步主动权完全在自己手里出了任何问题也容易定位。2.2 SPI读写地址编码寄存器地址为什么要左移一位这块是很多新手第一次卡住的地方。IIS3DWB的SPI协议和普通SPI Flash有点不一样它的第一个字节并不是简单的寄存器地址而是“7位寄存器地址1位读写标志”读写标志位放在最低位。读操作时最低位为1写操作时最低位为0。举个例子读WHO_AM_I寄存器它的地址是0x0F那么SPI发送的第一个字节应该是 (0x0F 1) | 0x01 0x1F。写CTRL1寄存器地址是0x20发送的第一个字节就是0x40。这个左移一位的操作如果忘了你会发现自己读到的数据永远是错的但逻辑分析仪上看起来又一切正常特别迷惑人。还有一个重要细节IIS3DWB允许通过CTRL3寄存器的IF_ADD_INC位开启多字节读模式。开启后连续读数据时地址会自动递增你只需要在第一个字节里给出起始地址后面的字节会依序从0x28、0x29、0x2A……往下走。读三轴加速度时从OUT_X_L开始连续读6个字节正好把X、Y、Z的低位和高位一次性拿完效率远高于单字节读。2.3 寄存器地图和软件复位上电后先做这几步我把这个项目里用到的关键寄存器整理成了一张表方便随时查阅寄存器名地址关键作用WHO_AM_I0x0F设备IDIIS3DWB固定返回0x72CTRL10x20输出数据率ODR配置默认上电是Power-DownCTRL20x21自检和中断配置本项目暂不使用CTRL30x22软件复位SW_RESET、BDU、IF_ADD_INCSTATUS_REG0x1Ebit0为加速度数据就绪标志OUT_X_L~OUT_Z_H0x28~0x2D三轴加速度输出每个轴16位低字节在前上电后我强烈建议先做一次软件复位再开始配置。原因是传感器的寄存器在刚上电时可能处于不确定状态尤其是如果你之前有过异常掉电寄存器内容未必是默认值。把CTRL3的SW_RESET位置1等一段时间再操作后面读到的寄存器状态会稳定很多。复位之后别忘了验证设备ID读WHO_AM_I确认返回0x72后再进行后续配置。这第一步能过滤掉一大半的接线和硬件问题。我调试时见过不少“数据读出来全是0”的情况最后发现是CS没接对导致传感器根本没进入SPI模式这时候设备ID会告诉你一切。3. CubeMX下SPI外设配置这些参数决定数据能不能读对3.1 时钟与分频如何配合10kHz采样需求CubeMX建工程时第一步是选好芯片型号然后进入System Core的RCC配置。STM32C5的时钟树和多数STM32类似PLL把一个较低频率的HSE或HSI倍频到系统主频再由AHB预分频器和APB预分频器分发到各外设总线。SPI的时钟源挂在哪条APB总线上决定了你能拿到的SCK上限。举例来说如果SPI1挂在APB2上APB2时钟是100MHz那么SCK频就是100MHz除以预分频系数。SPI预分频可以选2、4、8、16等对应SCK为50MHz、25MHz、12.5MHz、6.25MHz。这里要做一个取舍IIS3DWB最高支持10MHz的SPI时钟理论上分频到5MHz或6.25MHz都有余量但实际布线如果有杜邦线或者PCB走线较长我会保守一点选10MHz甚至5MHz。10kHz采样率下每100us才需要读一次数据SPI时钟哪怕降到2MHz读6字节也只需要约24us完全来得及。与其盲目追求快时钟引入信号完整性问题不如用稳定的中速时钟。3.2 模式、极性、相位不同CPOL/CPHA的组合SPI配置里最容易被忽略的是Clock Polarity和Clock Phase。IIS3DWB数据手册里明确支持SPI Mode 0CPOL0CPHA0和Mode 3CPOL1CPHA1两种模式但很多人在CubeMX里默认选了最后一个选项结果传感器完全不理你。我实测稳定的配置是Master、8位数据帧、MSB First、Clock PolarityLow、Clock Phase1Edge也就是SPI Mode 0。如果你用了Mode 3理论上也能工作但我个人建议固定用Mode 0和ST传感器的参考驱动保持一致减少不必要的变量。帧格式和传输方向也需要注意。SPI全双工时主机发送地址字节的同时会从MISO上收到一个无效数据字节然后主机继续发送dummy数据通常是0x00或0xFF传感器才会把寄存器内容移位输出。很多刚接触传感器SPI的工程师以为发一个字节就能收到数据实际上必须发满两个字节。这个“先补一个时钟周期”的细节我们在代码封装时会遇到。3.3 轮询、中断还是DMA三种读取方式怎么选读取策略直接关系到系统整体的实时性。最简单的是轮询每次需要读数据时调用阻塞式SPI收发函数等它传完再返回。这个方式代码最简单、调试最直接但缺点是CPU在等待SPI期间被占住。10kHz采样率下每100us就要读一次虽然SPI传输本身只有几十微秒但配合数据解析、滤波、存储等操作CPU会变得很满。中断方式比轮询好一些每个SPI字节完成都会触发中断CPU不需要死等。但对这个项目来说每帧6个字节每100us就有一轮中断中断频率相当高反而可能挤占主循环时间。我最推荐的是DMA方式。把SPI接收挂到DMA上CPU只需要初始化时配置一次DMA描述符每次读取时启动传输在DMA完成回调里处理数据CPU只在最后整理数据那一刻介入。STM32C5的DMA带宽完全够用10kHz采样率下SPI的DMA负载很轻。跑起来之后主循环可以腾出时间做振动特征值计算、通信上报这些事。新手可以先把轮询跑通确认传感器和时序都没问题后再平滑迁移到DMA方案。我自己的开发顺序就是这样一次只引入一个变量出了问题也容易判断是代码问题还是硬件问题。4. 从寄存器到物理量完整代码实现与解算4.1 底层的SPI读写封装函数先封装最底层的CS控制和单字节读写。写这个封装时我特意把“寄存器地址左移一位再拼接读写标志位”的逻辑写进了函数内部调用者不需要操心协议细节直接传寄存器地址就行。static void cs_select(void) { HAL_GPIO_WritePin(IIS3DWB_CS_GPIO_Port, IIS3DWB_CS_Pin, GPIO_PIN_RESET); } static void cs_release(void) { HAL_GPIO_WritePin(IIS3DWB_CS_GPIO_Port, IIS3DWB_CS_Pin, GPIO_PIN_SET); } uint8_t iis3dwb_read_reg(uint8_t reg) { uint8_t tx[2] {0}; uint8_t rx[2] {0}; tx[0] (reg 1) | 0x01; /* 读标志位为1 */ cs_select(); HAL_SPI_TransmitReceive(hspi1, tx, rx, 2, HAL_MAX_DELAY); cs_release(); return rx[1]; } void iis3dwb_write_reg(uint8_t reg, uint8_t value) { uint8_t tx[2] {0}; tx[0] reg 1; /* 写标志位为0 */ tx[1] value; cs_select(); HAL_SPI_Transmit(hspi1, tx, 2, HAL_MAX_DELAY); cs_release(); }这里有几个细节。第一读操作必须使用TransmitReceive而不是Transmit否则MISO上的数据不会进到rx缓冲区里。第二HAL_SPI_TransmitReceive的参数长度是2即地址字节加数据字节CS必须在这两个字节传输期间始终保持低电平。第三函数返回的是rx[1]因为rx[0]是第一个字节期间收到的无效数据真正有效的数据在第二个字节里。多字节读的封装思路一样只是把传输长度从2变成7第一个字节发起始地址读标志后面6个字节发0x00作为dummy时钟void iis3dwb_read_xyz(int16_t *x, int16_t *y, int16_t *z) { uint8_t tx[7] {0}; uint8_t rx[7] {0}; tx[0] (0x28 1) | 0x01; /* 从OUT_X_L开始读 */ cs_select(); HAL_SPI_TransmitReceive(hspi1, tx, rx, 7, HAL_MAX_DELAY); cs_release(); *x (int16_t)(rx[1] | (rx[2] 8)); *y (int16_t)(rx[3] | (rx[4] 8)); *z (int16_t)(rx[5] | (rx[6] 8)); }用这个函数的前提是CTRL3的IF_ADD_INC位已经置1。如果没有置1就只能每个寄存器单独发一个读事务效率会低很多代码也啰嗦。4.2 初始化序列与设备ID校验初始化函数是整个项目的第一关我把它分成了三步校验设备ID、软件复位、配置CTRL1和CTRL3。uint8_t iis3dwb_init(void) { uint8_t id iis3dwb_read_reg(0x0F); if (id ! 0x72) { return 0; /* 设备ID异常 */ } /* SW_RESET置1同时开启BDU和IF_ADD_INC */ iis3dwb_write_reg(0x22, 0x8C); HAL_Delay(50); /* 配置CTRL1高位是ODR这里以最高档为例具体按手册掩码 */ iis3dwb_write_reg(0x20, 0x80); return 1; }CTRL3写0x8C意味着三件事一起做bit7置1让传感器软复位bit3置1开启BDU块数据更新bit2置1开启寄存器地址自动递增。BDU这个位值得单独强调它的作用是保证在主机读取X轴高字节和低字节期间寄存器内容不会因为新数据到来而改变。如果不开启高速采样时可能出现高字节来自第N组数据、低字节来自第N1组数据的错位情况。CTRL1里的ODR位段在不同ST传感器上布局接近一般在高四位。我示例代码里写的0x80是配到最高输出率档位但具体数值请务必对照你手头最新数据手册的CTRL1定义页来填。不同版本的芯片手册ODR位段的映射可能有差异。4.3 三轴数据读取与加速度换算初始化完成后读取一行数据只需要两步等待数据就绪然后调用多字节读函数。最简单的方式是轮询STATUS_REG的bit0int16_t x, y, z; while ((iis3dwb_read_reg(0x1E) 0x01) 0) { /* 等待DRDY置位 */ } iis3dwb_read_xyz(x, y, z);这个循环在这个项目里可以用但我得提醒一句在10kHz采样率下做非常高频的轮询等STATUS会占用不少CPU时间。进阶做法是把传感器的DRDY引脚接到STM32的EXTI中断上每来一个数据就中断一次在中断服务函数里触发DMA读取。这样CPU只在一个数据周期内工作一小段时间其余时间全释放给业务逻辑。拿到x、y、z三个原始值后换算物理量这一步很多人会算错。IIS3DWB的量程固定为±16g按数据手册标称灵敏度1.076 mg/LSB来计算加速度g值等于原始值乘以灵敏度再除以1000。float ax_g (float)x * 1.076f / 1000.0f; float ay_g (float)y * 1.076f / 1000.0f; float az_g (float)z * 1.076f / 1000.0f;注意这里的符号扩展问题。rx[1]是低字节rx[2]是高字节拼在一起后必须转成int16_t让编译器按补码规则处理负数。如果直接拿uint8_t左移相加负加速度会变成一个很大的正数后面所有分析全废。5. 实测调试中最常见的几个坑5.1 WHO_AM_I读取异常从电气和时序两方面排查这个项目里我第一个遇到的就是WHO_AM_I不对。板子焊好代码写完一读0x0F寄存器返回的是0xFF。当时第一反应是传感器没上电但量了VDD明明有3.3V。后来用逻辑分析仪挂在MISO线上才发现CS信号根本没把传感器选通因为我在CubeMX里配置的SPI引脚和实际接线不一致PA4被配置成了别的复用功能。所以排查顺序很重要。第一步用万用表确认传感器VDD和GND正常第二步确认CS引脚在CubeMX里被正确设成了GPIO输出并接对了传感器第三步用逻辑分析仪抓SPI波形看片选是否有拉低动作、SCK上是否有时钟、MOSI上发的第一个字节是不是预期值。如果第一步和第二步都没问题第三步基本能直接定位问题出在时钟配置还是数据线上。读回0x00的情况也遇到过那次是真的把SDI和SDO接反了传感器根本没收到地址自然也不会往MISO上送数据。四线制SPI不像I2C那样有唯一地址接反了从逻辑上很难发现只能靠波形排查。5.2 数据恒为0或满量程先看CTRL1是否配了ODR有相当一部分人会把初始化里的CTRL1配置漏掉因为默认上电状态下传感器处于Power-Down模式所有输出寄存器保持初始值不变。这时候读出来的数据看起来就是三个0或者某些位因为内部上拉下拉变成固定值。这个坑太典型了。我建议初始化函数里设备ID校验通过后第一步就写CTRL1把ODR从Power-Down切出来然后再去配置其他寄存器。顺序反了的话可能后面所有写寄存器操作都成功了但传感器就是不出数。如果CTRL1已经配了ODR但数据仍然恒定那就要检查传感器是否一直处于复位状态。有一个容易忽略的操作写完SW_RESET后必须留足够时间等复位完成再接着写CTRL1。我一般至少延时50ms别指望上电后马上就能正常操作。5.3 高速连续读时DMA和CS的配合问题从轮询切到DMA后我遇到过一个非常隐蔽的问题DMA传输完成中断里拉高CS但时间点不对。HAL_SPI_TransmitReceive_DMA的完成回调是在最后一个字节接收完成后触发但此时SCK上可能还有半个周期的尾巴没有完全结束。如果在这个回调里立刻拉高CS就相当于把最后一帧的最后一个比特切断了有时候能读对有时候最后一个字节的高位是错的完全随缘。解决办法有两种。一种是在回调里加一个极短的延时等SPI彻底空闲后再拉高CS。另一种是干脆在定时器中断里触发DMA下一轮DMA启动前再处理CS给硬件留足结束时间。用HAL库还有一个细节DMA方式下不能每次都用TransmitReceive因为地址字节和dummy字节是连续发出来的DMA描述符里要配置好发送缓冲区和接收缓冲区的长度一致。如果发送缓冲区和接收缓冲区长度不一致HAL库会直接报错或者行为异常。5.4 拿到振动数据之后的处理建议SPI这边稳定后大部分人拿到原始加速度波形就开始做FFT但直接对原始数据做频谱分析往往得不到干净的频谱图。原因很简单传感器输出里叠加了重力分量、温度漂移导致的零点偏移还有低频趋势项。如果不过滤直接FFT低频大能量会把轴承故障特征频率这些小峰值压得几乎看不见。我的建议是先把直流和趋势项去掉。最简单的方法是先算一组数据的平均值每个点减去这个平均值后再做FFT或者用一个高速滤波器去掉1Hz以下的成分。对于10kHz采样的振动信号后续再考虑加窗函数、重叠平均、特征频率提取等处理。另外如果要做多通道同步采集比如同时采振动和转速信号建议把DRDY引脚作为硬件同步触发信号而不是靠软件对齐时间戳。软件对齐在采样率低的时候没问题到了10kHz就容易出现一两帧的偏差做阶次分析时这个偏差会直接影响频率对应关系。说到底IIS3DWB的SPI读取本身并不复杂难点在于把时序细节、寄存器配置和数据链路都理顺。这套STM32C5加IIS3DWB的方案我跑了一段时间整体表现很稳定10kHz采样率下的SPI链路没有出现丢帧DMA方案下CPU余量也很充足。按照本文的顺序先确认设备ID再配好CTRL3的BDU和地址递增然后设置ODR最后用多字节读取拉数据基本一次就能打通。如果你在做类似的振动采集项目希望这几轮的踩坑经验能帮你少走点弯路。
返回列表