搞嵌入式这么多年,跟传感器打交道算是家常便饭,但真正让我觉得“这个芯片有点意思”的,还是最近在 STM32C5 上跑通 IIS3DWB10IS 这件事。这颗宽带振动传感器带宽最高能到 6kHz,噪声又低,做设备状态监测、预测性维护非常合适。我这次直接用 SPI 接口把三轴震动数据读出来,整个过程从 CubeMX 配置、寄存器读写到最终换算成 mg 单位,踩了不少坑,也攒了不少经验。这篇文章就把完整思路和实操细节整理出来,给正在用或者准备用 IIS3DWB10IS 做振动采集的工程师一些参考。
1. 为什么是 STM32C5 + IIS3DWB10IS
1.1 项目要解决什么问题
先说你到底在做什么事。所谓“震动计数据”,本质是加速度传感器输出的三轴加速度数值。设备一振动,传感器内部的质量块就会跟着动,通过电容或压阻效应转成电信号,再经过 ADC 量化成寄存器里的 16 位数字量。你要做的,就是把 SPI 总线上的这些数字量准确、稳定、及时地搬回 MCU,再换算成有物理意义的 mg(毫g)或者 mm/s²。
这个场景最常见的应用是工业状态监测。电机、轴承、泵、风机这类旋转设备,运行的时候会有固定的振动特征频率。一旦轴承磨损、转子不平衡、齿轮断齿,振动特征就会变。传感器采集振动数据,MCU 做 FFT 频谱分析,就能在设备彻底坏之前预警。IIS3DWB10IS 这颗芯片定位就是宽带低噪声加速度计,非常适合这类应用。
用 STM32C5 的原因也很实际:它基于 Cortex-M33 内核,主频足够跑 FFT 和后续算法,外设也齐全。ST 自家的传感器配自家 MCU,文档和例程能对上,省去了不少跨厂商适配的麻烦。如果你手头只有 F103 或者 G4 系列,其实也可以,只是高采样率下连续 DMA 搬运和数据处理压力会更大。
1.2 为什么选 SPI 而不是 I2C
这是项目里最值得说清楚的一个决策。IIS3DWB10IS 同时支持 SPI 和 I2C,但我强烈建议用 SPI。原因不是 I2C 不能用,而是振动监测这个场景对带宽和吞吐的要求太高。
I2C 标准模式只有 100kbps,快速模式 400kbps,高速模式虽然能到 1Mbps 甚至 3.4Mbps,但很多 MCU 的 I2C 外设和传感器组合实际跑不到理论值,而且 I2C 有应答、地址、重复起始位这些额外开销。振动传感器要连续读 6 个字节(XYZ 各两字节),如果采样率跑到 1kHz 以上,I2C 总线负载会很高,稍微有点干扰就容易 NACK。
SPI 就简单多了。全双工、移位寄存器、没有应答位,时钟推着数据走,效率极高。IIS3DWB10IS 的 SPI 时钟最高能到 10MHz,理论上一次读 6 个字节只要 6 微秒左右,即使加上 CS 切换和软件处理,1kHz 采样率下总线占用率也才百分之几。更重要的一点是,SPI 没有外部上拉电阻的要求,时序也更容易用逻辑分析仪去抓。
一句话总结:I2C 适合低速配置和低采样率场景,SPI 才是高数据率振动采集的正解。这个选型纯属工程取舍,不是偏好问题。
1.3 整体框架与数据流
整个系统的数据流是这样的:
IIS3DWB10IS 的加速度信号经过内部 ADC 量化后放入输出寄存器,MCU 通过 SPI 以特定速率读取 X/Y/Z 三轴共 6 字节原始数据,再由软件换算成 mg 或 mm/s²。读到的数据可以缓存到环形缓冲区,后续做窗口处理、FFT、特征提取,或者直接通过串口、CAN 转发到上位机。
需要特别注意的是“采样率”这个词在传感器和 MCU 两个层面的含义。传感器内部有固定的 ODR(输出数据率),比如 1.6Hz 到 6kHz 之间可配。单片机应当以等于或略高于 ODR 的频率去读数据,读太快会读到重复或半新数据,读太慢会丢数据。如果 MCU 的读取频率和传感器内部更新频率不同步,最直接的表现是波形上出现不正常的毛刺和跳变。
2. 硬件准备与接线
2.1 引脚确认与连接
先别急着写代码,硬件上确认几件事能省很多排查时间。IIS3DWB10IS 常见的封装是 LGA-12,引脚不算多,但不同板子丝印可能不一样。我这边用的是定制的传感器小板,关键引脚如下:
- VDD:1.8V~3.6V,我直接接 3.3V
- GND:电源地
- CS:SPI 片选,低有效
- SPC:SPI 时钟
- SDI/SDO:SPI 数据输入(MOSI)
- SDO/SA0:SPI 数据输出(MISO),同时兼作 I2C 地址选择引脚
STM32C5 这边我选了 SPI1 外设,对应引脚是 PA5(SCK)、PA6(MISO)、PA7(MOSI)、PA4(CS)。之所以选 SPI1,是因为它挂在 APB2 总线上,时钟频率更高,分频后的 SPI 时钟选项更灵活。如果你用 SPI2 或 SPI3 也可以,但要注意 APB1 的外设时钟最高频率可能低于 APB2,分频计算结果不一样,最终 SPI 实际速率可能达不到预期。
接线就这么几条,但有个细节很容易被忽略:传感器和 MCU 的电平域是否一致。IIS3DWB10IS 的 VDD 接 3.3V,那 SPC、SDI、SDO 的电平也是 3.3V 逻辑。如果 MCU 是 5V 供电,SPI 引脚输出高电平可能是 5V,直接接到传感器 3.3V 引脚上,长期工作轻则信号畸变、重则损坏芯片。所以务必确认逻辑电平一致,或者加电平转换。
2.2 电源与去耦
振动传感器对电源纹波非常敏感。你想想,加速度计量的是微小的机械位移变化,电源上哪怕几十毫伏的纹波,都可能被 ADC 当成加速度信号采进去。
我实测过,如果 VDD 引脚旁边只放一个 0.1μF 电容,静态时输出数据噪声明显偏大;加上一个 4.7μF 的钽电容或陶瓷电容后,噪声下降了一个数量级。建议的做法是 0.1μF 高频去耦电容尽量靠近 VDD 引脚放置,1μF~10μF 的大容量电容放在稍远位置。如果系统里还有电机、继电器这类大电流负载,最好单独给传感器用 LDO 供电,别和数字电路共用一个开关电源。
另外,如果有 I2C 地址选择功能,SA0 引脚不要悬空。悬空状态下引脚电平不确定,可能导致 SPI 模式下误识别为 I2C 地址配置,出现通信不稳定的怪问题。用万用表测一下这个引脚的默认电平,确定后明确接高或接低。
2.3 硬件 NSS 还是软件 CS
SPI 的片选信号有两条路:一是直接用 MCU 的硬件 NSS 引脚,二是用普通 GPIO 软件控制。IIS3DWB10IS 的 CS 引脚对时序有要求,建议用软件 GPIO 控制,理由后面展开讲。
STM32 的硬件 NSS 有两种模式:NSS 输出模式和 NSS 输入模式。输出模式下,SPI 外设会在传输开始时自动拉低 NSS,结束后拉高,省一个引脚也省 CPU 干预。听起来很好用,但实际有风险:如果 SPI 总线上挂了多个设备,硬件 NSS 只能控制一个固定的引脚,不能动态切换片选;如果 SPI 配置成 NSS 脉冲模式,还会在每次字节传输之间产生片选脉冲,这在大多数传感器场景下是不能接受的。
软件 CS 就不一样了。你可以把任意 GPIO 配置成推挽输出,操作顺序完全自己掌控。读数据之前先拉低 CS,确保传感器 SPI 状态机复位到命令接收状态,传输完成后延时几微秒再拉高,避免片选释放太快导致最后一个字节没锁存。这个可控性是硬件 NSS 给不了的。
3. STM32CubeMX 配置 SPI
3.1 CubeMX 里 SPI 参数怎么填
打开 STM32CubeMX,选中 STM32C5 型号后,在 Pinout & Configuration 面板里找到 SPI1,模式选 “Full-Duplex Master”。然后看 Parameter Settings 那一栏,重点配置以下几个参数:
- Frame Format:Motorola 模式
- Data Size:8 bits
- First Bit:MSB First
- Prescaler:根据实际时钟算
- Clock Polarity(CPOL):Low
- Clock Phase(CPHA):1 Edge
- NSS:Software
CPOL=Low 表示空闲时钟线为低电平,CPHA=1 Edge 表示数据在时钟上升沿采样、下降沿移位。这个组合对应 SPI Mode 0,也是 IIS3DWB10IS 数据手册推荐的默认时序。
有些资料把 IIS3DWB 的 SPI 时序写成 Mode 3(CPOL=High,CPHA=2 Edge),其实手册里是支持 Mode 0 和 Mode 3 的,具体看 CTRL 寄存器里的配置。如果后面调试时读数不对,可以顺手把 CPOL 改 High、CPHA 改 2 Edge 试试,我当时就被这个坑折磨过——按默认 Mode 0 配,WHO_AM_I 读得对不对,但数据偶尔跳变,后来发现是 Mode 3 更稳。
Prescaler 的值不能拍脑袋填。IIS3DWB10IS 的 SPI 最高 10MHz,STM32C5 的 SPI1 挂在 APB2 上,假设 APB2 时钟 80MHz,那 Prescaler 选 8 分频,SPI 时钟就是 10MHz。如果选 4 分频就是 20MHz,超了传感器规格,属于危险操作。边界情况下可以试,但带负载能力下降,长线传输时波形劣化明显。保守起见,先 8 分频跑通,再根据实际需要提速。
3.2 硬件片选和软件片选在 CubeMX 里的表达
有的新手在 CubeMX 里看到 “NSS” 类型选项就慌了,不知道该选 Hardware 还是 Software。我的建议是选 Software,然后在代码里自己控制 CS 引脚。
在 CubeMX 里选 Software 之后,还需要手动把一个 GPIO 配置为 Output,并起个清晰的名字,比如 CS_IIS3DWB。生成的代码里,这个 GPIO 初始化为推挽输出,默认电平由你在 GPIO Output Level 里决定——这里记得选 High,因为 CS 低有效,空闲时必须为高,否则传感器一上电就被选中,SPI 状态机可能提前乱掉。
如果你用的是硬件 NSS,CubeMX 会自动把 NSS 引脚配置成复用功能。这种方案省事,但就像前面说的,多设备共总线时动态切 CS 很不方便,所以我建议用软件 GPIO。
3.3 时钟树与分频关系
SPI 频率的计算逻辑是:SPI 时钟 = SPI 外设总线时钟 / Prescaler。STM32C5 的 SPI1 在 APB2 上,SPI2/SPI3 在 APB1 上。CubeMX 的 Clock Configuration 页面里,先把系统时钟配置好,比如 160MHz 主频,然后看 APB1 和 APB2 分频后的实际频率。
这里有个容易忽略的点:SPI 外设的输入时钟不一定等于总线上看到的 80MHz 或者 40MHz,还要看芯片手册里 SPI 外设是否有独立的时钟使能位。STM32 系列一般没这么复杂,SPI 挂哪个总线就用哪个总线时钟,但具体到 STM32C5,建议在生成的 main.c 里打断点查看HAL_RCC_GetPCLK2Freq()的返回值,确认实际频率,再反推 Prescaler。
如果你不确认某个配置对不对,就用逻辑分析仪抓 SCK 引脚的频率,一抓一个准。示波器能看具体波形边沿质量,逻辑分析仪能直接读出频率和时序关系,两个都很有用。
4. SPI 原理与寄存器读写实现
4.1 SPI 时序怎么映射到代码
SPI 本质上是一个环形移位寄存器。主机往 MOSI 上移出一个 bit 的同时,从 MISO 上采样一个 bit。所以读操作其实也是写操作——读的时候主机必须持续输出时钟,并且发送一个“空字节”(通常 0x00 或 0xFF)来产生时钟,传感器才会把数据放到 MISO 上。
对于 ST 的传感器,SPI 读命令的格式是:第一个字节的高位是读标记,通常地址的最高位(bit7)设为 1。例如要读地址 0x0F,就发送 0x8F。如果是多字节读,地址的 bit6 如果是自动递增标记(IF_ADD_INC),那第二个字节开始地址会自动加 1,很方便,不用对每个寄存器重新发命令。
写命令则相反,bit7 为 0,例如写 0x20 寄存器就发送 0x20。
实际代码建议封装两个基础函数。一个是读单字节,一个是写单字节。读多字节和写多字节可以在这两个基础上扩展。
#define IIS3DWB_CS_LOW() HAL_GPIO_WritePin(CS_IIS3DWB_GPIO_Port, CS_IIS3DWB_Pin, GPIO_PIN_RESET) #define IIS3DWB_CS_HIGH() HAL_GPIO_WritePin(CS_IIS3DWB_GPIO_Port, CS_IIS3DWB_Pin, GPIO_PIN_SET) uint8_t IIS3DWB_ReadReg(uint8_t reg) { uint8_t txData = (reg << 1) | 0x80; // 有些传感器地址是7位,左移1位,bit0表示读写 uint8_t rxData = 0; uint8_t dummy = 0x00; IIS3DWB_CS_LOW(); HAL_SPI_Transmit(&hspi1, &txData, 1, 10); HAL_SPI_TransmitReceive(&hspi1, &dummy, &rxData, 1, 10); IIS3DWB_CS_HIGH(); return rxData; }这里有个细节:不同型号的 ST 加速度计,SPI 命令地址格式可能不一样。有的直接用 8 位地址加读写位(bit7 = 1 读),有的要求地址左移一位再加读写位。IIS3DWB10IS 的寄存器地址定义是 8 位,我用的是reg | 0x80表示读,reg & 0x7F表示写。如果你拿到的是官方例程,仔细看 ReadReg 的实现,别直接照搬别的传感器代码。
4.2 读 WHO_AM_I 验证通信
代码写好后,第一件事不是读加速度,而是读 WHO_AM_I 校验通信链路。这个寄存器地址通常是 0x0F,读出来的值对 IIS3DWB 系列应该是 0x4B。
这一步的价值在于:如果 ID 读不对,说明你 SPI 时序、接线、供电、引脚配置至少有一样有问题。如果 ID 读对了,通信链路基本就通了,后面寄存器配置和读数据都是基于这个基础。
我调试时遇到过一种怪异情况:第一次上电 WHO_AM_I 读出来是 0x4B,但程序复位一次之后变成 0xFF,再复位又变成 0x4B。排查了很久,发现是传感器上电后需要一段稳定时间(几毫秒),MCU 复位后立刻去读,传感器 SPI 还没准备好。解决方案是在初始化函数开头加 10ms 以上的延时,再做第一次通信,问题就消失了。
4.3 配置传感器寄存器
通信验证通过后,就可以配置传感器的工作模式了。IIS3DWB10IS 的关键寄存器不多,主要有几个:
- CTRL1(通常 0x20):配置 ODR 和低通滤波器带宽
- CTRL2(通常 0x21):软件复位等
- CTRL3(通常 0x22):SPI 读地址自动递增使能等
- CTRL4(通常 0x23):满量程范围、BDU 等
先说 CTRL3 里的 IF_ADD_INC 位。这个位要使能,否则每次读一个寄存器都得重新发地址,极低效。配置方法是在写 CTRL3 时把这个位置 1。这算是我见过最影响读取效率的寄存器位之一。
// 使能寄存器地址自动递增 uint8_t ctrl3 = 0x00; ctrl3 |= 0x04; // IF_ADD_INC = 1 IIS3DWB_WriteReg(0x22, ctrl3);再说 ODR。IIS3DWB10IS 的 ODR 最高能到 26.7kHz,带宽 6kHz。但实际项目里,我一般先配 1kHz 或 3.3kHz。为什么不全开最高?一个是功耗,采样率每翻一倍,电流消耗大概跟着涨;另一个是数据量,26.7kHz 意味着每秒钟要读 26.7k 次、每次 6 字节,也就是 160KB/s 的数据流,对 STM32C5 来说能扛,但后续处理和存储压力大。
满量程这边,IIS3DWB 支持 ±2g、±4g、±16g。做状态监测时我习惯先用 ±16g,防止设备启动阶段瞬间冲击导致削波。如果确认被测设备振动幅度不大,再改 ±4g,分辨率更高。
BDU 位也要说一下。BDU 是 Block Data Update 的缩写,把它置 1 后,传感器在高、低字节更新时会锁存另一字节,防止你读到“半个新数据 + 半个旧数据”的错位组合。这个在高速采样时必须开,否则波形上会出现随机的跳变点,干扰后续 FFT 分析。
// 配置 CTRL1:ODR = 1kHz uint8_t ctrl1 = 0x00; ctrl1 |= (0x08 << 4); // 具体值由ODR决定,这里示意 IIS3DWB_WriteReg(0x20, ctrl1); // 配置 CTRL4:满量程 ±16g,BDU 使能 uint8_t ctrl4 = 0x00; ctrl4 |= 0x80; // BDU = 1 ctrl4 |= 0x40; // FS = ±16g,这里需要按手册确认编码 IIS3DWB_WriteReg(0x23, ctrl4);配置完寄存器,强烈建议回读一遍,确认写入成功。有些芯片存在“写时忽略某些位”的情况,如果只写不读,配置错了一时半会也发现不了。
4.4 读原始数据并换算成 mg
配置好之后,就是真正读取三轴加速度数据。IIS3DWB 的输出寄存器从 OUT_X_L、OUT_X_H、OUT_Y_L、OUT_Y_H、OUT_Z_L、OUT_Z_H 开始,连续 6 个字节。使能了 IF_ADD_INC 后,可以用一次 SPI 读命令连续读完。
关键点是:读命令只发一次地址,后面连续收 6 个字节。主机在接收每个字节时都要发送一个 dummy 字节(通常 0x00)来产生 SCK。
typedef struct { int16_t x; int16_t y; int16_t z; } AxisRaw_t; AxisRaw_t IIS3DWB_ReadAccel(void) { uint8_t txBuf[7] = {0}; uint8_t rxBuf[7] = {0}; AxisRaw_t raw = {0}; txBuf[0] = 0x28 | 0x80; // 0x28 = OUT_X_L,读命令加读标志 for (int i = 1; i < 7; i++) { txBuf[i] = 0x00; // dummy } IIS3DWB_CS_LOW(); HAL_SPI_TransmitReceive(&hspi1, txBuf, rxBuf, 7, 10); IIS3DWB_CS_HIGH(); raw.x = (int16_t)((rxBuf[2] << 8) | rxBuf[1]); raw.y = (int16_t)((rxBuf[4] << 8) | rxBuf[3]); raw.z = (int16_t)((rxBuf[6] << 8) | rxBuf[5]); return raw; }注意字节顺序。IIS3DWB 数据手册里默认是小端模式,也就是低字节在前、高字节在后。所以rxBuf[1]是 X 轴低字节,rxBuf[2]是高字节。如果你配置了大端模式(BLE 位),拼接顺序就要反过来。
得到 int16_t 的原始值后,换算成 mg 需要乘以灵敏度。不同满量程对应的灵敏度不同:
- ±2g:0.061 mg/LSB
- ±4g:0.122 mg/LSB
- ±16g:0.488 mg/LSB
举个例子,读到的原始值是 2048,满量程 ±4g,那对应的加速度就是 2048 × 0.122 = 249.856mg,约等于 0.25g。这个数值就是振动加速度的瞬时值,后续可以做 RMS、峰值、FFT 等分析。
换算时要注意数据类型。int16_t 乘 float 时会自动转成 float,没问题,但如果你用 int 存结果可能会截断。建议用 float 存物理量:
float IIS3DWB_ConvertToMg(int16_t raw, float sensitivity) { return (float)raw * sensitivity; }5. 用 DMA 和中断提高采样稳定性
5.1 为什么阻塞读取不够用
最开始我图省事,直接在 while 循环里调用 HAL_SPI_TransmitReceive 阻塞读。低速采样还好,一旦把采样率提到 1kHz 以上,问题就来了。
阻塞读意味着 CPU 在 SPI 传输完成之前不能干别的。SPI 时钟 10MHz、读 6 字节需要大约 4.8 微秒(命令 1 字节 + 数据 6 字节,共 7 字节,7×8/10MHz = 5.6μs),这本身不算长。但 HAL 库的 HAL_SPI_TransmitReceive 有锁定标志、等待超时等额外时间,实测一次调用可能要 20~30μs。以 1kHz 采样周期(1000μs)来算,占用率 2%~3%,问题不大。可如果后面要同时跑 FFT、LCD 刷新、串口打印,这些时间叠加起来就很可观,容易造成采样周期抖动。
更关键的是,传感器有 FIFO 容量和数据更新周期,如果 MCU 不能在规定时间内把数据读走,新数据会覆盖旧数据,你自以为采集到了完整序列,实际上波形已经出现缺口了,FFT 出来的频谱全是混叠噪声。
5.2 切入 DMA 模式
DMA 模式的核心思想是:CPU 只负责启动传输,SPI 外设借助 DMA 自动把数据搬到内存,传输完成后触发中断通知 CPU。这样 CPU 在传输期间可以去处理其他任务,比如浮点运算、状态机切换。
CubeMX 里配置 DMA 也很简单。在 SPI1 的 DMA Settings 选项卡里添加 DMA Request,一个用于发送(SPI1_TX),一个用于接收(SPI1_RX),方向分别选 Memory-To-Peripheral 和 Peripheral-To-Memory,模式选 Normal,数据宽度选 Byte。
启动 DMA 传输的代码用的是 HAL_SPI_TransmitReceive_DMA:
uint8_t txDmaBuf[7] = {0}; uint8_t rxDmaBuf[7] = {0}; void IIS3DWB_StartDmaRead(void) { txDmaBuf[0] = 0x28 | 0x80; for (int i = 1; i < 7; i++) { txDmaBuf[i] = 0x00; } IIS3DWB_CS_LOW(); HAL_SPI_TransmitReceive_DMA(&hspi1, txDmaBuf, rxDmaBuf, 7); }然后在 SPI 的 TxRxCpltCallback 回调里拉高 CS,处理数据:
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi->Instance == SPI1) { IIS3DWB_CS_HIGH(); process_accel_data(rxDmaBuf); IIS3DWB_StartDmaRead(); // 准备下一次读取 } }这个思路能跑得很稳,但有几个注意点。一是 DMA buffer 必须是全局或静态变量,不能是局部变量,否则栈上地址 DMA 访问不到。二是回调里处理数据要快,不能做耗时操作,比如浮点 FFT 应该放主循环或另一个任务里,不能再在中断里做。三是连续 DMA 启动时,CS 拉高后立刻拉低,中间最好加几个空的延时,否则传感器 SPI 状态机可能来不及复位,导致后续读取错位。
5.3 传感器 FIFO 和中断引脚配合
除了 DMA,IIS3DWB10IS 还带了 FIFO。FIFO 的作用相当于一个缓冲水池,传感器内部按 ODR 往 FIFO 里存数据,MCU 可以攒一批再一起读走。
用 FIFO 的好处是降低 MCU 的实时性要求。比如 ODR 1kHz,FIFO 深度足够的话,你可以每 10ms 读一次,每次读 10 组数据,而不是每 1ms 读一次。这样 CPU 有大量时间处理其他任务。
FIFO 的读取不能盲读。建议先把中断引脚 INT1 连接到 MCU 的 EXTI 或 GPIO 中断,配置为 FIFO 阈值中断。当 FIFO 里的数据达到设定值(比如 15/16 满),INT1 拉高,MCU 中断触发,然后再通过 SPI 把一批数据读出来。这样最省 CPU,也绝不会丢数据。
不过 FIFO 模式会引入一定复杂度,初期调试建议先把单次读取跑通,再用 DA 的方式提速,最后再加 FIFO 做优化。一步一步来,出问题好定位。
6. 实测数据与波形观察
6.1 三轴数据怎么可视化
数据读出来了,怎么看对不对?最简单的方法是串口打印出来,用串口调试助手或者 Python 脚本绘图。
我常用的是 Python + pyserial + matplotlib,读串口数据,实时画三轴波形。代码不复杂,但有个点要注意:串口打印会占用大量时间,如果你一边用 DMA 高速采样一边 printf,可能互相干扰。建议的做法是把数据存在一个环形缓冲区里,主循环低优先级慢慢打印,不实时打,这样采样中断不会被阻塞。
打印格式建议用 CSV 一行一行的形式:
timestamp,x_mg,y_mg,z_mg 1000,12.3,-5.6,1010.2这样后续处理非常方便,Excel 也能直接打开。
6.2 静态与振动对比
拿到波形后,先做一个静态测试。把传感器平放在桌面上,Z 轴应该读到约 1000mg,X 和 Y 轴接近 0mg。这是因为重力加速度 1g 作用在 Z 轴上。如果你测出来 X 轴是 1000mg、Z 轴是 0mg,说明传感器摆放方向和你预期不一样,不是 bug。
然后用手轻轻敲击桌面,或者用手机振动马达贴在传感器附近,观察波形是否能捕捉到瞬态冲击。这一步主要看响应速度和幅值是否合理。初次调试时,如果读数一直在某个固定值附近抖动,但抖动幅度只有几个 LSB,那是正常噪声;如果抖动幅度几百个 LSB,那就要检查电源去耦和 SPI 读时序是否有问题。
6.3 实测中发现的一个典型问题
我记得第一次高速读取时,FFT 频谱里总是有一个 500Hz 左右的尖峰,怎么都消不掉。我开始以为是传感器本身频率响应问题,后来用示波器测传感器电源引脚,发现上面叠加了一个 500Hz 的纹波,来自隔壁 DC-DC 电源的开关频率。
把传感器供电单独拉出去,用 LDO 供电并加大滤波电容后,这个尖峰消失了。这类问题在振动测量里特别典型——你测到的可能不是设备振动,而是电源噪声。所以做振动采集,电源设计怎么强调都不过分。
7. 常见问题排查与避坑
7.1 WHO_AM_I 读回来全 0xFF 或全 0x00
这是 SPI 通信最常见的故障,几乎所有人都遇到过。我用一个表格整理排查路径,方便你对照:
| 现象 | 可能原因 | 排查办法 |
|---|---|---|
| 读回 0xFF | MISO 悬空或电平不对 | 检查 MISO 接线和焊点,确认传感器供电 |
| 读回 0x00 | 传感器没进入 SPI 模式 | 确认 CS 电平、检查 SA0 引脚是否悬空 |
| 读回错误但稳定值 | SPI 模式不匹配 | CPOL/CPHA 试四种组合 |
| 时好时坏 | 电源不稳定或 CS 毛刺 | 加大滤波电容,CS 加 10kΩ 上拉到 VDD |
| 复位后第一次失败 | 上电时序问题 | 初始化前加 10ms~100ms 延时 |
7.2 数据全是 0 或者恒定
如果通信正常(WHO_AM_I 对),但加速度数据全是 0,优先检查寄存器配置有没有生效。尤其是 CTRL1 里的 ODR 配置,如果把 ODR 配成了 0,传感器可能不更新数据,输出寄存器一直保持上电默认值。
另一种情况是传感器进入了低功耗模式或者待机模式。有些传感器上电默认是待机模式,必须写 CTRL1 切到连续测量模式才更新数据。看数据手册的“上电默认状态”章节,确认默认模式。
还有一个很低级但常见的坑:读出来数据字节顺序搞反了。如果你用(rxBuf[1] << 8) | rxBuf[2]而不是(rxBuf[2] << 8) | rxBuf[1],数据会表现为在小范围跳动,但数值明显不对,比如 Z 轴不是 1000mg 而是几万 mg。这种一旦发现,改一下拼接顺序就好。
7.3 CS 片选毛刺问题
软件 CS 比较灵活,但如果控制不好,会在数据线上引入毛刺。最常见的问题是 CS 拉低之前,MOSI 上还没有准备好数据。这就要求你在 CS_LOW 之后、SPI 传输之前,稍微延时几个时钟周期,让 MOSI 稳定下来。HAL 库的 TransmitReceive 会自动准备,但如果你自己拼凑逻辑,最好加一个短延时。
CS 拉高也有讲究。如果最后一个字节的 SCK 下降沿刚结束,立即拉高 CS,传感器可能来不及锁存最后一个字节。稳妥的做法是等 SPI 传输完成标志置位后再拉高 CS,这也是为什么 DMA 回调里拉高 CS 更可靠的原因。
7.4 SPI 速率和信号质量
把 SPI 速率调到 10MHz 后,如果用了杜邦线连接,波形会明显变差。杜邦线有分布电容和电感,高速边沿会被拉缓,MISO 数据在采样时刻可能还没稳定。建议用短导线或者直接焊接,SPI 时钟 10MHz 时导线尽量控制在 10cm 以内。
实在要用杜邦线,可以考虑把 SPI 速率降到 1MHz~2MHz,传输时间多几十微秒,但在大多数振动监测场景下完全够用。稳定性比极限速度重要得多。
7.5 SPI 和 I2C 共用一个总线的风险
IIS3DWB10IS 的 SPI 和 I2C 共用引脚。如果你在系统里既想用 I2C 又想用 SPI,或者把传感器同时接到两条总线上,必须确保 CS 引脚在 I2C 模式下被拉高。否则传感器会误以为工作在 SPI 模式,I2C 通信完全异常。同理,SPI 模式下不要让 SPC 和 SDI 长时间浮空,最好通过初始化配置把它们拉到确定的电平。
一个经验:SPI 模式下,把 CS 内部上拉使能或外部加 10kΩ 上拉,能有效防止上电期间的片选毛刺。这个细节在很多参考设计里都有,值得照做。
8. 后续扩展方向
跑通 SPI 读取 IIS3DWB10IS 之后,可以做很多实际应用。我自己接下来的方向是在 STM32C5 上做实时 FFT 频谱分析。STM32C5 的 Cortex-M33 主频足够,可以跑 2048 点 FFT,每 100ms 输出一次频谱,再配合阈值判断,实现简单的设备异常报警。
还想试的功能是多传感器同步。一个系统里挂两个或多个 IIS3DWB,分别贴在设备的不同位置,SPI 总线共用,CS 分别控制。采集时需要注意同步采样,因为振动信号相位信息很重要,两台设备不同步测出来的相位差是假的,没法用于模态分析。
如果对无线传输感兴趣,也可以把 STM32C5 接 WiFi 或 BLE 模块,采集到的数据实时上传到上位机或云平台,做成远程监测系统。不过无线传输的吞吐量和实时性有限,建议在边缘端做压缩或特征提取,只上传特征值而不是原始波形。
最后再分享一个小技巧:开发阶段建议把 SPI 的几条线引出测试点,方便逻辑分析仪抓波形。我调试时被 SPI 时序折腾过好几回,有了测试点之后,一个逻辑分析仪扫描,命令和应答一目了然,定位问题效率高很多。项目做完了再考虑删掉测试点或者改成小焊盘。