标签: CAN总线

  • 2025 Ford F-150 Gen14 的 HS-CAN3 车身信号接入

    结论:这份档案记录的是一台 2025 款福特 F-150(第 14 代)上的车身 CAN 接入实践:车主没有从 OBD 诊断口取数,而是接到 HS-CAN3(500 kbit/s) 上的 C3154A(音响功放)连接器,用 ESP32 板读取床灯、门锁、变速箱 P 挡与电池状态,并反过来驱动床灯与工具箱继电器。它的价值不在「有多少条解码」,而在于一条不改原厂线束的取数路径:具体车身报文的逐字节定义并不在这份文档里,而是引用自公开的 ford_lincoln_base_pt.dbc。下述结论仅限源文档记录的这一台车,不能外推到其他年款、配置或市场版本。

    车辆与适用范围

    源文档是一位车主对自己车辆的改造记录,属于单台车、单人实验,不是车厂或第三方机构的批量采集。文档明确声明「不提供任何支持」,并反复警告接线错误可能危及生命。

    项 内容
    车型 Ford F-150(Generation 14,2025 款),Lariat V8
    年款 / 市场版本 2025 款;源文档未给出市场版本,仅说明为 501A Mid 配置包(无车内氛围灯、无 Bed Utility Package)
    ECU / 改装状态 原厂未改动线束;作者以尾插(pigtail)转接件接到 C3154A,追求「可完全还原」
    采集跨度 源文档未给出日志数或帧数;固件附带 49 个原生单元测试
    适用范围 仅限这一台 2025 Gen14 F150;不同配置的车身电路差异很大(例:尾门独立锁电路只存在于低配车型)

    总线与寻址

    源文档从原厂维修手册得到各条总线的走向,最终选定 HS-CAN3 作为取数总线。物理接入走 C3154A:HS-CAN3 High 在 1 脚(22ga 绿-蓝线),HS-CAN3 Low 在 11 脚(22ga 白-绿线),连接器为 Molex 34728-0200;相邻的 C3154B 提供常电(SBA40,40 号保险)与接地。

    项 内容
    物理层 双绞线 CAN,HS-CAN3 高/低线;需 120Ω 终端
    链路层 / 比特率 500 kbit/s(源文档明示 HS-CAN3 at 500kbps)
    寻址方式 源文档未给出 11 位 / 29 位说明;报文的逐字节定义引用自外部 DBC
    CAN FD 源文档提到曾用某款双通道 CAN FD HAT 抓包,但未说明车辆是否运行 CAN FD;实际按 500 kbit/s 经典 CAN 处理
    接入硬件 AutoSport Labs ESP32-CAN-X2(ESP32-S3 + 双收发器 + 车规电源),Arduino 框架 + PlatformIO

    已解码信号

    这份主素材没有给出任何逐字节报文 ID,它记录的是「这台车被读取/被控制的功能清单」以及对应的硬件引脚。为守住「只抄源文档里有证据的条目」这条线,下表登记源文档明确写出的功能与位置,涉及具体 ID、字节与缩放的条目一律指向被引用但未包含在本快照内的 DBC。

    信号 / 功能 总线或位置 方向 证据
    床灯(CHMSL bed lights)状态 HS-CAN3 只读 README「Features」
    迎宾灯 / 水坑灯(puddle lamp)状态 HS-CAN3 只读 README「Features」
    车门锁状态 HS-CAN3 只读 README「Features」
    变速箱 P 挡状态 HS-CAN3 只读 README「Features」
    蓄电池状态 HS-CAN3 只读 README「Features」
    床灯继电器输出 GPIO5(BEDLIGHT_PIN) 写 README「Configuration」
    工具箱开启继电器 GPIO4(TOOLBOX_OPENER_PIN) 写 README「Configuration」
    工具箱解锁按钮 GPIO17(TOOLBOX_BUTTON_PIN) 读 README「Configuration」
    系统就绪指示 GPIO18(SYSTEM_READY_PIN) 写 README「Configuration」
    CAN 缓冲溢出计数 MCP2515 / TWAI 诊断 诊断 README「Buffer Overflow Detection」
    TWAI 驱动状态 运行时诊断 诊断 README「can_status」
    RX / TX 错误计数(<128 为健康) 运行时诊断 诊断 README「can_status」

    读取方法与注意事项

    接入顺序在源文档里有明确交代:先在手套箱后方的在线连接器上反探测做初步实验,因为车尾拖车连接器处在车辆未运行时「似乎不广播有用报文」。确定 HS-CAN3 承载所需信号后,才改到 C3154A 做正式取数。取数用尾插转接件,不切断原厂线束;文档强调压接与焊点都要做拉拔测试。

    软件侧,固件通过串口(115200)提供诊断命令:can_status 看驱动状态与错误计数,can_debug 连续 10 秒打印全部报文,can_buffers 看缓冲溢出,can_reset 做恢复。若完全收不到报文,文档建议先把 ENABLE_HARDWARE_CAN_FILTERING 置 0 再编译烧录,以排除过滤配置问题。

    一个明确的工程约束是缓冲深度:源文档指出 MCP2515 的硬件缓冲只有 2 条报文,会丢帧。文档给出的缓解方向包括把主循环延时(当前 10ms)调小、把 MAX_MESSAGES_PER_LOOP(当前 10)调大,或改用 ESP32 的 TWAI 控制器。溢出会表现为「繁忙期报文突然停止」「日志出现 Suspected buffer overflow」「静默后出现突发」等。

    证据等级与边界

    本档案的信号清单属于单车主记录:只有一台 2025 Gen14 F150 Lariat V8(501A Mid)作为样本。总线走向、连接器针脚、GPIO 分配这些属于实车观测与手册交叉,可核对;而「车身报文里门锁、床灯具体是哪个 ID、哪些字节」在本文档里没有答案,它来自作者引用的公开 DBC,属于外部旁证,本项目并未逐条复现。

    三处不能外推。第一,配置差异会改变电路:源文档明确说只有低配车型才为尾门保留独立锁电路,而带电动尾门的高配由尾门内模块控制——同型号不同配置不得套用。第二,接入点与线序是针对这台车枚举出来的,其他年款/市场的 HS-CAN3 连接器编号与针位可能不同。第三,作者本人声明不提供任何支持,并警告可能失去保修、损坏车辆甚至造成人身伤害,因此任何照搬都超出源文档的证据范围。

    关于「安全访问」类问题:源文档未提供任何绕过方法,且不建议尝试;本文只整理公开文档登记的可读/可控功能,不涉及对车辆安全机制的规避。

    常见问题

    2025 F-150 的 OBD 口能直接抓到这些车身信号吗?

    源文档没有走 OBD 诊断口,而是接到 HS-CAN3 的 C3154A(音响功放)连接器。OBD 口在本车是否可读这些车身报文,源文档未给出结论,因此不能据此推断。

    为什么接上后收不到报文?

    文档给出的排查顺序是:先确认 CAN 高低线序与 120Ω 终端,再把 ENABLE_HARDWARE_CAN_FILTERING 置 0 用 can_debug 看总线上是否有任何流量,然后用 can_status 看错误计数,用 can_buffers 看是否溢出。若在车尾拖车连接器处取数,车辆未运行时可能本就没有有用报文。

    床灯和门锁的 CAN 报文 ID 是什么?

    源文档未给出具体 ID。它说明所需报文定义来自公开的 ford_lincoln_base_pt.dbc,本档案不代填字节与 ID。

    这套接入会不会影响保修?

    作者明确警告可能失去保修、烧毁车辆甚至危及生命,并声明不提供任何支持。本档案只是对公开文档的整理,不构成任何实车操作建议。

    延伸阅读

    参考资料

    • 项目 01 —— public-can-dbc-datasets/additional/jantman-ford-f150-gen14/README.md(本项目车型档案主来源:车辆信息、硬件、接线、固件与诊断说明;本快照仅含此文件)。

    说明:本文整理自第三方开源项目的公开文档,仅代表该文档所记录的单个车辆样本,不代表同型号全系车型。CAN 与诊断操作存在风险,任何实车操作前请自行复核并遵守当地法规。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11

  • Vtrux 与 Coda EV:网关型改装车的 CAN 信号档案

    结论:Vtrux(2014 款 Silverado 1500 的 EREV 改装车)最典型的数据特征是「一车三总线 + 主动网关」:OBD-II 口后面不是一条总线,而是三条独立 CAN,由 VCM 做跨总线网关,并且 VCM 会主动改写 ECM 的发动机数据——真实转速只在 OBD 总线上,Vehicle 总线上是约 800 RPM 的合成值。同仓库记录的 Coda EV 则是「一车四总线 + 网关转发」。以下结论仅限源文档记录的这两辆车及其固件与校准。

    车辆与适用范围

    本文数据来自第三方开源项目对两台实车的逆向记录:一台是 2014 款 Silverado 1500 的 EREV 改装车 Vtrux,另一台是 Coda EV 纯电轿车,两者出自同一仓库。

    项 内容
    车型 Vtrux —— 2014 款 Silverado 1500 的 EREV 改装车;同仓库另记录一台 Coda EV 轿车
    年款 / 市场版本 Vtrux 底车为 2014 款;Coda 年款与市场版本源文档未确认
    动力与改装状态 A123 LFP 电池包 116 节串联,标称约 383 V(约 360 V@10% SoC 至约 422 V 充满);原 GM 变速箱换成双 Remy 电机配 Dana TM4 CO150 逆变器;Woodward Motohawk 整车控制器(Via Motors VCM)兼作 GM CAN 网关
    采集跨度 源文档为多条实车日志(充电、公路巡航、隔离与互锁故障、过热等场景)加台架回放,未给出统一时长与帧数
    适用范围 仅限源文档记录的这两辆车与所列日志;不适用于量产 Silverado,也不保证适用于同型号其他改装车

    总线与寻址

    两台车都以经典 CAN 与 11 位标准帧为主,源文档未给出这两辆车的总线波特率。Vtrux 的核心特征是「三总线 + 主动网关」,Coda 是「四总线 + 网关转发」。日志通道编号都会随采集器插拔顺序变化,因此必须按报文内容识别总线。

    项 内容
    帧格式 车控主用 11 位标准帧;Bel 充电机/逆变器与 DCDC 使用 29 位 J1939 扩展帧
    Vtrux 三条总线 Powertrain(DLC 引脚 CAN-H 1 / CAN-L 9)、Vehicle(引脚 6 / 14)、OBD(引脚 10 / 2)
    Vtrux 总线识别 含 0x051 即 Powertrain;含 0x1E5 与 0x500 即 Vehicle;含 0x040–0x044 即 OBD。不要用 0x441–0x464 判 OBD,该区间含被网关桥接的 GMLAN 报文
    Coda 四条总线 B CAN(引脚 2 / 3)、D CAN(引脚 10 / 11)、C CAN(引脚 7 / 15,直读单体电压)、Diag CAN(引脚 6 / 14,标准 OBD 引脚);另有不可经 OBD 访问的私有 Motor CAN(DLCM↔MCM)
    Coda 总线识别 C CAN 恒含 0x000–0x019(104 节单体电压);B CAN 含 0x1D5、0x48A、0x275、0x381、0x383;D CAN 含 0x308、0x312、0x325、0x330、0x580
    网关 Vtrux 用 VCM 跨三总线选择性桥接并改写 ECM 数据;Coda 用 GWM 把 Diag CAN 路由到 J1962 并选择性转发

    已解码信号

    下表从源文档已标注置信度的条目中抽取。Vtrux 的置信度分级为 CONFIRMED(编码与用途均已独立验证)、CONFIRMED-SYMM(由对称结构推出)、CONFIRMED-DECOMPILE(编码由反编译确认,用途仍为标签)、PARTIAL(编码确认但角色或刻度存疑)与 REMOVED(已按策略移除)。Coda 部分沿用源文档的 CONFIRMED 标注。

    信号 位置(ID / DID / 字节) 解码 证据
    bcm_soc(电池 SoC) 0x0410 B3 raw × 0.5 % CONFIRMED(与 0x411 高分辨率 SoC 相关 r=0.9999)
    bcm_ibat(电池电流) 0x0410 B4:B5(大端 uint16) raw × 0.025 − 1000 A CONFIRMED(raw=40000 对应 0 A,发电机启停处穿越零点)
    bcm_vbat(电池包电压) 0x0410 B6[3:0]:B7(12 位) raw × 0.25 V CONFIRMED(接触器闭合后与 GENE_HighPwr_Volt 差不超过 1.25 V;启动哨兵值 1023.5 V 表示无效)
    bcm_cell_vmax / vmin 0x0430 B0[4:0]:B1 / B2[4:0]:B3(13 位) raw × 0.001 V CONFIRMED(稳定窗口内 r 大于 0.99)
    bcm_cell_tmax / tmin 0x0430 B4 / B5 raw × 0.5 − 40 ℃ CONFIRMED
    bcm_lvbat(12 V 电压) 0x0430 B7 raw × 0.1 V CONFIRMED(140=14.0 V DCDC 工作,127=12.7 V 静置)
    bcm_mainc_stat(主接触器状态) 0x0440 B0[5:2](4 位,16 态) 状态机枚举 CONFIRMED(充电日志观测到 ALL_OPEN→CLOSE_MAINR→WAIT_PRECHARGE→… 的状态转移)
    bcm_vbus_pos / vbus_neg 0x0440 B4:B5[7:4] / B5[3:0]:B6(12 位) raw × 0.25 V / raw × 0.25 − 500 V CONFIRMED(正、负直流母线对地电压)
    GENE_Mot_Trq(发电机扭矩回馈) 0x0471 B0:B1(小端 uint16) raw × 0.125 − 4096 Nm CONFIRMED(与 0x051 扭矩指令相关 r=0.9984)
    GENE_HighPwr_Volt 0x0482 B1:B2[2:0](11 位小端) raw × 1 V CONFIRMED(高速巡航稳定在 383–384 V)
    GENE_HighPwr_Curr 0x0482 B3:B4[5:0](14 位小端) raw × 0.25 − 2048 A CONFIRMED(巡航约 −40 A)
    parkLockState(驻车锁状态) 0x055C B0[2:0](3 位,7 项) 枚举 CONFIRMED(两车原始日志交叉验证)
    ess_power(电池包功率) 0x0598 B0:B1 (raw − 32768)× 0.01 kW CONFIRMED(线上格式不变;源文档另记录某类商业诊断工具自身解码有偏置,该信号已迁出原 DBC 归入实验 DBC)
    VehicleOdometer(整车里程) 0x0625 B0:B1:B2(24 位大端) raw × 1 km CONFIRMED(日志 raw=95,517 km 与仪表读数一致)
    DLCM_TorqueRequest(Coda 扭矩请求) 0x212 B4[3:0]:B5(12 位 nibble 拆分) raw × 0.244 Nm,零点 raw=2000 CONFIRMED(与 0x1C0 扭矩相关 r=0.999,峰值 293 Nm)

    读取方法与注意事项

    最关键的工程约束是总线识别。两台车的日志通道号都不可信——采集器插拔顺序一变,同一通道对应的总线就变了。做法是用 anchor ID 判定:先用一条稳定广播的专属 ID 认出总线,再做解析。Vtrux 还提醒,0x441–0x464 区间混入了被 VCM 桥接到 Vehicle 总线的 GMLAN 报文,用它判 OBD 会误判。

    第二条是网关的影响。Vtrux 的 VCM 会对转发内容做改写:ECM 的真实转速只出现在 OBD 总线(0x0C9,熄火时为 0,运转时为真实值),而 Vehicle 总线上是约 800 RPM 的合成值。轮速被掩零后,ECM 自行算出零车速并广播 0x3E9,形成级联——这不是 VCM 直接改写车速,而是屏蔽输入后的自然结果。因此同一物理量在不同总线上可能有不同含义。

    第三条是周期与新鲜度。同一 ID 在不同固件上周期可能不同(例如某电池电流报文在一种 BMS 变体上是 1 Hz,另一种上是 10 Hz),源文档做相关分析时对不同周期分别用了零阶保持与线性插值,并指出周期差异会影响标定。解析前应先统计每个 ID 的实测频率。

    证据等级与边界

    源文档对每条信号都给了证据分级。CONFIRMED 表示编码与用途都被独立验证(物理试验、日志相关或与参考交叉匹配);CONFIRMED-DECOMPILE 只说明编码来自反编译,用途仍是原厂标签;CONFIRMED-SYMM 由已确认信号的对称结构推出,没有独立测试;PARTIAL 表示编码确认但角色或刻度仍有未决问题——例如油压信号 GeneOilPress 的零点偏移未确认,其换算在怠速下会得到物理上不合理的数值。

    还有一类被明确记录为「异常」的条目。0x0451 曾被某类商业诊断工具误标为牵引逆变器扭矩,日志显示它其实是 GMLAN 报文,源文档已把对应条目移除;牵引逆变器热信号的真实 ID 是 0x044A,而非工具里列的 0x0459(后者在所有日志中为零帧)。

    边界上必须强调:以上结论仅来自源文档记录的这一台 Vtrux 与这一台 Coda 及其固件与校准。不同年款、不同 BMS 变体,甚至同一车型换了电池包,位置与刻度都可能变化,实车读取前应先在目标车上复核。

    常见问题

    Vtrux 的 OBD 口能直接抓到全部三条总线吗?

    三条总线在 J1962 口上都有引脚可达(Powertrain 1/9、Vehicle 6/14、OBD 10/2),但日志里的通道号是任意的,必须用 anchor ID 判断当前通道对应哪条总线。另外 VCM 会对桥接内容做过滤与改写,因此「在车上抓到」不等于「拿到原始数据」。

    为什么同一辆车上车速会有两个来源?

    因为 VCM 掩零了发给 ECM 的轮速输入,ECM 于是算出零车速并广播 0x3E9,OBD 总线上的该广播因此恒为零。真实车速在 Vehicle 总线上,由 VCM 用真实轮速生成;标准 OBD PID 0x0D 也返回真实车速,但它由 VCM 自己应答,而不是把请求桥接给 ECM。

    标注 CONFIRMED 的信号可以直接拿来用吗?

    可以用,但要区分级别。CONFIRMED 表示编码与用途都被独立验证;CONFIRMED-DECOMPILE 与 CONFIRMED-SYMM 只解决了「编码是什么」,用途仍是推断或标签,源文档已明确保留这一不确定性。PARTIAL 条目在使用前应先补验证。

    这些解码能套用到别的 Vtrux 或 Coda 上吗?

    不能。源文档的全部结论都限定在被记录的这两辆车及其固件与校准上,并明确指出不同 BMS 变体、不同固件之间字节偏移与周期存在差异。换车使用前必须重新复核。

    延伸阅读

    参考资料

    • 项目 01 —— public-can-dbc-datasets/additional/nickyivyca-vtrux-coda/projects/vtrux/README.md(Vtrux 总线、网关与模块拓扑主来源)
    • 项目 01 —— public-can-dbc-datasets/additional/nickyivyca-vtrux-coda/projects/vtrux/notes/epri-verification.md(已解码信号的置信度分级与证据)
    • 项目 01 —— public-can-dbc-datasets/additional/nickyivyca-vtrux-coda/projects/coda/README.md(Coda 四总线、anchor ID 与解码条目)

    说明:本文整理自第三方开源项目的公开文档,仅代表该文档所记录的单个车辆样本,不代表同型号全系车型。CAN 与诊断操作存在风险,任何实车操作前请自行复核并遵守当地法规。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11

  • Hyundai 与 Honda 车型协议:为什么品牌目录不等于通用协议

    结论:品牌目录不是通用协议。Hyundai 的经典 CAN 可选 hyundai/hyundaiLegacy、CAN FD 用 hyundaiCanfd;Honda 分 Nidec 与 Bosch,Bosch 还要再分雷达版/无雷达版及 CAN FD。它们是不同的能力组合,必须声明精确平台、固件范围与总线拓扑。CarController 的软件限幅不替代 panda 安全钩子,CarState 的解析结果也不替代固件对驾驶员扭矩、制动和巡航状态的独立采样。

    Hyundai:经典 CAN 与 CAN FD

    Hyundai 按 CANFD 分支:用相机和 E-CAN 指纹判别 LKA/LFA、按钮、档位及 SCC 来源,LONG、能源类型和限制参数写入 safetyParam。这里要记住:alphaLongitudinalAvailable 与 openpilotLongitudinalControl 分开,前者不能保证后者启用。

    层 关键入口 已核对的实现
    ECU 生命周期 init()、deinit() 自控纵向且非相机 SCC 时发送 UDS 通信控制,目标地址依平台/转向架构改变;退出尝试恢复。
    输入 CarState.update()、update_canfd() 分别把经典 CAN/CAN FD 的速度、驾驶员输入、巡航、转向、车门等信号映射到统一 CarState。
    输出调度 CarController.update() 经典 CAN 与 CAN FD 分流至 create_can_msgs()/create_canfd_msgs()。
    构帧 hyundaican.py、hyundaicanfd.py 经典 CAN 的 LKAS11/CLU11/SCC 帧与 CAN FD 的转向、取消、ACC、LFA 抑制帧分别构造;CAN FD 的 CanBus 映射 E-CAN、A-CAN、相机总线,不能硬编码单一 bus。
    固件接收/发送 hyundai_rx_hook()、hyundai_tx_hook()、hyundai_canfd_tx_hook() 两条分支检查转向扭矩/请求间隔、加速度及按钮权限,诊断地址仅准许指定 tester-present 载荷。
    模式组装 hyundai_init()、hyundai_legacy_init()、hyundai_canfd_init() Legacy 因旧报文缺计数器/校验而采用不同接收检查,不能视作安全规则全等。

    Honda:Nidec 与 Bosch

    Honda 的 Bosch 用 hondaBosch、Nidec 用 hondaNidec;Bosch 继续区分雷达版、无雷达版及 CAN FD。Bosch CAN FD 在此快照中不开放实验纵向。

    层 关键入口 已核对的实现
    ECU 生命周期 init()、deinit() 有雷达的 Bosch 自控纵向会尝试禁用雷达 ECU 通信,退出时尝试恢复。源码明确警告此操作会禁用原车 AEB。
    输出调度 CarController.update()、compute_gas_brake() 区分 Bosch/Nidec;actuator_hysteresis() 与 brake_pump_hysteresis() 处理 Nidec 制动输出。
    构帧 hondacan.py 按平台总线构帧;CanBus 映射 PT、radar、camera 等总线。
    固件接收 honda_rx_hook() 备用刹车报文 0x1BE 避免把某些车型电脑制动误判为用户踩刹车;Nidec 的原车 AEB 制动与 openpilot 制动比较后决定转发。
    模式与转发 honda_nidec_init()、honda_bosch_init()、honda_nidec_fwd_hook() Nidec 对相机制动帧 0x1FA 进行动态转发;Bosch 实验纵向在固件中受 ALLOW_DEBUG 编译门限制,不能只看 Python 配置推断可用。

    另外两处细节也影响拆分:Hyundai 侧,经典 CAN 可选 hyundai/hyundaiLegacy,CAN FD 转向灯能力还可能涉及额外 ECU;Legacy 因旧报文缺计数器/校验而采用不同接收检查,测试入口覆盖相机 SCC、Legacy、能源类型、纵向、AEB 位、按钮和诊断场景,但不等于覆盖所有实车变种。

    Honda 侧,honda_tx_hook() 对 Nidec 检查制动/HUD,对 Bosch 检查加速度/油门,均阻止主机发送 AEB 执行位;无使能时转向只能为零,Bosch 按钮只能取消,诊断地址仅准许指定 tester-present。备用刹车报文 0x1BE 用于避免把某些车型电脑制动误判为用户踩刹车。

    Hyundai 的固件发送侧还要区分纵向状态:hyundai_canfd_tx_hook() 在非自控纵向时仅允许符合取消语义的 ACC 帧,经典 CAN 则另检查 FCA11/SCC12 的 AEB 执行位。Honda 侧的转发也不对称:honda_nidec_fwd_hook() 对相机制动帧 0x1FA 动态转发,而 Bosch 实验纵向在固件中受 ALLOW_DEBUG 编译门限制,不能只看 Python 配置推断可用。

    为什么必须按能力组合拆分

    同一品牌下,CAN/CAN FD 与 Nidec/Bosch 是不同能力组合,不能互相套用。Honda 的原车 AEB 保留与否依硬件架构和具体纵向方案而变:Nidec 用相机制动帧的动态转发,而 Bosch 雷达禁用方案是另一条路径,不能把 Nidec 转发逻辑套到 Bosch 雷达禁用方案。Hyundai 侧,hyundai_canfd_tx_hook() 中的转向限值还包含驾驶员扭矩抵消、速率与 EPS 转向请求切断容差;这些是固件拒绝候选帧的条件,不应在插件层开放为任意可改配置。

    换句话说,能力组合必须由「平台 + 固件范围 + 总线拓扑 + 传感器架构」共同定义,任何一项缺失都可能把另一套组合的结论错误套用过来;这也解释了为何同品牌不同年款需要各自的证据链。

    车载硬件与插件化含义

    1. 车型插件应声明精确平台、固件范围、总线拓扑、DBC、能力与安全模式参数;同一品牌的 CAN/CAN FD 或 Nidec/Bosch 必须是不同能力组合。不能让插件动态改写 panda 固件的 TX 白名单和物理限制。
    2. ECU 通信控制必须是受控生命周期操作,记录请求、响应、超时及恢复结果。尤其 Honda Bosch 雷达禁用涉及原车 AEB;目标硬件设计应先明确是否允许此工作模式,而不是默认启用。
    3. 硬件网关需要保持总线来源与时间戳、独立看门狗、断电/主机失联回退、板上 RX/TX/转发安全门;E-CAN/PT/camera 总线映射错误是安全风险,不是普通配置瑕疵。
    4. 每个目标车型建立指纹和固件识别、DBC 解析/打包、安全测试、日志回放、断线与错误帧注入、SIL/HIL、封闭场地验证的证据链。未知或模糊匹配保持被动,不以品牌名放行执行。

    常见问题

    为什么同一品牌要按经典 CAN 与 CAN FD 分开?

    因为两者有不同的输入映射、构帧函数与安全模式:Hyundai 的经典 CAN 可选 hyundai/hyundaiLegacy、CAN FD 用 hyundaiCanfd;Legacy 还因旧报文缺计数器/校验而采用不同接收检查。它们是不同能力组合,不能视作同一套安全规则。

    Honda 禁用雷达 ECU 通信有什么代价?

    源码明确警告:有雷达的 Bosch 自控纵向禁用雷达 ECU 通信的操作会禁用原车 AEB。因此不能把它当作无副作用的模式切换,目标硬件设计应先明确是否允许此工作模式。

    Bosch 的实验纵向能在固件里开放吗?

    在固件中受 ALLOW_DEBUG 编译门限制,不能只看 Python 配置推断可用;此外 Bosch CAN FD 在此快照中不开放实验纵向。

    Nidec 的相机制动帧转发能套用到 Bosch 吗?

    不能。Honda 的原车 AEB 保留与否依硬件架构和具体纵向方案而变;Nidec 对相机制动帧 0x1FA 的动态转发与 Bosch 雷达禁用方案是不同路径,不能互相套用。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/HYUNDAI_HONDA_DEEP_DIVE.md(取用了 Hyundai 经典/CAN FD 链路、Honda Nidec/Bosch 链路,以及车载硬件与插件化含义)。
    • 项目 02 —— openpilot-research/HYUNDAI_CANFD_PANDA_SAFETY_DEEP_DIVE.md(仅用于确认 CAN FD 逐模式 RX/TX 总线矩阵与纵向构建门的引用位置)。

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11

  • Ford panda 安全模式:双车速曲率门与 CAN FD 纵向的编译门

    结论:Ford safety 的核心不是「有没有白名单」,而是双车速源、质量位、曲率/jerk 门与构建变体共同构成的判据。ABS 与 PCM 第二车速名义 50 Hz、yaw 100 Hz,曲率由 yaw 除以经下界保护的车速得到;CAN FD 的自控纵向 TX 白名单只在 ALLOW_DEBUG 且纵向参数位置位时开放。因此官方测试套件默认带 ALLOW_DEBUG,不能用来证明 release 固件开放该能力。

    函数与责任

    函数 输入、状态或约束
    ford_get_counter()、ford_get_checksum()、ford_compute_checksum() 从 ABS 车速与 yaw 帧提取/计算计数和校验;其它 RX 消息靠检查表选择忽略。
    ford_get_quality_flag_valid() ABS、PCM 第二车速和 yaw 的指定质量位必须为 3;其余 RX 帧在配置表中显式忽略质量位。
    ford_rx_hook() ABS 与 PCM 双车速进入共享状态;yaw 除以经过下界保护的车速后形成实测曲率;另更新停车、油门、刹车和原车巡航状态。
    ford_tx_hook() 检查 ACCDATA、方向盘按钮、Lane Assist 和两种 LateralMotionControl;返回 false 拒绝候选帧。
    ford_init() 选择 6 条 RX 健康规则、经典 CAN/CAN FD TX 白名单及调试编译门;没有 Ford 专属转发钩子。

    RX 数据与新鲜度

    总线 0 上,ABS 车速和 PCM 第二车速名义 50 Hz、yaw 100 Hz、刹车/巡航 10 Hz、油门 100 Hz、停车状态 50 Hz。ABS 帧检查校验、4 位计数和质量位;yaw 帧检查校验、8 位计数和质量位;PCM 第二车速帧忽略计数器和校验和,但仍检查质量位,源码注释将此与某些 ECU 的跳计数/短暂校验异常联系起来。其余三类无计数/校验帧在配置中也忽略质量位。

    共享 rx_msg_safety_check() 先更新检查状态;只有当前帧有效且匹配 RX 检查表时,safety_rx_hook() 才调用 ford_rx_hook()。坏校验/质量帧不能按这条调用链更新 Ford 车况,而且有效性失败可撤销 controls_allowed。共享曲率检查中的 speed_mismatch_check() 再比较双速度源;自制网关若合并来源、丢失质量位或用单一速度伪造两路,将破坏这层判据。一个关键细节:yaw 可能先于新车速到达,此时曲率使用的是已保存车速,不能声称两个采样硬件同步。

    ford_init() 选择 6 条 RX 健康规则;FORD_COMMON_TX_MSGS 对两个按钮总线设 check_relay=false,其余列出的发送帧设为 true;共享 safety_fwd_hook() 依据当前 TX 表静态阻断从相机总线向主总线转发的受控地址,因此经典 CAN 与 CAN FD 的阻断集合也随模式变化。这些白名单都含 Steering_Data_FD1 的 0/2 两路和 ACCDATA_3、Lane_Assist_Data1、IPMA_Data 的 0 路;Ford 函数单独调用不能代表完整安全判定。

    TX 模式矩阵与位级门

    选择 ACCDATA 横向报文 依据
    经典 CAN,任意纵向参数位 白名单包含 LateralMotionControl FORD_LONG_TX_MSGS;并非参数位才开放。
    CAN FD,默认或 release 固件 白名单不含 LateralMotionControl2 FORD_CANFD_STOCK_TX_MSGS。
    CAN FD,ALLOW_DEBUG 且纵向参数位置位 白名单包含 LateralMotionControl2 FORD_CANFD_LONG_TX_MSGS 与 ford_init() 的编译门。

    ford_tx_hook() 对 ACCDATA 同时检查请求油门、预测油门、制动加速度及制动预充/减速位;CmbbDeny_B_Actl 为 1 一律拒绝,以免禁止原车 AEB。get_longitudinal_allowed() 还要求使能且驾驶员未踩油门;禁用时仅允许各字段的中性编码。cancel 只在前次原车巡航已接合时放行,resume 只在 controls_allowed 时放行;Lane Assist 的执行动作必须为零。

    两个横向格式分别解析曲率、曲率率、路径角/偏移,后面三项必须保持源码规定的 inactive 原始编码。steer_curvature_cmd_checks() 再检查绝对曲率、随车速变化的横向加速度/jerk、与实测曲率的偏差及非使能中性值;它会维护 desired_last,拒帧时清零,因而更换插件或安全模式时不能把拒帧当作完全无状态事件。另外,ACCDATA_3 与 IPMA_Data 在 Ford TX 钩子中没有专属载荷检查,不能把白名单解释成逐信号验证。

    证据、缺口与插件边界

    test_ford.py 定义经典 CAN 纵向、CAN FD 原车纵向和 CAN FD 自控纵向三类具体测试配置。2026-10-04 在本地用原版 CANPacker、CFFI 和编译的 libsafety 运行 python -m unittest opendbc.safety.tests.test_ford -q:102 项中 91 项通过、11 项跳过。跳过的是三种具体配置均无 _user_regen_msg 的九个通用再生制动用例,以及两个抽象基类的 setUpClass,不是 Ford 曲率或纵向边界失败。

    但要注意:测试库的 _build_libsafety() 默认加 ALLOW_DEBUG,所以这次官方套件不能证明 release 固件开放 CAN FD 自控纵向 TX 白名单。七项帧级离线探针分别编译不带/带 ALLOW_DEBUG 的原 libsafety,确认:界内曲率帧通过;坏 ABS 质量位不更新车速并撤销 controls_allowed;两路合法但相差 5 m/s 的车速在曲率 TX 时触发失配退出。面向「万物即插件」,Ford 车型插件应声明经典/CAN FD、总线拓扑、DBC、纵向能力、固件构建变体和 safetyParam,并把两路车速及 yaw 的来源、质量位、时间戳分别传到受信任安全层。

    常见问题

    为什么 yaw 先到会用到旧车速?

    因为源码注释明确指出 yaw 可能先于新车速到达,此时曲率使用的是已保存车速;因此不能声称两个采样硬件同步。探针也确认,先更新 ABS 车速再更新 yaw 时,实测曲率在新 yaw 到达前保留旧值。

    PCM 第二车速为什么忽略计数器与校验和?

    源码注释将此与某些 ECU 的跳计数/短暂校验异常联系起来。但该帧仍检查质量位,质量位不为 3 时不能更新车速。

    CAN FD 的自控纵向在 release 固件下能开吗?

    默认或 release 固件的白名单不含 ACCDATA;只有 ALLOW_DEBUG 且纵向参数位置位时才使用 FORD_CANFD_LONG_TX_MSGS。官方测试套件默认带 ALLOW_DEBUG,所以不能据此证明 release 固件已开放该能力。

    白名单里出现 ACCDATA,说明每个信号都被校验了吗?

    不是。白名单只核对地址/总线/长度并调用 ford_tx_hook(),而 ACCDATA_3 与 IPMA_Data 在该钩子中没有专属载荷检查。白名单准入不等于逐信号验证,也不等于 ECU 接受。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/FORD_PANDA_SAFETY_MODE_DEEP_DIVE.md(取用了 RX 数据与新鲜度、TX 模式矩阵与位级门、测试证据与缺口,以及插件边界)。
    • 项目 02 —— openpilot-research/PLUGIN_REFACTOR.md(仅用于确认车型插件与安全模式归位的引用位置)。

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11

  • 100 Hz 的 card 网关:sendcan 不按 valid 拦截

    结论:card 是「车型发现 → 车况发布 → 控制帧网关」的中央进程,但它发出的东西按语义要分成四层:控制请求已生成、sendcan 已发布、panda 接受、车端确认动作。最容易踩的坑是:card 把 sendcan.valid 赋为 CS.canValid,而 pandad.can_send_thread() 本身并没有根据 Event.valid 字段筛掉 sendcan——这个元数据不是板端的发送阻断。以下基于源码快照 c8fb906 的静态阅读与假依赖探针,未构建、未做车型回放、未做实车验证。

    初始化与车型身份

    Car.__init__() 创建 can 订阅、pandaStates/carControl/onroadEvents 的 SubMaster,以及 sendcan/carState/carParams/carOutput/radarTracks 发布端。正常路径等首个非空 CAN,读取 AlphaLongitudinalEnabled 与 CarParamsCache,调用 get_car() 做识别、固件查询与接口构建,再按指纹取得 RadarInterface;识别失败在 get_car() 内回退 MOCK,这不是允许未知车发送通用控制帧。

    阶段 关键函数/参数 含义
    固件查询复用 obd_callback() 写 ObdMultiplexingEnabled 请求,先删 ObdMultiplexingChanged,再阻塞等待 pandad 写确认位;四项假 Params 探针确认假确认返回假仍记成功,且无超时或板端模式读回。
    指纹完成 FirmwareQueryDone get_car() 返回后写真;pandad 在该位之前不取最终车型参数。
    被动模式 OpenpilotEnabledToggle、CI.CC、CP.dashcamOnly 没有控制器、用户关闭或车型只支持 dashcam 时,CP.passive=True,并把 safetyConfigs 改为 noOutput。
    SecOC secOcRequired、SecOCKey 可从指定缓存文件导入 32 个字符,再把十六进制字符串解析为 16 字节;源码只检查长度与解析结果,不等于密钥来源或权限已验证。
    参数缓存 CarParamsCache、CarParamsPersistent、CarParamsPrevRoute 缓存用于本次 manager 生命周期的快速指纹;不能拿旧缓存代替当前 CAN/固件匹配。

    SecOCKey 解析处调用 bytes.fromhex(),没有局部捕获非法十六进制的 ValueError;静态上长度正确但内容错误的键可令初始化异常。此处尚未构造故障注入测试,量产硬件应验证密钥导入、轮换、无效格式与清除时的安全默认态。

    一次 card 步进的消息顺序

    把单次 step() 展开,顺序是固定的:

    1. pandad.can → Car.state_update();
    2. 调用 CI.update(can_list) 与 RI.update(can_list)(雷达可返回 None);
    3. VCruiseHelper 更新 CS.vCruise、CS.vCruiseCluster;
    4. Car.state_publish() 依次发布 carOutput、carState、radarTracks;
    5. onroadEvents 不含 selfdriveInitializing 时进入 Car.controls_update();
    6. 首次调用 CI.init() 后写 ControlsReady;
    7. CI.apply(carControl) → sendcan → pandad.can_send_thread → panda 固件安全门。

    can_comm_callbacks() 把 cereal 的 can/sendcan 消息与 CanData 列表互转,供指纹、接口初始化与诊断查询使用。state_update() 等待并批量读取原始 CAN,再分别调用车型与雷达 update();空 CAN 轮次累加 can_rcv_cum_timeout_counter,回放时使用 CAN 消息中的单调时间戳。三项原 CAN 回调探针确认:接收回调保留 Event 批次但不返回外层有效位/时间戳;诊断发送回调沿编码器默认值生成 valid=True 的 sendcan——这些回调没有按 carControl 活性或 CS.canValid 过滤。

    车况发布:四个 valid 位各有定义

    state_publish() 约每 50 秒发布一次 carParams;每步先发布 carOutput(上次 CI.apply() 的执行器输出回报),再发布 carState。四个有效位不能合并成一个「整车健康」标志:

    字段 取值来源 边界
    carState.valid CS.canValid 只反映车况信号是否有效
    carOutput.valid carControl 的 SubMaster.all_checks() 与车况有效性各自独立
    radarTracks.valid 雷达错误字段;None 时不发布该主题 错误与点仍在消息内
    carParams 节拍 sm.frame 对 50/DT_CTRL 取模 不是独立墙钟定时器

    六项原 state_update()/state_publish() 探针还确认:空原始接收批次仍会调用 CI.update()、RI.update() 并累加超时计数;只要原始事件列表非空,即使替身解码成空 CAN 列表也不累加;巡航 helper 先更新当前 CS,控制使能上升沿再用 CS_prev 初始化设定值;carOutput/carState/radarTracks 的有效位可分别为假/真/假。

    控制门链:谁真正拦住了发送

    step() 在发布车况后,要求 CP.passive=False、已收到 onroadEvents、且其中不含 selfdriveInitializing,才进入控制更新。首次 controls_update() 会调用 CI.init() 做车型接口/ECU 初始化,然后写 ControlsReady;pandad 的 configureSafetyMode() 只有在 FirmwareQueryDone 与 ControlsReady 两个门都满足后才读取 CarParams 配置安全模式。若 carControl 仍活跃,CI.apply() 产出请求执行器回报和 CAN 帧并发送 sendcan;否则本分支不发新帧。

    七项原 controls_update()/step() 探针确认:carControl 活跃而 CS.canValid=False 时,仍会编码并发布带 valid=False 的候选帧;initialized_prev=True 时跳过重复初始化;回放模式给 CI.apply() 使用记录的 CAN 时间。这就直接指向前述结论——sendcan.valid 只是元数据,车型 CarController、panda 固件的 TX 安全钩子和 controlsAllowed 才是另外的强制约束,硬件插件不能绕过这些层。

    故障或差异 当前代码可证实的处理 待验证
    无非空 CAN 初始化持续等待;运行期空批次累加超时计数 线束断开、总线睡眠与线程退出时序
    未识别车型或禁用控制 回退 MOCK/被动 noOutput 实板继电器与线束默认态
    OBD 复用确认未到 obd_callback() 阻塞等待 Params 确认 固件重连/断电恢复、超时策略
    carControl 丢失 all_alive 不满足时不再调用 CI.apply() 最后一帧、panda 超时与原厂系统恢复时延
    CAN 无效但已有控制帧 发布 sendcan.valid=False;发送线程未按 valid 拦截 车型控制器与 panda 安全钩子的拒绝/回退

    时序上,card 由 CAN 输入驱动,配置 100 Hz Ratekeeper 监测节拍,实际迭代频率取决于总线输入与处理时间;params_thread() 约每 100 毫秒刷新单位与实验模式,cumLagMs 来自 Ratekeeper 剩余时间。不同进程不是原子事务:不能假定最新 carState、最新 carControl 和本轮 CI.apply() 精确一一对应。

    常见问题

    sendcan.valid=False 会阻止 panda 发送吗?

    不会自动阻止。pandad.can_send_thread() 只检查时间戳是否小于 1 秒,本身没有根据 Event.valid 字段筛掉 sendcan。真正的强制约束在车型 CarController、panda 固件的 TX 安全钩子和 controlsAllowed。

    收到 carState 就说明整车数据都健康吗?

    不能。carState.valid、carOutput.valid、radarTracks.valid 各自有定义,可以分别为假/真/假。它们不能合并成一个「整车健康」标志。

    OBD 复用握手为什么会卡住?

    obd_callback() 写请求后阻塞等待 pandad 写确认位,而这个握手没有显式超时,也没有板端模式读回。四项假 Params 探针确认假确认返回假仍记成功,因此固件重连/断电恢复与超时策略需要额外设计。

    识别失败回退 MOCK 会不会乱发控制帧?

    不会直接发送。回退只发生在识别阶段,仍由后续参数与安全配置决定输出;被动路径会把 safetyConfigs 改为 noOutput,而不是允许未知车发送通用控制帧。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/CARD_VEHICLE_GATEWAY_DEEP_DIVE.md(card 车型发现、车况发布与控制帧网关,含源码行号索引)
    • 项目 02 —— openpilot-research/CARD_OBD_MULTIPLEXING_HANDSHAKE_DEEP_DIVE.md(源文档指向的 OBD 复用握手专题)
    • 项目 02 —— openpilot-research/PANDAD_PYTHON_CAN_CAPNP_ADAPTER_VALIDITY_DEEP_DIVE.md(源文档指向的 CAN 适配与有效位专题)

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11

  • Hyundai CAN FD safety:LKA 与 LFA 的总线矩阵差在哪

    结论:Hyundai CAN FD safety 的「模式」不是一个开关,而是LKA/LFA × 纵向/非纵向 × 按钮格式 × SCC 来源的组合,每一组对应不同的 RX 检查表与 TX 白名单。纵向(LONG)只在 ALLOW_DEBUG 构建下生效:无该宏的构建强制 hyundai_longitudinal=false,因此上层设置 openpilotLongitudinalControl 不会自动改变固件事实。此外,替代转向地址是条件性接口不匹配,不能靠扩充普通插件的 CAN 发送权限来规避。

    车型资格与总线编号:LKA 与 LFA 是两套拓扑

    资格判定在 CarInterface._get_params():根据相机指纹判定 LKA/LFA,确定按钮、SCC 来源和 safetyParam。源文档特别强调,alphaLongitudinalAvailable、实际启用和 ECU 静默成功是三个不同状态,不能混为一谈。

    总线编号由 CanBus.__init__() 决定,而且两种模式恰好相反:

    模式 A-CAN E-CAN 相机
    LKA 0 1 2
    LFA 1 0 2

    相机总线固定为 2,再叠加多设备偏移。这里要注意:表中 bus 号是单个 panda safety 实例的本地编号,多 panda 场景的偏移由上层配置处理,不能把逻辑编号硬编码成自制板引脚。

    模式与总线矩阵

    hyundai_canfd_init() 用 LKA/LFA、LONG、替代按钮、相机 SCC 和替代转向地址选择 RX 检查与 TX 白名单。下面这张表是整篇的核心:

    模式 动力总线 / 原车 SCC RX 关键 TX 白名单
    LKA 非纵向 1 / 1 0x50+0x2A4 或替代 0x110+0x362 在 bus 0;0x1CF 在 bus 1
    LKA 纵向 1 / 不检查原车 SCC LKA 0x50+0x2A4 在 bus 0;LFA、HUD、SCC、若干 ADRV 与 0x730 在 bus 1;0x51 在 bus 0
    LFA 雷达 SCC 非纵向 0 / 0 LFA、HUD、0x1A0 在 bus 0;0x1CF 在 bus 2
    LFA 相机 SCC 非纵向 0 / 2 上行相同,另有 0x160 在 bus 0
    LFA 雷达 SCC 纵向 0 / 不检查原车 SCC LFA、HUD、SCC、0x160、0x7D0 在 bus 0;0x1CF 在 bus 2
    LFA 相机 SCC 纵向 0 / 不检查原车 SCC LFA、HUD、SCC、0x160 在 bus 0;0x1CF 在 bus 2

    表头里的「原车 SCC RX」指的是 safety 必需 RX 检查与巡航状态消费,不是该总线上物理帧是否存在。几个细节:LKA 两种分支的按钮 RX 固定使用 0x1CF;LFA 分支依据替代按钮标志,选择动力总线上的 0x1CF 或 0x1AA。所有配置均监视油门备选组、制动 0x175、轮速 0xA0、驾驶员扭矩 0xEA——油门三种地址是同一检查组的备选格式,不等于三种都必须到达。

    构建门:LONG 参数只在 ALLOW_DEBUG 下生效

    hyundai_common_init() 只在 ALLOW_DEBUG 下读取 LONG 参数;无该宏的构建强制 hyundai_longitudinal=false。这是一道编译期门,而不是运行期开关。

    校验侧码分布同样要记住:hyundai_canfd_get_counter() 对 8 字节普通按钮取字节 1 高四位的计数,其余帧取字节 2;校验值取前两字节。hyundai_common_canfd_compute_checksum() 对字节 2 起的载荷与地址执行 CRC16,24/32 字节分别异或长度常数。特别之处在于:按钮 RX 明确忽略 checksum,但仍检查计数。

    权限与失效边界

    这些边界在源文档里逐条列出,直接决定「白名单里有这帧」和「这帧会被执行」之间的差距:

    1. 按钮:普通按钮 TX 0x1CF 仅允许「原车巡航已接合时 Cancel」或「controls_allowed 时 Resume」。替代按钮 RX 0x1AA 不在 TX 白名单中;但设置替代按钮并不会从全部 TX 表删除 0x1CF,不能误写成「该模式彻底禁止发送任何按钮帧」。
    2. 转向:非纵向 LKA 对 0x50/0x110 检查扭矩;LKA 纵向及全部 LFA 对 0x12A 检查。共享扭矩门限制最大 270、实时变化 112、每帧增加 2/减少 3,并含驾驶员反扭矩与转向请求位节拍。
    3. SCC:非纵向 LFA 的 0x1A0 仅允许 ACCMode=4 且 aReqRaw/aReqValue 同时为零;该钩子未逐位审核 0x1A0 的其它信号。
    4. 诊断与附属帧:0x730/0x7D0 的 8 字节数据仅可为 02 3E 80 00 00 00 00 00,且仅相应模式在白名单中。0x51、0x160、0x1EA、0x200、0x345、0x1DA、HUD 和相机抑制帧没有同等级的专属载荷校验。
    5. 转发:Hyundai CAN FD 没有专属 fwd 钩子,使用通用 0↔2 转发规则及 check_relay 静态阻断;bus 1 不在该通用转发对内。

    第 4 条尤其重要:白名单准入不等于 ECU 接受,也不等于 AEB 不受影响。缺少逐字段校验的帧,只能保证「被允许发送」,不能保证「被接受后效果正确」。

    替代 LKA 与纵向的跨层组合

    CarInterface._get_params() 可从相机指纹识别 0x110 替代 LKA,并把该标志传到 safety 参数;若有 ADAS ECU 且未被其他条件禁止,纵向能力仍可能开启。此时主机侧 create_steering_messages() 继续生成 0x110 LKAS_ALT,create_suppress_lfa() 继续生成 0x362。

    但 hyundai_canfd_init() 的纵向 LKA 分支固定选择普通 0x50/0x2A4 白名单,而非按替代标志切换;原测试只有普通 LKA 纵向类,没有替代 LKA 纵向类。离线探针确认:同一替代标志加 LONG 后,调试构建拒绝 0x110/0x362、接受 0x50/0x2A4;无调试宏构建则忽略 LONG,采用非纵向替代 LKA 白名单。也就是说该组合在两类构建中的失败形态不同,均不能仅凭主机 CarParams 宣称纵向或替代转向可执行。

    源文档对此的定性是关键:这是条件性接口不匹配,不应通过扩充普通插件的 CAN 发送权限来规避;正确做法是先确定该车型/纵向组合是否应被允许,再联合修订主机消息选择、固件白名单、转向限制和回归测试。现有 test_hyundai_canfd.py 在测试基座默认编译 ALLOW_DEBUG;本机运行该模块共 981 项、117 项跳过,跳过项及未覆盖的替代 LKA 纵向组合不能视为验证通过。

    常见问题

    LKA 和 LFA 的总线编号为什么会相反?

    因为 CanBus.__init__() 明确按模式分配:LKA 时 A-CAN/E-CAN 为 0/1,LFA 时为 1/0,相机固定为 2。这是逻辑编号,多 panda 场景的偏移由上层配置处理,不能直接当作物理引脚。

    启用纵向控制后,固件就一定允许吗?

    不一定。hyundai_common_init() 只在 ALLOW_DEBUG 下读取 LONG 参数,无该宏的构建会强制 hyundai_longitudinal=false。所以上层设置 openpilotLongitudinalControl 不会自动改变固件事实,alphaLongitudinalAvailable、实际启用和 ECU 静默成功是不同状态。

    0x1A0 在白名单里就可以自由发送吗?

    不可以。非纵向 LFA 的 0x1A0 仅允许 ACCMode=4 且 aReqRaw/aReqValue 同时为零;纵向则分别调用共享加速度检查。而且该钩子并未逐位审核 0x1A0 的全部其它信号。

    为什么设置「替代按钮」不等于禁用 0x1CF?

    因为替代按钮 RX 0x1AA 虽不在 TX 白名单,但设置替代按钮并不会从全部 TX 表删除 0x1CF。现有 test_button_sends() 构造的是替代格式 0x1AA,因此没有证明 0x1CF 被禁用。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/HYUNDAI_CANFD_PANDA_SAFETY_DEEP_DIVE.md(取用了模式与总线矩阵、构建门、权限与失效边界、替代 LKA 与纵向跨层组合)。
    • 项目 02 —— openpilot-research/CAR_PROTOCOL_IMPLEMENTATIONS.md(仅用于确认 Hyundai CAN FD 分支与 set_safety_hooks() 的注册位置)。

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11

  • panda 主机 CAN 组包:70 字节线格式与 XOR 校验为什么晚于安全钩子

    结论:主机到 MCU 的 CAN 字节流是一台分片组包机:CANPacket_t 头部固定 6 字节,DLC 查表最长对应 70 字节线格式,packed, aligned(4) 结构在本机编译为 72 字节。校验顺序是「先安全钩子、后 XOR 校验」:can_send() 先运行 safety_tx_hook(),坏 XOR 帧虽然不会上总线,却可能先触达有状态的安全钩子。returned 回执只代表已提交硬件 FIFO,不等于车端已接收。

    6 字节头与 72 字节结构:线格式到底传多少

    报文的字节布局由 CANPacket_t 与 pack_can_buffer() 定义:6 字节头包含 DLC、bus、FD、地址、标志与 XOR;DLC 查表对应 0–64 字节载荷,因此最长有效线格式为 70 字节。C 结构带 packed, aligned(4),在本机编译为 72 字节,尾部的对齐字节不应传输。

    Python 编码显式组出首字节 DLC:4 | bus:3 | fd:1、随后四字节小端地址/状态位和 XOR;原生 C++ can_header 则把首位命名为 reserved,发送时零初始化。两者的 6 字节头部形状相同,但发送 FD 的策略不同——这一点在下一节展开。

    环节 关键值 含义
    头部长度 6 字节 DLC、bus、FD、地址、标志与 XOR
    载荷范围 0–64 字节 由 DLC 查表决定
    最长线格式 70 字节 6 字节头 + 最大载荷
    本机 C 结构 72 字节 尾部对齐字节不传输
    MCU 写缓冲 72 字节数组 容纳一个最大协议帧

    主机到车辆:一半包留在哪里、什么时候补齐

    Python 把完整消息串接成字节流:can_send_many() 经 USB EP3,SPI 由 endpoint3 自身事务长度承载,两者进入同一 comms_can_write()。MCU 侧用 can_write_buffer 保存不完整帧及剩余字节数;补齐后构造 CANPacket_t 并调用 can_send(..., false),其余输入逐帧处理。

    环节 实现 行为
    主机分块写入 can_send_many() / USB EP3 / SPI endpoint3 USB 按客户端块切分,SPI 按自身事务长度;进入同一 comms_can_write()
    MCU 写入组包 comms_can_write() 半包存于 72 字节缓冲;补齐后调用 can_send(..., false)
    MCU 读出分片 comms_can_read() 先发上次帧尾,再取 RX 队列;按 max_len 返回实际字节数
    主机接收重组 can_recv() / unpack_can_buffer() 拼接残包,逐帧验证 XOR 并保留下次所需残包
    状态清空 comms_can_reset() 连接时主机发 0xc0,清 MCU 读/写半包指针;不清 CAN RX/TX 队列
    容量流控 refresh_can_tx_slots_available() 三路 TX 队列同时满足 USB 51 帧或 SPI 170 帧余量时恢复接收

    两个边界必须分清:读出侧不保证传输块与 CAN 帧边界重合,所以主机必须能拼接残包;主机缓存与 MCU 的两个缓存(写半包、读残包)是三份不同状态。另外,51 帧 / 170 帧是背压门槛,不代表安全钩子放行或总线已发送。

    校验与许可的真实顺序

    这是整篇文章最容易被写反的一点。对完整帧,comms_can_write() 直接调用 can_send();can_send() 先运行 safety_tx_hook(),再决定入 TX 软件队列或生成 rejected 回执。直到 process_can() 从队列取出时,才运行 can_check_checksum();校验失败只增加 total_tx_checksum_error_cnt。

    顺序 环节 结果
    1 USB/SPI 字节进入 comms_can_write 组出完整包
    2 can_send 调用 safety_tx_hook 拒绝则 rejected 回执入 RX 队列
    3 允许则入 TX 软件队列 尚未校验 XOR
    4 process_can 校验 XOR 错误则增加 tx checksum error
    5 正确则提交 FDCAN TX FIFO 产生 returned 回执

    由此得到两个结论:坏 XOR 帧不会由这条发送路径上总线,但可能先触达有状态的安全钩子——例如 Toyota 的 toyota_tx_hook() 会调用 steer_torque_cmd_checks(),其中可能更新 desired_torque_last。源文档对此的措辞很克制:具体可触发条件及后续有效帧影响需要隔离测试,本文不宣称已复现控制偏差。另一个结论是:returned 仅代表提交硬件 FIFO,不能解释为车端已确认接收。

    读出侧还有一个实现细节:comms_can_read() 用自定义 memcpy() 把残包向左搬移。当前实现是正向复制,因此该方向可工作;但换成标准库 memcpy 后,重叠内存没有可移植保证。

    CAN FD 位:主机两条路径的差异

    原生 pandad connect() 给三路总线打开 canfd_auto,而原生 pack_can_buffer() 不显式置 FD 位。板端 process_can() 据 canfd_auto ? canfd_enabled : to_send.fd 决定 FDF,其中 canfd_enabled 在收到 CAN FD 帧后才设置。因此在首次收到 FD 帧之前,原生主机候选帧的 FDF 取决于板端尚未观察到 FD 的状态;即使 DLC 表可编码 12–64 字节,也不能据此断言硬件已经按 CAN FD 发送。

    Python 侧相反:Panda.connect() 关闭自动切换,pack_can_buffer(..., fd=...) 明确设置首位。此外固件 0xdd 暴露的 CAN 包版本由 can.h 原文字节 SHA-256 前四字节形成;Python 的 can_send_many()/can_recv() 用装饰器比对这个值,而原生 C++ pandad 不显式调用 0xdd,主要依赖固件签名一致性门。该哈希不是对主机 can_header 源文件或运行时硬件 FD 状态的证明。

    插件化边界

    源码 test_comms.py 有半包 reset、随机 64 字节块写入和 5 万条接收帧重组用例,但这些用例在 allOutput 模式下验证主要组包行为,没有直接证明坏 XOR 帧经过车型安全钩子前后状态不变,也不覆盖中断并发、物理 CAN 时序或全部车型许可分支。

    对自制硬件或「万物即插件」重构,主机 CAN 流适配、分块与遥测可以插件化,但需要在受信任入口先验证包长、DLC、bus、XOR/传输完整性,再交给有状态的安全策略;最终发送仍须固件独立许可。插件不得通过原始 USB/SPI 端点绕过验证,也不能把 returned 回执解释为车端已确认接收。

    常见问题

    为什么说 XOR 校验晚于安全钩子?

    因为完整帧进入 comms_can_write() 后直接调用 can_send(),而 can_send() 先运行 safety_tx_hook(),之后才入 TX 队列;can_check_checksum() 要等到 process_can() 从队列取出时才执行。所以坏 XOR 帧不会上总线,却可能先触达有状态的安全钩子。

    6 字节头和 72 字节结构到底哪个上总线?

    线格式只传 6 字节头加 DLC 对应的有效载荷,最长 70 字节;data 偏移为 6。带 packed, aligned(4) 的完整 C 结构在本机编译为 72 字节,但其尾部对齐字节不应传输。

    returned 回执能说明车端收到了吗?

    不能。returned 仅代表已提交硬件 TX FIFO,不等于车端 ECU 已确认接收。源文档明确要求不要把它解释为车端确认。

    为什么主机两条路径的 CAN FD 行为不同?

    原生 pandad 给三路总线打开 canfd_auto,且原生 pack_can_buffer() 不显式置 FD 位,FDF 由板端在收到 FD 帧后才置的 canfd_enabled 决定;Python 的 Panda.connect() 关闭自动切换,由 pack_can_buffer(..., fd=...) 明确设置首位。新硬件须分别验证这两条路径及冷启动。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/PANDA_CAN_COMMS_FRAGMENT_ASSEMBLY_DEEP_DIVE.md(取用了 CANPacket_t 字节布局、组包函数契约、安全钩子与 XOR 的真实顺序、CAN FD 位差异与插件边界)。
    • 项目 02 —— openpilot-research/CAR_PROTOCOL_IMPLEMENTATIONS.md(仅用于确认 can_comms.h 调用 can_send(..., false) 与 safety_tx_hook 的衔接位置)。

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11

  • panda 板级固件:三条 CAN 路径与 1 Hz 巡检的真实边界

    结论:panda 板级固件不是一个「CAN 转发器」。主机候选发送、车辆报文接收、板内总线转发是三条独立路径,各自经过不同的安全门与回执机制。启动时安全模式先被置为 SILENT,只有车型模式才驱动拦截继电器;而 1 Hz 的 safety_tick() 只做超时、最低频率与有效性的巡检,不能当成毫秒级断线检测。本文为固定源码归档的静态阅读,未编译固件、未连接实板。

    三条 CAN 路径与各自的安全门

    把三条路径混为一谈,是理解 panda 安全边界时最常见的错误。主机写入的候选帧、车辆发来的报文、以及板内转发,处在不同的钩子和不同的准入条件之下。

    路径 关键调用链 安全语义
    主机候选发送 pandad::can_send_thread() → comms_can_write() → can_send(..., false) → safety_tx_hook() → process_can() 主机先丢弃超过 1 秒的 sendcan;板端每个完整 CAN 包经过 TX 白名单、车型限值和继电器故障门。被拒帧作为 rejected 回执进入 RX 队列,不会进 TX FIFO。
    车辆报文接收 can_rx() → safety_rx_hook() → comms_can_read() → pandad::can_recv() 板端先从 FDCAN FIFO 取帧并附 bus/长度/FD 标志;安全 RX 仅对通过对应检查和白名单的帧调用车型状态钩子。
    板内总线转发 can_rx() → safety_fwd_hook() → can_send(..., true) 转发准许由独立的 safety_fwd_hook() 决定;true 表示跳过主机发送的 TX 钩子。当前代码在 safety_rx_hook() 之前计算转发并入队,因此不能声称接收帧必须先通过 RX 有效性检查才会被转发。

    safety_fwd_hook() 默认只映射 0↔2;继电器故障或模式设置 disable_forwarding 时阻断,并静态屏蔽需由 openpilot 替代发送的原车报文。另有细节:主机 CAN 组包维护跨传输分片的缓存,comms_can_reset() 在连接重建时清空它,避免半包污染下一连接。

    启动顺序也值得单独看:main() 先将安全模式设为 SILENT,再启用收发器、定时中断和 USB/SPI。主机侧 PandaSafety::fetchCarParams() 要求 FirmwareQueryDone、ControlsReady,随后 setSafetyMode() 发送安全模式和参数。这道门是启动协调,不是对任意主机代码或插件的独立授权机制。

    三条路径共享同一套健康上报(get_health_pkt())与继电器状态,但准入条件各不相同,这正是「主机能发」与「板端放行」必须分开验证的原因。

    安全模式与心跳:秒级兜底不是唯一保护

    安全模式不是普通运行时插件可任意替换的配置,切换会影响继电器和 CAN 队列,必须按车型、固件和线束验证。

    函数/状态 当前实现 硬件含义
    set_safety_mode() 调用 set_safety_hooks();未知模式退回 SAFETY_SILENT,连 SILENT 初始化都失败则停止。 SILENT/NOOUTPUT/ELM327 释放拦截继电器;车型模式才驱动继电器再初始化 CAN。
    set_safety_hooks() 注册模式、重置安全状态。MG、PSA、BYD、Subaru Preglobal、Volkswagen MLB/PQ、ALLOUTPUT 等仅在 ALLOW_DEBUG 编译块注册。 有目录和测试不代表 release 固件可选择该安全模式;必须核对实际固件构建与主机 safetyConfigs。
    safety_tx_hook() 车型模式检查地址、bus、长度白名单及车型 TX 约束;继电器故障会否决。 「panda 永远过滤任意主机帧」是错误表述;诊断与调试模式不具备车型执行边界。
    safety_tick() 每秒检查期望 RX 报文的超时、最低频率和有效性,异常时清 controls_allowed。 不能将 1 Hz 巡检误写为毫秒级断线检测。
    tick_handler() 8 Hz 调用风扇、线束、软件看门狗;1 Hz 处理点火、心跳、safety_tick()。点火时主机心跳 5 秒未到、非点火时 2 秒未到,切 SILENT。 心跳退出是秒级兜底,不是新硬件对快速危险动作的唯一保护。
    get_health_pkt() 上报安全模式、参数、心跳、RX/TX 拦截计数、队列溢出、CAN 错误、线束状态、电压/电流/温度。 主机应把健康状态和车型配置对齐,而不是仅判断 USB 在线。

    通信、线束与物理边界

    这些边界决定了「代码静态链路」与「电气默认态」之间的差距。队列里的 XOR 校验只保证主机与固件之间的传输完整性,与车型 CAN 报文自身的 checksum 不是一回事。

    组件 函数/实现 设计观察
    CAN 队列与校验 can_push()、can_pop()、can_set_checksum() RX/TX 使用临界区环形队列;队列满时累计溢出。包内 XOR 是传输完整性检查,不等于车型 CAN 报文 checksum。
    线束方向与继电器 harness_detect_orientation()、set_intercept_relay() SBU 电压判断正反插;方向改变时重新映射 bus、初始化 CAN 和安全模式。实际断电默认态须以原理图和台架证明。
    控制请求 comms_control_handler() USB/SPI 命令能设安全模式、CAN 速率、FD 行为、继电器、心跳和电源;0xc5 继电器控制在代码中并无 ALLOW_DEBUG 条件(注释写 DEBUG)。
    启动完整性 bootstub.main() 验证应用长度、版本、SHA 与 RSA 发布签名;启动签名约束固件映像,不代表运行时 USB 控制命令已获得逐操作鉴权。

    电源与错误恢复另有边界:set_power_save_state() 省电时关部分中断/收发器但保留主总线用于点火检测,源码明确要求安全功能执行期间不进入硬件低功耗模式;FDCAN 的发送前包校验、总线 off、RX FIFO 丢失与 ACK 错误会进入健康计数与控制器恢复路径,CAN FD/BRS 可由收到的帧检测并改变总线配置。现有测试入口包括车型安全单元测试、USB 协议测试,以及 HITL 的线束、CAN 回环与延时、NOOUTPUT 三组;本地归档未连接实板、也未运行这些测试,代码静态链路不证明电气默认态或故障覆盖率。

    自研车端网关的最低验收点

    1. 先复现 SILENT 启动、未知模式回退、车型 RX/TX/转发独立门、报文来源总线与长度、心跳丢失、原车 ECU 冲突检测;插件只可提交受限候选命令,不可获得原始控制端点。
    2. 分别注入心跳超时、软件 tick 间隔故障、FDCAN bus-off、队列满、主机失联、线束正反插与电源断开;记录 MCU 输出、继电器触点与原车总线波形,不能只看 health_t 计数。
    3. 板型 red、tres、cuatro 的 GPIO/收发器/供电差异要逐板核对;PANDA_CAN_CNT=3 与线束方向映射只是当前实现,不应直接推导新 PCB 的引脚或额定参数。
    4. 固件签名、版本、发布/调试模式、主机权限和可升级回滚是共同边界;不能用调试固件测试结果替代发布固件证据。

    常见问题

    panda 的 1 Hz safety_tick() 能替代毫秒级断线检测吗?

    不能。safety_tick() 每秒检查期望 RX 报文的超时、最低频率和有效性,异常时清 controls_allowed,其丢帧容忍窗口偏保守。它是秒级巡检,额外的主机超时、CAN 控制器故障与硬件失效仍需单独验证。

    主机发送的每一帧都会被 panda 无条件过滤吗?

    不是。车型模式检查地址、bus、长度白名单及车型 TX 约束,但 ELM327 和调试固件的 ALLOUTPUT 特例会把白名单视为通过。因此「panda 永远过滤任意主机帧」是错误表述,诊断与调试模式不具备车型执行边界。

    为什么板内转发不经过 safety_tx_hook()?

    因为转发以 can_send(..., true) 调用,true 跳过主机发送的 TX 钩子,其准入由独立的 safety_fwd_hook() 决定(默认只映射 0↔2)。这与 USB/SPI 客户端经 can_send(..., false) 的发送许可不是同一机制。

    固件签名能保证运行时 USB 命令被逐操作鉴权吗?

    不能。启动签名只约束固件映像;运行时控制命令的准入需主机与通信入口另行约束,USB/SPI 控制请求应纳入可信计算基,不应直接向第三方插件暴露。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/PANDA_BOARD_DEEP_DIVE.md(取用了三条 CAN 路径与安全门、安全模式与心跳、通信/线束/物理边界,以及自研网关验收点)。
    • 项目 02 —— openpilot-research/CAR_PROTOCOL_IMPLEMENTATIONS.md(仅用于确认车型差异与插件契约的引用位置)。

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11

  • 从使能到车轮:执行链上的八道门,以及它们各自不保证什么

    结论:从「软件认为该动作」到「车轮真的动了」,中间至少有八层各自独立的门。任何一层通过,都不代表下一层会放行:使能状态为真不代表执行器活动,控制消息的 valid 只是元数据,主机发出去的帧还可能被板端白名单拒掉。把「发出去」当成「执行了」,是这类系统里最贵的误判。

    八层门各自检查什么

    本文基线为源码快照 c8fb906,静态串联「状态机 → 控制器 → 车型网关 → 主机发送 → 板端固件」这条链:

    层 真正检查了什么 不能据此推断什么
    使能状态 非被动模式且已初始化才计算使能与活动 使能为真不等于横纵执行器都活动,也不等于板端已放行
    横纵活动位 横向还要求无临/永久转向故障与静止转向许可;纵向要求纵向能力且无纵向覆盖事件 此处没有统一的全量检查;纵向活动位基于「使能」而非「活动」
    控制数值与发布 关闭活动位时重置控制器;曲率限幅;非有限数值置零 消息的 valid 是元数据,不是硬阻断
    车型网关 非被动且事件集不含初始化中,才进入控制更新;控制消息存活时才应用车型控制器 该分支只检查单条消息存活,不是全量检查;sendcan.valid=False 仍可能带着候选帧
    主机 CAN 回调 指纹、固件查询、初始化可另发帧;安全模式握手等两个完成信号才切车型安全模式 「所有发送都来自控制器」不成立;握手参数也不是本机代码的权限认证
    发送线程 事件时间距现在小于 1 秒且非测试发送才走硬件发送 该函数不读事件有效性、不读使能状态,一秒是陈旧帧门而非安全截止期限
    心跳线程 约 10 Hz 依据状态消息的存活/有效与使能计算是否接管 心跳与发送线程相互独立;未接管不是逐帧否决
    板端发送 完整 CAN 包经白名单、车型发送钩子与继电器故障门 白名单中有帧不等于动作获准;车型钩子通常还查许可以及扭矩/角度/加速度限值

    失效时各层怎么反应

    这是排查线上问题的实用部分——同一类故障在不同层的行为并不一致:

    情况 本快照中的处理 仍待验证
    状态消息失联或无效 心跳侧据此令后续心跳显示未接管;控制器本轮计算并未据此统一停止 各进程更新顺序、残留帧、车型钩子与原厂接管时延
    控制消息失联 网关不再发布新的控制帧 独立诊断/初始化回调、最后一帧生效时间、板端与车辆超时
    CAN 无效但仍发了控制帧 网关把发送标记为无效,但发送线程只查年龄与测试标志 车型控制器是否真生成帧、板端是否拒帧、故障消息调度顺序
    主机帧陈旧 年龄达到 1 秒的发送事件被丢弃,板端从未见到该包 低于 1 秒但超过车型安全周期的行为及总线/接口堵塞
    板端心跳中断 点火时约 5 秒、非点火约 2 秒阈值后切入静默并清除接管标志 中断与接口故障的最坏墙上延迟、继电器与车端实际恢复
    许可与接管标志不一致 板端 1 Hz 段连续累计 3 次后清许可;上位机侧也对不一致累计并进入不匹配路径 两条路径异步,不能把 3 次与 200 次解释成相同的失效时限

    三个容易被当成「安全证明」的东西

    1. 消息的 valid 字段。它由 CAN 有效位赋值,是元数据。它既不阻断车型控制器生成帧,也不阻断发送线程,更不能替代板端校验。
    2. 界面上的使能图标。使能状态为真只说明状态机走到了这一步,横向与纵向是否真的活动要分别看活动位,板端是否放行还要再往下一层。
    3. 安全模式握手的完成参数。握手表示板端可以切到对应车型的安全模式,它不是本机代码的权限认证,也不是「动作已被批准」的凭据。

    做故障注入该覆盖哪些场景

    如果自研硬件或做插件化改造,下面这些场景必须逐条注入,而且顺序上应先回放与软硬件在环,再进封闭场地:

    注入场景 要观察什么
    CAN 无效 是否会带着无效标记继续产生候选帧,板端是否拦截
    控制消息丢失 最后一帧的实际生效时刻与停帧确认
    状态消息丢失 心跳切换与控制器是否继续计算之间存在的时间差
    送板延迟与接口堵塞 1 秒陈旧门以下、车型安全周期以上的中间地带
    心跳中断 板端静默阈值的实际墙上时间
    校验错与原厂接管 原厂巡航/制动/转向接管与诊断回调并发时的行为

    插件化改造的硬要求

    把传感器、车型或控制算法做成可替换插件时,状态消息与控制消息主题不应成为无认证的任意写入口。核心要维持唯一控制权、消息来源与新鲜度校验,板端保留独立的发送限制与安全模式回读。

    特别要把这五个环节分开记录:候选帧生成、发送消息发布、接口发送、板端接受、车辆执行。把这五件事混成一个「已下发」的日志,等于放弃了事后定责的能力。

    常见问题

    为什么「使能」了动作却没执行?

    因为使能只是第一层。横向和纵向各有独立活动条件(故障、静止许可、纵向覆盖事件),网关、发送线程和板端白名单还会各自过滤。任何一层不通过,动作就不会发生。

    发送消息标记为无效,为什么还可能发出去?

    因为有效性是元数据,发送线程实际只看事件年龄和测试标志,不读有效性也不读使能。所以带无效标记的候选帧仍可能到达板端——真正的拦截点在车型钩子和板端。

    板端心跳中断后多久会停发?

    源代码里的名义阈值是点火时约 5 秒、非点火约 2 秒后切入静默并清除接管标志。但这是 1 Hz 检查的计数阈值,最坏墙上延迟还要看中断与接口故障情况。

    安全模式握手完成就代表动作被批准了吗?

    不是。握手只表示板端可以切换到对应车型的安全模式,它既不是权限认证,也不代表具体动作已获准。动作是否放行仍由板端白名单与车型钩子逐帧判定。

    延伸阅读

    参考资料

    • 项目 02 —— NAS 谭健强/openpilot-research/ACTUATION_GATE_CHAIN_DEEP_DIVE.md(执行链门控与失效边界,含源码行号索引)
    • 项目 02 —— openpilot-research/CAN_PARSER_HEALTH_DEEP_DIVE.md(解析健康语义)
    • 项目 02 —— openpilot-research/CAR_STATE_NORMALIZATION_DEEP_DIVE.md(车辆状态归一化)

    说明:本文为对固定源码快照的静态分析,描述的是多层控制请求与板端过滤,不是实车执行时延证明。本轮未运行测试、未刷写板卡、未发送 CAN 报文、未连接车辆。车型控制器、板级电气与硬件在环结果仍须逐车核验。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11

  • CAN 报文解析的「健康位」在说什么:校验、计数器与总线空窗

    结论:CAN 解析器的「健康位」只回答一个问题——这个报文历史上成功解析过、且最近一次成功没有超时。它不保证本轮收到了数据,不保证帧长与 DBC 声明一致,也不保证物理总线存在。把健康位当成数据可信度来用,是车载数据链路里最常见的一类误读。

    健康位回答了什么,没回答什么

    在开源 ADAS 栈的实现里(本文基线为源码快照 c8fb906 的 opendbc/can/parser.py),解析器维护的是一组「最近值」而不是「本轮值」。每轮更新会清空本轮的 vl_all,但 vl 保留上一次成功解析的数值。因此一个信号有数值,只证明历史上曾成功,不证明本轮收到。

    时间戳同理:ts_nanos 记的是最近一次成功解析的报文时间,初值为零;它不等于 ECU 实际采样时刻。消费者如果有逐信号的安全要求,必须把时间戳、帧年龄和报文/总线健康三者合起来看,单独读 vl 是不够的。

    一帧数据要过哪几道关

    解码不是「取到字节就完事」,而是逐信号取值后按 DBC 声明做校验,任一失败即整帧不提交:

    关卡 判定内容 失败后的行为
    总线与地址过滤 只处理匹配 bus 且已注册的地址 整帧直接丢弃,不进入解码
    长度上限 数据长度不超过 64 字节 不处理
    信号解码 按起始位、长度、大小端取值并做倍率与偏移换算 解析失败则该信号不提交
    校验(checksum) 按 DBC 提供的函数比对 本帧不提交新值,也不刷新成功时间戳
    计数器(counter) 与上次计数加一取模比较 错则累计失败;连续达到阈值后拒绝本帧

    这里有两个容易忽略的细节。第一,长度只与 64 字节比,没有与 DBC 里声明的报文长度比——短帧在其它校验未拒绝的情况下,可能被补零式解码。所以硬件入口必须自行校验长度,不能依赖解析器。第二,计数器给的容错比想象的宽:初始计数为 0,单次坏计数只是累计失败,达到阈值才拒帧;连续正确则逐次递减。把「被解析」读成「序列完全正确」是不成立的。

    超时阈值是怎么定的

    「多久没收到算失效」不是全局常数,而是按报文注册方式分三种情况:

    注册方式 超时阈值 适用与风险
    显式指定频率 f 10/f 秒 需要准确知道真实频率;标称频率不等于实际频率
    未指定频率,运行时动态注册 先按 1 Hz / 10 秒 样本足够后按观测估计频率,上限 100 Hz;首帧可能要等下一个总线批次
    频率显式写成 nan 直接跳过活跃检查 报文仍能解码,但不参与健康判定

    这不是纸上推演。有的车型把某个实际约 50 Hz、但会周期性暂停半秒的报文配置成 1 Hz——因为标称频率不能直接当所有平台的故障阈值。也有车型把转向灯状态报文的频率写成 nan,意思是「这个报文我能解码,但别拿它来判断总线是否活着」。

    总线空窗不等于总线正常

    解析器还维护一个总线级标志:只看指定 bus 上最近一次非空批次距当前的时间差,阈值取 500 毫秒与各注册报文超时阈值中的较小值。问题在于,任意同 bus 的帧都能刷新这个时间,哪怕地址未注册、哪怕解析失败。另外,如果所有注册报文都跳过了活跃检查,这个标志会恒为假。

    所以它检测的是「这条总线上还有没有电信号活动」,而不是「我需要的信号还在不在」。这和逐报文的超时判定是两个不同的布尔位,不能互相替代。

    两个实现层面的陷阱

    1. 读取有副作用。健康判定会在被读取时累计缺失次数:缺失时加一,有效时清零,连续累计到阈值即判定失效。同一更新周期里多次读取,会更快把计数推到阈值。也就是说,多读几次可能改变结论。
    2. 空解析器会「假装健康」。上游把多个解析器的健康位做聚合(逐项求与、总线空窗求或)时,如果某个解析器一个报文都没注册,它没有可检查项,健康位会直接清零失效计数,总线空窗也会因为对空集合求值而返回假。「健康」在这种情况下只是「无话可说」,不是「一切正常」。

    自制硬件与插件该记录哪些字段

    如果把车载解析能力做成产品,接收侧的契约至少要覆盖下列字段——重点是把「缺失」「未配置」「观测为假」三种状态明确分开,不能都塌缩成一个 false:

    字段 为什么必须有
    bus 与地址 区分同 ID 在双总线上的不同含义
    DBC 版本 同一车型不同年款的信号定义会变
    帧长度与声明长度 长度不一致必须能被上层发现
    checksum / counter 结果 解码成功不等于校验通过
    采样时间与最后成功时间 区分「本轮值」与「历史保留值」
    未配置 / 缺失 / False 三分 避免把「没检查」当成「检查通过了」

    常见问题

    解析健康能用来说明车辆数据可信吗?

    不能。它只说明报文曾成功解析且未超时。可信度还要看帧长校验、校验和与计数器结果、时间戳新鲜度,以及车型侧对字节的具体解释。

    为什么短帧会被「成功解码」?

    因为接收路径只把数据长度与 64 字节上限比较,没有与 DBC 声明的报文长度比较。短帧在其它校验不拒绝时可能被补零处理。长度校验必须放在硬件入口。

    总线上还在跳,能说明我的信号没问题吗?

    不能。总线空窗只看该总线上有没有非空批次,未注册地址或解析失败的帧同样能刷新它。要判断具体信号,必须看该报文的逐项超时与时间戳。

    为什么同一周期多读几次健康位会影响结果?

    因为缺失计数是在读取时累计的,读取不是纯查询。连续累计到阈值会判定失效,有效读取则清零。设计消费逻辑时要把这一点当作状态机而不是取值函数。

    延伸阅读

    参考资料

    • 项目 02 —— NAS 谭健强/openpilot-research/CAN_PARSER_HEALTH_DEEP_DIVE.md(CANParser 健康语义,含源码行号索引)
    • 项目 02 —— openpilot-research/CAN_PARSER_FRAME_LENGTH_SIGNAL_FRESHNESS_DEEP_DIVE.md(短帧与信号新鲜度专题)
    • 项目 02 —— openpilot-research/CAR_STATE_NORMALIZATION_DEEP_DIVE.md(车况归一化与健康消费)

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实校验和与计数器组合。文中所有行号索引与阈值均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11