1. CAN总线物理层波形观测与位时间参数测量

CAN总线物理层波形观测与位时间参数测量 学习卡片

任务说明

本案例通过 CAN 分析仪(或示波器)采集 CAN_H、CAN_L 与差分电压,测量显性/隐性电平、位时间、边沿时间,并计算波特率误差。核心是:把采集到的原始采样点转换为可判定的位时间与波特率,并给出误差判定。

环境与依赖

  • Python 3.8+,仅用标准库(csv、statistics)。
  • 输入:CSV 文件,列 t_us,can_h,can_l,时间单位微秒,电压单位伏特。
  • 采集方式:示波器导出 CSV,或 CAN 分析仪(如 PCAN-View、CANoe、USB-CAN 工具)导出带 CAN_H/CAN_L 的采样数据。

测量原理

CAN 差分电压 Vdiff = CAN_H - CAN_L。显性位 Vdiff 约 2V,隐性位约 0V。用阈值 0.9V 判定逻辑电平,统计连续同电平采样段得到位时间,取所有位时间中位数作为位时间估计,波特率 = 1 / 位时间。误差 = (实测波特率 - 标称波特率) / 标称波特率。

采集CSV 计算Vdiff 阈值判定电平 提取位时间 中位数→波特率 误差=(实测-标称)/标称

完整代码

import csv, statistics, sys

V_TH = 0.9          # 显性/隐性判定阈值(V)
NOMINAL_BAUD = 500000  # 标称波特率

def load(path):
    rows = []
    with open(path, newline='') as f:
        for r in csv.DictReader(f):
            rows.append((float(r['t_us']), float(r['can_h']), float(r['can_l'])))
    return rows

def measure(rows, nominal=NOMINAL_BAUD):
    # 1. 差分电压与逻辑电平
    samples = [(t, h - l) for t, h, l in rows]
    levels = [(t, 1 if v >= V_TH else 0) for t, v in samples]

    # 2. 显性/隐性电平统计
    dom = [v for _, v in samples if v >= V_TH]
    rec = [v for _, v in samples if v < V_TH]

    # 3. 提取连续同电平段作为位时间
    bit_times = []
    start = levels[0][0]
    cur = levels[0][1]
    for t, lv in levels[1:]:
        if lv != cur:
            bit_times.append(t - start)
            start, cur = t, lv
    if len(bit_times) < 2:
        raise ValueError("采样点不足,无法提取位时间")

    bit_t = statistics.median(bit_times) * 1e-6  # us -> s
    baud = 1.0 / bit_t
    err = (baud - nominal) / nominal

    # 4. 边沿时间:10%~90% 上升/下降
    def edge_time(idx):
        v0, v1 = samples[idx-1][1], samples[idx][1]
        lo, hi = min(v0, v1), max(v0, v1)
        t10 = t90 = None
        for t, v in samples[idx-1:idx+1]:
            if t10 is None and v >= lo + 0.1*(hi-lo): t10 = t
            if t90 is None and v >= lo + 0.9*(hi-lo): t90 = t
        return None if t10 is None or t90 is None else abs(t90 - t10)

    edges = [edge_time(i) for i in range(1, len(samples))
             if (samples[i][1] >= V_TH) != (samples[i-1][1] >= V_TH)]
    edges = [e for e in edges if e is not None]

    return {
        "dominant_v": statistics.mean(dom) if dom else None,
        "recessive_v": statistics.mean(rec) if rec else None,
        "bit_time_us": bit_t * 1e6,
        "baud": baud,
        "error_pct": err * 100,
        "edge_us": statistics.mean(edges) if edges else None,
    }

if __name__ == "__main__":
    path = sys.argv[1] if len(sys.argv) > 1 else "can_wave.csv"
    res = measure(load(path))
    print(f"显性电平: {res['dominant_v']:.3f} V")
    print(f"隐性电平: {res['recessive_v']:.3f} V")
    print(f"位时间:   {res['bit_time_us']:.3f} us")
    print(f"波特率:   {res['baud']:.0f} bps")
    print(f"波特率误差: {res['error_pct']:.3f} %")
    print(f"平均边沿时间: {res['edge_us']:.3f} us" if res['edge_us'] else "边沿时间: 无")

运行与验证

python can_measure.py can_wave.csv

CSV 示例(每行一个采样点):

t_us,can_h,can_l
0,2.5,2.5
1,3.5,1.5
2,3.5,1.5
3,2.5,2.5

判定标准:显性电平约 2V、隐性约 0V;位时间与标称位时间(500kbps 为 2us)接近;波特率误差绝对值小于 0.5% 视为通过。若误差超限,检查采样率是否足够(建议 ≥ 10 倍波特率)以及阈值设置。