STM32C5驱动IIS3DWB:SPI接口振动数据采集实战
2026/9/8 11:37:11 网站建设 项目流程

搞工业状态监测、做旋转机械振动分析的朋友,应该都绕不开对振动数据的高频率采集。最近我在STM32C5系列上做一版低功耗振动监测节点,传感器选了ST的IIS3DWB,一颗三轴数字振动计,最大输出带宽能干到6kHz,配合SPI接口正好适合这种需要连续吞吐数据的场景。这篇把IIS3DWB通过SPI挂到STM32C5、把振动数据完整读出来的过程整理一下,给正要调这颗传感器或者准备选型的人做个参考。

篇幅不会太短,因为光是SPI时序、寄存器细节、片选策略和数据对齐这几个点,就够新手栽几个跟头。我会把每一步为什么这么做的理由讲清楚,毕竟光抄代码不解决后面调试时的“玄学问题”。

1. 振动计选型与接口评估

1.1 IIS3DWB到底强在哪

IIS3DWB不是普通的加速度计。日常用的加速度计,比如给姿态检测、跌倒检测用的传感器,带宽一般就几百Hz到1kHz出头,做做交互和姿态还行,但拿去测轴承故障、齿轮啮合频率、切削颤振这类机械设备上的振动特征,带宽不够就会把高频分量直接滤掉,故障特征根本看不见。

IIS3DWB的定位就是“工业振动监测专用”,三轴输出,16位分辨率,量程支持±2g到±16g,带宽最高到6kHz。这个6kHz意味着它配得上输出数据速率最高约26.7kHz的采样能力,拿来做轴承外圈、内圈故障的特征频率捕捉,完全撑得起来。噪声密度大概在75μg/√Hz级别,这种底噪水平在现场环境下也不会被淹没。功耗方面,正常工作模式下电流很低,适合做电池供电的无线监测节点。

项目里我选它还有一个原因,就是它内部集成了FIFO和一些中断功能。在STM32C5这种主控上只想做数据采集的话,FIFO能帮我们缓冲数据,不用MCU时时盯着。后面我会讲怎么用SPI把数据导出来。

1.2 为什么这里用SPI而不用I2C

ST这颗传感器I2C和SPI都支持,但具体项目里基本没得选,直接上SPI。原因很好理解,拿两者对比一下就清楚了:

特性I2CSPI
信号线数量2根(SCL、SDA)4根(SCK、MOSI、MISO、CS)
工作模式半双工全双工
典型速率最高400kHz(标准快模式)最高10MHz(IIS3DWB规格)
从机选择靠地址寻址,多设备靠地址区分靠CS片选,独立控制
时序复杂度有起始/停止/应答位,较繁琐简单,时钟边沿采样
数据吞吐每字节都要ACK,开销大连续读写效率高

振动数据一帧是三轴×2字节=6字节,如果ODR开到20kHz以上,每秒钟的数据量就是120KB以上。I2C那400kHz的速率在这种吞吐面前非常吃紧,加上应答位和地址帧的开销,实际有效数据率还要打折扣。SPI这边10MHz全双工,一个字节8个时钟周期下来,跑满也不是难事,而且CS拉低之后可以连续读寄存器,效率非常高。

另外从代码实现角度看,SPI的驱动逻辑比I2C简单得多。I2C要考虑总线忙、NACK、重复起始条件这些状态,SPI基本就是“拉CS、发地址、收数据、拉CS”,出问题排查起来也直观。这也是我在这个项目里坚持用SPI的原因之一。

2. STM32C5端SPI工程搭建

2.1 CubeMX配置和时钟设置

STM32C5系列的主频比老一代Cortex-M0+内核的芯片高不少,SPI外设的性能也跟着上来了。用CubeMX建工程时,SPI1或SPI2都可以选,优先选和传感器布局顺路的引脚,减少走线绕弯带来的信号质量问题。

我这边SPI配置如下:

  • Master Full-Duplex模式,8位数据宽度,MSB First
  • CPOL=Low、CPHA=1Edge,也就是SPI Mode 0
  • 预分频根据你的时钟树调,保证SCK不超过10MHz
  • 禁止CRC校验,NSS设成Software模式

关于SPI模式这里多说一句。IIS3DWB的数据手册里写的是支持SPI Mode 0和Mode 3,也就是说CPOL和CPHA的组合可以有两种选择。实际程序里我用Mode 0就完全正常,如果你在别的代码里看到Mode 3也不要惊讶,芯片都兼容。关键是时钟极性不要配反,否则读出来的数据很可能出现位错位或者全是0xFF的现象。

CubeMX里把引脚分配确认好,SCK、MOSI、MISO三个引脚的模式都会自动配置成SPI功能。CS片选引脚这里先不急着配成硬件NSS,我后面详细说为什么。

配完直接生成工程。STM32C5的HAL库API和老的STM32系列差异不大,之前写过STM32G4或者F4的SPI驱动,上手很顺。

2.2 片选到底用硬件NSS还是软件GPIO

SPI片选这个问题,平时看着不起眼,真到调试的时候就容易卡住。很多初学朋友习惯性用CubeMX默认的硬件NSS引脚,觉得省事,结果数据总是偶发错乱,半天定位不到原因是片选时序不对。

我的习惯是:除非从机要求快速连续切换CS,否则都用普通GPIO软件控制片选。

原因有几个:

  1. 硬件NSS在某些MCU上需要配合“片选极性”和“输出模式”去设置,一旦忘记使能NSS输出,片选信号就一直是无效电平,从机不响应。
  2. 硬件NSS经常和字长、帧格式绑定在一起,如果SPI外设配置成“硬片选自动拉低”,遇到数据FIFO残留或者超时,CS电平容易卡在中间状态。
  3. 软件GPIO控制CS,代码逻辑一目了然:拉低表示开始通信,拉高表示结束。多从机场景下还能随意切换,不受外设本身片选引脚的约束。

写成代码就是最原始的两行:

#define IIS3DWB_CS_LOW() HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET) #define IIS3DWB_CS_HIGH() HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET)

有的朋友会有顾虑,觉得软件片选加上GPIO翻转操作,会占用CPU时间,影响SPI速率。这个担心对低速从机完全没必要。一次GPIO翻转也就几个时钟周期,和SPI传输一个字节的时间相比可以忽略。真要到DMA流水线传输那种场景,再用硬件NSS也不迟,但读个传感器寄存器,软件片选完全够用。

3. 驱动代码实现细节

3.1 SPI读写函数怎么写才稳

IIS3DWB的SPI通信遵循一个固定套路:先把CS拉低,发送一个地址字节,紧接着发送或接收数据字节,最后把CS拉高。地址字节的最高位是读写标志位,读操作为1,写操作为0。

这颗传感器支持连续地址读,也就是你在数据寄存器起始地址发出读命令后,可以连续读多个字节,内部地址自动递增。这个特性对读6字节的三轴加速度数据特别有用,一次通信就能把6个字节全部拉回来。

先贴核心的字节读写函数:

uint8_t IIS3DWB_ReadReg(uint8_t reg) { uint8_t txBuf[2] = { reg | 0x80, 0x00 }; uint8_t rxBuf[2] = { 0 }; IIS3DWB_CS_LOW(); HAL_SPI_TransmitReceive(&hspi1, txBuf, rxBuf, 2, 100); IIS3DWB_CS_HIGH(); return rxBuf[1]; } void IIS3DWB_WriteReg(uint8_t reg, uint8_t value) { uint8_t txBuf[2] = { reg & 0x7F, value }; IIS3DWB_CS_LOW(); HAL_SPI_Transmit(&hspi1, txBuf, 2, 100); IIS3DWB_CS_HIGH(); }

读寄存器这里用HAL_SPI_TransmitReceive同时完成发送地址和接收数据,比先Transmit再Receive更稳,因为CS拉低到通信结束之间,发送接收是连续完成的,不会出现中间断开的情况。返回rxBuf[1]是因为第一个字节是地址阶段收到的无效字节,第二个字节才是指定寄存器的数据。

读多字节数据同理:

void IIS3DWB_ReadMultiReg(uint8_t reg, uint8_t *pData, uint16_t len) { uint8_t txBuf[1] = { reg | 0x80 }; uint8_t dummyTx[32] = { 0 }; uint8_t rxBuf[32] = { 0 }; IIS3DWB_CS_LOW(); HAL_SPI_Transmit(&hspi1, txBuf, 1, 100); HAL_SPI_TransmitReceive(&hspi1, dummyTx, rxBuf, len, 100); IIS3DWB_CS_HIGH(); memcpy(pData, rxBuf, len); }

注意这里的len最好限制在32字节以内,避免堆栈数组开太大。对纯寄存器读取来说32足够用了。

这里有个细节很多人容易忽视:HAL_SPI_TransmitReceive的第三个参数是接收缓冲区,但发送缓冲区和接收缓冲区长度必须一致,且都指向有效内存。如果只发长度1、收长度2,很多HAL版本的驱动会报参数错误或者直接卡死。所以统一用长度相同的数组来操作。

3.2 传感器初始化与ID确认

刚上电的传感器是不能直接读数据的,需要先确认通信链路是否建立成功。最好的方式就是读WHO_AM_I寄存器,地址0x0F,IIS3DWB的默认值是0x4B。

uint8_t id = IIS3DWB_ReadReg(0x0F); if (id != 0x4B) { // 通信异常,检查SPI配置、接线、供电 }

这一步写在初始化函数的开头,一旦读到0x4B,说明SPI时序、引脚配置、供电都没问题,后面的寄存器操作才值得继续。如果ID都读不对,先不要往下调,回头查硬件。

传感器内部有软件复位,初始化的时候可以先复位一下,让寄存器回到默认状态。然后配置CTRL1寄存器,设置输出数据速率和量程。IIS3DWB的CTRL1寄存器里面包含ODR和FS位段,具体位定义不同档位,写的时候最好参照数据手册寄存器表来配,因为这里的值直接决定你采到的振动数据频率范围。

我的初始化流程大致这样:

  1. 读WHO_AM_I确认ID
  2. 软复位,等待复位完成
  3. 配置CTRL1:选择量程和ODR
  4. 配置CTRL3:开启块数据更新(BDU),确保高字节低字节成对更新
  5. 检查STATUS寄存器,等待数据就绪

BDU这个功能强烈建议打开。不开启的话,如果你读的高低字节刚好跨越了一次数据更新,就会发生“高低字节拼接错位”,数据会出现比较大的跳变。这种问题不仔细看还以为是传感器坏了,实际上是读出时序不对。

3.3 三轴数据读取与换算

三个轴的加速度数据存储在从0x28地址开始的连续6个字节里,顺序是X_L、X_H、Y_L、Y_H、Z_L、Z_H。读取方法和上面的多字节读一样,把6个字节一次性拿回来:

typedef struct { int16_t x; int16_t y; int16_t z; } AxisData_t; AxisData_t IIS3DWB_ReadAccel(void) { uint8_t raw[6] = { 0 }; AxisData_t data = { 0 }; IIS3DWB_ReadMultiReg(0x28, raw, 6); data.x = (int16_t)((raw[1] << 8) | raw[0]); data.y = (int16_t)((raw[3] << 8) | raw[2]); data.z = (int16_t)((raw[5] << 8) | raw[4]); return data; }

拿到int16_t原始值是第一步,要换算成物理单位还需要结合量程。以±16g量程为例,16位ADC的输出范围是-32768到+32767,对应-16g到+16g,所以灵敏度是2048 LSB/g。加速度 = 原始值 / 2048,单位是g。

如果你要把数据送去上位机做FFT频谱分析,建议直接把原始int16_t值打包上传,把量程信息也一起发过去,物理换算放到上位机做。这样既省MCU算力,也保留了完整的动态范围,后续调整量程不用改固件里的换算逻辑。

4. 采样率、带宽与数据链路优化

4.1 输出数据速率和量程怎么配

IIS3DWB的输出数据速率最高能到约26.7kHz,对应6kHz的可用信号带宽。采样率和信号带宽的关系就是奈奎斯特定理,信号频率不能超过采样率的一半,否则会产生混叠。

实际配置的时候要反过来想:先确定你的目标振动频率能到多少,再决定ODR开多大。比如普通电机轴承故障特征频率通常在几百Hz到3kHz之间,那ODR开到10kHz以上就比较充裕;如果是齿轮箱里面的高速轴啮合频率,可能需要把ODR拉到最高档,才能把高频边带看清楚。

量程的选择同样要看应用场景。新能源汽车电驱动、高速主轴这类振动量级小的设备,±2g灵敏度更高,小信号看得更清楚;破碎机、大型风机这类冲击大、振动量级大的设备,不开大量程很容易削顶,±16g更稳妥。

如果现场工况信号不确定,建议先用±16g大量程摸底,看波形有没有明显削顶。波形幅度普遍不到量程的一半,再考虑降量程提高分辨率。

ODR和量程都在CTRL1里面配置,代码本身不复杂,难点在于理解这个配置和你的机械对象是否匹配。

4.2 轮询、中断和DMA怎么取舍

读传感器数据有三种方式:简单轮询、中断触发、DMA传输。

初期验证直接轮询就行,读STATUS寄存器判断有没有新数据,有了就去读6字节。这一套逻辑代码量最小,跑通链路最重要。

真正做高ODR连续采集的时候,轮询会占掉不少CPU时间,而且主循环里其它任务一拖,就可能丢数据。这时候建议用数据就绪中断,把传感器的INT1引脚接到STM32C5的外部中断输入,在中断回调里置一个标志位,主循环看到标志位就去读数据。这种方法不丢数据,CPU也能腾出来干别的。

再往上走就是DMA。SPI + DMA可以在不占用CPU的情况下把数据从传感器搬到内存,适合要把采集数据存到SD卡或者通过无线模块发送的场景。不过DMA的启动和停止逻辑比中断复杂,尤其要处理好SPI总线的忙状态和FIFO残留问题,建议前面两种方式都验证稳定后再上DMA,否则出了问题很难判断是哪一层引起的。

我个人的建议是:功能验证用轮询,连续采集用中断,批量存储用DMA。这个顺序能帮你把问题分层定位,不会一上来就被复杂的传输机制绕晕。

5. 调试实录与常见坑

5.1 我踩过的那些坑

调试IIS3DWB的过程中,最典型的几个问题和排查思路,我整理成下面的速查表:

现象可能原因排查方法
WHO_AM_I读出来是0xFF或0x00SPI模式不对、引脚配置错、接线断、供电不足先用示波器看SCK/MOSI波形,确认电平,再检查CubeMX引脚复用
数据全是0x00MISO没接好、传感器进入掉电模式、CS时序不对万用表量CS引脚电平变化,读STATUS寄存器确认数据就绪
数据有一定规律但数值跳变BDU未开启,高低字节跨更新打开CTRL3里的BDU位
偶尔读到某轴数据异常大SPI速率过高,信号质量差降低SPI分频,让SCK在10MHz以内,检查走线长度
读出来的值总是偏移一个字节地址字节的读写位没置对确认读操作地址最高位为1,写操作最高位为0
传感器能读ID,但数值一直不动传感器配置了错误ODR,或者没有进入连续测量模式检查CTRL1配置,确认ODR位段没配置成power-down

其中SPI模式下读ID失败是最常见的。如果你确认接线没问题,先用逻辑分析仪抓一下波形,对比数据手册里面的时序图看CS拉低之后SCK是否正常翻转。波形没问题再怀疑芯片,波形都不正常的话,问题通常出在CubeMX配置或者引脚冲突上。

第二常见的就是数据跳动但波形看起来还有点规律。这种我踩过一次,当时没开BDU,又恰好把读取频率和传感器更新频率错开了一点,偶尔就能把前一帧的高字节和后一帧的低字节拼在一起。打开BDU之后瞬间就好了。

5.2 排查工具与方法

调试SPI外设,最趁手的工具排序是:逻辑分析仪 > 示波器 > 串口打印。

逻辑分析仪便宜又好用,能直接把SCK、MOSI、MISO、CS四路信号都抓到,还能按SPI协议解析出每个字节的值。出现通信异常时,先抓一段完整波形看CS拉低期间的字节内容,基本就能定位。

示波器主要用来看信号质量。如果MISO数据边沿明显变圆、有振铃,说明走线或者负载有问题,这时候不是调代码能解决的,要降SPI速率或者改短走线。

串口打印适合放在轮询读数据阶段,把原始int16_t值打出来,直接看数值范围是否合理。这样快速验证传感器有没有在动,也能顺势检查量程换算是否匹配。

还有一个小技巧是故意让传感器静止,打印出来的三轴数据里,重力轴应该稳定在1g左右,另外两个轴接近0。如果重力轴读数明显不对,比如只有0.5g,那量程配置或者换算公式肯定有问题;如果三个轴读数乱跳,总线时序和硬件连接优先级最高。


调完这一版,把传感器的数据在串口上以二进制格式往上传,配合上位机做FFT,整个振动监测链路就通了。如果你也刚上手IIS3DWB,先别急着写算法,把SPI这条链跑稳,让数据连续读出来。波形能在一段时间内保持稳定不丢帧,后面做特征提取和故障诊断才会顺很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询