1. CAN FD总线物理层波形观测与眼图分析

CAN FD总线物理层波形观测与眼图分析 学习卡片

任务目标

用示波器(或差分探头)捕获 CAN FD 总线 CAN_H/CAN_L 差分信号,测量显性/隐性差分电平、上升/下降时间,并基于多次位跳变叠加生成眼图,评估信号质量。本案例给出可复现的 Python 分析脚本:输入示波器导出的差分波形 CSV,输出电平、边沿时间与眼图 PNG。

环境与依赖

  • Python 3.9+,依赖:numpy、matplotlib
  • 示波器导出 CSV:两列(时间 s、差分电压 V),或单列差分电压加固定采样率
  • 建议采样率 ≥ 1 GSa/s,带宽 ≥ 500 MHz,使用差分探头或 CH1-CH2 数学运算

测量原理

CAN FD 差分电压 Vdiff = CAN_H − CAN_L。显性位(逻辑 0)差分约 1.5~3 V,隐性位(逻辑 1)差分约 0 V。上升/下降时间按 10%~90% 幅度定义。眼图通过在每个位边界处对齐、叠加多个位窗口得到,用于观察抖动与噪声裕量。

CAN FD 差分波形观测与眼图分析流程 示波器捕获 CAN_H/CAN_L 差分运算 Vdiff=H−L 电平/边沿测量 显性/隐性/tr/tf 位边界对齐 叠加生成眼图 输出:眼图 PNG + 测量报告 判定要点 • 显性电平 1.5~3 V,隐性 ≈ 0 V • 上升/下降时间 < 位时间的 10% • 眼图张开度越大,噪声裕量越好

核心代码

import numpy as np
import matplotlib.pyplot as plt

def load_waveform(path, fs=None):
    data = np.loadtxt(path, delimiter=',', skiprows=1)
    if data.ndim == 1:
        v = data
        t = np.arange(len(v)) / fs
    else:
        t, v = data[:, 0], data[:, 1]
    return t, v

def measure_levels(v):
    vmax, vmin = np.percentile(v, 99), np.percentile(v, 1)
    vth = (vmax + vmin) / 2
    dominant = v[v > vth].mean()
    recessive = v[v < vth].mean()
    return dominant, recessive, vth

def edge_times(t, v, vth, dominant, recessive):
    hi = recessive + 0.9 * (dominant - recessive)
    lo = recessive + 0.1 * (dominant - recessive)
    tr_list, tf_list = [], []
    for i in range(1, len(v)):
        if v[i-1] < lo and v[i] >= hi:
            tr_list.append(t[i] - t[i-1])
        elif v[i-1] > hi and v[i] <= lo:
            tf_list.append(t[i] - t[i-1])
    return np.array(tr_list), np.array(tf_list)

def eye_diagram(t, v, vth, bit_time, n_traces=200):
    idx = np.where(np.diff((v > vth).astype(int)) != 0)[0]
    if len(idx) < 2:
        return None, None
    edges = t[idx]
    span = int(bit_time / (t[1] - t[0]))
    traces = []
    for e in edges[:n_traces]:
        i0 = np.searchsorted(t, e)
        if i0 + span < len(v):
            traces.append(v[i0:i0+span])
    if not traces:
        return None, None
    traces = np.array(traces)
    x = np.linspace(0, bit_time, traces.shape[1])
    return x, traces

if __name__ == "__main__":
    t, v = load_waveform("canfd_diff.csv", fs=1e9)
    dom, rec, vth = measure_levels(v)
    tr, tf = edge_times(t, v, vth, dom, rec)
    print(f"显性电平: {dom:.3f} V, 隐性电平: {rec:.3f} V")
    print(f"上升时间均值: {tr.mean()*1e9:.2f} ns, 下降时间均值: {tf.mean()*1e9:.2f} ns")

    bit_time = 1e-6  # 1 Mbps 数据段位时间
    x, traces = eye_diagram(t, v, vth, bit_time)
    if traces is not None:
        plt.figure(figsize=(6, 4))
        for tr_ in traces:
            plt.plot(x * 1e9, tr_, color='steelblue', alpha=0.15)
        plt.axhline(dom, color='r', ls='--', lw=0.8)
        plt.axhline(rec, color='g', ls='--', lw=0.8)
        plt.xlabel("时间 (ns)"); plt.ylabel("差分电压 (V)")
        plt.title("CAN FD 眼图")
        plt.tight_layout(); plt.savefig("canfd_eye.png", dpi=150)
        print("眼图已保存: canfd_eye.png")
    else:
        print("边沿不足,无法生成眼图")

运行与验证

  1. 将示波器导出的差分波形保存为 canfd_diff.csv(首行表头,两列:时间、电压)。
  2. 运行 python canfd_analysis.py。
  3. 观察终端输出:显性电平应落在 1.5~3 V,隐性电平接近 0 V;上升/下降时间应小于位时间的 10%。
  4. 检查生成的 canfd_eye.png:眼图张开度越大表示信号质量越好;若眼图闭合或边沿时间过长,说明终端匹配、线缆长度或驱动能力存在问题。

通过条件:脚本无报错运行,输出电平与边沿时间数值合理,眼图 PNG 成功生成且可见清晰的张开区域。