标签: 控制执行

  • OSCC 开源车辆控制:归一化命令、200 ms 保活与三层安全边界

    结论:OSCC 的价值在于它选了一条最难但最可辩护的路线:不欺骗 CAN 报文、不改原厂安全用例、不破解 ADAS 功能,而是把控制命令注入到转向盘扭矩传感器、油门位置传感器与制动位置传感器这三个物理接口上。代价是必须自己补上原厂本来承担的安全机制——文档里能明确数出来的有三层:enable/disable 门控、每 200 毫秒必须调用一次的看门狗、以及默认开启的操作者超控。任何把这三层当可选项的实现,都偏离了它的设计前提。

    接口定位:低层注入而不是报文欺骗

    这套系统由软件 API、固件与硬件设计三部分构成,通过车载通信网络与控制系统的模块化接口实现计算机控制。它的能力边界可以拆成三件事:向车辆发送控制命令、从车辆的 OBD-II CAN 网络读取控制报文、转发当前车辆控制状态报告(如转向角与轮速)。控制命令并不直接操纵执行器,而是作用于部件 ECU 的传感器输入端——转向盘扭矩传感器、油门位置传感器、制动位置传感器。汽油版起亚 Soul 不具备线控制动能力,因此额外加装了一套辅助执行器来实现制动。

    这种低层接口的意义在于完整保留原厂安全用例:原车的失效检测、冗余与机械链路都还在,注入的只是一个「驾驶员意图」。这与在总线上伪造一帧转向命令有本质区别——后者绕过了原厂所有校验,前者仍然在原厂设计的信号链里。目前官方支持的车型是 2014 年及以后的起亚 Soul(汽油版与纯电版),但 API 与固件在设计时就为新增车型留出结构:按既有车型头文件的格式新增一个头,并补充一个构建选项即可。

    版本矩阵:四件事必须同时对齐

    需要跟踪的版本有四件:传感器接口板(油门与转向,版本印在板正面)、车辆控制模块(纯电版制动)、执行器控制板(汽油版制动),以及共用同一版本号的 API 与固件。文档对版本更新给的评价很重——每次有新版本都必须更新,以确保没有使用带有已知安全问题的版本。这不是礼貌性提醒,因为板卡版本与固件版本的配对关系会直接影响启动自检能否通过。

    板卡 板卡版本 要求的固件版本 说明
    传感器接口板 大于等于 1.0.0 大于等于 1.0.0 承担油门与转向
    车辆控制模块 0.1.0 大于等于 1.1.0 纯电版制动
    执行器控制板 大于等于 1.2.0 大于等于 1.0.0 汽油版制动
    执行器控制板 小于 1.2.0 大于等于 1.0.1 需额外关闭启动自检

    最后一行是唯一需要额外操作的组合:较新的执行器控制板利用了新的引脚做启动阶段的附加安全检查,旧板没有这些引脚,因此构建时要显式关闭制动启动测试,否则自检会失败。这是硬件演进与安全检查相互耦合的直接例子。

    命令契约:归一化取值范围与 200 毫秒保活

    API 只暴露三个发布函数,参数全部归一化,与具体车辆的电压标定解耦:制动与油门位置取 0.0 到 1.0,转向扭矩取负 1.0 到正 1.0,其中负为逆时针、正为顺时针。API 负责把归一化值换算成需要注入的欺骗电压,并内置安全检查以确保不会发出超出车辆预期范围的电压。这个换算层是安全边界的一部分——应用层永远碰不到原始电压。

    保活要求是这套接口里最容易被忽略的硬约束:三个发布函数每一个都必须至少每 200 毫秒被调用一次,否则模块会判定与上位机失去通信并自行禁用。它把「上位机死机」和「主动松开控制」统一收敛成同一种失效结果,这正是看门狗该有的行为。反过来说,一个只在上位机有输出时才发命令的控制循环,一旦进入长时间稳定状态就会自己把系统关掉。

    接口 方向与取值 约束
    oscc_open / oscc_close 通道编号 建立与终止到固件的 CAN 通道连接
    oscc_enable / oscc_disable 无参数 禁用时仍可接收报告,但无法发送命令
    publish_brake_position 0.0 至 1.0 每 200 毫秒至少调用一次
    publish_throttle_position 0.0 至 1.0 每 200 毫秒至少调用一次
    publish_steering_torque 负 1.0 至正 1.0 负为逆时针、正为顺时针,同样需 200 毫秒保活
    subscribe_to_brake_reports 等 回调函数 制动、转向、油门、故障四类报告各自独立订阅
    subscribe_to_obd_messages 回调函数 转发非本系统的 CAN 帧,可按 ID 自行解析

    还有一条实现层面的注意事项:API 会捕获 CAN 帧到达时产生的信号并为其安装处理函数,把有效报文转发给应用。因此应用中对中断敏感的代码段,应在该段执行期间临时阻塞该信号、结束后再解除,否则回调可能在临界区中被触发。

    三层安全边界

    把这些约束放在一起看,OSCC 的安全设计是分层的,任何一层单独失效都不会立刻导致失控。

    层级 机制 失效时的行为
    应用层 oscc_enable 与 oscc_disable 门控 未使能时命令发不出去,但报告照常接收
    通信层 三个发布函数各需 200 毫秒内再次调用 超时即判定通信丢失,模块自行禁用
    信号层 API 内置电压范围安全检查 换算后的欺骗电压被限制在车辆预期范围内
    操作层 操作者超控检测,默认全部开启 驾驶员施加扭矩即退出,转向灵敏度可调
    硬件层 启动自检(新板用新引脚做附加检查) 自检不通过则不上线
    流程层 强制跟随版本更新 避免停留在带已知安全问题的版本

    转向超控为什么默认开启且不建议关闭

    所有模块的操作者超控检测默认开启,但文档对转向给出了额外警告:系统激活时试图抓住转向盘可能导致严重伤害,因此推荐的转向超控方式是制动踏板或急停按钮,而不是去扳方向盘。若确实需要关闭,可在构建时传入关闭标志,此时灵敏度由对应车型头文件中的扭矩差阈值控制。

    这个阈值是一个典型的双向权衡:调低会让模块对超控更敏感,但在转向齿条机械极限或快速反复变向等高扭矩区域产生误判;调高则误判更少,但人工接管转向变得更困难。文档没有给出默认值之外的推荐值,因为最优解取决于具体的转向系统与应用场景,这正是需要在实车上标定的参数。

    构建、刷写与测试命令

    固件用 CMake 构建而非 Arduino IDE,目的是绕开 IDE 的限制并支持命令行刷写。前置条件是 Arduino Core 1.8.5 及以上与 CMake 2.8 及以上。构建时通过车型标志选择目标:汽油版 Soul、纯电版 Soul 与 Niro 各有对应标志。生成的构建目标可以整体构建,也可以按模块单独构建,四个模块分别是制动、CAN 网关、转向与油门。

    环节 命令或标志 说明
    选择车型 构建时传入车型标志 汽油版 Soul、纯电版 Soul、Niro 三选一
    关闭转向超控 追加关闭超控标志 默认开启,不建议关闭
    关闭制动启动测试 追加关闭启动测试标志 仅旧版执行器控制板需要
    发行构建 指定构建类型为 Release 关闭调试符号并开启优化
    单模块构建 制动 / 网关 / 转向 / 油门四个目标 可只构建所需模块
    刷写 单模块上传目标与全量上传目标 按字母顺序连接模块可获得稳定端口分配
    单元测试 打开测试标志后运行单元测试目标 依赖 Cucumber 2.0.0 与 Boost
    属性测试 打开测试标志后运行属性测试目标 依赖 Rust、Cargo 与 libclang

    测试分两类且覆盖角度不同。单元测试以行为描述的方式验证报文处理,例如油门模块的「接收命令」用例会给出传感器读数与两路 DAC 输出值的对照表,四个样例点分别是 0 对应 0、256 对应 1024、512 对应 2048、1024 对应 4096。属性测试则验证控制器库的不变式,一组运行包含积分项、微分项、比例项、反转输入、相同输入产生相同控制、以及清零共七项。两类测试都有分模块的独立运行入口,也有一次性运行全部测试的便捷入口。

    许可证分工:硬件与软件不是同一种授权

    这一点常被忽略但对集成决策影响很大:硬件源材料——原理图、电路板布局、接线图、数据手册、物理安装文档、三维模型等——采用 CC BY-SA 4.0 授权;固件与软件源则采用 MIT 授权,除非第三方依赖另有声明。也就是说,照着板卡设计做改动后的再分发义务,与修改代码后的义务并不相同。此外文档整体声明按现状分发、不提供任何担保。

    组成部分 许可证 主要义务
    硬件源材料(原理图、布局、接线图、安装文档、三维模型) CC BY-SA 4.0 署名并以相同方式共享衍生作品
    固件与软件源(源码、头文件、文档、构建脚本) MIT 保留版权与许可声明
    第三方依赖 以各自声明为准 需逐项核对

    项目还提供商用的整车控制接口方案作为交钥匙选项,覆盖纯电版 Soul 与 Niro 混动,包含集成控制模块、车规级线束与急停开关。对研究者而言,开源版本与商业版本的区别在于支持范围与工程化程度,接口契约本身是一致的。

    常见问题

    OSCC 是在 CAN 总线上伪造报文吗?

    不是。它把控制命令作用于部件 ECU 的传感器输入端——转向盘扭矩传感器、油门位置传感器与制动位置传感器,属于低层接口,因此能在不改动原厂安全用例、不欺骗 CAN 报文、不破解 ADAS 功能的前提下实现全量程控制。纯电版以外的汽油车因不具备线控制动能力,需额外加装辅助执行器。

    为什么控制命令必须每 200 毫秒发一次?

    这是模块侧判定上位机是否存活的看门狗:三个发布函数中任意一个超过 200 毫秒未被调用,模块就认定与上位机失去通信并自行禁用。它把上位机死机与停止控制统一成同一结果。因此控制循环不能只在数值变化时才发命令,长时间稳态下也必须持续调用。

    可以关闭转向超控吗?

    可以在构建时传入相应标志关闭,但不建议。文档明确警告系统激活时抓握转向盘可能造成严重伤害,推荐的转向超控方式是制动踏板或急停按钮。若保留开启,灵敏度由车型头文件中的扭矩差阈值控制:调低会增加高扭矩区域的误判,调高会让人工接管更困难。

    板卡版本与固件版本不匹配会怎样?

    会直接影响启动自检。较新的执行器控制板用新引脚做附加的启动安全检查,旧板没有这些引脚,必须用关闭制动启动测试的构建标志处理。其余组合按官方配对表选择固件版本即可,文档同时强调每次发布新版本都应及时更新,以避开已知安全问题。

    硬件设计和软件代码的授权一样吗?

    不一样。原理图、电路板布局、接线图、数据手册、物理安装文档与三维模型等硬件源材料采用 CC BY-SA 4.0,衍生作品需署名并以相同方式共享;固件与软件源采用 MIT,只需保留版权与许可声明;第三方依赖以各自声明为准。集成前应按组成部分分别核对义务。

    延伸阅读

    参考资料

    • 项目 01 —— public-can-dbc-datasets/additional/polysync-oscc/README.md(接口定位、版本矩阵、API 契约与 200 ms 保活、构建与测试命令、许可证分工)
    • 项目 01 —— public-can-dbc-datasets/additional/polysync-oscc/LICENSE.md(硬件源材料的 CC BY-SA 4.0 条款与固件软件的 MIT 条款全文,含免责与责任限制)

    免责声明

    本文所述内容来自公开开源项目的说明文件,不代表车辆制造商的官方数据,文中数字均摘自上述文件,未经独立实车验证。该项目按现状分发、不提供任何担保:硬件源材料(原理图、电路板布局、接线图、数据手册、物理安装文档与三维模型)采用 CC BY-SA 4.0,衍生作品须署名并以相同方式共享;固件与软件源采用 MIT,须保留版权与许可声明;第三方依赖以各自声明为准,集成前应逐项核对。对车辆进行任何控制改装都涉及人身与财产安全,本文仅作技术资料梳理,不得作为上路或载人测试的依据;实际操作须在封闭场地、由具备资质的人员在急停开关与完善的失效处理机制下进行,并以官方资料与实际测量为准。

    更新记录

    • 2026-10-15 初稿

  • Talaria 电动越野车 CAN 信号:250 kbit/s 下 15 个 ID 与四处证伪

    结论:Talaria 电动越野车的车载 CAN 是一条 250 kbit/s、11 位标准 ID、15 个报文 ID、约 92 帧每秒的总线,用一颗 3.3 V 收发器挂在 ESP32-S3 的 TWAI 控制器上即可只读取数。真正有复用价值的不是那张 14 项信号表,而是表里被明确证伪的四处:0x103[0] 不是电量、0x202[6:8] 不是里程、0x490 不是温度也不是单字段、节流需求偏移随固件而变。把这几条排除后再取数,才不会把「看起来像」当成「就是」。

    总线前提与接入方式

    这条总线在电机控制器处即可挂到 CAN_H 与 CAN_L,硬件侧只需要一个 3.3 V 的 SN65HVD230 收发器:发送脚接 GPIO7、接收脚接 GPIO8,直连而非交叉——这一点与同一块板上的 GPS 串口不同,后者是交叉的。CAN 外设用的是 ESP32-S3 自带的 TWAI 控制器,外部模块只承担物理层。

    两个电气细节必须在上电前处理。第一,模块板上那个标着 121 的 120 欧终端电阻要拆掉:车辆总线两端已经各自终端,跨 CAN_H/CAN_L 量到的是 60 欧,再并一个电阻会掉到 40 欧,波形裕量直接被吃掉。第二,一旦 CAN 双线接入,设备的地就通过降压模块的负极与车辆共地,此时再插 USB 会把车辆电池负极接到保护地,同时 USB 的 5 V 与降压模块的 5 V 在 VUSB 上相撞;拔掉车端连接器可以同时隔离这两个风险,遥测数据改走无线链路读取。

    总线默认是只读取模式:收发器从不驱动总线,甚至不应答,因此即便波特率猜错也不会扰动车辆。这一点是整套方案能从「只读」安全扩展到「可写」的前提。

    参数 取值 备注
    波特率 250 kbit/s 扫描子命令会遍历波特率与标准/扩展 ID 组合
    ID 类型 11 位标准 ID 与整车厂常见的 29 位扩展 ID 不同
    总线报文 ID 数 15 固件当前只解析其中 8 个
    帧率 约 92 帧/秒 全总线合计
    终端电阻 60 欧(熄火状态下测量) 读到 120 欧说明只挂到了单端
    接地 无需单独地线 通过降压模块负极已与车辆共地
    DBC 规模 9 条报文、26 条信号行 每条信号的验证过程写在 CM_ 注释里

    已解码信号与字节定义

    下面这张表是这份研究的核心产出。所有偏移都按小端处理,物理量的缩放因子在最后一列。值得注意的是,电芯最高/最低电压与序号共用同一帧的不同字节组,序号分别落在字节 4 与字节 5。

    信号 帧 ID 字节与位 缩放
    车速 0x303 字节 0–1,小端 0.1 km/h
    电机转速 0x203 字节 0–1,小端 1 rpm
    功率 0x203 字节 2–3,小端 1 W
    电流 0x302 字节 4–5,小端 0.1 A
    电池包电压 0x101 字节 0–1,小端 0.1 V
    电量百分比 0x401 字节 0 1 %
    最高电芯电压与序号 0x201 字节 0–1 小端,序号在字节 4 1 mV
    最低电芯电压与序号 0x201 字节 2–3 小端,序号在字节 5 1 mV
    边撑 0x202 字节 0 第 7 位 1 为放下
    骑行模式 0x202 字节 0 第 5:4 位 1 节能、2 运动
    动能回收等级 0x490 字节 0 第 2:0 位 1 至 4
    骑行模式(仪表指令) 0x490 字节 0 第 5:3 位 1 节能、2 运动
    里程 0x402 字节 2–3,小端 1 km
    转把需求 0x202 字节 3–4,小端 单位未确认

    交叉验证给了两个可信锚点:车速与转速在两次独立骑行中保持 5.887 的固定比值,相关系数 0.999,对车速积分也能还原出合理的骑行距离;最高与最低电芯电压的平均值乘以 16 能复现电池包电压到 0.02 V 以内,这坐实了 16 串的电池包结构。这两条把「解析对了」从感觉变成了可检验的判据。

    遥测包里的 CAN 块与门控位

    解码后的车辆数据占遥测包第 50 至 69 字节,配套一个状态字节:第 0 位表示总线存活,第 1 位是边撑,第 3:2 位是骑行模式,第 6:4 位是回收等级。规则只有一条但必须遵守——超过 2 秒没有收到任何帧时,整个字节块会被清零、存活位同时拉低,因此判空要门控在存活位上,而不能相信「速度为零」。否则一辆停着的车和一根拔掉的线在数据上完全一样。里程字段在第 88 字节,取的就是 0x402[2:4],读到 0 表示还没见过该帧。

    四处已被证伪的字段

    这份研究最值得照搬的不是结论,而是它把「曾经以为」逐条推翻并留下判据的过程。四条错误里前两条是典型的形状误导,第三条是位域切分错误,第四条是跨设备外推错误。

    被误认的字段 为什么看起来像 证伪判据 正确取法
    0x103[0] 当电量 数值形态像百分比 实际电量在别处变化时它恒为 100,几乎可以确定是健康度 取 0x401 字节 0
    0x202[6:8] 当里程 单调递增,形态极像里程计 与车速的相关系数为负 0.0005,两次骑行的每公里计数相差 34% 取 0x402[2:4],整公里
    0x490[0] 当温度或单字段 整字节与模式的相关系数 0.99,看起来整字节都是模式 第 5:3 位是模式、第 2:0 位是回收等级;节能模式下第 3 位被置起,回收 1 会读成 0x09 而非 0x01 掩 3 位而不是 4 位
    转把需求偏移固定 同车型同帧 ID,默认偏移可复用 本车在 0x202[3:5],参考日志里那辆车在 0x202[2:4] 且根本不跟随转把 换车必须重新验证偏移

    里程的发现过程有一条通用经验值得单独记:这份研究曾长期写着「总线上没有找到里程计」,而这个结论是从固件捕获的 8 个帧里得出的,0x402 恰恰属于没人解码的那 7 个。后来是照着车辆仪表自己显示的 400 km 去全总线捕获里找这个数,0x402[2:4] 正好读出 400。由于总线上存在 1200 这类整常数(同时出现在 0x101[4:6] 与 0x302[6:8]),单次对上整数只是提示而非证明,因此它被明确标注为待验证——需要一次实际骑行看它自增。

    从只读到可控:0x490 是命令不是回声

    0x490 长期被当作控制器的回声,直到一次按键对比实验才被推翻:连续五次按键里,0x490[0] 每次都先动,0x202 在 20 至 27 毫秒之后才跟着变,同时带上新的模式位与新的需求下限(运动 1100、节能 750)。在仪表持续以 5 Hz 发运动模式的同时注入节能,控制器在整个注入期间被钉在需求 750 上。结论是仪表与控制器是两个不同节点,控制器服从总线上最近一次 0x490,而且从不锁存。

    这就决定了「覆盖」不能以命令的方式实现,只能以持续争论的方式实现:仪表下一帧在 200 毫秒后就会把单次发送的内容推翻,所以只要覆盖还生效,就必须应答仪表发出的每一个 0x490。实测差距很明显——自由运行在 20 Hz 注入会输掉 24% 的帧,而收到即回则在控制器自己的 297 帧里守住了 297 帧。决定成败的是延迟,不是发送频率。

    机制 行为 后果
    锁存 控制器不锁存,服从最近一帧 覆盖必须持续应答,发一次无效
    注入方式 收到即回,守住 297/297;自由运行 20 Hz 丢失 24% 延迟是机制,频率不是
    释放路径 无线断连、CAN 超时、车把按键任一触发 100 毫秒内回到仪表控制
    可验证性 模式可见,回收等级无法验证 回收不带动总线上任何其他量,功率与电流都是无符号的
    仪表显示 仪表不监听总线 覆盖期间仪表显示与实车状态真实不一致
    回收限制 电量高于约 90% 时车辆自行抑制 指令发出但正确地不产生效果

    另外,喇叭与灯光根本不上总线,它们是车把开关直接控制的 12 V 回路;刹车拉杆在部分车上有信号,改装的无传感器拉杆什么也发不出。这些边界决定了这套数据能覆盖到什么程度。

    嗅探工具与验证命令

    逆向过程被固化成四个子命令,全部跑在主机侧,配合一个串口转 CAN 适配器工作。 scan 负责扫波特率与 ID 类型组合,如果到处都是零帧,最常见的原因是 CAN_H 与 CAN_L 接反——无害,换过来即可;sniff 给出实时表格,包含解码后的物理量、每个 ID 的速率、逐字节变化图;events 先自学哪些字节会自行漂移,然后只打印真正的变化,使用时一次只操作一个控件,就能定位该控件落在哪个字节;log 落盘为 .asc、.blf 或 .csv 供离线分析。

    子命令 用途 典型判据
    scan 扫描波特率与标准/扩展 ID 全组合零帧通常是 CAN_H/CAN_L 反接
    sniff 实时表格:物理量、每 ID 速率、逐字节变化图 用于确认波特率与帧清单
    events 过滤自发漂移,只打印真实变化 一次只动一个控件
    log 录制 .asc/.blf/.csv 供离线回归与跨骑行比对

    配套环境搭建只需两条命令:先建虚拟环境并安装依赖,再用 250000 的波特率参数调用对应子命令,串口设备名按实际枚举结果传入。整个链路不需要改动车辆任何部件,这也是只读取数能长期稳定运行的底气。除了这套主机侧工具,固件侧还有一个专门的 CAN 探测程序,它把解码后的仪表直接输出到屏幕与无线链路上——之所以刻意走无线,是因为车辆与 USB 主机任何时候都不能同时连接。

    最后补一条容易被忽略的经验:总线上的 15 个 ID 与实际解析的 8 个之间的落差,本身就是最大的风险来源。任何「总线上没有某个信号」的结论,只有在完整捕获全部 ID 之后才成立;而车辆仪表已经显示的数值,是验证解码结果最便宜的基准。

    常见问题

    这条总线能直接拿来读取车辆数据吗?

    可以,但必须只读取:收发器不驱动总线也不应答,波特率猜错也不会扰动车辆。前提是先拆掉收发器模块上的 120 欧终端电阻,否则总线的 60 欧会变成 40 欧。另外不要同时接 USB 与车辆,两者会在 VUSB 上相撞并把车辆地接到保护地。

    为什么我读到的电量一直是 100?

    因为取的是 0x103 字节 0,那个字段在真实电量变化时保持不变,几乎可以确定是电池健康度。真实的电量百分比在 0x401 的字节 0,缩放为 1%。

    里程到底在哪个帧?

    在 0x402 的字节 2 至 3,小端,单位 1 公里。曾经被误认为里程的 0x202[6:8] 虽然单调递增,但与车速相关系数只有负 0.0005,两次骑行的每公里计数相差 34%,已被排除。由于总线上存在 1200 这类整常数,该结论仍标注为待一次自增骑行验证。

    能不能通过总线改骑行模式?

    可以,但不能用「发一次命令」的思路。控制器服从最近一次 0x490 且不锁存,仪表每 200 毫秒就会覆盖一次,因此必须持续应答仪表的每一帧;实测收到即回守住 297/297,自由运行 20 Hz 会丢失 24%。同时要注意仪表不监听总线,覆盖期间仪表显示与实车真实状态不一致,而回收等级发出后无法从总线验证。

    换一辆同型号车能直接复用这套偏移吗?

    不能。转把需求在本车位于 0x202[3:5],在参考日志那辆车上位于 0x202[2:4] 且完全不跟随转把。上游明确提示字节偏移随固件版本变化,换车必须重新验证,且这套解码本身是以另一份公开的 Talaria CAN 日志项目为地基的派生研究,并非独立来源。

    延伸阅读

    参考资料

    • 项目 01 —— public-can-dbc-datasets/additional/kbirand-talaria-tripper/README.md(总线参数、硬件接入、14 项解码信号表、四个字段证伪过程与覆盖机制实测)
    • 项目 01 —— public-can-dbc-datasets/additional/kbirand-talaria-tripper/SOURCE.md(DBC 的 9 报文 26 信号行规模、里程字段的待验证声明、派生来源与权利状态说明)

    免责声明

    本文所述的总线参数与信号定义均为社区研究报告产物,不代表车辆制造商的官方数据,文中数字均来自上述公开仓库的说明文件,未经独立实车验证。上游明确指出该解码是以另一份公开的骑行日志为地基的派生研究,适用车型、固件版本与其他车辆均未确立,字节偏移随固件变化,换车必须重新验证。该仓库快照中未发现任何许可证或逐文件再分发授权,仅可作私有离线研究副本,再分发或集成前须先行确认权利。涉及写入总线的操作请仅在封闭场地、由具备资质的人员执行,并以官方资料与实际测量为准。

    更新记录

    • 2026-10-15 初稿

  • 特斯拉 iBooster Gen1 台架解码:双总线、已锁存故障与先验证清单

    结论:这份 Tesla iBooster Gen1(件号 1037123-00-B,适配 2016 至 2020 年 Model S)的台架笔记给出三条硬结论:只给 12 V 常电、地和点火三根线,助力就会出现,因此纯只听的监视方案成立;两条 500 kbit/s 总线的引脚和极性都已完成实测;踏板行程可以换算到毫米。但这些结论有一条严厉的前置要求——作者在专门的「先自行验证」文件里写了:踏板传感器引脚、件内部终结电阻、峰值工作电流等项目必须先在台架上量出来,未经核实不得写进固件、DBC 或线束。

    台架前提:哪些已经量过,哪些只是假设

    区分「已测得」和「来自其他车型的社区资料」是这份档案的第一原则。电源与 CAN 引脚是实测量;踏板传感器那几组引脚属于借用其他 iBooster 变体(不同厂商平台)的推论,置信度低。

    引脚 功能 状态 如何收口
    1 常电 12 V,40 A 熔断 已确认 配合 9 与 20 即可出现助力
    9 地 已确认 与壳体接地螺柱连通验证
    20 点火 12 V,5 A 熔断 已确认 只有在它带电时才出现助力
    25 / 16 车辆总线 CAN-H / CAN-L 已确认,含极性 该极性下抓到干净的 0x39D 帧
    18 / 10 YAW 总线 CAN-H / CAN-L 已确认,含极性 该极性下抓到 0x38E 与 0x38F
    17 常电 12 V,5 A 熔断 低置信度 Gen1 应有此针,本件未量
    2 / 8 / 22 / 23 踏板行程传感器 低置信度 社区对同代也有分歧,需台架确认
    24 / 4 制动信号输出 / CLIN 低置信度 可能本件根本不存在
    连接器 26 针 EuCon 中等置信度 按壳体标记与针腔数量核对

    另外三条至今没有量:助力时的峰值电流(社区说法是「几十安」,保险丝规格 40 A,未测前一律按电瓶供电准备);只接引脚 1 与 9 不给点火时是否还发报文;上电自检是否会让推杆移动(在证明不会之前按会来处理)。

    两条总线与关键报文的定位

    两条总线同为 500 kbit/s、11 位 ID,都不带内部终结电阻,因此适配器侧的 120 欧终结跳线必须闭合,并在部件端并一颗松脱的 120 欧电阻,形成两节点总线;上电前的高低速线间拓扑检查应读到约 60 欧。

    总线 引脚(H / L) 静态帧率 报文 ID 承载内容
    车辆总线 25 / 16 36.4 帧每秒(静态 4 个 ID,踏板动作时 11 个) 以 0x39D 为主 16 位踏板行程
    YAW 总线 18 / 10 149.5 帧每秒 0x38E(99.7 Hz)、0x38F(49.8 Hz) 12 位位置、状态与两个制动标志

    实际作业中用两台适配器时,USB 索引会重排,识别哪台在哪条总线上必须看内容——谁看到 0x39D 谁就在 25/16 上,绝不能按索引或 USB 地址判断。还有一个非常具体的陷阱:这条总线只有两个节点,只听模式下没有节点发应答位,发送错误计数会以每次 8 递增,约 32 次尝试就进总线关闭状态,再自动恢复,如此循环。同一器件、同一 12 秒窗口的对照结果是:只听模式抓到 1,006 帧每秒但只有 1 个唯一 ID,而应答模式是 37 帧每秒却有 4 个 ID。所以捕获一律用应答模式。

    0x39D:踏板行程的字节定义与两套标定

    这条报文是 500 kbit/s、DLC 4、25.0 Hz。它的 b0 是校验和,算法为 b1 + b2 + b3 + 0xA0 取低字节,在 2,250 帧上 100% 通过;b1 是半字节活跃计数器(每次加 1,模 16,高四位恒为 0);b2:b3 是小端无符号 16 位行程值,静态约 262,观测到的最大值为 14070。

    样例 解析
    B7 0E 08 01 计数器 14,行程 264(静止)
    85 00 C8 1D 计数器 0,行程 7624(行程中段)
    D4 0B F8 31 计数器 11,行程 12792(接近全行程)

    筛选方法值得一提:按「平滑度」而不是极差排序候选字段,判据是相邻差值均值除以值域。校验和与计数器铺满 00 到 FF 但帧间跳变随机,物理量则平滑移动;b2:b3 小端组合得分 0.009,计数器与校验和那对约 0.12。

    位置 实测原始值 样本数
    静止 263.1 319
    21 mm 6114.0 161
    42 mm 13125.2 80
    终点限位脉冲峰值 13606(合 43.44 mm) 无

    第一次跑给出 counts = 333.866 × mm − 897.2,即约 0.0030 mm 每计数。但静止点不落在这条直线上:263 计数外推得 3.475 mm 而不是零,合理解释是传感器啮合前约有 3.5 mm 自由间隙,低于该点读数被压在约 263 的底值上。第二次跑保持在 10、20、30 mm,结果与第一次不自洽——20 mm 读到 6886.4,反而高于第一次 21 mm 的 6114.0。单调传感器不可能如此,因此至少有一组物理位置是错的。

    组合 计数每毫米
    第一次跑(21、42 mm) 333.87
    第二次跑(10、20、30 mm) 361.93
    五个点全用 320.68

    结论很干脆:瓶颈是尺子而不是 CAN 数据。单次保持内的总线读数离散为约正负 20 计数(0.062 mm),而两次跑之间的物理位置偏差可达 4.4 mm——信号比它的测量手段精确约 70 倍。要真正标定,需要百分表配固定基准,并且一次跑完整个量程(因为误差出现在两次跑之间,而不是一次跑的内部)。用于显示和记录已足够,档案给出的组合拟合是 mm = (counts − 3.3) / 320.68。

    0x38E 与 0x38F:状态、两个制动标志与校检

    0x38E 是 500 kbit/s、DLC 8、99.7 Hz,静止值为 77 2B 00 40 11 00 00 00。它的 b0 不是数据,而是覆盖 b1 到 b7 的 CRC-8/SAE-J1850。b3 只是位置的低字节,b4 的低四位才是高字节位——这是这份档案纠正过的最重要一处。

    字节 字段 说明
    b0 CRC-8 覆盖 b1 至 b7,无 数据
    b1 活跃计数器 0x20 至 0x2F,高四位固定为 2
    b3 位置低字节 静止稳定值为 0x40,会因进位而回绕
    b4 低四位 位置高字节位 与 b3 组成 12 位位置,静止 320,全行程约 3052
    b4 高四位 状态 0 初始化中、1 正常助力、2 故障
    b6 制动标志 0 或 1,带约 0.55 mm 回差

    把 b4 高位从位置里剥离是人为诱发的 sensor 故障验证的:拔掉行程传感器后该 nibble 从 1 跳到 2,而 b3 完全没动,旧的 16 位读法整整偏移了 0x1000。健康工况下它恒为 1,所以两种解释只差一个常数,这也是早先的 16 位模型能拟合所有健康捕获、相关性仍达 0.9999 的原因。上电轨迹还能独立 佐证这个拆分:约 1.47 秒前 b4 读 0x01,之后读 0x11,而两侧的 12 位位置都是 320。

    标志 触发位置 回差 次数
    0x38E b6 建立 2.18 至 2.42 mm 约 0.55 mm 4 次上电周期
    0x38E b6 释放 1.72 至 1.75 mm 释放阈值在四次上电中重复到 2 个计数内
    0x38F b2 bit0 建立 1.45 至 1.66 mm 释放边沿未被采样间隔分辨 跨 4 次上电
    0x38F b2 由 0xE3 转 0xDB 39.75 至 40.16 mm 回程 38.77 至 39.51 mm 4 次

    0x38F 是同一总线的另一条报文,49.8 Hz,静止值 3F 2D E2 53 02 00 00 00,同样用 b0 承载同一套参数的 CRC-8。它的 b2 是状态字节:跨四次上电只见到六种取值,0xE2 为就绪且制动释放、0xE3 为制动施加、0xDB 为接近行程末端、0xCC 为故障,另三个只在启动前两秒依次出现。b3 虽然常变,但与位置的相关性只有 0.009、与踏板速度 0.000,也不是计数器,档案刻意留着不解码。两套制动标志对应两个阈值,形状上像稳定程序期望的冗余开关对;而 0x38F 的释放边沿在 49.8 Hz 下常只有一两帧落在过渡区,观测点散布在 1.45 到 4.11 mm,不要引用它的释放值。

    整份档案还有一条反向但有用的结论:独立的故障状态报文并不存在,状态就藏在位置报文的一个字段里。完整性方案则分两种:车辆总线的 0x39D 用加性校验和,YAW 两条报文用 CRC-8/SAE-J1850(多项式 0x1D、初值 0x00、异或输出 0x0A,输入输出均不反射),在四次上电周期的 46,960 帧 0x38E 与 23,478 帧 0x38F 上 100% 通过,合计 70,438 帧零失败;两条 ID 共用一套参数,说明 CRC 并未用报文 ID 做种子。

    故障行为:会锁存,且两条总线会不一致

    拔掉行程传感器可以诱发故障,助力随之消失,三个信号在约 10 ms 内一起变化。这里最关键的一条是它的可恢复性边界。

    步骤 状态 nibble 0x38E 位置 0x39D 行程 助力
    正常 1 实时更新 实时更新 有
    拔掉传感器 2 无 钉在 16354(0x3FE2) 无
    重新插回传感器 仍为 2 恢复实时(320 到 401) 仍钉在 16354 仍无
    断电重启 1 实时 实时 有

    两件事由此定下来。第一,钉住值带有效校验和,16354 在物理上不可达(终点限位才 13606),所以消费者不必认识这个哨兵值也能按量程拒绝。第二,状态位是「助力可用性」标志,不是「数据有效性」标志:插回传感器后位置已经有效并持续更新,状态却仍是 2、助力依旧关闭,只有断电重启才恢复。锁存期间的另一条后果是两条总线不再一致——YAW 总线报告真实位置,车辆总线仍钉在哨兵值,因此要位置就得用 0x38E。

    对实车的影响值得提示:插头瞬时接触不良(振动、腐蚀、压接不良)会把助力器锁进无助力状态,直到点火循环才恢复——踏板变硬并持续如此,而原车仪表没有任何东西能解释原因。制动仍然可用,只是需要大得多的踏板力。

    刹车后的突发报文:行为数据与推理教训

    最后一组可用数据是事件性的。0x31D、0x34D、0x36D、0x37D、0x38D 每次各发一帧、集中在约 120 ms 内,0x33D 也属于这一组。合并六份日志得到 12 次完整突发,其中 10 次跟在制动释放之后 0.64 至 2.20 秒,没有一次踏板施加会漏掉;另外 2 次是上电突发,约在累计运行 6.8 秒时出现,与踏板无关。

    字段 含义 标度 相关性
    0x38D b5:b6 小端 u16 该次施加的推杆行程峰值 129.3 计数每毫米 0.99975
    0x37D b0:b1 小端 u16 制动施加持续时间 27.50 计数每秒 0.99877
    0x31D b2 高四位 同上持续时间的对数分桶 1 到 7,约每翻倍一级 0.884
    0x31D b0:b1、0x3AD b0:b1 累计运行时间,非事件数据 9.99 计数每秒(100 ms 时基) 无

    0x38D 的峰值与标定过的行程在 34 mm 量程内相差在 0.66 mm 以内,比尺标定的 2 mm 不确定度还紧;三次上电中四次打到终点限位的读数彼此相差在 8 个计数内。但这一节所有数字都建立在 10 个事件上,候选字节远多于事件数:两个 0.999 级的拟合通过了 20,000 次置换检验,而相关性在 0.9 附近及以下的都只当线索。峰值与持续时间在这批标定扫描中部分耦合(踩得深通常也踩得久),还需要 20 到 30 次独立控制深度、速度与保持时间的施加才能收口。

    这节还留了两条反面教训。一是量程规则会被误用:针对消费者的「行程超出 13700 即拒」对分析是错的——终点限位读数 13606,但快速撞击会过冲到 14070,套用会剪掉真实行程,曾因此把系数写成 129.8(恢复过冲后为 129.3,且拟合更好)。二是运行时间会伪造相关性:后期事件往往更深更久,于是运行时间足以冒充测量量,0x37D b5 与峰值相关 0.90 却和运行时间相关 0.82,0x34D b5 与运行时间相关 0.88,两条都没敢主张。

    常见问题

    这份解码能不能直接写进 DBC 或线束?

    踏板行程、位置、状态与两条 ID 的校验部分是实测量,可以用;但标注为低置信度的行不能落地。作者在「先自行验证」文件里点名了必须先在台架上量出来的项目:踏板传感器引脚(2、8、22、23)、17 号针、24 与 4 号针是否存在、两总线是否真的没有内部终结电阻、助力时的峰值电流、以及上电自检是否会让推杆移动。未按日期把假设替换成实测之前,不要写进固件、DBC 或线束。

    为什么 0x38E 的 b4 高位不能当作数据有效位?

    因为它是助力可用性标志,不是数据有效性标志。人为诱发故障后把传感器插回,0x38E 的 12 位位置已经恢复实时更新(320 到 401),而该状态位仍为 2、助力依旧关闭,只有断电重启才恢复。位置本身的合法性应当另按量程判断:故障 Sentinel 是 16354,这个值带有效校验和且在物理上不可达。

    为什么捕获必须用 ACK 模式而不是只听?

    因为这条总线只有两个节点,只听模式下没人发应答位,发送错误计数以每次 8 递增,约 32 次尝试就进入总线关闭并自动恢复,循环往复。同一器件、同一 12 秒窗口的对照是:只听模式 1,006 帧每秒但只有 1 个唯一 ID,应答模式 37 帧每秒却有 4 个 ID——那 1,006 帧是重传循环而不是信号,而且它把其余三个 ID 全部隐藏了。

    踏板行程为什么会有两套换算系数?

    第一次跑只有 21 与 42 mm 两个点,得到计数等于 333.866 倍毫米减 897.2;第二次跑取了 10、20、30 mm,得到 361.93 计数每毫米。两次不自洽的标志是 20 mm 的读数高于 21 mm 的读数,单调传感器不可能这样,所以问题出在物理位置测量而不是总线数据。五个点合并后的系数是 320.68,用于显示与记录足够,而要把精度提高到毫米级则需要百分表加固定基准,并且一次跑完整个量程。

    传感器插头瞬时接触不良会发生什么?

    会把助力器锁进无助力状态:状态 nibble 变为 2,车辆总线的行程值钉在 16354,助力关闭。即使插头立刻恢复接触,YAW 总线的位置已经恢复更新,状态仍保持 2、助力也仍不回来,必须等到点火循环才恢复。实际后果是踏板突然变硬并持续变硬,而原车仪表上没有任何东西能说明原因;制动仍然可用,只是需要大得多的踏板力。

    延伸阅读

    参考资料

    • 项目 01 —— public-can-dbc-datasets/additional/pelosim-tesla-ibooster/docs/DECODE.md(两条总线的引脚与帧率、0x39D 与 0x38E、0x38F 字节定义、校验方案与故障行为)
    • 项目 01 —— public-can-dbc-datasets/additional/pelosim-tesla-ibooster/docs/PINOUT.md(引脚假设表、置信度分级、台架接线与实测结果)
    • 项目 01 —— public-can-dbc-datasets/additional/pelosim-tesla-ibooster/docs/BENCH_LOG.md(首次点亮记录、只听陷阱、二次离线分析的推行数据与推理教训)
    • 项目 01 —— public-can-dbc-datasets/additional/pelosim-tesla-ibooster/VERIFY_FIRST.md(必须先自行验证的清单与「打勾即等于用实测替换假设」的要求)

    免责声明

    本文内容涉及制动助力部件。所有 CAN ID、字节定义、系数与阈值均来自单一零件号的公开台架笔记,未在任何独立实车上复现,不构成装机、改装或维修依据。文中明确区分了已实测量与借用其他 iBooster 变体的假设,凡标注低置信度或未打勾的项目,在自行测量之前不得进入固件、DBC 或线束。制动系统属于安全相关件:助力失效时踏板力会显著上升,任何介入都应在具备夹持、限位与断电条件的台架上进行,并由具备资质的人员确认。

    更新记录

    • 2026-10-15 初稿

  • ADS-DV 2021 的 CAN 架构:7 个 API、8 ms 节流与两套 DBC

    结论:2021 版 FS-AI ADS-DV 接口库把无人方程式赛车的 CAN 交互收敛成 7 个函数加两套 DBC:VCU 与 AI 之间的接口为 16 条报文、97 个信号,随车 PCAN-GPS 传感器定义为 23 条报文、67 个信号。真正的工程约束不在接收侧而在发送侧——内部定时器把帧重发周期限制在不小于约 8 ms,推荐每 10 ms 调用一次发送函数,调用过慢会触发整车 ECU 的 CAN 超时诊断。

    7 个函数构成的调用契约

    库对外只暴露 7 个函数以及配套的数据结构与枚举,方向划分得很干净:初始化一个、VCU 到 AI 一个、AI 到 VCU 一个、IMU 与 GPS 各一个、CAN 统计读写两个。

    函数 方向 调用约束
    fs-ai_api_init(接口名, debug, simulate) 初始化 debug 非 0 时向 stdout 输出调试信息,否则静默;simulate 非 0 时载入仿真值
    fs_ai_api_vcu2ai_get_data() VCU → AI 异步接收,每帧到达即缓冲,取到的总是最新值
    fs_ai_api_ai2vcu_set_data() AI → VCU 须足够频繁以免触发 ECU 的 CAN 超时诊断;内部定时器限制重发周期不小于约 8 ms,每 10 ms 调用最优
    fs_ai_api_imu_get_data() PCAN-GPS → AI 异步接收,逐帧缓冲
    fs_ai_api_gps_get_data() PCAN-GPS → AI 异步接收,逐帧缓冲
    fs_ai_api_get_can_stats() 库内部 用于调试 CAN 接收状况
    fs_ai_api_clear_can_stats() 库内部 清空统计结构

    这里有一个容易被误用的点:simulate 参数载入的仿真数据并不代表车辆运行行为,它只是每次调用 fs_ai_api_vcu2ai_get_data() 时变化一次。它适合验证链路是否连通,不能用于任何算法或控制逻辑的验证。

    两套 DBC 与节点划分

    节点划分可以从两套 DBC 的分工直接读出来:VCU 与 AI 之间是双向控制接口,PCAN-GPS 是单向传感器源,CAN 统计则是库内的调试支路,不占整车网络节点。

    DBC 文件 报文数 信号数 节点角色
    ADSDV_2021_VCU_AI_interface_v2.dbc 16 97 VCU ↔ AI,双向:遥测下行、控制上行
    PCAN-GPS for ADS-DV.dbc 23 67 PCAN-GPS → AI,单向:IMU 与 GPS 传感数据

    值得注意的是报文数与信号量的不对称:传感器侧的报文更多(23 对 16)而信号更少(67 对 97)。这与两类接口的形态一致——VCU 接口把状态与控制量打包进少量高密度报文,GPS/IMU 则按物理量分成多条短报文。库只暴露 2021 赛季 DDT 比赛所必需的那部分接口,完整规范在 ADS-DV_Software_Interface_Specification_v4.0.pdf 中,该文件同时发布在 IMechE 网站上。

    2021 相对 2019 的变化

    这次发布把交付形态从二进制改成了源码,功能上也补上了新赛季新增的执行器能力。

    维度 2019 2021
    交付形态 静态库 libfs-ai_api_amd64.a,供静态链接 MIT 源码,可为任意 Linux 架构编译并按需修改
    制动能力 未包含调制液压制动 更新的 CAN 接口支持调制液压制动
    向后兼容 — 接口向后兼容 2019 车辆
    示例程序 — Console 与 Tester 示例程序得到改进
    版本状态 — release candidate,供车队参考实现车辆接口代码

    对车队来说最有价值的一条是向后兼容:同一份 2021 接口代码可以跑在 2019 车辆上,这意味着制动等执行器能力的差异被吸收在接口层,而不是让每支车队各自维护分支。

    构建、运行与证据边界

    构建路径很直接:进入 FS-AI_API_Tester 或 FS-AI_API_Console 目录执行 make,库会按需自动构建或重建;随后以接口名为参数运行,虚拟总线用 vcan0,实车接口用 can0。仓库内的 setup.sh 给出 Linux 下配置 CAN 接口与创建 can0、vcan0 的具体命令。

    证据边界需要如实标注:素材为固定快照(提交 33bcb61d2519ee1ac4935396d9bf465c6ebc81c1),两份 DBC 均按 Git blob 原样保留;PCAN-GPS 文件可能包含第三方传感器厂商的定义,其独立数据权利并未被确认,再分发前应复核该部分信号来源。仓库为 MIT 许可,许可文件与 README 一并保留。最重要的是,这些文件描述的是赛事用无人方程式车辆,不是量产道路车。

    常见问题

    2021 版接口相对 2019 版改了什么?

    交付形态从静态库 libfs-ai_api_amd64.a 改为 MIT 源码,可为任意 Linux 架构编译并按需要修改;CAN 接口更新以支持新增的调制液压制动能力,同时保持对 2019 车辆的向后兼容;Console 与 Tester 两个示例程序也得到改进。该版本被标注为 release candidate,供车队参考实现车辆接口代码。

    发送函数应该多久调用一次?

    推荐每 10 ms 调用一次 fs_ai_api_ai2vcu_set_data()。调用必须足够频繁,否则会触发整车 ECU 的 CAN 超时诊断;但过于频繁又会占用总线,因此库内置定时器禁止以小于约 8 ms 的周期重发帧。每 10 ms 调用可以在两者之间取得最优平衡,数据更新会以尽可能快的速度下发到车辆。

    simulate 模式的数据可以用来验证算法吗?

    不能。文档明确说明仿真数据不代表车辆运行行为,它只是每次调用 fs_ai_api_vcu2ai_get_data() 时变化一次。它适用于确认链路连通与数据结构正确,任何算法或控制逻辑的验证都必须使用真实车辆数据。

    这两套 DBC 能直接用到量产车上吗?

    不能。它们描述的是 IMechE FS-AI ADS-DV 赛事用无人方程式车辆的 VCU 与 AI 接口以及随车 PCAN-GPS 传感器定义,不是量产道路车的网络定义。此外 PCAN-GPS 文件可能包含第三方传感器厂商定义,其数据权利未独立确认,再分发前应复核来源并保留 MIT 归属。

    延伸阅读

    参考资料

    • 项目 01 —— public-can-dbc-datasets/additional/fs-ai-ads-dv-2021/README.md(7 个函数的调用契约、8 ms 节流与 10 ms 建议、构建运行方式与规范文档出处)
    • 项目 01 —— public-can-dbc-datasets/additional/fs-ai-ads-dv-2021/SOURCE.md(两套 DBC 的报文与信号数、快照提交、权利与证据边界)

    免责声明

    本文所述内容来自公开开源仓库的说明文件与数据库文件,描述对象为赛事用无人方程式车辆,不代表任何量产道路车的网络定义,也未经实车验证。两份 DBC 均按固定快照原样保留,其中 PCAN-GPS 部分可能包含第三方传感器厂商定义,其独立数据权利并未被确认,再分发前应复核来源并保留 MIT 许可与归属。涉及车辆控制与执行器的改动请在封闭场地、专业人员监督下并遵守赛事规则进行。

    更新记录

    • 2026-10-14 初稿

  • MARV 无人车的 CAN 集成层:35 报文与 115 信号

    结论:MARV 无人车的 CAN 集成层只有一份 marv.dbc,规模是 35 条 BO_ 报文、115 行 SG_ 信号,平均每条报文 3.29 条信号。它落在 marv_driver 包的 resource/ 目录里,与自定义消息包、场景包、惯导驱动并列。真正限制复用的是三处未核验:平台硬件、DBC 信号校验、以及它并非量产车型定义。

    集成层的落点:一份 DBC 与四个 ROS 包

    归档只保留了一个文件:colcon_ws/src/marv_driver/resource/marv.dbc。把它放在 driver 包的 resource/ 下,说明这个项目把报文定义当作驱动的一部分来管理,而不是外部配置文件。这个位置决定了 DBC 的更新节奏与驱动代码同步,改信号定义等于改驱动包。

    同一工作空间里并列着四个包。理解它们的分工,才能判断一条 CAN 信号从总线到控制回路之间要穿过几层。

    包 上游说明 在集成链中的位置
    marv_driver Basic system functions 归档的 marv.dbc 位于该包 resource/ 目录,是报文定义的落点
    marv_msgs Custom messages package 自定义消息类型定义,说明文档未给出消息清单
    marv_scenarios 用户实现的场景,例如 waypoint_navigation 场景层,说明文档仅举了航点导航一例
    sbg_ros2 The SBG INS driver 惯性导航数据源,与总线信号并行

    规模与统计口径

    35 条报文与 115 行信号是归档方统计 BO_ 与 SG_ 行数得出的。这个口径只说明文件里定义了多少条报文、多少行信号,不代表每一条都已被实车验证。作为对照,同为车队级归档的水冷式方程式项目通常在 50 到 115 条报文的量级,MARV 属于偏小的一侧。

    指标 数值 口径说明
    报文定义(BO_) 35 按 BO_ 行数统计
    信号定义(SG_) 115 按 SG_ 行数统计
    平均信号 / 报文 3.29 由上述两项相除得出
    归档文件数 1 marv_driver/resource/marv.dbc

    归档说明还给出了该文件的完整性校验值:Git blob 182c2fb5f6c02d8ef26c26b2adaa564174f1c2f1,SHA-256 4aab0907c9c43435b11c8729c725c092f98667deb868cc7c6356ed5eb8f3dddc,快照修订为 5b7ed5f6a5547a63407562572b31243f9c5a35a7,采集日期 2026-10-04。

    构建、运行与开机自启

    构建链路相当固定:先在 REACH 车载计算机上按「reach setup」文档装齐依赖,再 source ROS 2 galactic,最后在 colcon_ws 目录内执行 colcon build --symlink-install。这里没有给出依赖清单,意味着环境不可复现——换一台机器时,第一步的「装齐依赖」只能靠文档之外的信息完成。

    运行方式有三套,分别对应调试、常规与部署。测试用途下 ./marv_start.sh 会启动一个 screen 终端会话,适合现场看输出;常规使用则依赖服务在开机时自动拉起,需要人工干预时另有 ./marv_restart、./marv_stop、./marv_start 三个脚本。

    场景 命令 说明
    测试用途 ./marv_start.sh 启动一个 screen 终端会话,便于直接观察节点输出
    常规使用 开机自动启动;./marv_restart、./marv_stop、./marv_start 需先在 reach setup 文档中完成服务配置
    服务管理 sudo systemctl enable marv_package、start、stop、systemctl status marv_package enable 用于开机自启,start 与 stop 用于手动控制,status 查状态
    会话清理 screen -r 重新接入;killall screen 接入后用 ctrl + C、ctrl + D 逐个关闭终端

    读这份文档时有一处需要自己纠正:上游把「禁用服务」的命令也写成了 sudo systemctl enable marv_package,与启用命令完全相同。按字面执行达不到禁用效果,正确做法应是对应的 disable。这类笔误在自维护文档里很常见,抄命令之前先确认语义,不要照单全收。

    日志与仿真这两套机制合起来,覆盖了从录制到回放再到脚本自检的完整链路,但它们的边界都写在文档里而没有含糊处理。使用时最容易犯的错误是把「跑通了」当成「跑对了」。

    两套 rosbag 与 scenario_starter 的仿真边界

    日志侧提供两套 rosbag:一套记录全部 topic,另一套只记录指定 topic(如导航数据),具体 topic 可在 start_specific_ros_bah.sh 脚本中修改。这种「全量 + 定向」的双轨设计很实用——全量用于事后回溯,定向用于长时间跑车时控制数据量。

    仿真侧则由 scenario_starter 这个额外的 ROS 节点承担,用来模拟 MARV 系统其余部分的行为。它模拟 12V Auto 状态,以及 OCU 发出的 start scenario 命令。文档对它的边界写得很克制:它不模拟任何传感器数据,作用只是在正式路试前先在「陆地」上验证场景脚本能否正常启动。

    手段 覆盖 明确不覆盖
    rosbag(全量) 每一个 topic 说明文档未涉及落盘策略与容量限制
    specific rosbag 指定 topic,例如导航数据 其余 topic 不记录;需手工维护脚本内的 topic 列表
    scenario_starter 节点 12V Auto 状态、OCU 的 start scenario 命令 任何传感器数据;只能给出脚本是否启动正确的指示

    把这三行放在一起看,结论很直接:这套工具链能验证「脚本会不会崩」,但不能验证「车会不会开对」。传感器数据必须来自真实车辆或另行搭建的仿真。

    归档快照与权利状态

    归档只保留了一个文件,因此完整性校验相对简单:路径 colcon_ws/src/marv_driver/resource/marv.dbc,Git blob 182c2fb5f6c02d8ef26c26b2adaa564174f1c2f1,SHA-256 4aab0907c9c43435b11c8729c725c092f98667deb868cc7c6356ed5eb8f3dddc,快照修订 5b7ed5f6a5547a63407562572b31243f9c5a35a7,采集日期 2026-10-04。归档方确认保留字节与所钉住的 Git blob 一致,且在本地存档中没有字节相同或经 CRLF 归一化后相同的重复项。

    权利侧的表述需要读仔细:归档保留了仓库的 MIT 许可证与上游 README,同时写明各组件数据的独立来源并未确立。这两句话不矛盾,但含义差别很大——MIT 覆盖的是仓库自身携带的内容,不等于覆盖项目所引用的第三方组件数据。把它并入闭源产品或对外再分发之前,组件部分仍需单独确认。

    常见问题

    marv.dbc 的 35 条报文分别对应哪些硬件?

    归档说明没有给出逐条映射。文件位于 colcon_ws/src/marv_driver/resource/marv.dbc,规模为 35 条 BO_ 报文与 115 行 SG_ 信号;归档方同时写明具体平台硬件与 DBC 信号校验均未独立检查,且这不是量产车型定义。要确定报文与硬件的对应关系,需要结合 marv_driver 的源码自行核对。

    为什么构建步骤要固定 ROS 2 galactic?

    因为构建与运行步骤是按这个发行版写的:构建前需 source ROS2 galactic,相关脚本按 /opt/ros/galactic/setup.bash 的路径组织。换成其他发行版时这些路径与包名需同步调整,而说明文档没有提供其他发行版下的验证记录。

    两套 rosbag 应该怎么选?

    按用途分:一套记录全部 topic,适合事后完整回溯;另一套只记录指定 topic(例如导航数据),适合长时间跑车时压住数据量。定向那套的 topic 列表写在 start_specific_ros_bah.sh 脚本里,改场景时要记得同步维护,否则关键信息不会被记录。

    scenario_starter 能不能代替实车路试?

    不能。它只模拟 12V Auto 状态与 OCU 发出的 start scenario 命令,用于在正式路试前验证场景脚本能否正常启动。上游说明明确指出它不模拟任何传感器数据,因此只能给出「脚本是否启动正确」的指示,无法验证控制行为是否正确。

    这份归档的权利状态如何?

    归档方保留了仓库的 MIT 许可证与上游 README,同时说明各组件数据的独立来源并未确立。MIT 覆盖的是仓库本身携带的内容,不等于覆盖随项目引用的第三方组件数据;再分发前需要单独确认各组件部分的权利。

    延伸阅读

    参考资料

    • 项目 01 —— public-can-dbc-datasets/additional/case-lab-marv-ros/SOURCE.md(marv.dbc 的路径、35 条 BO_ 报文与 115 行 SG_ 信号、Git blob 与 SHA-256、快照修订与采集日期、平台硬件与信号校验未核验的声明、保留 MIT 许可证与上游 README 的权利说明)
    • 项目 01 —— public-can-dbc-datasets/additional/case-lab-marv-ros/UPSTREAM_README.md(colcon_ws 内四个包的分工、依赖与 galactic 环境下的 colcon build –symlink-install 构建、两套 rosbag 与 start_specific_ros_bah.sh 的 topic 配置、marv_start.sh 与 screen 会话、systemd 服务命令、scenario_starter 节点的仿真覆盖范围与不模拟传感器数据的边界)

    免责声明

    本文所述报文与信号数量、包结构、命令与运行方式均来自公开开源仓库的说明文档,属第三方工程产物,未经独立实车核验。归档方已明确声明平台硬件与 DBC 信号校验未独立检查,且该定义并非量产车型模型。不同平台版本与组件固件可能存在差异。涉及安全相关用途时请仅作参考,并以实际测量与官方资料为准。

    更新记录

    • 2026-10-14 初稿

  • VW Grand California 生活舱 CAN:7 个 ID 与 9 条命令

    结论:VW Grand California 的生活舱 CAN 独立于 Crafter 底盘总线,社区已给出 500 kbit/s 的接入方式、7 个 29 位 ID 的功能归属、13 条解码样例与 9 条 cansend 命令。但同一个 ID 在不同字节位上混装多种含义,电流与电压两条规则作者未给出可复核的推导,因此这份表格适合做功能定位与命令复现,不适合直接当计量依据。

    接入:从树莓派到技术柜里的 9、10 针

    项目面向 Grand California 600 与 680 的生活舱总线,作者在免责声明中明确这是教育与研究用途,相关知识产权归属大众集团。硬件用 Raspberry Pi 3B+(作者说明 Pi Zero 2 W 更小巧省电)加一块 Waveshare RS485 CAN HAT(CAN 控制器 MCP2515、485 收发器 SP3485)。软件侧是 can-utils 加 Node-RED 的 node-red-contrib-socketcan 节点,用来搭可在浏览器里打开的仪表盘。

    总线初始化命令为 sudo ip link set can0 up type can bitrate 500000 loopback off listen-only off,监听用 cansniffer can0 -c -t0,发送用 cansend can0 18EF70C9#00.40.00.00.00.00.00.00。物理接入点选在 RGB 灯带控制器(零件号 7C4 947 022)的 9、10 针,该控制器位于 230V 配电盘正下方的技术柜内——这是全篇最省事的一条经验,避免了拆仪表台找线。

    作者还为 SavvyCAN 提供了一份 DBC:VW_GrandCalifornia_J608.dbc。

    七个 ID 的功能归属:一帧多用是常态

    生活舱总线的 ID 全部是 29 位扩展格式(形如 18EFxxxx),且普遍存在「一帧承载多个功能」的情况。下表按作者给出的归属整理,是判断该盯哪一帧的第一层筛选。

    ID 承载功能 备注
    18EF70C9 显示屏开关、卫星(SAT)开关、氛围灯 / 外部灯 / 内部灯 也是文档示例里的发送目标
    18EF70EB 卫星开关、灯光开关、电池剩余时间、车门锁状态 状态回读帧
    18EF71C9 露营模式、WiFi、音响(含蓝牙)、踏板自动 / 伸出 / 收回 命令侧主要目标
    18EF71EB 接收大量内容;露营模式、WiFi、音响状态 作者标注「接收很多东西」
    18EF72EB 灯光开关、230V 输入、电池电流、电压、电量百分比 电池参数集中在这一帧
    18EF73EB 230V 输入、清水与灰水水位 水位需第 1 字节为 01 才有效
    18EF60C9 设置灯带颜色 Y / R / G / B 四组预设值

    注意 18EF72EB 同时承担电流、电压、电量三项:这三项的字节位置完全不同,且分属不同的码制,因此不存在「一个 ID 对应一组语义」的捷径。

    解码规则与位序陷阱

    下表按作者原文的 0 起字节编号整理(原文写作 0ºB、1ºB……)。每行都附了原文给出的样例帧,这是判断规则可信度的关键——有样例的规则可以复现,无样例的只能照抄。

    内容 ID 样例帧 解码规则 可复现性
    清水 / 灰水水位 18EF73EB 00 00 00 00 0B 00 04 00 0 起编号第 4 字节为清水百分比(样例 0x0B)、第 6 字节为灰水(样例 0x04);首字节为 01 时清水箱为空 高,样例与规则自洽
    电池电流(充电) 18EF72EB 50 00 00 8A 59 00 07 02 0 起编号第 6 字节为电流正值(样例 0x07);首字节大于 0 表示充电中 中,未给单位换算
    电池电流(放电) 18EF72EB 00 00 00 0A 4C 00 FF 01 0 起编号第 6 字节为负值,FF = −1、FE = −2、FD = −3,按 MOD 2 递推 中,负值为自定义编码
    电池剩余时间 18EF70EB 01 00 3A 26 00 00 00 00 原文称第 2 字节为小时、第 3 字节为分钟,样例对应 0x3A 与 0x26 中,需确认编号起止
    电池电压 18EF72EB 00 00 00 0A 50 00 FF 01 原文称取第 5 字节(样例值 0x50)转十进制,十位加 5 得整数部分、个位为小数部分 低,推导未说明,需实测复核
    电池电量百分比 18EF72EB 00 00 00 0A 50 00 FF 01 原文称取第 4 字节(样例值 0x0A)为百分比除以 10,经 10*10 换算为 100 低,表述与样例存在张力,需复核
    显示屏开 / 关 18EF70C9 00 40 … / 00 00 … 由 I/O 按键触发,0 起编号第 1 字节为 40 时开、00 时关 高
    车门锁 18EF70EB 00 04 3A 26 00 00 00 00 0 起编号第 1 字节为 04,随后回到 00 中,仅给出单帧切片
    灯带颜色 18EF60C9 1E.00.64.2F… / 08.00.64.25… / 7D.00.64.23… / F5.00.64.2F… 四组预设分别对应 Y、R、G、B,0 起编号第 0 与第 3 字节变化 高,可整帧复现

    两条被标为「低」的规则是最该警惕的:电流与电压共用同一帧,且放电电流用 FF / FE / FD 表示负值、按 MOD 2 递推,与电压那条「十位加 5」的规则都不是标准编码。在没有万用表对照之前,这两项只能看趋势。

    字节编号在同一份表里并不统一

    这是使用这份表格时最容易踩的坑。同一份文档里,水位行说「4ºB = 0B」,只有按 0 起编号才能在样例帧 00 00 00 00 0B 00 04 00 里对上;电流行说「6ºB 是安培值」,也只有 0 起编号才能在 50 00 00 8A 59 00 07 02 里取到 0x07。但电压行与电量百分比行的样例值(0x50 与 0x0A)在同一帧里分别位于从 1 起数的第 5 与第 4 个字节。也就是说原文的编号起止在不同行之间不一致。正确做法是先拿样例帧反推真实位置,再写解析规则,不要直接信任编号本身。

    九条可复现的命令

    作者把可发送的命令单独列了一张表,全部基于 cansend,这也是整份资料里最容易验证的部分。

    功能 命令载荷 目标 ID 效果
    露营模式 00.40.00.00.00.00.00.00 18EF71C9 开 / 关切换
    WiFi 00.01.00.00.00.00.00.00 18EF71C9 开 / 关切换
    踏板自动 04.00.00.00.00.00.00.00 18EF71C9 开 / 关切换
    踏板伸出 10.00.00.00.00.00.00.00 18EF71C9 单次动作
    踏板收回 40.00.00.00.00.00.00.00 18EF71C9 单次动作
    氛围灯 04.40.00.00.00.00.00.00 18EF70C9 开 / 关切换
    外部灯 00.41.00.00.00.00.00.00 18EF70C9 开 / 关切换
    内部灯 01.40.00.00.00.00.00.00 18EF70C9 开 / 关切换
    卫星(SAT) 00.50.00.00.00.00.00.00 18EF70C9 开 / 关切换

    这张表的结构是「同一 ID 内按位区分功能」:18EF71C9 上,0 起编号第 1 字节的 40 / 01 / 04 / 10 / 40 分别对应露营模式、WiFi、踏板自动、踏板伸出、踏板收回;18EF70C9 上则靠 0 起编号第 0 与第 1 两个字节的组合区分氛围灯(04 40)、外部灯(00 41)、内部灯(01 40)与卫星(00 50)。做自动化控制时,正确做法是先读回当前状态再发送,而不是按记忆中的固定序列盲发。

    常见问题

    生活舱 CAN 和 Crafter 底盘 CAN 是同一条总线吗?

    不是。项目说明明确指出生活舱 CAN 独立(或几乎独立)于 Crafter 底盘总线,本文涉及的 ID、命令与解码只适用于生活舱部分。想取发动机、变速箱或底盘数据,需要另外接入底盘侧总线,不能指望从这些 ID 里读出来。

    从哪里接入生活舱总线最省事?

    作者最终选择接在 RGB 灯带控制器(零件号 7C4 947 022)的 9、10 针上,该控制器位于 230V 配电盘正下方的技术柜内。原厂接线图上也标注了多个可接入「便利 CAN」的点位,但灯带控制器这个点被作者评价为最方便。

    电池电流的负值为什么用 FF、FE、FD 表示?

    这是作者从样例帧中归纳出的自定义编码:放电时第 6 字节按 FF = −1、FE = −2、FD = −3 的方式递推,原文写作 MOD 2,与充电时的正值编码不是同一套。由于文档未给出单位与系数推导,实际使用前应与钳形表对照测量。

    清水和灰水水位在同一帧里怎么区分?

    靠字节位置区分:样例帧 00 00 00 00 0B 00 04 00 中,0 起编号第 4 字节(0x0B)是清水百分比、第 6 字节(0x04)是灰水百分比。另外原文注明当首字节为 01 时清水箱判定为空,因此解析时必须先看首字节再取水位值。需注意的是原文编号起止在不同行之间并不统一,实际接入应先按样例帧反推。

    延伸阅读

    参考资料

    • 项目 01 —— public-can-dbc-datasets/additional/mk4001-vw-grand-california/README.md(硬件与软件清单、500 kbit/s 初始化命令、接入点位与免责声明)
    • 项目 01 —— public-can-dbc-datasets/additional/mk4001-vw-grand-california/Table.md(7 个 ID 的功能归属、13 条解码样例与 9 条 cansend 命令)

    免责声明

    本文内容整理自公开开源项目,原作者已声明该项目仅用于教育与研究,相关 CAN 代码与协议的知识产权归属大众集团,不构成对任何专有系统的授权访问或修改许可。文中字节位置与换算规则均为原作者基于自车观察给出的结果,未经独立验证,不同年款与配置可能存在差异。

    更新记录

    • 2026-10-14 初稿

  • 2025 Corolla TSS 3.0 的 0x160:32 字节控制报文与 E2E 校验

    结论:2025 Corolla Hybrid TSS 3.0 的横向与纵向控制都收敛到同一条报文:摄像头发出的 32 字节 CAN FD 帧 0x160,约 40 Hz。加速度是字节 4–5 的 15 位有符号量(0.001 m/s²/count),转向请求是字节 22–23 的大端有符号量(537.7 counts/degree),E2E 保护为对字节 2–31 做 CRC-16/CCITT 并附加小端数据 ID 0x444A。这一条报文也是 Panda 白名单里的唯一入口。

    三条总线上的报文分工

    移植说明把总线关系写得很清楚:动力总成在 Panda bus 1,被中继的 ADAS 报文对在 bus 0,摄像头在 bus 2。控制指令只在 bus 0 上发出,且只有 32 字节这一种形态被接受。网关发出的 0x1A0 属于另一条路径,该分支从不发送它。

    总线 承载内容 在控制链中的角色
    Panda bus 1 动力总成 被观测,不作为指令出口
    Panda bus 0 中继后的 ADAS 报文对 唯一允许发送 32 字节 0x160 的出口
    摄像头 bus 2 摄像头原始 0x160 E2E 计数器与未知字节的来源

    配套的 DBC 规模是 15 条 CAN/CAN FD 报文、120 条信号条目。需要留意的是,DBC 自带的表头写的是「2023 Toyota Corolla」,并标注位位置是从被动日志推断的,而移植目标是 2025 年款。因此它应被当作候选研究定义,而不是已验证的年款网络地图。

    0x160 的字段布局与 E2E 校验

    E2E 保护是这套控制路径里最容易被低估的部分。它不是简单的校验和,而是 AUTOSAR E2E 风格的「计数器 + CRC」组合:字节 2 是摄像头计数器,字节 0–1 是小端 CRC,CRC 覆盖字节 2–31 并附加小端数据 ID 0x444A。

    字节位置 含义 编码 换算
    0–1 E2E CRC 小端 16 位 CRC-16/CCITT,覆盖字节 2–31 + 数据 ID 0x444A
    2 摄像头计数器 8 位 控制器每收到一个新计数器只发一次
    4–5 加速度请求 15 位有符号 0.001 m/s²/count
    22–23 转向请求 大端有符号 16 位 537.7 counts/degree
    其余字节 摄像头未知字段 — 原样保留,不做猜测

    控制器的行为边界也由这张布局决定:它只改已知的加速度与转向字段,保留摄像头的未知字节,重算计数器与 E2E 校验,并且当某个轴不由 openpilot 控制时,原样中继该轴的字段。这意味着「替换控制」不是构造新报文,而是在原报文上做最小改动。

    为什么 537.7 counts/degree 是关键数字

    转向字段的换算系数决定了限幅的实际含义。Panda 侧的转向限幅写的是每接受一帧最多变化 1,500 counts,换算后对应的角度增量才具备物理意义。脱离换算系数去谈限幅是没有意义的——这也是为什么这类移植必须先把 scale 钉死,再谈安全包络。

    安全边界:三层限幅与单一白名单

    TSS3 的 Panda 模式只有在使用 ALLOW_DEBUG 构建时才存在。它的发送白名单里只有一条指令:bus 0 上的 32 字节 0x160。旧款 8 字节 0x160、0x1A0、老款丰田转向报文以及所有无关地址都被拦在白名单外。

    约束项 Panda 侧包络 控制器侧实现
    加速度 -3.5 .. 2.0 m/s²(实测原厂中继包络) 额外夹到 -1.5 .. 1.5 m/s²
    转向变化率 每接受帧最多 1,500 counts 被拒绝的帧不推进限幅基线
    发送地址 仅 bus 0 的 32 字节 0x160 从不发送 0x1A0
    构建开关 需 ALLOW_DEBUG 生产构建会拒绝该输出

    「被拒绝的帧不推进限幅基线」这一条值得单独注意。如果拒绝帧也推进基线,攻击者或故障状态就能通过连续发送被拒帧把限幅额度消耗掉,进而让下一次合法大动作被吞掉。这里的实现避免了这个问题。

    仍然没有闭环的部分

    项目明确列出了几项未解决项:转向灯信号不在三条被接的总线上,因此无法用已验证信号来暂停转向;脱手警告看起来由扭矩超时强制执行,未找到可抑制的 CAN 位,仍需手扶方向盘;0xDA 报文里的四个字段尚未可靠区分驾驶员扭矩与 EPS 扭矩,因此 steeringPressed 保持为 false 而不猜测;驾驶员物理扭矩信号已知在 0x030 上,但可接受的超控阈值未做动态验证。车辆参数目前沿用 E210 Corolla 的 3,060 lb、2.67 m 轴距、13.9 转向比。

    常见问题

    0x160 的 E2E 校验到底覆盖了哪些字节?

    覆盖字节 2 到字节 31,算法为 CRC-16/CCITT,并附加小端序的数据 ID 0x444A。字节 0–1 存放小端序的 CRC 结果,字节 2 是摄像头计数器,这两部分不参与自身校验。

    为什么必须手动选择车型而不是自动指纹识别?

    因为现有的行驶日志无法唯一关联到 2025 年的 F181 身份。已处理的 rlog 在逻辑 bus 1 上有 147 个地址/DLC 对,加上启动段后并集为 152 对,逻辑 bus 0 上有稳定的 22 条报文 ADAS CAN FD 结构;但这一并集与一份 TSS3 Camry 的普查结果重叠,且行驶日志里 carParams 为 MOCK 而非在路上的 F181 响应。自动匹配存在误判车型身份的风险。

    生产版 Panda 能用这套控制吗?

    不能。TSS3 Panda 模式只在以 ALLOW_DEBUG 构建时编译进固件,生产安全构建不会启用这个实验性白名单,因此会拒绝控制器发出的 0x160 输出。这是有意设置的状态,直到剩余的车辆级策略与验证工作完成。

    这份 DBC 能当作 2025 年款的完整网络地图吗?

    不能。DBC 表头写的是 2023 Toyota Corolla,并注明位位置由被动日志推断;移植目标却是 2025 年款。上游也明确把若干字段身份、比例与整车标定列为未解决项,另有不透明的 BYTE* 信号与已解释信号重叠,严格解析器可能报重叠。它是候选研究定义。

    EPS 应用层的比对结论能推到整车 CAN 信号吗?

    不能。证据只支持转移 EPS 应用代码的结论:2025 与 2023 的高应用区从 0x17E00 起逐字节相同,F181 记录为 8965F1208000 与 8A3111213000,MCU 为 R7F701383。这不等于证明每一条整车 CAN 信号与标定都相同。

    延伸阅读

    参考资料

    • 项目 02 —— public-can-dbc-datasets/additional/spanconstant5-corolla-tss3-2025/docs/COROLLA_2025_TSS3_PORT.md(0x160 字段布局、E2E 算法、失败闭合边界与 CAN 普查数据)
    • 项目 02 —— public-can-dbc-datasets/additional/spanconstant5-corolla-tss3-2025/README.md(状态表、安全边界、已知车辆行为与离线检查命令)
    • 项目 02 —— public-can-dbc-datasets/additional/spanconstant5-corolla-tss3-2025/SOURCE.md(DBC 规模 15 报文 / 120 信号、年款偏差与证据边界)

    免责声明

    本文所述控制路径来自公开仓库的移植说明,属于实验性研究成果,未经过独立实车验证。文中涉及的报文 ID、字节偏移、换算系数与安全限幅仅适用于该仓库描述的目标车辆与固件状态,不同年款、市场与 ECU 版本可能存在差异。任何涉及车辆控制的实现都必须以实际测量与合规的安全验证为准,本文不构成发送报文的操作指引。

    更新记录

    • 2026-10-14 初稿

  • 实验模式三层状态:UI 图标、Params 请求值与运行值为何不一致

    结论:「实验模式是否生效」在源码里其实是三个不同的状态,很容易被一个 UI 图标混淆。一是请求值 ExperimentalMode,是用户意图,多个 UI 入口都能写;二是界面值,大屏行车按钮的新图标最多保持约两秒就回到消息报告的模式;三是运行值 selfdriveState.experimentalMode,由 selfdrived 在运行时车型能力门后发布,plannerd 在收到下一次模型更新时才使用它。源码没有提供端到端「点击已被规划器接受」的事务回执。以下为固定快照 c8fb906 的静态分析,不代表模型行为或实车交互已验证。

    请求入口:五个 UI 路径写同一个参数

    请求值可以从多处写入。大屏行车按钮的 ExpButton._update_state()/_handle_mouse_release() 从 selfdriveState.experimentalMode 取当前模式,在确认且 UI 认为有纵向控制时翻转 ExperimentalMode 参数;其 _engageable 只影响绘制透明度,不参与切换允许判断。大屏驻车主页的按钮点击进入设置,展示取自 Params,不直接切换模式。

    设置页的 TogglesLayout._handle_experimental_mode_toggle() 以 UI 能力门决定可用性,首次开启需确认,确认后阻塞写两个参数,不可用时清除请求参数。mici 首页的 HomeLayout._update_state() 在长按超过 0.5 秒且 UI 能力与确认均成立时,修改 UI 本地布尔值并阻塞写 ExperimentalMode;mici 设置页首次开启走确认页面,后续写请求参数,但这里的普通 put_bool 未指定 block=True。

    阶段 代码入口 源码可确认的行为
    大屏行车按钮 ExpButton._update_state()、_handle_mouse_release() 从 selfdriveState.experimentalMode 取当前模式;确认且 UI 认为有纵向控制时翻转参数。
    大屏按钮反馈 _held_or_actual_mode() 新图标保持约两秒后回到消息报告的模式;保持期只是视觉反馈。
    大屏驻车主页 HomeLayout._setup_callbacks()、ExperimentalModeButton.show_event() 点击进入设置;展示取自 Params,不直接切换模式。
    大屏设置 TogglesLayout._handle_experimental_mode_toggle() UI 能力门决定可用性;首次开启需确认;不可用时清除请求参数。
    mici 首页 HomeLayout._update_state() 长按超过 0.5 秒且能力与确认成立时,阻塞写 ExperimentalMode。
    mici 设置 TogglesLayout._on_experimental_mode() 首次开启走确认页;后续写请求参数,普通 put_bool 未指定 block=True。

    UI 能力计算集中在 UIState.update_params():它从 CarParamsPersistent 读车型能力,若 alphaLongitudinalAvailable 则展示门取 AlphaLongitudinalEnabled,否则取 CP.openpilotLongitudinalControl,后台约每 0.2 秒刷新。也就是说「按钮能不能点」和「模式实际会不会生效」用的是两套判定依据。

    参数持久化与两个布尔键

    ExperimentalMode 与 ExperimentalModeConfirmed 都是持久化布尔键。要点在于写盘语义:Params.put_bool(..., block=True) 会等待写盘,而不带 block=True 的调用只提交写入;这两种调用都不等于下游已消费。mici 首页调用的是通用 Params.put(..., block=True),其布尔值经类型转换后写入,与 put_bool 的 API 形式不同。

    键 / 调用 含义 边界
    ExperimentalMode 用户请求值 多入口可写;无纵向控制能力时可能被清除。
    ExperimentalModeConfirmed 首次确认记录 持久化布尔键,用于首次开启确认。
    put_bool(block=True) 等待写盘 写盘完成不等于下游已消费。
    put_bool() 仅提交写入 mici 设置的普通调用属此类。

    运行值由能力门决定

    运行时一侧,UIState.update_params() 从 CarParamsPersistent 读车型能力:若 alphaLongitudinalAvailable,展示门取 AlphaLongitudinalEnabled,否则取 CP.openpilotLongitudinalControl,后台约每 0.2 秒刷新。而 SelfdriveD.__init__() 在无 openpilot 纵向能力时启动清除请求;params_thread() 约每 0.1 秒计算 ExperimentalMode AND CP.openpilotLongitudinalControl,主循环再发布 selfdriveState.experimentalMode。

    这里有一个条件性差异值得注意:UI 的能力展示读 CarParamsPersistent 且在 alpha 可用时依赖 AlphaLongitudinalEnabled,实际模式却按运行时 CP.openpilotLongitudinalControl 判定。若持久车型参数、运行时参数或刷新时点不同,UI 允许/展示与实际激活可短暂不一致;静态阅读不能证明这种差异会在某辆车上发生。

    规划消费与规划激活

    plannerd.main() 与 LongitudinalPlanner.update() 随 modelV2 更新读取 selfdriveState;实验模式改变巡航加速度计算,并把模型 desiredAcceleration 加入候选,最终按候选的最小加速度选目标——不是「模型始终直接控制油门刹车」。下游 controlsd 从 longitudinalPlan.aTarget 经纵向控制器生成候选执行器输出,card 还读取参数作为启用时巡航初值的输入,二者各有各自的更新时序。

    面向自制硬件与插件重构

    把 UI 插件的操作定义为 ModeChangeRequest,携带请求 ID、来源、单调时间戳和车辆能力版本;只授权写请求,不授予其直接发布 selfdriveState、longitudinalPlan 或 CAN 的能力。由固定核心层发布 requested / rejected / accepted / active 等结构化状态,其中 active 必须来自规划器消费确认,不能用图标、Params 写盘或 selfdriveState 单独代替;当前源码尚无此回执协议。

    能力门应以运行时 CarParams 与独立网关安全策略为权威,UI 可显示能力但其缓存不得成为执行许可。验证顺序上,先用回放验证参数切换、状态发布、规划候选选择及失联时序,再对目标车型验证 controlsd 活动门、card 输出与 panda 安全钩子。

    常见问题

    点了实验模式按钮,规划器就立刻用上了吗?

    不一定。按钮翻转的是请求值 ExperimentalMode,运行值 selfdriveState.experimentalMode 由 selfdrived 在能力门后发布,plannerd 要等下一次模型更新才使用;源码没有端到端「点击已被规划器接受」的事务回执。

    大屏上的新图标表示模式已切换成功吗?

    不能这样理解。_held_or_actual_mode() 里的新图标最多保持约两秒,然后回到消息报告的模式;这段保持期只是视觉反馈,不是参数写盘或规划激活的回执。

    put_bool(block=True) 写盘成功就说明下游已消费吗?

    不等于。block=True 只是等待写盘完成,不带 block=True 的调用只提交写入;两种调用都不代表下游已消费,mici 设置页用的就是不带 block 的普通 put_bool。

    为什么 UI 显示的可用性与实际激活会不一致?

    因为判定依据不同:UI 能力展示读 CarParamsPersistent、在 alpha 可用时依赖 AlphaLongitudinalEnabled,实际模式按运行时 CP.openpilotLongitudinalControl 判定。持久参数、运行时参数或刷新时点不同就可能短暂不一致,但不能据此断定会在某辆车上发生。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/EXPERIMENTAL_MODE_UI_PARAM_ACTIVATION_DEEP_DIVE.md(取用了五个 UI 请求入口、两个持久化布尔键、三层状态划分、运行值能力门与规划消费说明)。

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

    更新记录

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

  • Chrysler 四套构帧:0x292 与 0xa6 为什么不能互换

    结论:Chrysler 的构帧不是一套模板,而是四套并存的布局:Pacifica/常规 Jeep、Ram DT、Ram HD 与 Cherokee CUSW,三帧消息的地址、字节长度和所在总线都不同,不能互相套用。几个最容易踩的点是:Ram 的按钮帧走 bus 2 而其他走 bus 0;Ram HD 与 CUSW 的 TX/RX 只在 ALLOW_DEBUG 构建中注册且参数层标 dashcamOnly;Pacifica、Ram DT、Ram HD 的转向扭矩上限分别是 261、261、361,CUSW 为 250。字段名相同并不代表可共用原始载荷。以下为固定快照 c8fb906 的静态分析,未向车辆发送 CAN。

    四套发帧布局

    主机侧三类输出分别是 LKAS 转向命令、DAS_6 图标/车系码/警示,以及巡航按钮。CANPacker.pack() 会在未显式提供时按地址递增 COUNTER,并调用 DBC 对应的 CHECKSUM 函数;chrysler_ 系列 DBC 使用 chrysler_checksum(),即最后一字节之外的 CRC-8 J1850,初值与尾异或都是 0xff。

    平台族 LKAS / DAS_6 / 按钮(地址/长度) 主机 bus panda 限制
    Pacifica / 常规 Jeep 0x292/6 B、0x2a6/8 B、0x23b/3 B 三种都在 bus 0 扭矩最大值 261,速率上/下限每次 3。
    Ram DT 0xa6/8 B、0xfa/8 B、0xb1/3 B LKAS/HUD bus 0,按钮 bus 2 主机扭矩上限 261,固件白名单 350,实际受主机更紧约束。
    Ram HD 0x276/8 B、0x275/8 B、0x23a/3 B LKAS/HUD bus 0,按钮 bus 2 主机/固件扭矩上限均 361,速率 14;TX/RX 仅 ALLOW_DEBUG 注册。
    Cherokee CUSW 0x1f6/4 B、0x5dc/4 B、0x2fa/3 B 三种都在 bus 0 独立 safety model,扭矩 250,速率 4;参数层标 dashcamOnly。

    控制周期与按钮时序

    CarController.update() 以 DT_CTRL = 0.01 s 的帧计数运行。按钮只在距上次发送严格超过 0.05 s 时生成,优先取消、其次恢复;计数用 CS.button_counter + 1,再由 create_cruise_buttons() 截成 4 位。这里有一个平台差异:CUSW 的 update_cusw() 没有像常规分支那样刷新 button_counter,若生成按钮帧会反复使用初始化值加一,是否触发原车计数拒收仍需台架确认。

    DAS_6 的字段由 create_lkas_hud() 生成:它把激活态、车系码和告警转成图标与车道线字段;Ram 额外带原车自动远光状态,而 CUSW 的告警字段固定为零。也就是说,同一个「车道保持已激活」在四套布局里落在不同字段上,跨平台复用图标语义需要分别核对。

    HUD 每 25 帧尝试一次,且只有 CS.lkas_car_model != -1 才发送。hud_count 小于 4 的启动告警只覆盖最初四个实际发送的 HUD 帧,源码注释里的「first 3 seconds」不能当作精确时长。转向周期由 CarControllerParams 设为普通/Ram 50 Hz、CUSW 100 Hz;控制位则依 minSteerSpeed、平台迟滞和最近下降沿约 200 个控制帧的等待决定。由于 lkas_active 在本次控制位更新前读取上一状态,控制位首次置位的周期不会产生非零扭矩。

    时序项 取值 备注
    控制步长 DT_CTRL = 0.01 s 按钮与 HUD 都按帧计数节流。
    按钮最小间隔 严格大于 0.05 s 优先取消,其次恢复。
    HUD 周期 每 25 帧 仅当 lkas_car_model != -1。
    转向速率 普通/Ram 50 Hz、CUSW 100 Hz 由 CarControllerParams 决定。
    控制位迟滞 约 200 个控制帧 自最近下降沿起等待。

    控制字段与原始载荷不能共用

    create_lkas_command() 对 Ram 的控制字段编码为 2,其他为 1。这对应固件里的不同检查:常规固件在 Pacifica 上检查 bit 4、在 Ram 上检查字段值 2,而 CUSW 独立检查 bit 12。字段名相同并不代表可以共用原始载荷,这也是四套布局必须分别验证的原因之一。

    panda 是最后一道独立门

    主机发出的三类帧仍要经过 panda 安全钩子。常规模式仅允许表中的地址、总线与长度;转向再做扭矩与请求位检查;按钮只接受纯取消,或在 controls_allowed 下的纯恢复,CUSW 的取消/恢复位解析则不同。这套白名单与按钮限制应被视为独立于应用插件的最后一道门。生成式 Ram DBC 的地址映射(DAS_6 与按钮来自 Stellantis 共用 DBC 的地址替换)也应纳入构建一致性检查。

    硬件与插件验收边界

    把 DBC、总线、帧长、安全模式与固件构建作为同一车型插件的版本化契约;Ram 的按钮 bus 2 与普通平台的 bus 0 不可互换。台架应分别验证编码后的计数、CRC、控制位与 panda TX 的接受/拒绝矩阵,尤其覆盖 Pacifica、Ram DT、Ram HD、CUSW 四套布局——软件里有构帧函数,并不意味 HD/CUSW 的 release 固件允许发送。

    实车验证前应先只读记录原车的 DAS_6、按钮计数与测得 EPS 扭矩以及 LKAS 故障时序,确认线束与转发路径。本文没有 DBC 动态编码回放、目标板或车辆响应证据,不构成上路控制许可。

    常见问题

    四套 Chrysler 布局能共用同一套构帧代码吗?

    不能。Pacifica/常规 Jeep、Ram DT、Ram HD、Cherokee CUSW 的地址、长度和总线都不同:例如 Ram 的按钮帧走 bus 2,其他走 bus 0;Ram HD 与 CUSW 的 TX/RX 还只在 ALLOW_DEBUG 构建中注册。

    为什么控制字段取值在 Ram 和其他平台上不同?

    create_lkas_command() 对 Ram 的控制字段编码为 2、其他为 1,对应固件的不同检查:常规固件在 Pacifica 检查 bit 4、在 Ram 检查字段值 2,CUSW 则独立检查 bit 12。字段名相同不等于原始载荷可共用。

    按钮计数在 CUSW 上有什么特殊之处?

    CUSW 的 update_cusw() 没有像常规分支那样刷新 button_counter,若生成按钮帧会反复使用初始化值加一;这是否触发原车计数拒收仍需台架确认。

    软件里有构帧函数就说明 HD/CUSW 可发送吗?

    不能这样推断。Ram HD 与 CUSW 的参数层标 dashcamOnly,HD 的 TX/RX 只在 ALLOW_DEBUG 构建中注册;台架应分别验证四套布局的 panda 接受/拒绝矩阵,构帧函数存在不代表 release 固件允许发送。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/CHRYSLER_CAN_FRAME_CONTROL_CONTRACT_DEEP_DIVE.md(取用了四平台发帧矩阵、控制周期与按钮/HUD 时序、控制字段编码差异与 panda 安全门边界)。

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

    更新记录

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

  • 大众 MLB/PQ 构帧器:返回类型不匹配与固件白名单

    结论:大众 MLB 与 PQ 共用同一个 CarController 和一套「统一 CCS 插件接口」,但两者的纵向实现并不等价。静态源码里有两处硬边界:PQ 纵向助手返回帧列表(与 can_sends.extend() 一致),而 MLB 助手返回 CANPacker.make_can_msg() 的 (address, bytes, bus) 单帧三元组,extend() 会把三个成员分别加入列表——这是源码可见的返回类型不匹配;同时 MLB 固件安全配置只允许 HCA_01、LDW_02、两总线的 LS_01,没有 ACC_05/ACC_02。统一接口不能掩盖这些差异。

    共享控制器与两套构帧器

    基线是 openpilot 固定快照 c8fb906,覆盖 mlbcan.py 与 pqcan.py 的全部实现函数,并追踪共享车型控制器、接口配置、DBC packer 与 panda safety。仅做静态分析:未发 CAN、未改 ECU、未跑实车。共享控制器 CarController.update() 先限扭矩、做重复值缓解,再以非零扭矩作为 HCA 使能值。

    功能 MLB 实现 PQ 实现
    转向 create_steering_control() 输出 HCA_01,状态 7/3 与 lkas_enabled 对应,扭矩拆绝对值和符号。 create_steering_control() 输出 HCA_1,仅在 enabled 且扭矩非零时状态 7,否则 3。
    LDW / HUD create_lka_hud_control() 直接委托 MQB 构帧。 构造 LDW_Status,可复制五个原车警示字段,再覆盖车道线/告警/转向提示。
    巡航按钮 复制 LS_01 四个配置字段,原车 COUNTER+1 mod 16,覆写 cancel/resume。 对 GRA_Neu 作同类处理。
    纵向状态 acc_control_value() 与 acc_hud_status_value() 均固定返回零。 acc_control_value() 按活动/主开关返回 1/2/0;acc_hud_status_value() 另将故障置 6。

    按钮帧由 pcmCruise、原车 COUNTER 变化与 cancel/resume 请求共同决定发送,总线由 CAN.ext 选择;LDW/HUD 按平台 LDW_STEP 发送,输入来自 CarState 的原车缓存。纵向状态分支只在 openpilotLongitudinalControl 为真时走自控纵向及其 HUD 分支。

    需要说明的是,「什么时候发」和「发什么内容」由不同东西决定:LDW/HUD 依平台的 LDW_STEP 节拍发送,纵向 HUD 依 ACC_HUD_STEP 把单个返回值 append 到发送列表,而内容取自 CarState 缓存的原车配置与计数。节拍常量与原车缓存只保证本机按序产出候选帧,并不等于车辆已接受该帧。

    纵向路径与返回类型不匹配

    加速度与纵向 HUD 构帧是差异最集中的地方。MLB 的 create_acc_accel_control() 用空字段构造单个 ACC_05 三元组,create_acc_hud_control() 用空字段构造 ACC_02;PQ 的 create_acc_accel_control() 构造 ACC_System 并返回单元素列表(关闭时加速度字段用 3.01),create_acc_hud_control() 构造 ACC_GRA_Anzeige,包含状态、目标车速和距离。

    控制器统一调用 can_sends.extend(...),但两种助手的返回类型并不一致:PQ 返回 list[frame],与 extend() 语义一致;MLB 返回 CANPacker.make_can_msg() 的 (address, bytes, bus) 单帧三元组,extend() 会把三个成员分别加入 can_sends,不再是帧列表。这是源码可见的返回类型不匹配,MLB 自控纵向不能据此视为可工作。

    这类差异不会在简单调用里立刻显形:PQ 返回单元素列表,可直接被 extend() 展开为帧;MLB 返回的是 make_can_msg() 的三元组,被 extend() 展开后会把地址、字节和总线各自当成一个元素,破坏原本的帧列表结构。只有真正进入发送链路,这种结构错位才会以异常或错误帧的形式暴露。

    校验与固件白名单

    MLB 的 volkswagen_mlb_checksum() 对十个指定地址使用不同 XOR 初值,其他地址回退 MQB/MEB CRC;PQ 文件没有独立校验函数,由其 DBC/packer 负责已声明的计数和校验字段。CANPacker.pack() 按 DBC signal 类型选回调并填入校验,但它不替代 panda RX/TX 安全检查。

    换句话说,校验只回答「这段字节是否按 DBC 约定自洽」,不回答「这条帧是否应该被允许发出」。后者由固件白名单与安全模式独立裁定,两者不能互相代替。

    可达性边界 源码可见事实
    接口能力 接口对 PQ/MLB 都可在 alpha_long 置位时设置 openpilotLongitudinalControl,但两者纵向实现不等价。
    返回类型 MLB 纵向助手返回单帧三元组,被 extend() 拆成三个成员;PQ 返回帧列表。
    固件白名单 MLB 仅允许 HCA_01、LDW_02 与两总线的 LS_01,无 ACC_05/ACC_02。
    构建门 MLB/PQ 两个 safety 模式都仅在 ALLOW_DEBUG 下注册,发布构建不能因识别到平台便假定可启用。
    数据质量 按钮门的当前实现只比较 COUNTER 是否变化,未同时校验该帧接收时间或原车数据质量。

    PQ 的 ACC_System 虽然存在明确的活动/非活动加速度编码、固件也有单独的纵向配置与加速度限幅,但这仅说明源码中存在对应路径,不等于车辆已验证。按钮帧引用 CarState 缓存的原车配置及计数,接线/插件层需保留 bus、时间戳与报文健康,不能只传一个 cancel 布尔量。

    新硬件的验收要点

    新硬件的验收应分别列出 MLB/PQ 车型身份、网络位置、线束、DBC、固件 safety 配置、计数/校验、失联数据新鲜度和拒帧行为。尤其不能让「统一 CCS 插件接口」掩盖 MLB 的占位纵向实现或返回类型差异。

    验收项 MLB 关注点 PQ 关注点
    纵向可发性 返回类型须先修正,且固件白名单不含 ACC 帧。 返回列表与 extend 一致,但仍需实车验证。
    安全模式 仅在 ALLOW_DEBUG 注册。 仅在 ALLOW_DEBUG 注册。
    计数/校验 十地址 XOR 初值 + MQB/MEB 回退。 由 DBC/packer 声明字段负责。
    数据新鲜度 按钮门不校验接收时间。 按钮门不校验接收时间。

    本机隔离探针未完成:系统 Python 缺 numpy,工作区 Python 缺 capnp;本篇没有运行 DBC 字节回放、目标固件或实车试验。归档源码未编辑。

    常见问题

    为什么大众 MLB 的自控纵向「看起来有实现」却不能用?

    因为源码里 MLB 的纵向助手返回的是 CANPacker.make_can_msg() 的 (address, bytes, bus) 单帧三元组,而控制器用 can_sends.extend() 追加,会把三个成员拆开,不再是帧列表;同时 MLB 固件安全配置只允许 HCA_01、LDW_02 与两总线的 LS_01,没有 ACC_05/ACC_02。返回类型不匹配与白名单缺口是两处独立证据。

    PQ 和 MLB 的转向构帧有什么差别?

    MLB 的 create_steering_control() 输出 HCA_01,状态 7/3 与 lkas_enabled 对应,扭矩拆成绝对值和符号;PQ 输出 HCA_1,仅在 enabled 且扭矩非零时状态取 7,否则取 3。两者都经共享控制器先限扭矩、做重复值缓解。

    两个平台都设了 openpilotLongitudinalControl 就等价吗?

    不等价。接口对 PQ/MLB 都可在 alpha_long 置位时设置 openpilotLongitudinalControl,但两者的纵向实现(返回类型、固件白名单、状态编码)都不同;且 MLB/PQ 两个 safety 模式都仅在 ALLOW_DEBUG 下注册,发布构建不能因识别到平台就假定可启用。

    按钮帧只比较 COUNTER 变化够吗?

    源码中该门只比较 COUNTER 是否变化,没有在本地控制器同时校验该帧的接收时间或原车数据质量。接线/插件层因此需保留 bus、时间戳与报文健康,不能只传一个 cancel 布尔量。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/VW_MLB_PQ_CAN_BUILDER_CONTRACT_DEEP_DIVE.md(取用了 MLB/PQ 构帧函数矩阵、纵向返回类型不匹配、MLB 校验与固件白名单、验收要点)。
    • 项目 02 —— openpilot-research/VOLKSWAGEN_MLB_PANDA_SAFETY_DEEP_DIVE.md(仅用于确认 MLB safety 语境的接续关系,未取用其未公开内容)。

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

    更新记录

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