4、接口测试:模块间通信接口测试(SPI、I2C、UART),协议一致性验证

嵌入式系统里,模块之间怎么说话?说白了就是靠 SPI、I2C、UART 这三兄弟。我见过不少项目,功能逻辑写得天花乱坠,结果一联调,接口先崩了——要么波形对不上,要么时序差那么几个微秒,整个系统就卡死。

所以这一章,咱们就专门聊聊怎么给这三个接口做测试,以及怎么验证协议的一致性。嗯,这里说的“一致性”,不只是看波形对不对,还要看数据内容、时序参数、异常处理是不是都符合规格书。

4.1 三大接口的核心差异与测试关注点

先快速过一遍这三者的区别。你心里有个谱,后面测试才知道重点在哪。

特性 UART SPI I2C
信号线数 2(TX, RX) 4(SCLK, MOSI, MISO, CS) 2(SCL, SDA)
通信方式 异步,需双方约定波特率 同步,主设备提供时钟 同步,主设备提供时钟
主从关系 对等(无严格主从) 一主多从(片选区分) 多主多从(地址区分)
数据帧结构 起始位+数据位+校验位+停止位 无固定帧,靠时钟和片选 起始条件+地址+数据+应答+停止条件
测试核心 波特率误差、帧格式、溢出 时钟极性/相位、片选时序、数据对齐 地址仲裁、应答时序、时钟拉伸

这张表你最好存一下。每次做接口测试前,对着它列检查清单,基本不会漏项。

4.2 UART 接口测试:别小看波特率误差

UART 看起来最简单,两根线一接就能跑。但坑往往就藏在最简单的地方。

4.2.1 波特率一致性测试

我曾经在一个项目里,两边都配了 115200,结果跑起来总是丢字节。拿示波器一量,发送端实际波特率是 115400,接收端是 114800。差了 0.5% 左右,理论上在容差范围内,但连续传 1000 个字节,累积误差就把帧同步搞崩了。

测试方法其实不复杂:

  1. 用示波器或逻辑分析仪抓 TX 引脚上的一个字节波形。
  2. 测量起始位下降沿到第一个数据位上升沿的时间,这就是一个位的时间。
  3. 用 1 除以这个时间,得到实际波特率。
  4. 对比实际值与配置值,误差应小于 ±2%(工业级通常要求 ±1%)。
我的习惯: 在测试固件里加一个“回环测试”模式——把 TX 和 RX 短接,发一串已知数据,收回来比对。如果连续 10000 个字节无差错,波特率基本没问题。

4.2.2 帧格式与校验验证

UART 的帧格式包括数据位长度(7/8/9)、校验位(无/奇/偶)、停止位(1/1.5/2)。测试时,我一般会构造一个测试矩阵:

测试用例 配置 发送数据 预期结果
正常帧 8N1 0x55, 0xAA 接收端正确收到
奇校验 8O1 0x01(二进制 0000 0001) 校验位应为 0(1 的个数为奇数)
偶校验 8E1 0x01 校验位应为 1(1 的个数为偶数)
错误帧注入 8N1 人为拉低停止位 接收端报帧错误
注意: 很多芯片的 UART 在接收错误帧时不会主动通知应用层。你必须在驱动层注册错误中断回调,否则丢数据了你都不知道。

4.3 SPI 接口测试:时钟极性与相位是魔鬼

SPI 的测试,核心就四个字:CPOL 和 CPHA。CPOL 决定时钟空闲时的电平,CPHA 决定数据在哪个边沿采样。组合起来有四种模式(Mode 0~3)。

我遇到过最典型的坑:两个芯片手册上都写 Mode 0,结果一个把 CPOL=0、CPHA=0 叫 Mode 0,另一个把 CPOL=0、CPHA=0 叫 Mode 0 但内部实现反了。最后拿逻辑分析仪一抓,才发现数据是在错误的边沿被采样的。

4.3.1 时序参数测量

SPI 的时序参数很多,但测试时重点关注这几个:

  • SCLK 频率: 实际频率与配置频率的偏差,一般要求 < 5%。
  • CS 建立时间: CS 拉低到第一个 SCLK 边沿的时间,必须大于从设备的最小要求。
  • CS 保持时间: 最后一个 SCLK 边沿到 CS 拉高的时间。
  • 数据建立/保持时间: MOSI/MISO 数据在 SCLK 边沿前后的稳定时间。

实测案例: 某款 LCD 驱动芯片要求 CS 建立时间至少 100ns。我们的主控 SPI 时钟是 20MHz(周期 50ns),CS 和 SCLK 是同时产生的。结果 CS 建立时间只有 30ns,LCD 经常初始化失败。后来在 CS 线上加了一个 RC 延迟,才解决问题。

4.3.2 数据一致性测试

SPI 是全双工的,发一个字节的同时会收到一个字节。测试时,我习惯用“伪随机序列”做压力测试:

// SPI 回环测试示例(主控自测)
uint8_t tx_buffer[256];
uint8_t rx_buffer[256];

// 填充伪随机数据
for (int i = 0; i < 256; i++) {
    tx_buffer[i] = (uint8_t)(i * 7 + 13);
}

// 发送并接收
HAL_SPI_TransmitReceive(&hspi1, tx_buffer, rx_buffer, 256, 1000);

// 比对
for (int i = 0; i < 256; i++) {
    if (tx_buffer[i] != rx_buffer[i]) {
        // 记录错误位置和期望值
        printf("Mismatch at %d: tx=0x%02X, rx=0x%02X\n", i, tx_buffer[i], rx_buffer[i]);
    }
}

如果回环测试通过,说明 SPI 的物理层和驱动层基本没问题。接下来再挂上真实从设备,做端到端的功能测试。

4.4 I2C 接口测试:地址、应答与仲裁

I2C 比 SPI 复杂,因为它有地址机制、应答机制,还支持多主仲裁。测试时,这三个点一个都不能漏。

4.4.1 设备地址验证

I2C 设备地址有 7 位和 10 位两种。测试时,我一般先发一个“空写”操作(只发地址+写位,不发数据),然后检查是否收到 ACK。

避坑指南: 我曾经遇到一个传感器,手册上写地址是 0x68,结果实际是 0x69。原因是它的 AD0 引脚默认拉高了。所以测试时,一定要先读一下设备 ID 寄存器,确认地址是对的。

4.4.2 应答时序测试

I2C 协议规定:每个字节后,接收方必须在第 9 个 SCLK 周期拉低 SDA 表示 ACK。如果接收方没准备好(比如正在处理上一个数据),它可以拉低 SCLK(时钟拉伸)来让主设备等待。

测试时,用逻辑分析仪抓取以下场景:

  • 正常应答: 第 9 个 SCLK 高电平期间,SDA 为低。
  • 无应答(NACK): 访问不存在的地址,应看到 SDA 在第 9 个 SCLK 为高。
  • 时钟拉伸: 从设备拉低 SCLK 超过 50μs,主设备是否超时处理。

4.4.3 多主仲裁测试

这个测试在单主系统里可以跳过,但如果是多主系统,必须验证仲裁逻辑。测试方法:

  1. 两个主设备同时向同一个从设备发送数据。
  2. 观察 SDA 线上是否出现仲裁(一个主设备拉低,另一个检测到电平不一致后释放总线)。
  3. 验证仲裁失败的设备是否正确退出并重试。
注意: 多主仲裁测试最好用两个独立的 MCU 或一个双核 MCU 来做。用同一个 MCU 的两个 I2C 外设模拟多主,容易因为共用时钟源而测不出真实问题。

4.5 协议一致性验证的整体流程

上面讲的是单个接口的测试点。但实际项目中,我们需要一个系统化的验证流程。下面这张图是我自己总结的:

接口协议一致性验证流程 接口协议一致性验证流程 1. 物理层测试 2. 时序层测试 3. 数据链路层 • 电平标准(3.3V/5V) • 信号完整性(振铃/过冲) • 线缆长度与阻抗匹配 • 波特率/时钟频率误差 • 建立/保持时间 • 帧格式与边沿对齐 • 地址与应答机制 • 数据校验(奇偶/CRC) • 错误重传与超时处理 4. 协议一致性综合判定 ✅ 通过 → 进入集成测试 ❌ 失败 → 定位修复 回退到对应层重新测试 说明:每一层测试通过后才能进入下一层。物理层不过,时序层测了也白测。

这个流程我用了好几年。每次做接口测试,都按这个顺序来:先确认物理层没问题,再抓时序,最后验证数据链路层。三层都过了,协议一致性才算验证通过。

4.6 测试工具与自动化

手工测接口太累了。我建议你至少准备这几样工具:

  • 逻辑分析仪: 至少 8 通道,采样率 100MHz 以上。抓 SPI 和 I2C 时序必备。
  • 示波器: 带宽 100MHz 以上,用于测量信号质量和时序参数。
  • 串口调试助手: 带自定义帧发送和 CRC 校验功能的。
  • 自动化测试脚本: 用 Python 的 pySerial 或 pylibi2c 库,写一个批量测试框架。

我的自动化测试框架结构:

test_interface/
├── test_uart.py        # UART 测试用例
├── test_spi.py         # SPI 测试用例
├── test_i2c.py         # I2C 测试用例
├── test_common.py      # 公共函数(日志、比对、报告)
├── config.yaml         # 测试配置(波特率、地址、模式)
└── reports/            # 测试报告输出目录

每次跑完,自动生成 HTML 报告,包含每个测试项的通过/失败、波形截图、错误详情。这样不管是自己看还是给客户看,都一目了然。

4.7 本章小结

接口测试说难不难,说简单也不简单。关键是把每个接口的“脾气”摸清楚:UART 怕波特率漂移,SPI 怕时钟模式配错,I2C 怕地址冲突和应答超时。

我个人习惯是:每次新板子回来,先不跑应用逻辑,花半天时间把这三个接口的物理层和时序层测一遍。这半天花得值——它能让后面几周的集成调试少掉 80% 的坑。