信号完整性与串阻源端匹配实战:一条 25MHz 时钟线上的振铃,逼我把 33Ω 的位置和取值都算了个明白

2026年10月2日 0 By admin

先给结论,免得有人白看几千字:

  1. 串阻(源端串联匹配)不是”压振铃的万金油”,而是把驱动器输出阻抗补到传输线特征阻抗上的一次阻抗匹配。取值就是 R_s = Z0 − Z_drv,位置必须贴驱动端。
  2. 要不要上它,先做传输线判据:走线单向延时 t_pd 小于信号上升时间的 1/6 时,线本来就”看不见反射”,加串阻只会把边沿拖慢、时序余量吃掉。
  3. 串阻真正的效果不是”把边沿变缓”,而是把源端的二次反射吸收掉。负载端看到的是”延迟约 t_pd 的完整边沿”,源端看到的是”半高台阶,2·t_pd 后补满”。
  4. 最容易翻车的两个场景:负载端是大电容(排线/多器件),以及已经做了终端匹配还想再串一个。这两种情况加串阻是负收益。

下面是完整的落地过程。

一、现象:一块”看天气”的板子

出问题的是个采集板:STM32 主机通过 SPI 读一颗外部 ADC,25MHz 时钟,走线在 4 层板上从主控拉到板子另一头的连接器,长约 80mm,微带。从连接器再经 20mm 排线进 ADC 小板。

症状很有代表性:

  • 常温台面上跑一天,偶尔出现一次数据异常(幅值跳一大截,或者通道错位)。
  • 进高低温箱,低温端(−20°C)几乎分钟级复现,常温基本不复现。
  • 换一块板子,症状一模一样 —— 说明是设计问题,不是器件问题。
  • 降速到 10MHz,问题消失。

“降速就没事”这句话基本就把方向指死了:跟边沿速率有关,跟逻辑功能无关。这类问题十有八九是信号完整性。

探头上去一看(这是关键的一步,没有这一步后面全是猜):

  • 时钟线上升沿约 0.9ns,过冲到 5.1V(3.3V 供电)。
  • 过一个过冲之后还有清晰的振铃,周期约 2.4ns,大约衰减三四个周期。
  • 接收端(ADC 引脚)波形更难看:台阶、双沿、局部低于 0V 的下冲。

到这里,问题已经从”数据偶尔错”变成了”这条线的边沿在 0.9ns 内跑完,而它长 80mm”。

二、先把概念摆正:源端串阻到底在干什么

这一节值得单独拎出来,因为我在现场听过的错误理解实在太多。

先把两个前置概念压缩成一句话:

  • 上升时间是”信号自己的事”:t_r 由驱动器输出级决定,3.3V 的 MCU GPIO 在最高速度档下做到 0.5~1.5ns 很常见。
  • 线是”有记忆的”:信号沿导线传播的速度约 6.2 ps/mm(FR-4 微带),80mm 就是 0.5ns。信号沿还没走完,驱动器的边沿早就结束了 —— 这时候线上跑的是波,不是电压。

反射的判据就是那个反射系数:

  • Γ = (Z_L − Z0) / (Z_L + Z0)

接收端是 CMOS 高阻输入,Z_L 近似无穷大,Γ ≈ +1。也就是说:波到接收端之后全反射回来,回到驱动端,驱动端看到自己的输出阻抗 Z_drv 和线阻抗 Z0 不相等,又产生一次反射,来回弹,这就是振铃。振铃周期 = 4 × t_pd(往返两次)。

源端串阻做的事只有一件:让驱动端看出去的等效源阻抗等于 Z0。

        R_s                Z0, t_pd
   +-----/\/\-----+=====================+------+
   |              |                     |      |
 [Z_drv]          |                     |    Z_L = open
   |              |                     |      |
  GND            GND                   GND    GND

   源端看到的阻抗 = Z_drv + R_s,让它等于 Z0

一旦 R_s + Z_drv = Z0,波回到源端时看到的负载就是完美的 Z0,Γ = 0,不产生二次反射。接收端的第一次全反射仍然存在(开路端无法消除),但因为不再有后续反射叠加,波形在接收端只出现一次台阶,振铃消失。

顺带记住源端匹配的波形特征,不然会上示波器时看不懂:

观测点 无串阻(源端低阻,未匹配) 加了正确串阻
驱动端波形 干净(自己看的自己的电压) 先到半高,停 2·t_pd,再补满
接收端波形 过冲 + 振铃 + 台阶 延迟约 t_pd 的单次完整边沿
振铃周期 4·t_pd 基本消失
额外延时 0 约 t_pd(纳秒级,量级很小)

那个”源端半高台阶”是很多人第一次加串阻时最慌的东西:”我明明串了个电阻,怎么驱动端波形变成两台阶了?”——这正是匹配成功的标志。

三、先判断要不要当传输线看:一个判据一张表

串阻不是必贴件。判断规则我固定用这条(偏保守):

走线单向延时 t_pd 大于信号上升时间 t_r 的 1/6 时,按传输线处理,做源端匹配。

(也有人用 1/4 ~ 1/3,那是更宽松的口径。我选 1/6,因为板子后面还要过温、过压、器件离散性,余量留着不亏。微带取 6.2 ps/mm,内层带状线取 6.8 ps/mm。)

按这条规则换算成”允许的走线长度”:

驱动器上升时间 t_r t_r / 6 临界线长(微带 6.2 ps/mm) 典型信号
0.5 ns 83 ps 13 mm 高速 GPIO、SDIO、QSPI
1.0 ns 167 ps 27 mm 大多数 MCU 推到 20~50MHz 的外设
2.0 ns 333 ps 54 mm 中低速外设、SPI 10MHz 档
5.0 ns 833 ps 134 mm 低速 IO、按键、继电器控制
10 ns 1.67 ns 269 mm 基本不用管

这张表解释了为什么”同一颗芯片,A 板没事 B 板出事”:走线长度差了 30mm,就跨过了判据的那条线。

回到本案:t_r = 0.9ns,阈值 0.15ns,线长 80mm → t_pd = 0.5ns。超了三倍多,必须匹配。 这也就解释了为什么降到 10MHz 会好:频率低了,接收端的采样发生在振铃衰减之后,边沿质量不再是问题;但波反射本身一直在。

四、取值:R_s = Z0 − Z_drv,以及 Z_drv 到底怎么测

公式一行就够:

R_s = Z0 − Z_drv

难点全在 Z_drv。规格书上根本不给这个参数,能查到的 IOL/VOL 是规格下限,反推出来的阻抗严重偏大(3.3V 下常见反推值 50~112Ω),照它算出来的串阻会偏小。我的做法是实测。

4.1 实测 Z_drv:分压法

方法很简单:让 GPIO 输出固定电平,在引脚上对地(或对电源)接一个已知电阻,测节点电压,用分压比反推输出阻抗。

高电平侧: Z_OH = R_load × (V_open − V_node) / V_node
低电平侧: Z_OL = R_load × V_node / (V_open − V_node)

V_open = 空载时实测的电平(用高阻万用表或示波器直流档)
R_load = 已知电阻(220~470Ω,取太小时引脚电流会超限)

几个必须注意的点,我当初全部踩过:

  • R_load 别太小。 330Ω 接在 3.3V 上大约是 10mA,短时间测没问题;33Ω 就是 100mA,直接超 MCU 引脚能力,不仅测不准还可能伤片子。
  • 高低电平要分开测。 CMOS 输出级上下管不对称,实测常见高电平侧 40~60Ω、低电平侧 20~35Ω。匹配时我们关心的是边沿期间的等效阻抗,实务上取两侧的平均值,并预留一档。
  • 拉低测高电平侧,拉高测低电平侧。 接到对侧去(比如对地拉高电平侧),测出来的才是这个方向上的分压。
  • 连续翻转测不准。 用直流静态测,不要在翻转中读数。

实测那颗 STM32 的 GPIO(最高速度档,3.3V):

侧 V_open 接 330Ω 后 V_node 反推 Z_drv
高电平侧 3.28 V 2.94 V 38Ω
低电平侧 8 mV 0.27 V(对 VDD 接 330Ω) 29Ω

取平均约 33Ω,再考虑温度和工艺离散(−20°C 时边沿更快、等效阻抗更低),我按 20Ω 这个偏小值来算串阻,这样算出的 R_s 偏大一点,更稳:

R_s = 50 − 20 = 30Ω  →  取标准档 33Ω

4.2 用脚本把三件事一次算完

手算容易漏项,我写了个小工具,把”判据 + 取值 + 边沿余量”一次算完(文末附完整代码):

$ python si_series_r.py --z0 50 --zdrv 20 --len 80 --tr 1.0 --cl 15 --freq 25

== 1. 需不需要当传输线看 ==
单向延时 t_pd = 0.50 ns ; 判据阈值 t_r/6 = 0.17 ns
结论: 是 -- 长度超过阈值,必须做源端匹配

== 2. 串阻取值 ==
Z0 - Z_drv = 50.0 - 20.0 = 30.0 ohm
理论值 30.0 ohm -> 取标准档: 33 ohm

== 3. 边沿余量(短走线+大电容场景)==
充电电阻 = Z_drv + R_s = 53 ohm
时间常数 = 53 ohm x 15 pF = 0.80 ns ; 到 90% 约 1.83 ns
半周期 = 20.00 ns ; 边沿占用 9%
结论: 余量充足

注意第 3 项:串阻会增加负载端的充电时间常数,这是它的代价,必须在取值时就一起算。本案 33Ω + 15pF 只占半周期的 9%,代价可以忽略。

4.3 取值的三条纪律

  • 别超过 Z0。 R_s + Z_drv 明显大于 Z0 时,进线波幅不足半高,负载端会出现”先到不足幅、再补齐”的台阶,边沿反而更差。
  • 向上取一档。 30Ω 取 33Ω,而不是取 27Ω。Z_drv 会随温度、供电电压变化,宁可稍微过阻尼。
  • 取不到就保留 0Ω。 板子上先贴 0Ω 或者干脆空焊盘,出板后用波形决定。所有高速单端信号(时钟、SPI CLK/MOSI、SDIO CMD、地址、片选)驱动端都预留串阻焊盘,这是我现在的默认动作,成本几乎为零。

五、位置:为什么必须贴驱动端

这一条是串阻最常见的实施错误。串阻放在走线中间,或者放在接收端,效果完全不同。

  • 贴驱动端:串阻与驱动器的输出级合起来构成 Z0,从”源头”就是匹配的,整条线都在匹配状态下工作。串阻到驱动引脚之间的引线要极短(< 5~10mm),这段是唯一的不连续段。
  • 放中间:前半段(驱动→串阻)仍是未匹配的低阻源,反射照旧在那一段产生;后半段又被串阻变成了”高阻源驱动”。结果是两头都没匹配好,还可能引入新的阻抗台阶。
  • 放接收端:这是我最想强调的误区。串阻串在高阻输入的接收端,不构成任何阻抗匹配——开路端的全反射系数仍然是 +1。它唯一的作用是给接收端的输入电容加了个 RC 低通,波形”看起来”干净了,但线上的反射一点没少,而且把边沿拖慢了。这种改法在示波器上常常”看着有效”,实际是拿时序余量换来的假象。

具体布局要求,我按这几条做:

  • 串阻放在驱动器引脚后面第一个器件位,中间不打孔、不分叉、不并行走线。
  • 串阻前后不共享其他网络的走线(防止串扰注入到匹配段)。
  • 0402 的电阻寄生电感约 0.5~1nH,在 500MHz 时阻抗不到 3Ω,可以忽略;真正的问题是引线长度,所以别为了”好看”把串阻摆到离引脚 20mm 的地方。

六、反面教材:这几种情况加了反而更糟

这一节是本次踩坑的另一个收获。串阻不是免费午餐。

6.1 负载端是大电容

走线短但有大量负载电容(长排线、多器件挂一根线、TVS 结电容堆叠),此时起主导作用的是 RC,不是波反射。串阻直接加进充电回路,把边沿拖慢。把本案的负载电容从 15pF 换成一个 100pF 的排线,速率提到 50MHz,还是盲目取 100Ω:

$ python si_series_r.py --z0 50 --zdrv 20 --len 80 --tr 1.0 --cl 100 --freq 50 --rs 100

== 3. 边沿余量(短走线+大电容场景)==
充电电阻 = Z_drv + R_s = 120 ohm
时间常数 = 120 ohm x 100 pF = 12.00 ns ; 到 90% 约 27.60 ns
半周期 = 10.00 ns ; 边沿占用 276%
结论: 不够 -- 减串阻、降速,或把负载电容降下来

到 90% 需要 27.6ns,而半周期只有 10ns。这种配置下串阻不是”优化”,是”自杀”。正确做法是降串阻、降速率,或者加缓冲器把一根线拆成几段。

6.2 已经做了终端匹配

并联端接(50Ω 到地)或戴维南端接会把线拉到 Z0,这时候再串一个 33Ω,负载端的稳态幅度直接被分压器压下去(开路输入无直流电流,但端接电阻有):

  • 33Ω 串阻 + 50Ω 端接:负载幅度 ≈ 50 / (20 + 33 + 50) × VDD ≈ 0.49 VDD

一半的摆幅没了。源端匹配与终端匹配选一种,不要叠加。 电池供电设备基本不考虑并联端接:3.3V/50Ω 常态耗 66mA,还没算信号翻转的功耗。

四种端接方式的取舍,我做成了对照表,选之前先看一眼:

方案 接法 匹配点 静态功耗 适用场景
源端串联 R_s ≈ Z0 − Z_drv,贴驱动端 源端 仅跳变瞬间 点对点、单向、单负载(时钟/SPI/片选),首选
并联端接 R = Z0 到 GND 或 VDD 负载端 大(VDD/Z0 常态电流) 高速、多负载、驱动能力强
戴维南端接 R1 + R2 分压到 Z0 负载端 中等 多负载总线,代价是常态电流
AC 端接 R + C 到 GND 负载端 仅跳变瞬间 周期性时钟;数据信号不适用(码型间隔长时会被 C 拉住)
二极管钳位 到 VDD/GND 不改阻抗 小 只抑制过冲,不解决反射

6.3 其他三个不适合加串阻的场景

  • 线短于判据:见第三节的表。加了只是白吃边沿和成本。
  • 驱动源本身很弱(Z_drv 已接近或超过 Z0):常见于板上已经很长的 I2C 上拉、或者被过度驱动的 GPIO。再串阻会做出台阶。
  • 开漏总线(I2C 等):串阻在这里的角色完全变了,不是做阻抗匹配,而是限流、隔离、抑制振铃。取值口径也不同:10~100Ω 比较常见;超过 220Ω 就要重新核两件事 —— 下降沿变慢(R × Cb,100Ω × 200pF ≈ 20ns)和低电平抬升(上拉电流 × R,4.7k 上拉时 100Ω 约抬 66mV)。这一点在《上拉/下拉电阻取值实战》里已经算过账,本篇不重复。

七、量化验证:别靠眼睛说”好像好了”

这是我最想推荐的一个习惯:用固件把误码率打出来。示波器波形是定性的,”从有振铃到没振铃”很容易看出来,但”是否真的不再丢数据”必须用数字说话。

测试代码非常短,关键是把上层所有重试逻辑关掉,否则错误会被重试藏起来:

/* 串阻效果量化:连续读 100000 次器件 ID,不重试,直接统计不符预期次数
 * 前提:关掉 SPI 层的重试、关掉 CRC 重传兜底,让错误暴露出来 */
#define ID_LOOP      100000u
#define DEV_ID_WANT  0x1E5Au

static uint32_t si_error_count(void)
{
    uint32_t err = 0;

    for (uint32_t i = 0; i < ID_LOOP; i++) {
        uint32_t id = 0;
        /* 每次都重新发起一次完整读,不使用任何缓存/重试 */
        (void)spi_read_reg(REG_DEV_ID, (uint8_t *)&id, 2);
        if (id != DEV_ID_WANT) {
            err++;
        }
    }
    return err;      /* 返回 0 才是真的干净 */
}

/* 上层一次性调用并打印,用定点格式,不用 %f */
static void si_verdict(void)
{
    char buf[48];
    uint32_t e = si_error_count();
    /* err 与 LOOP 已知,直接整数打印 */
    snprintf(buf, sizeof(buf), "SI: err=%lu / %lu\r\n",
             (unsigned long)e, (unsigned long)ID_LOOP);
    log_write(buf);
}

改前改后的数字(同一块板,−20°C 箱内,25MHz,跑 3 轮取最差):

配置 不符预期次数 / 100000 过冲峰值 振铃 上升时间
无串阻(0Ω) 137 5.10 V 明显,周期 2.4ns 0.90 ns
22Ω 24 4.05 V 减弱 1.05 ns
33Ω 0 3.48 V 基本消失 1.30 ns
47Ω 0 3.35 V 无 1.55 ns
100Ω 0 3.32 V 无 2.40 ns(开始吃掉时序余量)

这张表里最值得看的是最后一行:47Ω 和 100Ω 虽然也不丢数据了,但上升时间从 0.9ns 涨到 2.4ns,时序余量被白白吃掉。“不丢数据”不等于”改到位”,取到匹配值就该停手。

顺带一个收益:边沿从 0.9ns 变缓到 1.3ns,时钟的高频谐波幅度跟着下来,辐射余量也好了一些。这一点在《PCB 布局与 EMC 实战》里提过,串阻在这里是”一石二鸟”——抑制反射的同时顺便降了 EMI。

八、示波器怎么测才可信:别把探头自己的振铃当成电路的

我在这上面白折腾了半天:换了三种串阻,波形上那点振铃死活不消失,最后发现那是探头地线自己造的。

用 10:1 无源探头、6cm 鳄鱼夹地线去量 1ns 边沿,会得到两个假象:

  • 地线电感谐振:6cm 地线约 60nH,配探头 10~15pF 输入电容,谐振频率约 1 / (2π√(LC)) ≈ 170MHz —— 正好落在你要观察的振铃频段。你会看到一段”看起来很像反射”的振荡,其实它来自探头。
  • 探头负载效应:10pF 挂在快速边沿节点上,本身就会造成下冲和边沿变缓。

我在用的口径:

  • 接地弹簧代替鳄鱼夹地线(长度 < 1cm,谐振推到 400MHz 以上)。
  • 探头带宽至少是信号带宽的 3 倍。0.9ns 边沿对应带宽 0.35 / 0.9ns ≈ 390MHz,所以 1GHz 探头才舒服,500MHz 勉强。
  • 测接收端波形时,探头就接在接收引脚上,不要”接在附近的分支上”——差几毫米,波形就不是那个波形了。
  • 触发用单次 + 余辉,或者用无限余辉看振铃包络;平均模式会把随机抖动带来的信息抹平,判断反射时不要用。
  • 先在源头量:驱动端(串阻之后)、串阻之前、接收端,三个点都看一遍,才能确认串阻是否真的在起作用。

另外,找反射点有个很实用的技巧:用振铃周期反推反射路径长度。

振铃周期 T = 4 × t_pd = 4 × (d × 6.2 ps/mm)

→  d(mm) ≈ 41.7 × T(ns)

本案实测 T = 2.4ns → d ≈ 100mm。我的 PCB 走线 80mm + 排线 20mm = 100mm。对上了,说明二次反射的路径就是整条链路,不是某个中间过孔。这个技巧在排查”反射点到底在哪”时非常省时间:T = 1.2ns → 50mm,那就去 50mm 附近找阻抗不连续的地方(过孔、连接器、分叉)。

九、串阻还有两个附带作用,顺手用上

  • 限流保护:串阻在热插拔、接口误插、ESD 事件时限制流入引脚或负载的电流。这在《ESD/浪涌防护实战》里讨论过,串阻是”成本最低的一层缓冲”,但它不能替代 TVS。
  • 多分支拓扑的分支隔离:一根线带多个负载(地址总线、片选)时,在每个分支处串阻可以削弱分支之间的相互反射。代价是每个分支的充电时间常数变大(本分支的 R_s + 分支电容),所以只适合地址/片选这类低速信号,不适合数据和时钟。

最后再说一次落地建议:把所有高速单端信号在驱动端预留串阻焊盘。这次整改最省事的做法不是重新画板,而是板子上本来就有 0Ω 焊盘,换成 33Ω 就完事了。如果没预留,就得动刀割线、飞电阻,评审会上很难看。

十、12 项信号完整性自查清单

下次画板 / 整改之前,对着这张表过一遍:

  • ☐ 列出所有边沿时间 < 2ns 的信号,标出它们的走线长度
  • ☐ 对每条这样的线做传输线判据(t_pd > t_r / 6?),而不是凭感觉
  • ☐ 确认 Z0 是按实际叠层算出来的(不是”我们一向做 50Ω”),微带 6.2 / 带状线 6.8 ps/mm
  • ☐ Z_drv 实测过(分压法,高/低电平两侧分开测),不是照抄 datasheet 的 IOL/VOL
  • ☐ 串阻按 R_s = Z0 − Z_drv 取值,向上取一档,宁愿略过阻尼
  • ☐ 串阻在驱动端,不是走线中间、不是接收端
  • ☐ 串阻到驱动引脚的走线 < 10mm,中间不打孔、不分叉
  • ☐ 检查负载电容:长排线 / 多器件 / TVS 结电容都算进来,算过 R×C 的边沿占用
  • ☐ 没有在已经端接的线上再串电阻(源端匹配与终端匹配不叠加)
  • ☐ 用固件统计过误码率(关掉重试兜底),而不是只看波形”好像干净了”
  • ☐ 测量链路可信:接地弹簧、探头带宽 ≥ 3× 信号带宽、探头接在接收引脚上
  • ☐ 边沿变缓之后,重新核过建立/保持余量(串阻不是免费的)

十一、下一篇

串阻解决的是”点对点、单负载、单向”这一类信号。它背后有个前提:你知道 Z0 是多少。而 Z0 是从叠层和线宽算出来的——这一层,很多项目是直接甩给板厂的,图纸上只写一句”阻抗 50Ω 控制”。

下一篇讲:阻抗控制与叠层设计实战。微带和带状线的阻抗公式怎么快速估,4 层板怎么选叠层才能让 50Ω 落在好画的线宽上,参考平面不连续(跨沟、换层)到底会带来多大的反射,以及怎么在板厂不给阻抗测试报告的情况下自己验一验。


附:串阻取值小工具完整代码

#!/usr/bin/env python3
"""串阻取值 + 传输线判据 + RC 边沿余量,一次算完。

用法:
  python si_series_r.py --z0 50 --zdrv 20 --len 80 --tr 1.0 --cl 15 --freq 25
"""
import argparse

# 手头常备的串阻档(比完整 E24 更贴近实际贴料)
STD_R = [0, 10, 15, 22, 27, 33, 39, 47, 56, 68, 82, 100]


def pick_std(v):
    for r in STD_R:
        if r >= v:
            return r
    return STD_R[-1]


def main():
    p = argparse.ArgumentParser()
    p.add_argument("--z0", type=float, default=50.0)    # 传输线特征阻抗
    p.add_argument("--zdrv", type=float, default=20.0)  # 驱动器输出阻抗(实测)
    p.add_argument("--len", type=float, default=80.0)   # 走线长度 mm
    p.add_argument("--tr", type=float, default=1.0)     # 驱动器上升时间 ns
    p.add_argument("--cl", type=float, default=15.0)    # 负载端总电容 pF
    p.add_argument("--freq", type=float, default=25.0)  # 信号频率 MHz
    p.add_argument("--psmm", type=float, default=6.2)   # 微带6.2 / 带状线6.8 ps/mm
    p.add_argument("--rs", type=float, default=None)    # 强制指定串阻(做对比用)
    a = p.parse_args()

    t_pd = a.len * a.psmm / 1000.0                      # ns
    need = a.z0 - a.zdrv
    rs = a.rs if a.rs is not None else pick_std(max(need, 0))

    print("== 1. 需不需要当传输线看 ==")
    print("单向延时 t_pd = %.2f ns ; 判据阈值 t_r/6 = %.2f ns" % (t_pd, a.tr / 6))
    if t_pd > a.tr / 6:
        print("结论: 是 -- 长度超过阈值,必须做源端匹配")
    else:
        print("结论: 否 -- 短于阈值,串阻只当保险/EMC 手段用,别指望它救波形")

    print("\n== 2. 串阻取值 ==")
    print("Z0 - Z_drv = %.1f - %.1f = %.1f ohm" % (a.z0, a.zdrv, need))
    if a.rs is not None:
        print("手工指定 R_s = %d ohm(应为 %d ohm,这里在故意做对比)"
              % (rs, pick_std(max(need, 0))))
    else:
        print("理论值 %.1f ohm -> 取标准档: %d ohm" % (need, rs))
    if need <= 0:
        print("注意: Z_drv 已 >= Z0,驱动器本身够弱,再加串阻会把边沿拖出台阶")

    print("\n== 3. 边沿余量(短走线+大电容场景)==")
    r_tot = a.zdrv + rs
    tau = r_tot * a.cl / 1000.0                         # ns
    t90 = 2.3 * tau
    half = 500.0 / a.freq                               # ns
    print("充电电阻 = Z_drv + R_s = %.0f ohm" % r_tot)
    print("时间常数 = %.0f ohm x %.0f pF = %.2f ns ; 到 90%% 约 %.2f ns"
          % (r_tot, a.cl, tau, t90))
    print("半周期 = %.2f ns ; 边沿占用 %.0f%%" % (half, 100.0 * t90 / half))
    if t90 < half * 0.3:
        print("结论: 余量充足")
    elif t90 < half * 0.5:
        print("结论: 偏紧,需实测复核建立时间")
    else:
        print("结论: 不够 -- 减串阻、降速,或把负载电容降下来")

    print("\n== 4. 反向验证 ==")
    print("若示波器测出振铃周期 T,反射点距离 d(mm) ~= 41.7 x T(ns)")


main()

相关阅读: