第3章 USB电气特性:差分信号、D+/D-线、终端电阻与信号编码
USB能跑高速、抗干扰、支持热插拔,底层靠的是一套精密的电气设计。说白了,物理层要是没做好,协议栈再牛也白搭。我早年调一个USB摄像头驱动,死活枚举不过,最后拿示波器一抓——D+线上的终端电阻虚焊了。嗯,从那以后我养成了先查电气特性的习惯。
3.1 差分信号:为什么USB不用单端传输?
USB用两根线传数据:D+和D-。这不是浪费引脚,而是故意为之。单端信号(比如UART的TX/RX)容易受共模噪声干扰——地弹、电源纹波、隔壁信号串扰,都会让电平判断出错。
差分信号的核心思路是:传差值,不传绝对值。接收端只看D+减去D-的结果:
- D+ > D- → 判为逻辑1(差分电压 > 200mV)
- D+ < D- → 判为逻辑0(差分电压 < -200mV)
外部噪声同时叠加到D+和D-上,相减后噪声被抵消。这就是共模抑制。我在一个电机驱动项目里遇到过强干扰环境,单端信号根本没法看,换成差分后波形干净得像教科书。
关键指标:USB 2.0 差分电压摆幅为 400mV(峰峰值),接收阈值 ±200mV。低于这个范围,接收器可能误判。
3.2 D+/D-线:速度识别与设备插入检测
USB主机怎么知道插了个什么设备?靠D+/D-上的上拉/下拉电阻。设备端在D+或D-上接一个1.5kΩ上拉电阻到3.3V,主机端在两根线上各接一个15kΩ下拉电阻到地。
插上设备前,D+和D-都被主机拉低。插入后:
- 全速设备:在D+上拉 → D+变高,主机检测到“设备已连接”
- 低速设备:在D-上拉 → D-变高,主机识别为低速
- 高速设备:先以全速模式握手,再通过Chirp序列切换到高速
我见过不少新手把全速和高速的上拉电阻搞混。全速设备上拉在D+,高速设备在初始阶段也是D+上拉,但进入高速模式后会断开这个上拉。你想想看,如果固件里没处理好这个切换,设备就会一直卡在全速模式。
| 速度模式 | 上拉位置 | 上拉电阻 | 主机检测方式 |
|---|---|---|---|
| 低速 (1.5 Mbps) | D- | 1.5kΩ → 3.3V | D-被拉高 |
| 全速 (12 Mbps) | D+ | 1.5kΩ → 3.3V | D+被拉高 |
| 高速 (480 Mbps) | D+(初始) | 1.5kΩ → 3.3V(高速后断开) | Chirp握手后切换 |
避坑:我曾经调试一块USB Hub板,D+上拉电阻焊成了10kΩ,结果主机偶尔能识别、偶尔不行。示波器一看,D+上升沿太慢,刚好卡在主机检测窗口边缘。1.5kΩ是硬性要求,别自作聪明改阻值。
3.3 终端电阻:信号完整性的守护者
USB总线上为什么要有终端电阻?因为信号在线上跑,遇到阻抗不连续点就会反射。反射回来的信号叠加在原信号上,轻则抖动,重则误码。
USB 2.0 的特性阻抗是 90Ω(差分),对应单端约45Ω。终端电阻的作用就是匹配这个阻抗,吸收掉反射能量。
具体配置:
- 主机端:D+和D-各接一个45Ω电阻到地(串联终端)
- 设备端:D+和D-各接一个45Ω电阻到地(同样是串联终端)
- 全速/低速模式:主机端还有15kΩ下拉电阻,用于空闲状态电平维持
我调过一块高速USB采集卡,眼图总是闭合的。查了半天,发现PCB走线阻抗只有75Ω,终端电阻却还是45Ω。阻抗不匹配,反射严重。后来把走线改到90Ω,眼图一下就睁开了。
个人习惯:画USB PCB时,我会在D+/D-走线旁边留一排地过孔,方便调整阻抗。另外,终端电阻尽量靠近芯片引脚,别隔着老远放——反射会在中间来回弹。
3.4 信号编码:NRZI与位填充
USB不直接用NRZ(不归零)编码,而是用NRZI(不归零反转)。区别在哪?NRZ用高低电平表示0和1,NRZI用电平是否跳变来表示:
- 逻辑0:电平翻转
- 逻辑1:电平保持不变
为什么要这么折腾?因为NRZI能减少电平跳变次数,降低EMI辐射。但有个问题:如果连续传输多个1,电平就一直不变,接收端没法恢复时钟。
解决方案是位填充:发送端在连续6个1之后,强制插入一个0。这样接收端至少每7个bit就能看到一次跳变,时钟恢复不会丢。
举个例子,原始数据是 111111,位填充后变成 1111101(插入了一个0)。接收端看到连续6个1后,自动丢弃第7个bit(那个填充的0),还原原始数据。
// 位填充示例(C语言伪代码)
uint8_t bit_stuffing(uint8_t data, int *stuff_count) {
uint8_t stuffed = 0;
int ones = 0;
for (int i = 0; i < 8; i++) {
if (data & (1 << i)) {
ones++;
stuffed |= (1 << i);
if (ones == 6) {
// 插入一个0(实际是移位操作)
stuffed <<= 1;
ones = 0;
(*stuff_count)++;
}
} else {
ones = 0;
stuffed |= (0 << i);
}
}
return stuffed;
}
注意:位填充只在数据包阶段生效。SYNC、SOP、EOP这些包定界符不参与位填充。我见过有人把SYNC里的连续1也算进去,结果解码全乱套。
3.5 知识结构总览
下面这张图把USB电气特性的核心逻辑串起来了。从差分信号到终端匹配,再到NRZI编码和位填充,每一步都是环环相扣的。
3.6 实战中的常见陷阱
讲几个我踩过的坑,你们遇到类似问题可以少走弯路:
- D+/D-走线不等长:差分对要求等长,误差控制在5mil以内。不等长会导致信号到达时间不同,共模抑制失效。我见过一块板子差了30mil,眼图直接闭合。
- 终端电阻位置不对:电阻要放在总线末端,不是中间。USB是多点总线,每个节点的终端电阻都要靠近连接器。
- 位填充溢出:如果数据里连续1太多,位填充会插入大量0,导致包长度超标。USB规范要求位填充后的包不能超过8192字节,否则接收端会丢弃。
- 上拉电阻供电不稳:设备端的1.5kΩ上拉接的是3.3V,如果这个电压纹波大,D+/D-电平会抖动。我建议用LDO单独供电,别从VBus取电。
调试技巧:拿示波器看D+/D-波形时,记得用差分探头。单端探头测出来的波形是相对于地的,看不到真正的差分摆幅。没有差分探头?可以用两个通道做数学减法,但要注意探头延迟要匹配。