STM32C5这颗新片子,上篇已经吐槽过开发环境了——选型号、配时钟、点灯跑通,都是板上钉钉的事。但芯片能跑起来真不算什么,能把外部器件的数据实打实交换回来,才算把硬件口子打通。这篇就来玩 IIS3DWB,ST 专为工业振动监测出的那颗宽带宽三轴加速度计,用 IIC 把它里面的加速度原始值读出来。就像标题写的,这是系列第(2)篇,目标很明确:先回读 WHO_AM_I 确认 I2C 链路通不通,再把量程、输出速率几个寄存器配好,最后把 X/Y/Z 六个字节的原始数据换算成能用的 g 值或者 m/s²。过程中你会踩到不少 I2C 的经典坑,比如上拉电阻该取多大、时钟占空比为什么不是 50%、SCL 被拉死怎么复位,这些我都会挨个说清楚。适合手里有 STM32C5 板子加 IIS3DWB 模块、想从零把 IIC 通信跑通的朋友,也适合只想搞懂寄存器型传感器怎么跟 MCU 打交道的人。
1. 硬件准备与连接
1.1 板子与传感器模块
STM32C5 是 ST 新一代主流 MCU,Cortex-M33 内核,主频最高 250MHz,带 FPU 和 DSP 指令,外设设计上从 G4 继承了不少东西。我手头这块 C5 开发板已经调通了串口和点灯,这篇直接在同一个工程上继续。IIS3DWB 是 LGA-12 封装的小芯片,焊盘间距很密,我这边用的是手焊转接小板,把引脚引出来方便飞线,官方的适配小板也能用,接法大同小异。
开发板上电前先检查一件事:IIS3DWB 的供电电压范围是 1.8V 到 3.6V,数字接口电压 VDDIO 也是独立引脚。很多朋友直接把 VDD 接 3.3V,VDDIO 悬空,结果寄存器读不出来,其实就是芯片内部 IO 没有供电,总线根本没工作。这块传感器模块我 VDD 和 VDDIO 都接 3.3V,跟 MCU 逻辑电平保持一致,最省事。
1.2 I2C 接线与上拉电阻取值
IIS3DWB 的 I2C 接口只要四根线:SCL、SDA、VDD、GND,剩下一个 SA0 引脚决定器件地址最低位。SA0 接 GND 时 7 位地址是 0x69,接 VDD 时是 0x6B,模块上一般有默认下拉或跳线,我这里让它接地。接线对应关系如下:
| IIS3DWB | STM32C5 | 说明 |
|---|---|---|
| VDD | 3.3V | 模拟与数字电源 |
| VDDIO | 3.3V | IO 电源,务必接 |
| GND | GND | 共地 |
| SCL | PB8 | I2C1_SCL |
| SDA | PB9 | I2C1_SDA |
| SA0 | GND | I2C 地址 LSB 置 0 |
如果模块上不自带上拉,SCL 和 SDA 各需要接一个上拉电阻到 3.3V。取值大小直接决定通信能不能在高速率下稳定。I2C 是开漏结构,上拉电阻太小会让灌电流过大,太大则上升沿变得太缓,超过时序规范要求。工程上常用这样一组经验值:标准模式 100kHz 选 4.7k 到 10k,快速模式 400kHz 选 2.2k 到 4.7k,快速+模式 1MHz 选 1k 到 2.2k。我实测过,短线连接下 4.7k 也能跑 400kHz,但一旦杜邦线拉长到十几厘米,示波器上 SCL 上升沿就明显变圆,偶尔会读到错误字节,换 2.2k 立刻恢复正常。
严格算的话,上拉电阻上限可以由上升时间公式估算,R_max = t_rise / (0.8473 × C_bus)。400kHz 要求上升时间不超过 300ns,如果总线电容按 100pF 算,R_max 约 3.5k,所以 4.7k 本来就在临界边缘,2.2k 更稳妥。这个公式不用背,但心里要有概念,排查问题时会很有用。
1.3 电平匹配的坑
IIS3DWB 的 VDDIO 可以接 1.8V,如果你是低功耗系统,MCU 这侧也是 1.8V IO,其实更合适。但 C5 的 GPIO 基本是 3.3V 电平,VDDIO 就不建议设成 1.8V,否则 SDA 上拉到 3.3V 后芯片的 IO 不一定承受得住,或者内部保护二极管导通导致波形异常。我自己在这上面吃过亏:最初把 VDDIO 接 1.8V,结果通信时灵时不灵,原因是 I2C 总线被上拉到 3.3V,电平超出了芯片 IO 的耐压范围,换回 3.3V 后问题消失。所以这类传感器与 MCU 混接时,第一原则是让 VDDIO 和总线参考电压保持一致,别想当然各自为政。
2. IIS3DWB 传感器基本功
2.1 这颗传感器到底强在哪里
IIS3DWB 不是手机里那种普通加速度计,它带宽能做到 6kHz,输出数据速率最高能配到约 26.7kHz,专业方向就是工业振动监测——电机、泵、风机这些旋转设备的状态检测。普通加速度计带宽普遍只有几百赫兹,看高频振动信号根本不够用。IIS3DWB 数字接口支持 I2C 和 SPI,16 位数据输出,量程可选 ±2g、±4g、±8g、±16g。它最大的价值就是这个宽带宽,所以业内一般叫它“振动传感器”而不是简单叫加速度计。咱们这篇文章用 I2C 读它,更多是为了先把通信链路搞明白,如果想真正发挥 6kHz 带宽优势,后面多半要切 SPI,这个账我在第 5 章会细算。
2.2 I2C 地址与寄存器地图
跟寄存器型外设打交道,本质就是读写它内部寄存器,所以关键寄存器的地址必须心里有数。IIS3DWB 的几个核心位置如下:
| 寄存器 | 地址 | 作用 |
|---|---|---|
| WHO_AM_I | 0x0F | 固定回 0x52,验证通信 |
| CTRL1 | 0x20 | 主配置,高 4 位设 ODR |
| CTRL3 | 0x22 | bit2 是 IF_ADD_INC,连续读自动递增 |
| CTRL4 | 0x23 | bit6 是 BDU,bit[5:4] 设量程 |
| STATUS | 0x27 | bit0 是 DRDY,数据就绪标志 |
| OUT_X_L | 0x28 | 输出起始,连续 6 字节为 X/Y/Z 低高字节 |
WHO_AM_I 固定返回 0x52,这是最常用的通信自检手段。写配置前先读它一次,如果返回 0x52,说明器件地址、引脚、上拉、时序全都对了,后续配置才有意义。IF_ADD_INC 这个小位很多人容易忽略,它的作用是多字节读时寄存器地址自动加一。不开启的话,一次连续读 6 字节,每一字节读到的都还是 OUT_X_L,数据全错。
2.3 量程、灵敏度与输出格式
IIS3DWB 输出的是 16 位二进制补码,满量程 ±32768。不同量程下每个 LSB 代表的加速度不一样,常用灵敏度如下:
| 量程 | 灵敏度(mg/LSB) | 1g 对应的原始值 |
|---|---|---|
| ±2g | 0.061 | 约 16384 |
| ±4g | 0.122 | 约 8192 |
| ±8g | 0.244 | 约 4096 |
| ±16g | 0.488 | 约 2048 |
注意单位是 mg/LSB,不是 g/LSB。换算成 m/s² 时,要先把原始值乘上灵敏度得到“毫 g 数”,再除以 1000 变成 g,最后乘 9.80665。我在代码里为了省事,直接把系数合在一起,比如 ±2g 下每 LSB 对应 0.061 × 9.80665 / 1000 ≈ 0.000598 m/s²。这样打印的时候直接乘一个常量就行。
3. STM32C5 的 I2C 外设配置
3.1 C5 与 G4 的 I2C 外设对比
先说个结论:C5 的 I2C 外设在寄存器层面和 G4 基本是同宗同源,都靠 TIMINGR 寄存器量化 SCL 时序,都支持 Fast-mode Plus 的 1MHz,支持 SMBus/PMBus,支持模拟滤波和数字滤波。差别主要在外设时钟树:C5 的 I2C kernel clock 可以有多个来源,比如 PCLK1 或其他独立时钟,配置 TIMINGR 之前得先搞清楚当前 I2C 模块用的是哪个时钟源。如果你从 G4 项目迁移代码过来,I2C 部分几乎可以原样搬,只要在 CubeMX 里重新确认一下时钟配置就行。对使用 HAL 库的人来说,这个差异体感不明显,但自己写寄存器时就会碰到,所以先有个印象。
3.2 CubeMX 里的 I2C 配置
CubeMX 这边的操作很简单:选择 I2C1,I2C 模式选 I2C,Speed Mode 选 Fast Mode(400kHz),时钟源按默认 PCLK1 就行,CubeMX 会自动生成 TIMINGR 配置。引脚选 PB8/PB9,让它们复用为 I2C1_SCL 和 I2C1_SDA。生成代码后 HAL_I2C_Mem_Read / HAL_I2C_Mem_Write 就是两颗核心 API,它们内部自动处理“发设备地址 + 发寄存器地址 + 读数据”这套事务,比手动拼 START、STOP 省太多事。
这里有个细节:HAL_I2C_Mem_Read 的 DevAddress 参数要传入 8 位格式的设备地址,也就是 7 位地址 0x69 左移一位变成 0xD2。如果直接传 0x69,主机会发送错误的地址字节,从机不回应,函数直接返回超时。这个错误非常多见,轮询代码里第一处检查点就在这。
3.3 时钟占空比到底怎么算
I2C 时序里“占空比”是个容易被忽略的点。I2C 规范里 SCL 高、低电平的最低时间是明确写死的:标准模式 100kHz 要求高电平至少 4us、低电平至少 4.7us;快速模式 400kHz 要求高电平至少 0.6us、低电平至少 1.3us;快速+模式 1MHz 要求高电平至少 0.26us、低电平至少 0.5us。也就是说低电平时间必须长于高电平,占空比低于 50%,这和 PWM 那种对半开的直觉不一样。原因在于 I2C 的低电平期间允许从机拉低 SCL 做时钟拉伸,主机必须预留充足的低电平窗口,否则从机还没来得及拉低,主机就把时钟拉上去了。CubeMX 生成的 TIMINGR 已经把这些算好了,SCLH 和 SCLL 两个字段分别代表高、低电平用多少个 I2C kernel clock 周期计数。真要手动算,思路是用目标高/低电平时间减掉模拟滤波和数字滤波引入的延迟,再除以 I2C 时钟周期,最后对 SCLH、SCLL 取整并保证低电平计数不小于规范最小值。实际项目里我一般只在高速异常时才打开 CubeMX 生成的代码注释看 TIMINGR 值,平时不手算。
3.4 为什么 I2C 用开漏而不是推挽
这是新手最容易绕不过去的问题。I2C 总线是“线与”结构,SDA 上可以挂一大堆设备,任何一个设备主动拉低,整条总线就变低。如果大家都用推挽输出,两个设备一个想拉高一个想拉低,直接就是对拼短路,总线电平被强驱动,仲裁机制、时钟同步全部失效。开漏输出只能拉低,释放后靠外部上拉电阻把电平恢复高,天然实现了“谁都能拉低、没人拉低就自动回高”的效果。HAL 库初始化 I2C 时已经把引脚配成开漏复用模式,不用操心;但如果哪天你自己写寄存器初始化或者把引脚改成普通 GPIO 模拟 I2C,千万别把 SCL/SDA 配成推挽,这是模拟 I2C 的大忌。
4. 代码实现:从 WHO_AM_I 到加速度
4.1 底层读写函数封装
代码很直接,重点是先把设备地址和寄存器地址定义清楚。我习惯把所有关键寄存器宏定义放到头文件顶部,方便后面排查:
#define IIS3DWB_I2C_ADDR (0x69 << 1) // 7位地址0x69,HAL需要8位格式 #define IIS3DWB_REG_WHO_AM_I 0x0F #define IIS3DWB_REG_CTRL1 0x20 #define IIS3DWB_REG_CTRL3 0x22 #define IIS3DWB_REG_CTRL4 0x23 #define IIS3DWB_REG_STATUS 0x27 #define IIS3DWB_REG_OUT_X_L 0x28 static uint8_t iis3dwb_reg_read(uint8_t reg) { uint8_t data = 0; if (HAL_I2C_Mem_Read(&hi2c1, IIS3DWB_I2C_ADDR, reg, I2C_MEMADD_SIZE_8BIT, &data, 1, 100) != HAL_OK) { return 0xFF; } return data; } static void iis3dwb_reg_write(uint8_t reg, uint8_t val) { HAL_I2C_Mem_Write(&hi2c1, IIS3DWB_I2C_ADDR, reg, I2C_MEMADD_SIZE_8BIT, &val, 1, 100); }HAL_I2C_Mem_Read 的第一个参数 hi2c1 是 CubeMX 生成的实例名。需要特别说明,超时参数我给了 100ms,正常情况下单字节读写几个周期就完成了,但如果从机在拉低时钟拉伸,主机就会在这里等待。超时时间给太短反而会在调试时误报失败,给 100ms 比较稳。
4.2 校准通信:WHO_AM_I 验证
上电后第一件事别急着配寄存器,先读一次 WHO_AM_I:
uint8_t who = iis3dwb_reg_read(IIS3DWB_REG_WHO_AM_I); printf("WHO_AM_I = 0x%02X\r\n", who); // 如果打印 0x52,说明 I2C 通信链路已经通了这一步的价值在于把“通信问题”和“配置问题”彻底隔开。如果打印出来就是 0x52,说明器件地址、引脚、上拉、时序全部正确,后面配错只是寄存器数值的问题,好排查得多。如果读出来不是 0x52,先别怀疑传感器,先用逻辑分析仪看波形,再查地址和上拉,效率最高。我见过有人上来就配置流,配完发现数据全是 0xFF,回头才发现地址从 0x69 开始就传错了,白折腾半天。
4.3 初始化配置:CTRL1 到 CTRL4
WHO_AM_I 通过后,按顺序配置三个寄存器。我的初始化流程是:
void iis3dwb_init(void) { // 开启寄存器地址自增,连续读6字节时地址自动递增 iis3dwb_reg_write(IIS3DWB_REG_CTRL3, 0x04); // BDU使能,量程选 ±2g iis3dwb_reg_write(IIS3DWB_REG_CTRL4, 0x40); // ODR 设为 853.3Hz,高4位对应数值0x0A iis3dwb_reg_write(IIS3DWB_REG_CTRL1, 0xA0); HAL_Delay(50); }CTRL3 写入 0x04,是把 bit2 的 IF_ADD_INC 置 1。没有它,连续读多字节时只会反复读同一个寄存器。CTRL4 写入 0x40,bit6 是 BDU(Block Data Update),开启后输出寄存器在高字节被读取之前不会刷新,避免出现低字节是旧数据、高字节是新数据的错位;量程 FS 位为 00,对应 ±2g,这样灵敏度最高,静止时分辨率最好。CTRL1 写入 0xA0,高四位 0x0A 对应 ODR 约 853.3Hz。这个速率不算高,适合先把链路调通。配置完后延时 50ms,让传感器内部完成启动和首次数据产出,否则马上读数据很可能拿不到有效样本。
4.4 读取 XYZ 并换算成 g 值
读数据用一次 Mem_Read 连续读 6 个字节,因为已经开了 IF_ADD_INC,寄存器地址会自动从 0x28 递增到 0x2D,一次事务拿回 X/Y/Z 的完整数据:
void iis3dwb_read_xyz(float *x, float *y, float *z) { uint8_t buf[6]; if (HAL_I2C_Mem_Read(&hi2c1, IIS3DWB_I2C_ADDR, IIS3DWB_REG_OUT_X_L, I2C_MEMADD_SIZE_8BIT, buf, 6, 100) != HAL_OK) { *x = *y = *z = 0; return; } int16_t rx = (int16_t)(uint16_t)((buf[1] << 8) | buf[0]); int16_t ry = (int16_t)(uint16_t)((buf[3] << 8) | buf[2]); int16_t rz = (int16_t)(uint16_t)((buf[5] << 8) | buf[4]); const float sens = 0.061f; // mg/LSB @ ±2g *x = (float)rx * sens * 0.001f * 9.80665f; *y = (float)ry * sens * 0.001f * 9.80665f; *z = (float)rz * sens * 0.001f * 9.80665f; }这里有个非常典型的符号扩展坑。buf[1] 是 uint8_t,左移 8 位后是 int 类型,如果直接和 buf[0] 做或操作再强转 int16_t,在某些编译器里可能因为符号位处理不当而出错。稳妥写法是先拼成 uint16_t,再强转 int16_t,这样符号扩展是确定的。我代码里专门套了一层 uint16_t 转换,就是为了防这个。换算公式里 sens 的单位是 mg/LSB,先乘 0.001 变成 g,再乘 9.80665 变成 m/s²。
4.5 轮询读取主循环
主循环里最简单的读法是直接调用 read_xyz 然后打印,但 I2C 读操作本身需要时间,如果 ODR 较高,读得太慢会拿到重复数据。我习惯先看一下 STATUS 里的 DRDY 位,等数据就绪再读:
while (1) { if (iis3dwb_reg_read(IIS3DWB_REG_STATUS) & 0x01) { float x, y, z; iis3dwb_read_xyz(&x, &y, &z); printf("X=%7.3f Y=%7.3f Z=%7.3f m/s^2\r\n", x, y, z); } HAL_Delay(10); }如果 ODR 很低,DRDY 位还没置位就去读,读到的可能是上一次的值,这在调试看起来像“数据不更新”,其实只是没等新数据。加了 DRDY 判断后,逻辑上更严谨,也为后面高 ODR 采集打基础。
5. 实测:逻辑分析仪与数据验证
5.1 抓取 I2C 波形看时序细节
调通代码后,一定要把逻辑分析仪挂到 SCL/SDA 上抓一次波形。读 WHO_AM_I 时,波形应该是这样的顺序:起始位 SDA 在 SCL 高电平期间拉低,然后发送写地址 0xD2,从机回 ACK,接着寄存器地址 0x0F,从机再 ACK,随后是重复起始位,发送读地址 0xD3,从机 ACK,最后数据字节 0x52,主机回 NACK,发送停止位。看到 0x52 出现在数据字节位置,这套时序就是完全正确的。
抓波形时重点看两点。一是 ACK/NACK 的位置,如果地址阶段之后没有 ACK,说明器件地址不对或者模块没上电;如果寄存器地址阶段没有 ACK,可能是地址自增或寄存器配置有问题。二是上升沿形状,波形上升沿如果很缓,从上拉到高电平花了好几百纳秒,赶紧检查上拉电阻和总线长度。我调试时遇到过一块模块,插了杜邦线又并联了另一个传感器,390ns 的上升沿直接把 400kHz 时序搞坏,降到 100kHz 就正常,后来换 2.2k 上拉并缩短走线才恢复。
5.2 静止与翻转验证数据正确性
数据读回来后,最直接的验证方式就是把模块放平。正常情况下,Z 轴垂直指向地心,打印出来应该是 X≈0、Y≈0、Z≈+9.8,单位是 m/s²。把模块翻转 180 度,Z 会变成约 -9.8。如果 X/Y/Z 的符号或数值对不上,先检查拼字节的顺序,OUT_X_L 在前、OUT_X_H 在后,容易搞反。
这一步还有个容易被忽略的细节:传感器在静止时输出并不是完美零,而是围绕零点有小幅度波动,因为芯片本身有噪声。±2g 量程下,静止噪声通常在几个 mg 量级,折算成加速度就是几十 mm/s² 以内,只要不是出现几十上百倍的跳动都属于正常。如果打印值一直乱跳,检查 BDU 有没有开、DRDY 有没有判断,再查电源有没有纹波。我遇到过用劣质 USB 供电时数据噪声明显变大,换独立稳压供电后立刻稳定下来。
5.3 I2C 带宽到底够不够用
IIS3DWB 标称带宽最高 6kHz,ODR 最高能配到约 26.7kHz,但用 I2C 连续读数据前,务必算一笔账。一次读 6 字节的事务,包含起始位、设备地址、寄存器地址、重复起始、设备地址、6 字节数据和应答位,总共大概 83 个 bit。在 400kHz 下,一次事务约 207us,换算下来每秒最多能读约 4800 次;即使升到 1MHz,每秒也就 12k 次左右。这意味着 ODR 超过 3.4kHz 或 6.8kHz 这个级别,I2C 就会被数据流占满,更别提 26.7kHz 了。所以高频振动场景,最终还是要靠 SPI,或者用传感器内部的 FIFO 把数据攒一批再读。I2C 更适合演示、校准和低速监测,这篇先把 I2C 这条路走通,后面真正跑高频数据时再切成 SPI 就很顺理成章。
6. 常见问题与排查技巧
6.1 WHO_AM_I 读不到或读到 0xFF
先看设备地址:SA0 接地是 0x69,接 VDD 就是 0x6B,HAL 里传参要左移一位。接着查模块供电,VDDIO 没接或电压不对,芯片 IO 不工作,读回 0xFF 很正常。再查上拉和接线,SCL/SDA 接反、地线没共地,都会导致通信失败。最后把 I2C 速率降到 100kHz 试试,排除时序余量不足的问题。这四步挨个排查,基本能定位 90% 的通信故障。
如果读回的是 0x00,方向就反了,一般是总线被什么东西强制拉低,或者地址写错导致从机重复响应。这时候逻辑分析仪比万用表好使,直接看 ACK 位到底是不是低电平,一眼就能分辨器件有没有应答。
6.2 数据全是固定值或明显不对
如果 WHO_AM_I 读到 0x52,但加速度数据始终是同一个数,优先检查 IF_ADD_INC 有没有开。没开的话连续读 6 字节只会反复读 OUT_X_L,你会发现 Y、Z 和 X 的值完全一样。再检查寄存器顺序,输出是 X_L、X_H、Y_L、Y_H、Z_L、Z_H,拼字节时别把低高顺序搞反。数据如果溢出到满量程附近,多是量程配置和灵敏度换算不一致,比如寄存器配了 ±16g,代码里却按 ±2g 的 0.061mg/LSB 去换算,结果自然是错的。
6.3 I2C 总线卡死,SCL 或 SDA 一直为低
总线卡死是 I2C 最恼人的问题。现象是 SCL 一直低,或者 SDA 一直被拉低。原因多半是主机或从机的状态机进入了错误状态。软件恢复方法很经典:手动把 SCL 翻转 9 个时钟周期,从机内部状态机被强制复位,总线就能释放。代码如下:
void i2c_bus_recover(void) { GPIO_InitTypeDef gpio = {0}; gpio.Pin = I2C1_SCL_Pin; gpio.Mode = GPIO_MODE_OUTPUT_OD; gpio.Pull = GPIO_PULLUP; HAL_GPIO_Init(I2C1_SCL_GPIO_Port, &gpio); HAL_GPIO_WritePin(I2C1_SCL_GPIO_Port, I2C1_SCL_Pin, GPIO_PIN_SET); for (int i = 0; i < 9; i++) { HAL_GPIO_WritePin(I2C1_SCL_GPIO_Port, I2C1_SCL_Pin, GPIO_PIN_RESET); HAL_Delay(1); HAL_GPIO_WritePin(I2C1_SCL_GPIO_Port, I2C1_SCL_Pin, GPIO_PIN_SET); HAL_Delay(1); } HAL_GPIO_Init(I2C1_SCL_GPIO_Port, &(GPIO_InitTypeDef){ .Pin = I2C1_SCL_Pin, .Mode = GPIO_MODE_AF_OD, .Pull = GPIO_PULLUP, .Alternate = GPIO_AF4_I2C1 }); }注意补充 GP 初始化代码要适配实际工程。如果 9 个时钟仍然无效,就直接给传感器断电重新上电,C5 这边重新初始化 I2C。这个问题的根因,很多时候是通信过程中主机异常中断,或从机在时钟拉伸时主机提前释放,状态机卡住。所以轮询读取时,每次 HAL 函数返回 HAL_OK 再继续,中途报错就要做恢复处理,不能假装没看见。
6.4 数据跳动离谱
静止时数据跳得厉害,先分清是原始值跳动还是换算后跳动。换算后跳动要检查浮点运算和打印精度,原始值稳定的话多半是代码公式错误。原始值本身跳,一步步查:BDU 有没有开,没开会读到高低字节错位的数据;DRDY 有没有判断,没判断会把旧数据当新数据读;电源纹波大不大,劣质 USB 供电在传感器高 ODR 时影响尤其明显。最后提一句,I2C 总线如果走线太长、上拉电阻又太大,波形畸变会直接导致寄存器读写出错,数据自然乱七八糟,这也是我反复强调先抓波形的原因。
我自己实际玩 IIS3DWB 的时候,最初有个错觉,觉得 I2C 这套时序没啥好研究的。真踩过一轮坑之后才明白,从设备地址到上拉电阻再到时钟低电平占比,每一环都有讲究,任何一个细节松懈,数据就给你颜色看。下一步我准备把 FIFO 和 DRDY 中断打开,让 C5 配合 DMA 去接高频振动数据,再甩给串口看波形,这个方向咱们下一篇继续聊。