拓冰建站拓冰建站
首页 / 资讯中心 / 正文

STM32C5 SPI读取IIS3DWB振动传感器完整实战

这阵子一直在折腾新到的STM32C5开发板正好手头又有一颗ST的IIS3DWB振动传感器就顺手把“用SPI方式读取震动数据”这条路完整跑了一遍。这个组合其实很有意思STM32C5是ST新一代Cortex-M33内核MCU主频能拉到250MHz拿来做信号采集绰绰有余而IIS3DWB是一颗少见的宽带宽加速度计频率响应能覆盖到10kHz左右特别适合电机、压缩机、机床这类旋转设备的振动在线监测。这篇文章我会从接口选型、CubeMX配置、寄存器读写、数据换算一路写到常见坑给同样想快速上手这套组合的朋友一份可以直接抄的作业。1. 项目整体思路与硬件准备1.1 这个项目到底要做什么先说破题。项目标题里有两个主角STM32C5和IIS3DWB。STM32C5作为主控负责发起SPI通信、配置传感器、读取加速度数据IIS3DWB作为从设备固定在待测物体上把物体在X/Y/Z三个轴向上的振动加速度转换成数字量通过SPI总线交回给MCU。最终我们要在串口调试助手里看到类似这样的输出X1204, Y-0032, Z2048 X1198, Y-0028, Z2051这种数据是什么概念如果传感器平放桌面上Z轴应该稳定在1g左右的重力加速度数值而X、Y轴接近0。当你在桌面上敲一下数值立刻剧烈跳动这就是振动信号。我做这个项目的时候目标很简单先把最基础的SPI读取链路打通不要FIFO、不要DMA、不要中断纯轮询方式跑通数据流。等基础功能没问题了再去考虑高频采样的存储和传输问题。这也是我第一次上手STM32C5比较谨慎的考虑毕竟新片子资料少一步一步来最稳。1.2 核心器件选型解析选择STM32C5不是偶然。我以前主要用STM32F103系列Cortex-M3内核主频72MHz单纯读个传感器绰绰有余。但这次选STM32C5有很实际的考虑Cortex-M33内核主频最高250MHz带TrustZone安全特性最关键的是它内置了更丰富的定时器和通信外设。做振动监测后续一定会上DMA和FIFO甚至做FFT频谱分析这些运算量在F103上会很吃力在C5上就轻松很多。再来看IIS3DWB。这颗传感器最打动我的是它的带宽。普通加速度计比如常见的MPU6050带宽也就1kHz上下测测人体姿态没问题但要捕捉齿轮箱的啮合频率、轴承故障特征频率就力不从心了。IIS3DWB把带宽直接推到10kHz支持±2g/±4g/±16g三档量程输出速率最高13.33kHz这就是为工业振动监测设计的。芯片支持SPI和I2C两种接口SPI模式速率可达10MHz。硬件清单不复杂我列一个表格器件型号/规格用途主控板STM32C5系列开发板主控跑SPI和串口调试传感器模块IIS3DWB子板/贴片核心板震动加速度采集USB转串口模块任意CH340/CP2102模块连接PC查看采样数据面包板杜邦线若干快速搭电路去耦电容100nF陶瓷电容传感器电源滤波非常重要1.3 通信接口选型为什么选SPI而不是I2C或模拟时序很多初学者会在这里纠结。IIS3DWB同时支持I2C和SPI为什么我坚持用SPI答案很简单速度和数据完整性。I2C的标准模式是100kHz快速模式400kHz即使上到1MHz的高速模式和SPI的10MHz相比也有数量级差距。IIS3DWB在最高输出速率13.33kHz下每帧要传6字节三轴数据再加上寄存器地址和通道切换开销I2C在余量上就很紧张了。而且I2C是开漏结构需要上拉电阻总线上挂的设备多了还会有地址冲突、电容负载变大的问题在工业振动监测这种对实时性要求高的场景里不算最优解。SPI的优势还不止速度。它是一主多从的点对点结构每个从设备独立CS片选不存在地址竞争数据线是推挽输出抗干扰能力更强硬件全双工可以一边发指令一边收数据通信效率高。至于“IO口模拟三线SPI”这类野路子偶尔应急调试可以真做项目别这么搞时序抖动和CPU占用会让人头大。既然STM32C5自带硬件SPI没有理由不用。2. 硬件接线与SPI通信基础2.1 管脚分配与接线表我这次使用的是开发板上引出的SPI1接口具体引脚分配如下信号STM32C5引脚对应SPI1功能接IIS3DWB引脚SCKPA5SPI1_SCKSPC/SCLMISOPA6SPI1_MISOSDO/SA0MOSIPA7SPI1_MOSISDI/SDACSPA4GPIO输出软件片选CSVDD3V3-VDDGNDGND-GND注意我把CS接到了普通的GPIO口PA4而不是SPI的硬件NSS脚。这一步是我刻意为之。硬件NSS虽然接线省事但不同型号的单片机在NSS管理方式上有差异用不好容易出问题。软件片选就是纯粹的“拉低选中、拉高释放”逻辑直接调试的时候还能用示波器单独看片选时序非常直观。接线有一条关键纪律杜邦线越短越好尤其SCK和MISO这两根。SPI在10MHz速率下信号的上升沿已经很陡峭长线会引入振铃和串扰导致读回来的数据偶发性错误。我实测用20cm的杜邦线连接SPI时钟降到5MHz才能稳定工作换成10cm以内的短线7.8MHz也能跑。所以如果你的传感器模块是焊盘式的建议直接飞线到开发板排针尽量缩短距离。2.2 SPI模式、极性和相位怎么选SPI通信有4种模式由CPOL时钟极性和CPHA时钟相位组合决定。IIS3DWB数据手册写明支持SPI Mode 0和Mode 3实际开发我推荐选Mode 0即CPOL0、CPHA0。用大白话解释Mode 0SPI空闲时SCK保持低电平在SCK的第一个上升沿采样数据在下降沿更新数据。这就是大多数传感器默认的“教科书式”时序。如果你在选择时不确定可以先从Mode 0试起读WHO_AM_I寄存器验证。如果读出来全是0xFF最大的怀疑对象就是极性和相位设置反了改成Mode 3再试一次。微软的脑筋转弯方式我看过一句口诀“零极性低电平开始零相位上升沿采样”。只要记住这个CubeMX配置时就不会翻车。另外要注意数据帧格式。STM32C5的SPI外设默认数据宽度8位MSB先行这个配置不需要改。IIS3DWB的寄存器地址和命令字都是8位读操作命令字的最高位置1写操作最高位置0后面7位是寄存器地址。比如读WHO_AM_I寄存器地址0x0F发送的命令字节就是0x8F。2.3 SPI速率到底能跑多快时钟链路计算很多朋友直接抄网上的代码SPI波特率预分频器填个4或者8从来不问实际速度是多少。这不行做嵌入式对时钟要有概念。我用的STM32C5开发板外接8MHz HSE晶振通过PLL倍频后系统主频SYSCLK跑到250MHz。AHB总线250MHzAPB1和APB2外设时钟在CubeMX里我配的是125MHz。SPI1挂在APB2上所以SPI外设的输入时钟是125MHz。SPI最终波特率由波特率预分频器决定SPI时钟 PCLK2 / Prescaler PCLK2 125MHz Prescaler 16 SPI时钟 125MHz / 16 7.8125MHz7.8125MHz没有超过IIS3DWB规定的10MHz上限留出了约20%的余量。为什么不直接压到10MHz因为实测中7.8MHz和10MHz在数据读取成功率上没有肉眼可见区别但7.8MHz对布线的容忍度更高在杜邦线连接这种“实验室恶劣环境”下更可靠。如果你想跑得更高比如用12.5MHz那就要仔细检查布线质量并且强烈建议在MISO线上串联33Ω电阻抑制振铃。3. STM32CubeMX配置实战3.1 时钟树配置从HSE到250MHz主频打开STM32CubeMX新建工程选择STM32C5系列的具体型号。在Pinout视图里先把RCC设为Crystal/Ceramic Resonator启用HSE然后在Clock Configuration页面里手动拖时钟树。我的配置路径是HSE 8MHz → PLLM分频到1MHz输入 → PLLN倍频到250MHz → SYSCLK选PLL输出 → AHB Prescaler设为1 → APB1和APB2设为2分频得到125MHz。这里有一个容易忽略的细节APB1和APB2的定时器时钟会自动倍频为250MHz但SPI外设的时钟就是125MHz不会自动翻倍所以后面计算SPI分频时别把定时器那套逻辑套进来。时钟树配置完CubeMX会自动检查合法性。如果页面上的数值变成红色说明PLL参数超范围了适当调整PLLM和PLLN即可。3.2 SPI1外设参数配置细则在Pinout视图中选择SPI1点进去设置参数项设置值说明ModeFull-Duplex Master全双工主机模式Hardware NSS SignalDisable用软件片选不用硬件NSSData Size8 Bits8位数据帧First BitMSB First高字节先发Prescaler16得到7.8125MHzCPOLLow空闲低电平CPHA1 Edge第一个上升沿采样CRCDisable本项目不需要CRC校验这里重点说为什么Hardware NSS Signal要选Disable。选上硬件NSS后单片机可能会自动控制NSS引脚在某些库版本里会出现NSS外部被拉低导致SPI状态机错乱的诡异问题。反正我们已经在GPIO上手动控制了CS引脚外设这边禁用NSS最干净。CubeMX会自动把PA5、PA6、PA7配置为复用功能引脚不需要手动改GPIO模式。3.3 软件片选GPIO与调试串口配置CS引脚PA4我设为GPIO_Output模式初始电平设为High。这个初始电平非常重要传感器在上电瞬间如果CS处于低电平可能被误选造成SPI总线上的状态混乱。上电默认高电平确保传感器先进入未选中状态代码中再按需拉低。调试串口我用了USART1配置为Asynchronous异步模式波特率1152008数据位1停止位无校验。串口在调试阶段的价值不可替代——每读一次传感器就打印一次三轴数据这是最直观的调试手段。CubeMX生成代码时建议把外设初始化文件按外设拆分就是勾选“Generate peripheral initialization as a pair of .c/.h files per peripheral”。这样SPI、USART、GPIO的初始化代码各归各的文件后续改代码不会在main.c里滚成一团。4. 代码实现从读寄存器到拿震动数据4.1 寄存器读写封装SPI读写的核心函数IIS3DWB的寄存器地址映射不算复杂。我用到的基础寄存器列出来寄存器地址功能WHO_AM_I0x0F芯片IDIIS3DWB应返回0x2CSTATUS_REG0x1E数据就绪标志CTRL10x20主配置ODR、量程、BDUCTRL20x21中断配置等OUT_X_L0x28X轴低字节OUT_X_H0x29X轴高字节OUT_Y_L0x2AY轴低字节OUT_Y_H0x2BY轴高字节OUT_Z_L0x2CZ轴低字节OUT_Z_H0x2DZ轴高字节在代码里我习惯用宏定义把这些地址写清楚而不是散落在业务逻辑里。接下来是SPI单字节读函数。IIS3DWB的SPI读时序是CS拉低后先发送一个读命令字节最高位为1再发送一个假字节产生时钟同时接收返回的寄存器值最后CS拉高。#define SPI_CS_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET) #define SPI_CS_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET) uint8_t IIS3DWB_ReadReg(uint8_t reg) { uint8_t cmd 0x80 | reg; // 最高位1表示读 uint8_t tx_dummy 0x00; uint8_t rx_data 0x00; SPI_CS_LOW(); HAL_SPI_TransmitReceive(hspi1, cmd, rx_data, 1, 10); HAL_SPI_TransmitReceive(hspi1, tx_dummy, rx_data, 1, 10); SPI_CS_HIGH(); return rx_data; }写寄存器函数类似区别是命令字最高位为0而且CS拉低后连续发送两个字节即可不需要额外产生时钟接收数据void IIS3DWB_WriteReg(uint8_t reg, uint8_t val) { uint8_t tx[2]; tx[0] 0x00 | reg; // 最高位0表示写 tx[1] val; SPI_CS_LOW(); HAL_SPI_Transmit(hspi1, tx, 2, 10); SPI_CS_HIGH(); }这里我踩过一个坑HAL_SPI_Transmit在函数内部可能会等待TXE标志但如果CS拉低之前传感器处于某种异常状态SPI从机的MISO线会干扰总线状态。解决方法是确保CS高电平未选中时才开始SPI外设操作所以代码里CS控制必须包住整个通信过程不能只在开头拉低、结束前忘记拉高。4.2 初始化时序Check WHO_AM_I与配置CTRL1系统上电后传感器需要一小段时间完成内部上电复位。我习惯在SPI初始化完成后延时50ms再读WHO_AM_I确认通信链路正常。uint8_t IIS3DWB_Init(void) { uint8_t id; HAL_Delay(50); id IIS3DWB_ReadReg(0x0F); if (id ! 0x2C) { return 1; // 通信异常 } // 配置CTRL113.33kHz输出速率±16g量程使能BDU // 寄存器值请对照具体批次的手册确认必要时按手册寄存器位重新计算 IIS3DWB_WriteReg(0x20, 0xB8); return 0; }WHO_AM_I读操作是整个项目第一个验收节点。能读到0x2C说明供电、接线、SPI模式全部正确读不到后面全部免谈。所以我在初始化函数里做了强校验宁可复位卡死也不带着错误配置继续跑。CTRL1寄存器我重点说一下BDU位。BDU是Block Data Update的缩写它的作用是保证你在读多字节数据时寄存器内容不会在读取中途被新数据更新掉。如果没有置位BDU读取X轴低字节的瞬间新数据到来高字节和低字节可能来自不同时刻的采样拼出来的数据就是错的。在振动监测这种高速连续采样场景这个位必须置1。4.3 连续采样X/Y/Z的完整代码示例初始化完成后主循环的任务就是等待数据就绪、读取三轴数据。IIS3DWB的STATUS_REG寄存器第3位ZYXDA是数据就绪标志当新数据可读时该位置1。如果只是读一次三轴数据可以用单寄存器方式连续读6次每次重新拉低CS、发送命令、接收数据。这种方法稳定可靠唯一缺点是每个寄存器都要重发地址时间开销大一点。我还写了一个更高效的多字节读取版本利用传感器SPI地址自动递增功能。具体做法就是发送首地址命令后继续发6个假字节依次接收X、Y、Z的低高字节typedef struct { int16_t x; int16_t y; int16_t z; } IIS3DWB_Axes; IIS3DWB_Axes IIS3DWB_ReadAxes(void) { uint8_t cmd 0x80 | 0x28; // 从OUT_X_L开始读地址自动递增 uint8_t rx_buf[6] {0}; uint8_t tx_dummy[6] {0}; IIS3DWB_Axes acc; SPI_CS_LOW(); HAL_SPI_TransmitReceive(hspi1, cmd, rx_buf, 1, 10); HAL_SPI_TransmitReceive(hspi1, tx_dummy, rx_buf, 6, 10); SPI_CS_HIGH(); acc.x (int16_t)((rx_buf[0] 8) | rx_buf[1]); acc.y (int16_t)((rx_buf[2] 8) | rx_buf[3]); acc.z (int16_t)((rx_buf[4] 8) | rx_buf[5]); return acc; }注意了一点如果自动递增没有生效你连续读到的一定是同一个寄存器的重复数据。遇到这种情况先确认当前芯片是否需要在CTRL2这类寄存器中显式使能地址自增。如果确实不支持回到单寄存器读法最保险。主循环最终长这样int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_SPI1_Init(); MX_USART1_UART_Init(); if (IIS3DWB_Init() ! 0) { printf(IIS3DWB init failed!\r\n); while (1); } IIS3DWB_Axes acc; uint8_t status; while (1) { status IIS3DWB_ReadReg(0x1E); if (status 0x08) { acc IIS3DWB_ReadAxes(); printf(X%d, Y%d, Z%d\r\n, acc.x, acc.y, acc.z); } } }printf重定向到串口这部分我在工程里实现了fputc函数指向USART1这样日志输出非常省事但要注意在嵌入式环境中printf的浮点格式化会占用较多资源调试还可以正式采集不要每帧都打印浮点数。5. 数据处理与实测验证5.1 原始值怎么换算成真实加速度我们读到的int16_t原始值并不是最终加速度值需要根据量程换算。IIS3DWB在±16g量程下满量程16g对应16位有符号数的32768换算公式就是加速度(g) 原始值 × 满量程 / 32768如果量程设置为±16g那么1g对应的原始值 32768 / 16 2048也就是说当某个轴读到的原始值在2048附近该轴加速度约为1g。我在实际调试中就是用这个特征判断代码是否正确传感器平放Z轴应该稳定在2048附近X、Y轴在0附近飘动。在代码里做换算时建议把系数定义为宏方便调整量程#define ACC_FULL_SCALE_G 16.0f #define ACC_RAW_TO_G(RAW) ((RAW) * ACC_FULL_SCALE_G / 32768.0f)这个换算精度已经足够绝大多数振动分析场景。如果你追求极致的精度还需要考虑零点偏移实际传感器在0g时会有一个很小的偏置可以在静止状态下采集几百个点求平均值来校准。5.2 静态验证重力分量校验静态验证是整个项目最简单也最有说服力的一步。传感器平放桌面上我会盯着串口输出观察几分钟正常情况下Z轴数值稳定在2048附近但会有±几到几十个字的抖动这是传感器本身的噪声不是故障。X、Y轴则在小范围波动一般不会超过±50。如果Z轴数值不在2048附近先别怀疑传感器坏了检查一下是不是传感器放歪了或者壳体有磁性吸附。比如我们把传感器竖起来放X轴就会变成竖直方向此时X轴读数变成2048Z轴读数接近0。通过变换传感器的姿态三个轴的重力分量都会相应变化验证逻辑非常直观。5.3 动态验证手敲桌面的震动波形静态验证过了只能说明通信链路正确、寄存器配置正确还不能说明动态响应正常。我最常用的动态测试方法是把传感器模块放在桌面上用手指轻轻敲击桌面。敲击瞬间串口打出来的三轴数值会出现明显的尖峰突变。比如静止时Z轴在2048附近敲一下可能瞬间跳到2600或-3200之类的值。如果数值变化幅度很明显、敲击方向对应轴的变化最大说明传感器动态响应没有问题。还有一个测试技巧用螺丝刀的金属柄轻轻敲击传感器模块本体会产生一个衰减振荡波形从串口数值上能看到多个周期起伏。如果你用虚拟示波器或者串口绘图软件比如匿名上位机、SerialPlot这类工具把三轴数据画成曲线能看到一个非常漂亮的冲击响应波形。这也是为后面做FFT频谱分析做铺垫毕竟振动监测的核心价值不在时域而在频域。6. 常见问题与排查技巧6.1 问题排查速查表我在调试过程中把遇到过的典型问题整理成了一张速查表按频率从高到低排列现象可能原因排查方法WHO_AM_I读不到返回0xFFSPI极性和相位不对试Mode 0和Mode 3切换WHO_AM_I读不到返回0x00CS引脚未正确拉低示波器查CS波形确认GPIO配置数据全部为0MOSI/MISO接反确认SDI接PA7、SDO接PA6数据偶发性跳变杜邦线过长或接触不良缩短飞线重新插拔读数不更新没有置位BDU读到撕裂数据CTRL1置位BDU串口无输出printf没有重定向实现fputc重定向到USART1上电后卡死在Init判断传感器电源纹波大加100nF去耦电容再复位还有一个非常隐蔽的问题有些人会用面包板供电传感器的VDD和主控的3V3来自不同电源轨。SPI电平不兼容会导致通信时好时坏看起来像软件问题实际上在电源。确保传感器和MCU的VDD都接在同一组3V3上。6.2 共享SPI总线的几个注意事项如果你和我一样计划后续在SPI总线上再加一颗Flash芯片比如W25Q64或者OLED屏这里有几个经验必须提前收藏。第一片选信号必须独立且默认拉高。任何一颗SPI从设备只要它的CS处于低电平它就会响应总线上的时钟信号。两个设备CS同时拉低就等于把两个从机的MISO输出并接在一起总线冲突数据全部报废。第二SCK、MOSI这类共享信号线在空闲时电平要一致。IIS3DWB用Mode 0空闲时SCK是低电平如果你买的屏幕支持Mode 0那就没问题。如果设备之间SPI模式不一样要么软件切换模式要么干脆分时挂载别硬凑。第三挂载设备太多时总线的电容负载会上升SPI速率跑不上去。实测经验挂2个设备时7.8MHz没问题挂到4个设备建议降到3.9MHz求个稳字。6.3 高频采样场景的几点开发建议如果你的目标是真正把IIS3DWB的13.33kHz采样率用起来纯轮询的方式肯定撑不住。MCU要在极短时间内完成SPI读取、数据处理、传输中间还有打印开销很容易丢数据。这里有几个建议上DMA是第一步。把SPI接收从CPU等待改成DMA搬运一个完整的六字节读取过程不占用CPU时间CPU可以去做其他事或者进入低功耗等待DMA完成中断。用FIFO缓存数据。IIS3DWB自带FIFO功能可以把多次采样的数据暂存在传感器内部主控一次性批量读走减少SPI通信次数非常适合高频采样。FIFO涉及水位线中断、批次读取逻辑比单次读取复杂但值得学。别在中断服务函数里做浮点换算和printf。中断里只负责把原始数据拷走换算和打印放主循环或者任务里。浮点运算在小芯片上很费时间printf更是重量级操作放在关键路径上会直接拉低采样率。我个人在实际操作中的体会是这类传感器项目的成败很大程度不取决于代码难度而取决于你对时序和寄存器的敬畏程度。能对照数据手册一条条核实就绝不靠猜能用一个示波器观测波形就不要只对着串口号脉。最后再分享一个小技巧每次改完配置先用WHO_AM_I做个自检再读状态寄存器确认数据更新最后才看波形数据。三步排查法能帮你快速定位九成以上的问题。这篇只完成了SPI读震动数据的基础部分下次我打算把DMAFIFO链路跑完整把高频连续采样的坑也一并翻出来到时候继续聊。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门