适用人群:会用 HAL 库调 UART/SPI/I2C,但一旦不通就只会改代码瞎试 的同学。手上有块 STM32/ESP32 开发板、一个几十块的 USB 逻辑分析仪就够了。
读完你能得到:① 一套"通信不通先抓波形"的固定动作;② UART 波特率误差怎么手算、为什么同样是 8MHz@115200,STM32 没事 AVR 就废;③ SPI 的 CPOL/CPHA 在波形上到底长什么样、配错时你会看到什么;④ I2C 上拉电阻按公式算出来(不是抄 4.7k)、7 位/8 位地址在解码器里怎么对得上;⑤ 一张 10 条的踩坑表 + 5 个"故意搞坏再抓波形"的动手练。
和《工具实践_03 示波器和逻辑分析仪》互补:那篇讲仪器本身怎么用,这篇讲波形怎么读,没看过那篇也不影响。
一、先说说我为什么开始用逻辑分析仪
大二下我调一块 SHT30 温湿度板,HAL_I2C_Master_Transmit() 一直返回 HAL_ERROR。我干了什么?
- 换 I2C1 → 不行
- 改成软件 I2C → 还是不行
- 把延时从 10ms 改到 100ms → 依然不行
- 怀疑传感器坏了,又买了一个 → 一样
前后耗了整整两个晚上。后来学长把他那个 100 块的 USB 逻辑分析仪插上,抓了一次,屏幕上明明白白写着:
START → Address write: 22 → NACK → STOP
我代码里明明按手册写的是 0x44,可解码器读出来的地址是 0x22------正好是 0x44 的一半 。原因是 STM32 HAL 要的是"左移一位之后"的值 ,我少移了那一位,总线上跑的地址就缩水成了 0x22,自然没有从机应答(正确应该传 0x88,细节见第五节)。整个问题 30 秒定位完。
那天我明白一件事:
通信调试里,"改代码试"是最慢的方法。示波器和逻辑分析仪不是"高级玩家的玩具",而是让你从"猜"切换到"看"的开关。
这篇就把 UART / SPI / I2C 三种最常见的通信,在波形上错成什么样、怎么一眼认出来 讲清楚。工具用开源免费的 sigrok / PulseView,硬件用几十块的 FX2 逻辑分析仪,学生党零门槛。
💡 为什么不用示波器?示波器强在看"模拟质量"(电平、过冲、纹波、上升沿),逻辑分析仪强在看"数字内容"(这一串 0/1 到底是什么字节)。通信不通,90% 先用逻辑分析仪抓协议;只有怀疑电平/上升沿这种模拟问题时才上示波器。 后面 I2C 上拉那节你会看到两者的交界处。
二、开抓之前,这 5 个概念必须懂
新手第一次插上逻辑分析仪,最常见的失败不是"抓不到",而是"抓到一堆看不懂的东西"。原因基本都出在下面这 5 个设置上。
| 概念 | 白话解释 | 设错的后果 |
|---|---|---|
| 采样率(Samplerate) | 每秒对引脚电平"拍照"多少次 | 太低 → 窄脉冲被漏掉,波形看着"好好的"但解码全错 |
| 存储深度 / 采样数 | 这次一共拍多少张照 | 太小 → 只抓到开头一丁点,错误发生在后面根本没录到 |
| 触发(Trigger) | 满足什么条件才开始录 | 没设 → 录到的全是空闲电平,真正出错那一瞬间没抓着 |
| pre-trigger 比例 | 触发点之前保留多少数据 | 设成 0% 会漏掉第一个起始位(下面细说) |
| 协议解码器(Decoder) | 软件替你把 0/1 翻译成字节/地址/ACK | 参数配错 → 解出来是乱码,你会误以为硬件坏了 |
2.1 采样率到底要多高?
课本上说的奈奎斯特(2 倍)在这里不够用。数字解码要的是"每一位都能踩准中间点",工程上的经验值是:
最低要求:采样率 ≥ 信号最高频率 × 4
建议值 :采样率 ≥ 信号最高频率 × 10
(这里的"信号频率":UART 就是波特率,I2C/SPI 就是 SCL/SCK 的时钟频率)
套到我们三种协议上(这个表建议直接背下来):
| 协议 | 典型速率 | 一位/一个时钟的时间 | 建议采样率 |
|---|---|---|---|
| UART 115200 8N1 | 115200 bps | 1 bit ≈ 8.68 µs | 1 MHz(≈ 8.7 点/位,够用;求稳 2 MHz) |
| UART 921600 | 921600 bps | 1 bit ≈ 1.085 µs | 8~12 MHz |
| I2C 标准模式 | 100 kHz | 半周期 5 µs | 1 MHz |
| I2C 快速模式 | 400 kHz | 半周期 1.25 µs | 4~8 MHz |
| SPI | 1 MHz | 半周期 500 ns | 8~16 MHz |
| SPI | 8 MHz | 半周期 62.5 ns | 24 MHz 已经紧张,建议换更快的设备 |
⚠️ 便宜的 FX2(CY7C68013A)逻辑分析仪,板上是 24 MHz 晶振 ,sigrok 里实际能稳定用的档位到 24 MHz 。淘宝页面上写"48M 采样"的多半是单通道/超频宣传,USB 2.0 的带宽在 8 通道 24 MHz 时已经很吃紧,容易丢样本。结论:用它抓 UART、I2C、8MHz 以下的 SPI 很舒服;抓 20MHz+ 的 SPI Flash 就别为难它了。
按上面的表反推一个常见翻车现场:你用 1 MHz 采样率去抓 8 MHz 的 SPI,一个 SCK 周期只有 0.125 个采样点------你看到的根本不是时钟,是随机噪声。很多人以为"SPI 打不通",其实是"逻辑分析仪没看清"。
2.2 存储深度:别只抓开头那 1ms
FX2 这类设备是流式传输到电脑的,所以"深度"取决于你设的采样数和 USB 带宽。实战建议:
需要的采样点数 ≈ 采样率 × 你想观察的时间长度
例:1 MHz 采样,想看 1 秒钟的 I2C 通信
→ 1,000,000 × 1 = 1M samples
抓周期性上报(比如每秒读一次传感器)时,至少抓 2~3 个完整周期,否则你不知道"第一次成功、后面全失败"这种典型现象。
2.3 触发:别再"点开始然后手忙脚乱按复位"
没有触发,你的操作是这样的:点"运行" → 冲过去按开发板复位 → 回来一看,波形早跑完了。
FX2 这类廉价设备没有硬件触发 ,sigrok 用的是软件触发:数据先流进电脑,由 libsigrok 在数据流里找条件,条件满足前的数据丢掉。够用,只是不能像专业设备那样零丢包。
PulseView 里点通道名旁边的触发图标就能设:
| 触发条件 | 图标含义 | 什么时候用 |
|---|---|---|
| 下降沿 ↓ | falling edge | UART 抓起始位(空闲是高,起始位拉低);I2C 抓 SCL 第一个下降沿 |
| 上升沿 ↑ | rising edge | 抓片选释放、抓中断引脚 |
| 低电平 0 | low level | SPI 抓 CS 拉低期间(最常用) |
| 高电平 1 | high level | 抓某根使能信号有效期 |
⚠️ 这是官方文档白纸黑字写的坑 :sigrok wiki 在 uart 解码器页面明确写着------如果你在 PulseView 里把 pre-trigger capture ratio 设成 0% ,解码器很可能漏掉第一个起始位 ,导致开头几个字节因帧错误显示成乱码;把 pre-trigger 比例设成 1% 就能解决。
我第一次抓 UART 就栽在这:前 3 个字节全是乱码,后面全对,还以为是 MCU 上电时钟没稳。不是板子的锅,是软件设置的锅。
2.4 阈值与接地(唯一两个"硬件"注意事项)
逻辑分析仪 被测板
┌─────────────┐ ┌──────────────┐
│ CH0 ──────┼──────────────┤ PA9 (TX) │
│ CH1 ──────┼──────────────┤ PA10 (RX) │
│ GND ──────┼──────────────┤ GND ← 必接! │
└─────────────┘ └──────────────┘
- GND 必须接,而且要接被测板的 GND。不接地你会抓到一堆随机跳变------这是新手第一天最常问的"为什么波形在乱抖"。
- 电平要匹配 。FX2(CY7C68013A)是 3.3V LVTTL 输入、5V 容忍 ,判"高"要求输入电压约 2.0V 以上 ,所以抓 3.3V 系统毫无压力;但抓 1.8V 系统就不行了 ------1.8V 的高电平根本够不着 2.0V 门限,你会看到"一直是低"或时高时低。抓 1.8V 请换支持可调阈值的设备(比如 DSLogic)。
2.5 软件选型:学生就用 PulseView
| 方案 | 价格 | 优点 | 缺点 |
|---|---|---|---|
| sigrok / PulseView + FX2 分析仪 | 软件免费开源,硬件 30~100 元 | 100+ 协议解码器;Win/Linux/Mac 全平台;sigrok-cli 可脚本化 |
只有软件触发;24 MHz 封顶 |
| Saleae Logic 2 + 正品 Saleae | 硬件较贵 | 软件体验最好,解码器丰富,分析仪自带存储 | 学生党预算劝退 |
| DSLogic + DSView | 200~400 元 | 阈值可调、深存储、比 FX2 强一档 | 生态不如前两者广 |
我的建议:先花几十块买 FX2 的 8 通道版,配 PulseView。 能把 UART/SPI/I2C 全抓明白,等你真的需要抓 50MHz QSPI 时,你已经知道自己要买什么了。
安装(Ubuntu/Debian):
bash
# 装 PulseView + 命令行工具 + FX2 固件
sudo apt install pulseview sigrok-cli sigrok-firmware-fx2lafw
# 让普通用户能访问设备(否则要 sudo 才能识别)
sudo usermod -aG plugdev $USER # 重新登录生效
# 插上设备后确认能识别
sigrok-cli --scan
# 期望输出类似:fx2lafw:conn=1.5 - sigrok FX2 LA (8ch) with 8 channels: D0 D1 ... D7
Windows 直接去 sigrok 官网下 PulseView 安装包,里面带 Zadig 帮你换 WinUSB 驱动。
三、UART 实战:乱码 ≠ 波特率一定错
UART 是最容易调、也是最容易"看起来玄学"的协议。先把它的样子刻进脑子里。
3.1 一帧 8N1 长什么样
┌──── 8 个数据位(LSB 先发)────┐
idle │ │ idle
───────────┐ ┌───┬───┬───┬───┬───┬───┬───┬───┐ ┌──────────
│ │ D0│ D1│ D2│ D3│ D4│ D5│ D6│ D7│ │
└──┘ │ │ │ │ │ │ │ └───┘
↑START ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ STOP
(低) ▲采样点在每一位的正中间 (高)
一位宽度 Tbit = 1 / 波特率 115200 → 8.68 µs
一帧 8N1 共 10 位:1 起始 + 8 数据 + 1 停止 = 86.8 µs
三个关键点,考试和面试都爱问:
- 空闲是高电平 ,起始位是低,所以接收方靠"下降沿"发现有数据来了。
- 数据位 LSB 先发 。所以你在波形上从左往右读到
0 1 0 0 0 1 1 0,实际字节是把它倒过来0110 0010=0x62='b'。新手 90% 第一次手工解码都读反了。 - 接收方是在每一位的正中间采样的------这就是下面波特率容差的全部来源。
3.2 波特率能差多少?先把这个数算明白
UART 没有时钟线,收发双方各用各的时钟,全靠"约定好的波特率"对齐。那么允许差多少?
理论上限很好推:接收方在检测到起始位下降沿后开始计时,最后一个要判定的位是停止位,它的采样点在 9.5 个位宽之后(1 个起始 + 8 个数据 = 9 个位宽,再加半个位宽到停止位中央)。只要累计漂移不超过半个位宽(0.5 Tbit),就还能采对:
累计漂移 = 9.5 × Tbit × 误差率 < 0.5 × Tbit
⇒ 误差率 < 0.5 / 9.5 = 5.26%
但 5.26% 是"双方合计、且一切理想"的天花板,千万别当设计指标用。 真实 UART 接收器还要做多数表决(一位内采 3 次投票)、还有采样时钟的量化误差。Microchip 的 AVR 数据手册给了官方公式(S=16 表示每位采 16 次,S_F=8、S_M=9 是投票用的采样序号,D 是"数据位+校验位"的个数,8N1 对应 D=8):
Rslow = (D+1)·S / (S−1 + D·S + S_F) = 9×16 / (15 + 128 + 8) = 144/151 = 95.36%
Rfast = (D+2)·S / ((D+1)·S + S_M) = 10×16 / (144 + 9) = 160/153 = 104.58%
⇒ 绝对上限约 +4.58% / −4.64%
⇒ 手册"推荐的最大接收误差"这一列,D=8 时只给 ±2.0%
记住两个数就够了:工程上按 ±2% 设计,超过 ±3% 就开始随缘,接近 5% 必炸。
3.3 关键修正:8MHz 跑 115200 到底行不行?要分平台
网上(包括我以前写的笔记)到处流传一句话:"8MHz 主频跑 115200 误差太大,会乱码。" 这句话只对了一半 ,害我误判过一次。真相是:误差大小取决于这颗 MCU 的波特率发生器有没有小数分频。
情况 A:STM32(有小数分频)------ 完全没问题
STM32 的分频值不是整数,而是带小数部分的定点数------这就是它和 AVR 的根本差别:
text
(以下是手算过程,不是可编译代码)
/* STM32F1/F4 写法,USART 时钟 f_CK = 8 MHz,OVER8 = 0(16 倍过采样) */
USARTDIV = f_CK / (16 × baud) = 8 000 000 / (16 × 115200) = 4.34
/* BRR 用 12 位整数 + 4 位小数来存它,也就是分频值的分辨率是 1/16 */
BRR = round(4.34 × 16) = 69 = 0x45 /* 尾数 4,小数 5/16 → 实际分频 4.3125 */
实际波特率 = 8 000 000 / (16 × 4.3125) = 8 000 000 / 69 = 115 942 bps
误差 = (115942 − 115200) / 115200 = +0.64% ← 远小于 ±2%,稳
/* F0/F3/L4/G0/H7 等新系列的手册换了个等价写法:BRR 直接 = f_CK / baud
= round(8 000 000 / 115200) = round(69.44) = 69 = 0x45,结果完全一样 */
情况 B:AVR / 老式 8051(只有整数分频)------ 真的会炸
ATmega328P 这类 AVR 的波特率寄存器 UBRR 只能取整数,分辨率是"一整个分频档":
text
(以下是手算过程,不是可编译代码)
/* ATmega328P,F_CPU = 8 MHz,U2X0 = 0(16 分频) */
UBRR = F_CPU / (16 × baud) − 1 = 8000000 / (16 × 115200) − 1 = 3.34
UBRR 只能取整 → 3
实际波特率 = 8000000 / (16 × (3+1)) = 125 000 bps
误差 = (125000 − 115200) / 115200 = +8.51% ← 超过 5.26% 天花板,必乱码
/* 打开倍速模式 U2X0 = 1(8 分频)能救一点 */
UBRR = 8000000 / (8 × 115200) − 1 = 7.68 → 取整 8
实际波特率 = 8000000 / (8 × 9) = 111 111 bps
误差 = −3.55% ← 勉强能通,但超过手册推荐的 ±2%,长报文/温漂时容易出错
⚠️ 所以正确的说法是 :不是"8MHz 不能跑 115200",而是"没有小数分频的 MCU 在 8MHz 下跑 115200 误差过大"。AVR 圈子里为什么爱用 7.3728MHz / 11.0592MHz 晶振?就是因为这些频率能被 115200 整除,误差直接为 0。STM32 有小数分频,用 8MHz 内部/外部时钟跑 115200 误差只有 +0.64%,你要是照搬这句话去改 STM32 的时钟树,就是白折腾。
3.4 PulseView 里配 UART 解码器
抓完波形,点右上角 "Add protocol decoder" → 选 UART,然后配这几项:
| 选项 | 填什么 | 说明 |
|---|---|---|
| RX / TX | 选对应通道 | 只抓单向就只配一个,另一个留空 |
| Baud rate | 115200 | 填代码里设的值,不是你猜的值 |
| Data bits | 8 | |
| Parity | none | 配错的现象见下一节 |
| Stop bits | 1.0 | |
| Bit order | lsb-first | UART 默认 LSB 先发,别改 |
| Sample point | 50(%) | 在位中间采样;信号边沿很糟时可微调 |
| Invert | no | 直连 MCU 引脚选 no;接 RS-232 电平(±12V 且逻辑反相)才要考虑 |
💡 不同版本的 libsigrokdecode 选项名可能微调(例如老版本的校验位选项叫
parity_type,新版本叫parity)。用sigrok-cli --show -P uart可以打印出你这版解码器的确切选项名和取值,比猜靠谱。
命令行党可以直接一条命令抓+解码,不用开图形界面:
bash
# 1 MHz 采样 D0 通道抓 200ms,直接按 115200 8N1 解码
sigrok-cli --driver fx2lafw --config samplerate=1M \
--channels D0 --time 200ms \
--protocol-decoders uart:rx=D0:baudrate=115200:data_bits=8:parity=none:stop_bits=1.0 \
--protocol-decoder-annotations uart=rx-data
3.5 三种 UART 错误在波形上的样子
① 波特率不匹配 ------ 靠"量最窄脉冲"反推真实波特率
不知道对方实际跑多少波特率时,有个万能招:在波形上找最窄的那一段高电平或低电平,它的宽度就是 1 个位宽(因为连续相同的位会连成一片,最窄的那段一定只有 1 位)。
用 PulseView 的游标量出来:最窄脉冲 = 8.68 µs
实际波特率 = 1 / 8.68µs ≈ 115 200 → 和代码一致,波特率没问题
如果量出来 = 6.94 µs
实际波特率 = 1 / 6.94µs ≈ 144 000 → 比 115200 快 25%
25% ≈ 常见时钟树配错的比例(比如 HSE 晶振写成 8MHz 实际是 10MHz,
或者 PLL 分频写错),去查 SystemClock_Config()
波特率差多少才乱码? 按 3.2 的结论:差 2% 以内基本无感;差 3~5% 时短帧偶尔对、长帧开始错(这最坑,会让你以为是"偶发干扰");差 5% 以上稳定乱码。
② 停止位/数据位配错 ------ 解码器报 framing error
波形本身是对的,但你在 PulseView 里把 Data bits 填成 7、或者 Stop bits 填成 2,解码器就会在该出现停止位的位置读到低电平,标出帧错误(framing error)。
反过来也一样:MCU 那边设了 8E1(带偶校验),你的解码器配 8N1 ,那校验位会被当成停止位读------校验位是 0 时就报帧错误,是 1 时"蒙对"。所以你会看到一半字节正常、一半字节报错的诡异现象。
💡 判断口诀:解码全乱 → 先怀疑波特率;解码"时对时错、错得有规律" → 先怀疑数据位/校验位/停止位配置。
③ 电平反了 ------ 整条线空闲是低
抓到的波形空闲时是低电平 ,那多半是你接在了 RS-232 的 DB9 上(RS-232 逻辑 1 是 −3~−15V,且和 TTL 反相),或者中间过了一级反相驱动。这种情况不能直接接逻辑分析仪(负电压会伤设备),必须先过 MAX3232 转成 TTL 再抓。
四、SPI 实战:CPOL/CPHA 配错,波形"看着都对"但数据全废
SPI 有时钟线,所以不存在波特率误差问题,但它有个更阴的坑:四种模式。
4.1 CPOL 和 CPHA 到底在说什么
- CPOL(时钟极性):SPI 空闲(CS 未选中)时,SCK 是低(CPOL=0)还是高(CPOL=1)。
- CPHA(时钟相位) :在时钟的第一个边沿 采样(CPHA=0),还是第二个边沿采样(CPHA=1)。
| 模式 | CPOL | CPHA | 空闲时 SCK | 采样边沿 |
|---|---|---|---|---|
| Mode 0 | 0 | 0 | 低 | 上升沿采样,下降沿输出 |
| Mode 1 | 0 | 1 | 低 | 下降沿采样,上升沿输出 |
| Mode 2 | 1 | 0 | 高 | 下降沿采样,上升沿输出 |
| Mode 3 | 1 | 1 | 高 | 上升沿采样,下降沿输出 |
画成波形(以 Mode 0 和 Mode 3 对比,这两个最常用):
Mode 0 (CPOL=0, CPHA=0):空闲低,第一个边沿=上升沿,在它上面采样
┌──┐ ┌──┐ ┌──┐ ┌──┐
SCK ─────────┘ └──┘ └──┘ └──┘ └────
↑ ↑ ↑ ↑
采样 采样 采样 采样 (都是上升沿)
MOSI ──┬─────────┬─────┬─────┬─────┬────
│ b7 │ b6 │ b5 │ b4 │
└─────────┴─────┴─────┴─────┴
↑ 数据必须在第一个上升沿"之前"就摆好
CS ──┐ ┌──
└────────────────────────────────┘
↑ CS 拉低后、第一个时钟前,MOSI 已有效
Mode 3 (CPOL=1, CPHA=1):空闲高,第一个边沿=下降沿(不采样),第二个边沿=上升沿采样
SCK ─────────┐ ┌──┐ ┌──┐ ┌──┐ ┌────
└──┘ └──┘ └──┘ └──┘
↑ ↑ ↑ ↑
采样 采样 采样 采样 (还是上升沿!)
看出来了吗?Mode 0 和 Mode 3 的采样边沿都是上升沿,只是空闲电平相反。这就解释了一个新手常疑惑的现象:
为什么很多 SPI 器件的手册写着"支持 Mode 0 和 Mode 3"? 因为对从机来说,它只关心"在哪个边沿把 MOSI 采进去"。Mode 0 与 Mode 3 的采样边沿等效(都是上升沿采样、下降沿换数据),从机做时序对齐时可以不管 SCK 空闲时停在高还是低。W25Q64 这颗最常见的 SPI Flash 就是典型:Mode 0 和 Mode 3 都支持,Mode 1/2 不支持。
⚠️ CPHA=0 的隐含要求 :既然第一个边沿就要采样,那数据必须在第一个时钟边沿到来之前的半个周期内就建立好 (也就是 CS 拉低之后、SCK 动之前)。硬件 SPI 会自动处理,但你写**软件模拟 SPI(bit-bang)**时最容易漏掉这一步:先给 SCK 边沿再摆 MOSI,从机采到的永远是上一位。
4.2 实战:用读 JEDEC ID 当"SPI 通不通"的试金石
调 SPI Flash(W25Q64 这类)时,第一件事永远不是读数据,而是读 ID。它只有 4 个字节,不涉及地址、不涉及擦写,最适合验证物理连接和模式:
c
/* STM32 HAL,W25Q64 读 JEDEC ID(命令 0x9F)
* 正确返回:0xEF 0x40 0x17
* 0xEF = Winbond 厂商 ID
* 0x40 = 存储器类型(SPI Flash)
* 0x17 = 容量码,2^0x17 = 8MB = 64Mbit → 所以叫 W25Q64
*/
uint8_t w25q_read_jedec_id(SPI_HandleTypeDef *hspi, uint8_t id[3])
{
uint8_t cmd = 0x9F;
HAL_StatusTypeDef st;
HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_RESET); /* CS 拉低 */
st = HAL_SPI_Transmit(hspi, &cmd, 1, 100); /* 100ms 超时,不要写 HAL_MAX_DELAY */
if (st == HAL_OK) {
st = HAL_SPI_Receive(hspi, id, 3, 100);
}
HAL_GPIO_WritePin(FLASH_CS_GPIO_Port, FLASH_CS_Pin, GPIO_PIN_SET); /* CS 拉高 */
return (st == HAL_OK) ? 0 : 1;
}
⚠️ 超时参数别写
HAL_MAX_DELAY。SPI 从机不响应时(比如 CS 接错),这个函数会永远阻塞,看门狗一复位你还以为是"死机"。所有外设调用都给一个有限超时,这是工程习惯。
四种结果对应四种病因,对着查:
| 读到的 ID | 病因 | 怎么确认 |
|---|---|---|
EF 40 17 |
一切正常 | 收工 |
00 00 00 |
MISO 一直是低 / 从机没供电 / GND 没共 | 抓波形看 MISO 是不是死在低电平 |
FF FF FF |
MISO 悬空(上拉/浮空)、CS 没拉低、接线接反 | 抓波形看 CS 有没有真的拉低 |
DE 80 2E 这种"整体左移一位" |
CPHA 配反了 | 见下面分析 |
为什么 CPHA 配反会"左移一位"? 因为主机在错误的边沿采样,等于把第一位漏掉了、后面每一位都往前挪一格:
从机实际输出的位流: 1110 1111 0100 0000 0001 0111 = EF 40 17
↑漏掉第一位
主机采到的位流: 110 1111 0 100 0000 0 001 0111 X
重新分组: 1101 1110 1000 0000 0010 111X = DE 80 2E/2F
(错位往哪个方向、末位补什么,取决于相位错的方向和总线空闲电平------反过来配错时会变成"重复采到第一位"。你不用记具体值,记住"能通但整体错位"这个特征就够了。)
看到"能通信、但数据整体错位",第一反应就是查 CPOL/CPHA。 这比一个个改配置试快得多。
4.3 CS 时序:另一个高频翻车点
正确时序:
CS ───┐ ┌───
└──────────────────────────────┘
SCK ──────┐┌┐┌┐┌┐┌┐┌┐┌┐┌┐┌────────────────
└┘└┘└┘└┘└┘└┘└┘└┘
↑ CS 拉低之后 SCK 才开始动
↑ 全部时钟走完,CS 才拉高
翻车 1:CS 还没拉低,SCK 就开始了
CS ──────────┐ ┌───
└───────────────────────┘
SCK ──┐┌┐┌┐┌┐┌┐┌┐┌┐┌───────────────────────
└┘└┘└┘└┘└┘└┘└┘
↑ 这几个时钟从机根本没在听 → 后面全部错位
翻车 2:一次传输中途 CS 被拉高又拉低
CS ───┐ ┌──┐ ┌───
└────────┘ └────────┘
↑ 从机认为"这次交易结束了",内部位计数器清零
→ 多字节读写(比如页写 256 字节)会从中间截断
翻车 2 特别隐蔽 :如果你在 HAL 里用了"每发一个字节就拉一次 CS"的封装(有些例程真这么写),单字节命令能通,一到多字节读写就崩。抓波形一眼就能看出来 CS 中间抬起来过。
4.4 PulseView 里配 SPI 解码器
SPI 要接 4 根线(CLK/MOSI/MISO/CS),解码器选项对应如下:
| 选项 | 填什么 | 备注 |
|---|---|---|
| CLK / MOSI / MISO / CS | 对应通道 | CS 强烈建议接上,否则解码器不知道从哪开始分字节 |
| cs_polarity | active-low | 绝大多数器件低有效 |
| cpol | 0 或 1 | 和你代码里 hspi.Init.CLKPolarity 一致 |
| cpha | 0 或 1 | 和 hspi.Init.CLKPhase 一致 |
| bitorder | msb-first | SPI 默认 MSB 先发,和 UART 相反,别搞混 |
| wordsize | 8 | 位宽,STM32 也能配 16 位 |
小技巧:如果你不确定器件是 Mode 0 还是 Mode 3,就把 CPOL 两个值都解一遍 ------哪个解出来是 EF 40 17,哪个就是对的。这比翻手册快。
五、I2C 实战:坑最多的协议,但也最容易看懂
I2C 只有两根线,却是三个协议里翻车率最高的------因为它是开漏 + 上拉结构,既有协议问题,又有电气问题。
5.1 一次完整的 I2C 写操作长这样
先看整体结构 (以主机向 7 位地址 0x44 的 SHT30 写 2 字节命令为例):
┌───────┬──────────────────────┬─────┬──────────┬─────┬──────────┬─────┬──────┐
│ START │ 7位地址 1000100 + W=0 │ ACK │ 数据 0x2C │ ACK │ 数据 0x06 │ ACK │ STOP │
└───────┴──────────────────────┴─────┴──────────┴─────┴──────────┴─────┴──────┘
8 个 SCL 时钟 第9个 8 个时钟 第9个 8 个时钟 第9个
(线上跑的就是 0x88) ↑从机拉低 SDA 表示"收到"
再看位级规则(三张图,把 I2C 的全部时序规矩讲完):
① 普通数据位:SCL 低时才能改 SDA,SCL 高时 SDA 必须稳定
┌──── SDA 可以在这段变化 ────┐ ┌── SDA 必须钉住不动 ──┐
SCL ───┐ ┌───────────────────────┐
└───────────────────────────────┘ └────────
(SCL = 低) (SCL = 高,接收方在这里采样)
② START / STOP:唯一允许"SCL 高时 SDA 跳变"的两个特例
START(起始) STOP(停止)
SDA ─────┐ SDA ┌──────
└────── ─────────────────┘
SCL ──────────── SCL ────────────────────
(SCL 保持高,SDA 由高变低) (SCL 保持高,SDA 由低变高)
③ ACK 位(每个字节后的第 9 个时钟)
SDA ────────┐ ┌──── ← 主机松手,线被上拉拉高
└─────────────┘ 从机把它拉低 = ACK(收到)
线上仍然是高 = NACK(没人应答)
SCL ──┐ ┌──────┐
└─────────┘ └─────── ← 这就是第 9 个时钟
其余三条要背下来的规则:
- 地址和数据都是 MSB 先发(和 SPI 一样,和 UART 的 LSB 先发相反)。
- 地址字节的最低位是 R/W:0 = 主机写,1 = 主机读。
- SDA/SCL 都是开漏:谁都能把线拉低,但没人能主动拉高------拉高全靠上拉电阻(5.4 节细说)。
5.2 7 位地址 vs 8 位地址:这是我踩过最贵的一个坑
I2C 规范里,从机地址是 7 位 ,加上最低位的 R/W 才凑成实际在线上传输的那 8 位。问题就出在**"手册写的是哪个"和"函数要的是哪个"经常不一致**。
以 SHT30 为例,数据手册写"I2C 地址 0x44",这是 7 位地址。实际在总线上跑的第一个字节是:
7 位地址 0x44 = 100 0100b
左移一位,最低位补 R/W:
写操作: 1000 1000b = 0x88
读操作: 1000 1001b = 0x89
STM32 HAL 的官方说法 (stm32xxxx_hal_i2c.c 函数注释原文):
"@param DevAddress Target device address: The device 7 bits address value in datasheet must be shifted to the left before calling the interface"
翻译过来:HAL 要你传"左移后"的 8 位值。所以:
c
/* ❌ 错误:直接把手册上的 0x44 传进去 → 总线上跑的是 0x22 + R/W,没有任何从机应答 */
HAL_I2C_Master_Transmit(&hi2c1, 0x44, cmd, 2, 100);
/* ✅ 正确:左移一位再传 */
#define SHT3X_ADDR_7BIT 0x44
#define SHT3X_ADDR_HAL (SHT3X_ADDR_7BIT << 1) /* = 0x88 */
HAL_I2C_Master_Transmit(&hi2c1, SHT3X_ADDR_HAL, cmd, 2, 100);
⚠️ 别的平台不一样,这才是真正混乱的根源 :Linux 的
i2c-dev、Arduino 的Wire.beginTransmission()、ESP-IDF 的i2c_master_bus_add_device()用的都是7 位地址 (库内部帮你移位);只有 STM32 HAL 要你自己移。换平台移植代码时,这是第一个要检查的地方。
那 PulseView 显示的是哪个? sigrok 的 i2c 解码器有个 address_format 选项,默认值是 shifted ,意思是它会把线上抓到的 8 位右移一位后 显示------也就是显示 7 位地址。所以:
PulseView 上显示: Address write: 44 ← 7 位地址
你代码里应该写的: 0x88 ← 左移后的值
两者差一位,都是对的,别以为解码器错了。
(想让它显示 0x88,把 address_format 改成 unshifted)
5.3 没有 ACK 时,按这张表查
抓到 START → 地址 → NACK → STOP,说明"没有任何从机认领这个地址"。可能性按概率排序:
| 序号 | 原因 | 怎么快速验证 |
|---|---|---|
| 1 | 地址没左移(STM32 HAL) | 看解码器显示的 7 位地址是不是手册值的一半 |
| 2 | 地址引脚(ADDR/A0A1A2)接错 | 很多器件靠引脚拉高/拉低选地址,量一下那几个脚的电平 |
| 3 | 从机没供电 / 没共地 | 万用表量从机 VDD 和 GND |
| 4 | SDA/SCL 接反了 | 抓波形:时钟线应该是规整方波,数据线不规整 |
| 5 | 从机还在忙(上电时间没到) | SHT30 上电后需要约 1ms 才能响应,加个 HAL_Delay(2) 试试 |
💡 万能第一招:先跑一次地址扫描。 从
0x08到0x77逐个发一次"写"并看有没有 ACK,把总线上真实存在的地址打印出来。有的话说明硬件通了,问题在你代码的地址;一个都扫不到,那就是硬件(供电/接线/上拉)。
c
/* I2C 总线扫描:把总线上真实存在的设备地址打出来(STM32 HAL) */
void i2c_scan(I2C_HandleTypeDef *hi2c)
{
printf("Scanning I2C bus...\r\n");
for (uint8_t addr7 = 0x08; addr7 <= 0x77; addr7++) {
/* 注意这里同样要左移;trials=1, timeout=10ms */
if (HAL_I2C_IsDeviceReady(hi2c, (uint16_t)(addr7 << 1), 1, 10) == HAL_OK) {
printf(" found device at 7-bit addr 0x%02X\r\n", addr7);
}
}
printf("Scan done.\r\n");
}
5.4 上拉电阻:别再无脑抄 4.7k,它是能算出来的
I2C 的 SDA/SCL 是开漏输出------器件只能把线拉低,拉高全靠外部上拉电阻。所以这个电阻小了拉不动(灌电流超标),大了上升沿太慢(来不及到高电平就又被拉低了)。
下限(保证能拉到足够低的低电平): I2C 规范要求从机灌电流能力 3 mA 时 V_OL 不超过 0.4V,于是
Rp(min) = (Vdd − V_OL) / I_OL = (3.3 − 0.4) / 3mA ≈ 967 Ω (3.3V 系统)
(5.0 − 0.4) / 3mA ≈ 1533 Ω (5V 系统)
所以 3.3V 系统别用小于 1kΩ 的上拉。
上限(保证上升沿够快): NXP 的 I2C 规范(UM10204)给的公式是
tr
Rp(max) = ───────────────── (0.8473 = ln(7/3),来自 30%→70% 的 RC 充电时间)
0.8473 × Cb
tr 是允许的最大上升时间,规范规定:
标准模式 100 kHz → tr ≤ 1000 ns
快速模式 400 kHz → tr ≤ 300 ns
快速模式+ 1 MHz → tr ≤ 120 ns
Cb 是总线电容(每个器件约 5~10pF,加上走线,面包板飞线很容易到 100~300pF)
代进去算两个真实例子:
例 1:4.7k 上拉 + 100pF 总线电容(正常的小板子)
tr = 0.8473 × 4700 × 100e-12 = 398 ns
→ 100 kHz 要求 ≤1000ns:✅ 合格
→ 400 kHz 要求 ≤ 300ns:❌ 超标!这就是"降到 100k 就好了"的真相
例 2:10k 上拉 + 300pF(面包板 + 长杜邦线 + 挂了好几个器件)
tr = 0.8473 × 10000 × 300e-12 = 2.54 µs
→ 连最宽松的 100 kHz(1000ns)都超了 2.5 倍:❌ 直接违规,八成通不了
反推一下 400kHz 该用多大:
Rp(max) = 300e-9 / (0.8473 × 100e-12) = 3541 Ω → 选 3.3k(标称值往下取)
再看下限 967Ω → 所以 400kHz@100pF 的合理区间是 1k ~ 3.3k
💡 经验值也给你 :3.3V/100kHz 用 4.7k;3.3V/400kHz 用 2.2k~3.3k;线长、器件多(Cb 大)就往小选。"4.7k 万能"只在 100kHz、短线、器件少的时候成立。
波形上怎么看出来上拉不合适? 这里有个诚实的提醒:
逻辑分析仪看到的(只有 0/1): 示波器看到的(真实电压):
╭───────
────┐ ┌──────── ────┐ ╱
└────────┘ └─────╱
↑ 边沿看起来还是"垂直"的 ↑ 慢悠悠的 RC 斜坡
逻辑分析仪只判 0/1,看不到斜坡本身 ,它只会表现为"高电平变窄了、时序整体后移了",严重时直接判成一直是低。要量真实的 tr,必须用示波器 (测 30%→70% 或 10%→90% 的时间)。这就是文章开头说的"逻辑分析仪看内容,示波器看质量"的分界线------能解码但偶尔出错、降速就好,八成是电气问题,该上示波器了。
5.5 时钟拉伸:从机说"等我一下"
I2C 允许从机在还没准备好数据时把 SCL 摁住不放 ,主机看到 SCL 没升上来就得等。这叫时钟拉伸(clock stretching)。
波形上非常好认------某一个 SCL 低电平特别长:
SCL ──┐ ┌─┐ ┌─┐ ┌─┐ ┌───────────────────────┐ ┌─┐ ┌──
└─┘ └─┘ └─┘ └─┘ └─┘ └─┘
└── 从机摁住不放(可能几 ms)──┘
主机在这里必须等
SHT3x 正好可以拿来做对比实验,它的单次测量命令有两套:
| 命令 | 含义 | 主机该怎么写 |
|---|---|---|
0x2C 0x06 |
高重复性,启用时钟拉伸 | 发完命令直接读 6 字节,从机会拉住 SCL 直到测完 |
0x24 0x00 |
高重复性,不拉伸 | 发完命令后自己延时(高重复性约 15ms)再去读,读早了从机 NACK |
c
/* 方式 A:用时钟拉伸(0x2C06),STM32 作主机时硬件会自动等 */
uint8_t cmd_stretch[2] = {0x2C, 0x06};
HAL_I2C_Master_Transmit(&hi2c1, SHT3X_ADDR_HAL, cmd_stretch, 2, 100);
HAL_I2C_Master_Receive(&hi2c1, SHT3X_ADDR_HAL, buf, 6, 100); /* 从机会拉住 SCL 让你等 */
/* 方式 B:不用拉伸(0x2400),自己等 */
uint8_t cmd_nostretch[2] = {0x24, 0x00};
HAL_I2C_Master_Transmit(&hi2c1, SHT3X_ADDR_HAL, cmd_nostretch, 2, 100);
HAL_Delay(20); /* 高重复性约 15ms,留点余量 */
HAL_I2C_Master_Receive(&hi2c1, SHT3X_ADDR_HAL, buf, 6, 100);
⚠️ 两个坑 :
① 软件模拟 I2C 必须自己处理拉伸 ------拉高 SCL 之后要回读 SCL 引脚确认它真的变高了 再继续(还要给个超时,别死等),否则从机还摁着你就往下发,数据全乱。用 STM32 的硬件 I2C 作主机时不用管 ,它天然会等 SCL 真正释放;HAL 里那个
NoStretchMode(NOSTRETCH 位)只在从机模式下有意义,主机模式必须保持清零 (I2C_NOSTRETCH_DISABLE,也是 CubeMX 默认值)。② 有些主控不支持拉伸 (部分 SoC、某些 USB-I2C 转接器)。这种情况就必须用
0x2400+ 自己延时那套。选哪套,取决于你的主机能力,不是"哪个更高级"。
六、新手必踩的 10 个坑(我基本都踩过)
| # | 坑 | 现象 / 波形上看到什么 | 正确做法 |
|---|---|---|---|
| 1 | "8MHz 跑 115200 误差过大"一刀切 | 在 STM32 上照这句话去改时钟树,白折腾半天 | 分平台 :STM32 有小数分频,8MHz@115200 的 BRR=69,误差仅 +0.64% ,没问题;AVR 这类无小数分频 的,UBRR 取整后误差 +8.51% 才真的会乱码(开 U2X 也还有 −3.55%)。AVR 请换 7.3728/11.0592MHz 晶振 |
| 2 | SPI 的 CPHA 搞反 | 能通信,但读回来的字节像是"整体左移一位"(EF 40 17 读成 DE 80 2E) |
看到"错位型"错误先查 CPOL/CPHA。记住 Mode 0 与 Mode 3 采样边沿等效(都是上升沿采),所以器件常常两个都支持,Mode 1/2 反而不行 |
| 3 | I2C 忘接上拉电阻 | SDA/SCL 一直是低或飘忽,扫描一个设备都扫不到 | 开漏必须外部上拉。3.3V 系统 100kHz 用 4.7k、400kHz 用 2.2~3.3k。有些模块板载已带上拉,多个模块并联会让等效阻值变小,反而要拆掉几个 |
| 4 | 上拉阻值凭感觉抄 4.7k | 100kHz 能通,一提到 400kHz 就错 | 按公式算:Rp(max)=tr/(0.8473×Cb),Rp(min)=(Vdd−0.4)/3mA≈967Ω@3.3V。4.7k+100pF 的 tr=398ns,100k 合格但 400k(限 300ns)超标 |
| 5 | 采样率不够 | 波形看着"挺规整",解码却全是乱码或干脆没结果 | 采样率 ≥ 信号频率 ×4(建议 ×10)。FX2 便宜卡最高约 24MHz,别拿它抓 20MHz+ 的 SPI |
| 6 | 触发没设对 | 点了运行,抓到满屏空闲电平,真正出错那一瞬间没录到 | UART 用下降沿 触发(起始位);SPI 用 CS 低电平触发;I2C 用 SCL 下降沿。FX2 是软件触发,够用 |
| 7 | pre-trigger 设成 0% | UART 前几个字节乱码、报帧错误,后面全对 | sigrok 官方明说的坑:把 pre-trigger 比例调到 1% 就好。别去怀疑 MCU 上电时钟 |
| 8 | I2C 地址没左移(STM32 HAL) | START → 地址 → NACK → STOP,解码显示的 7 位地址正好是手册值的一半 |
STM32 HAL 要左移后的 8 位值(手册 0x44 → 代码写 0x88)。Linux/Arduino/ESP-IDF 反而要 7 位原值 |
| 9 | 忘了接 GND / 抓 1.8V 系统 | 波形随机乱跳,或同一根线一会儿判 1 一会儿判 0 | GND 必接被测板地;FX2 是 3.3V LVTTL 输入(判高约需 2.0V),1.8V 系统请换可调阈值设备(如 DSLogic) |
| 10 | UART 手工解码把位序读反 | 手动数出来的字节和解码器对不上 | UART LSB 先发 ,波形从左往右读完要倒过来 ;SPI/I2C 是 MSB 先发。三者别混 |
一份可以贴在桌上的"通信不通"固定动作
1. 接线:GND 共地?电平匹配?(30 秒)
2. 抓:采样率 = 速率×10,触发设对,pre-trigger 1%
3. 看物理层:线上有没有波形?空闲电平对不对?
└─ 没波形 → 引脚复用/时钟没开/CS 没动 → 回去查 GPIO 和 RCC
4. 看协议层:加解码器,参数照代码填
└─ 全乱 → 波特率/模式错
└─ 半乱 → 数据位/校验/停止位错
└─ 错位 → CPOL/CPHA 错
└─ NACK → 地址错 / 从机没活
5. 还不行 → 上示波器看上升沿、看电平幅度(电气问题)
这套动作跑一遍,通信类问题 90% 能定位。 剩下 10% 才是真正值得熬夜的东西。
七、动手练(重点是"故意搞坏",坏过一次你就永远记得)
看十遍不如自己抓一次。下面 5 个练习按顺序做,一个下午能全跑完。
练习 1:把 UART 波特率故意配错 5%,看它怎么烂掉
c
/* STM32 CubeMX 生成的 MX_USART1_UART_Init() 里改这一行 */
huart1.Init.BaudRate = 120960; /* = 115200 × 1.05,误差正好 +5.00%
已经贴到 5.26% 的理论天花板,是 ±2% 工程红线的 2.5 倍 */
- 让 MCU 循环发
"Hello, embedded!\r\n",PC 串口助手仍然设 115200。 - 逻辑分析仪抓 TX 引脚,采样率 1MHz,下降沿触发,pre-trigger 设 1%。
- UART 解码器填 115200 (模拟接收方视角)→ 观察:越靠后的字节错得越离谱、时不时报帧错误,因为误差是累积的。
- 把解码器改成 120960 → 立刻全部正常。这一步能让你彻底明白"波特率不匹配"到底错在哪一位上。
- 解码器固定回 115200 不动 ,只把发送端逐步改成 118656(+3%)、117504(+2%)、116352(+1%),看从哪个点开始不再出错------你会亲手测出那条 ±2% 的工程红线。
练习 2:把 I2C 上拉电阻拆掉,看上升沿怎么塌
- 先在正常状态(外部 4.7k 上拉)抓一次 SHT30 通信,记下解码结果
Address write: 44 → 2C → 06 → ACK。 - 拆掉外部上拉 ,只留 STM32 GPIO 的内部弱上拉(约 40kΩ)。按公式估一下:
tr ≈ 0.8473 × 40k × 100pF ≈ 3.4 µs,是 100kHz 规范上限(1000ns)的 3 倍多。 - 再抓一次:逻辑分析仪上你会看到高电平"变窄"甚至消失、ACK 判读失败;有示波器的话切过去看,SDA 上升沿是一条明显的 RC 斜坡。
- 把 I2C 速率从 100kHz 降到 10kHz 再试------大概率又能通了。这就是"降速能好"背后的物理原因:位周期变长,斜坡有时间爬到高电平。
- 换回 4.7k,收工。
⚠️ 练习 2 只是短时间实验,别让总线长期工作在违规状态。另外别把上拉接到 5V:除非两端器件都明确 5V 容忍,否则电流会经 3.3V 器件的 ESD 保护二极管倒灌进电源,轻则跑飞重则烧片。
练习 3:把 SPI 的 CPHA 故意配反,认出"错位"特征
c
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_2EDGE; /* 本该是 1EDGE(Mode 0),故意改成 Mode 1 */
抓 SCK/MOSI/MISO/CS 四根线,读 W25Q64 的 JEDEC ID。你会看到:波形完全正常、CS 时序也对、就是数据不是 EF 40 17 。然后在 PulseView 里把解码器的 cpha 从 0 改成 1 再解一次------同一段波形解出两个不同结果,这一刻你就真懂 CPHA 了。
练习 4:对比 I2C 时钟拉伸开/关
分别用 0x2C 0x06(拉伸)和 0x24 0x00 + HAL_Delay(20)(不拉伸)读一次 SHT30,各抓一次波形,对比 SCL 上有没有那个"特别长的低电平"。再故意把不拉伸那版的延时改成 HAL_Delay(2)(不够 15ms),看从机怎么用 NACK 拒绝你。
练习 5:验证 pre-trigger 那个坑
同一段 UART 通信,分别用 pre-trigger 0% 和 1% 各抓一次,对比开头几个字节。亲眼看过一次,以后你就不会再冤枉 MCU 了。
八、小结
| 你想查什么 | 用什么 | 关键动作 |
|---|---|---|
| 这串 0/1 到底是什么字节 | 逻辑分析仪 + 协议解码器 | 采样率 ≥ 速率×10,触发 + pre-trigger 1% |
| 波特率对不对 | 逻辑分析仪量最窄脉冲 | 波特率 = 1 / 最窄脉冲宽度;工程红线 ±2% |
| SPI 数据错位 | 逻辑分析仪,同一段波形换 cpol/cpha 重解 | Mode 0 / Mode 3 采样边沿等效 |
| I2C 没应答 | 先跑地址扫描,再看解码的 7 位地址 | STM32 HAL 要左移;PulseView 默认显示 7 位 |
| 上升沿慢不慢、电平够不够 | 示波器(逻辑分析仪看不到斜坡) | Rp(max)=tr/(0.8473×Cb)、Rp(min)≈967Ω@3.3V |
一句话收尾:调通信别再靠改代码碰运气。花几十块买个逻辑分析仪,把"猜"换成"看",这大概是嵌入式路上性价比最高的一笔投资。
文中的上拉电阻公式、波特率误差、各类芯片的时序参数都来自对应 Datasheet 和官方规范(NXP I2C、Winbond、Sensirion 等),选型时以你手上的版本为准。
下一篇:调试排错_03_栈溢出与内存踩踏定位