资讯动态

J1939-22 协议拆解:CAN FD 数据链路层帧格式与传输实操

发布时间:2026/10/21 6:59:41 来源:尧图企业网站定制
简介SAE J1939-22:2021 是面向车载网络通信领域的国际推荐实践英文原版文档聚焦 CAN FD 数据链路层的定义与实现适合汽车电子工程师、商用车网络开发者及协议研究人员参考。文档系统阐述了在 SAE J1939-17 物理层上使用 CAN FD 帧格式的通信方案涵盖范围、参考文献、适用文档、物理层要求以及数据链路层规范包括帧格式、数据传输机制与错误检测等内容相比传统 CAN 协议可支持更大负载与更高传输速率适用于卡车、巴士、建筑与农业设备及固定电力系统等场景。资源为单个 PDF 文件完整 82 页压缩包约 42.7MB保持原版英文排版与目录结构便于按章节检索查阅。目前已有 517 人学习下载可作为理解 J1939 与 CAN FD 融合设计、开展车载网络协议开发与标准对照的实用参考资料。1. 拆开 J1939-22 这份 82 页 PDFCAN FD 数据链路层到底改了什么如果你手头只有 J1939-21 的老底子第一次翻 J1939-22 会有点懵——同样是 29 位标识符同样跑在 J1939 网络上但帧格式、传输协议、路由逻辑几乎全换了。这份 2021 年 3 月发布的推荐实践核心就一件事把 J1939 从经典 CAN 的 8 字节载荷搬到 CAN FD 的 64 字节载荷上同时不破坏原有 PGN 寻址体系。它面向的是商用车、工程机械、农机以及固定动力系统里那些需要搬大块数据的场景比如标定文件上传、诊断日志回读、OTA 分包。82 页里真正影响你写代码的是第 6 章的技术要求和附录 A 的传输序列。下面按我实际翻文档、对着 CAN 分析仪抓包的顺序把这份资料拆成能直接抄作业的步骤。2. 从 A_PDU 到 D_PDUJ1939-22 的帧格式与路由逻辑2.1 A_PDU 与 D_PDU 的分工J1939-22 把协议数据单元拆成两层A_PDU 负责应用层路由D_PDU 负责数据链路层封装。A_PDU 沿用 J1939-21 的 PGN 概念但新增了 A_PDU1 和 A_PDU2 两种格式。A_PDU1 是标准 29 位标识符映射A_PDU2 则用于 Multi-PG 场景把多个 C-PG 打包进一个 CAN FD 帧。D_PDU 分三种D_PDU1 对应 29 位 ID 的经典格式D_PDU2 对应 29 位 ID 的 FD 格式D_PDU3 是新增的 11 位 ID 格式专门用于 FEFFFD Extended Frame Format和 FBFFFD Base Frame Format的区分。实际写驱动时你最先要判断的是当前报文走 FEFF 还是 FBFF。FEFF 用 29 位 ID兼容原有 J1939 寻址FBFF 用 11 位 ID适合点对点大块传输。文档第 6.4 节给了映射关系我一般会先画一张 ID 位域表再动手。2.2 29 位标识符的位域分配J1939-22 的 29 位 ID 布局和 J1939-21 有继承也有变化。优先级占 3 位保留位 1 位数据页 1 位PDU 格式 8 位PDU 特定 8 位源地址 8 位。变化在于 PDU 格式字段的取值当 PF 小于 240 时是 PDU1 格式目标地址在 PS 字段当 PF 在 240 到 255 之间是 PDU2 格式PS 字段变成组扩展。J1939-22 新增的 D_PDU3 则完全走 11 位 ID不再遵循这套 29 位规则。下面这段 Python 用来解析 29 位 ID我把它嵌在测试脚本里抓包后直接喂原始 ID 就能拆出各字段def parse_j1939_id(can_id): # 29 位扩展帧can_id 为整数 priority (can_id 26) 0x7 reserved (can_id 25) 0x1 data_page (can_id 24) 0x1 pdu_format (can_id 16) 0xFF pdu_specific (can_id 8) 0xFF source_addr can_id 0xFF if pdu_format 240: # PDU1 格式pdu_specific 是目标地址 pgn (data_page 16) | (pdu_format 8) dest pdu_specific else: # PDU2 格式pdu_specific 是组扩展 pgn (data_page 16) | (pdu_format 8) | pdu_specific dest None return { priority: priority, pgn: pgn, source: source_addr, dest: dest }逻辑说明先按位右移取出各字段再根据 PF 值判断 PDU1 还是 PDU2。参数上can_id必须是 29 位整数如果你从 socketcan 拿到的是带标志位的值先和0x1FFFFFFF做与运算。这个函数不处理 D_PDU3 的 11 位 ID遇到 FBFF 报文要单独走分支。2.3 Multi-PG 与 FD 传输协议的配合Multi-PG 是 J1939-22 里比较新的东西它把多个 C-PG 塞进一个 CAN FD 帧减少帧数量、提高总线利用率。文档 6.5 节给了打包规则每个 C-PG 带自己的 PGN 和长度按顺序排列接收端按长度逐个拆。FD 传输协议FD.TP则负责超过 64 字节的数据块用连接管理消息FD.TP.CM建链再用数据转移消息FD.TP.DT搬运。我一般把 Multi-PG 用在周期性的小数据聚合上比如同时发几个传感器状态FD.TP 用在诊断日志回读这种一次性大块传输。两者不要混在同一个 PGN 里否则接收端状态机会乱。3. 上手实操用 CAN FD 控制器发一帧 J1939-22 报文3.1 硬件与驱动准备要复现 J1939-22 的帧你需要一个支持 CAN FD 的控制器比如常见的 MCP2518FD 或 TCAN4550 这类芯片做的 USB 转 CAN FD 盒。驱动层在 Linux 下用 socketcan 的canfd模式Windows 下用厂商提供的 API。先确认内核版本支持CAN_RAW_FD_FRAMES用ip link看接口是否已经 up。# 加载 CAN FD 相关模块 sudo modprobe can sudo modprobe can_raw sudo modprobe can_dev # 配置 CAN FD 接口仲裁段 500k数据段 2M sudo ip link set can0 up type can bitrate 500000 dbitrate 2000000 fd on参数说明bitrate是仲裁段速率dbitrate是数据段速率fd on开启 FD 模式。仲裁段保持 500k 是为了兼容总线上可能存在的经典 CAN 节点数据段拉到 2M 才能体现 FD 的吞吐优势。如果你的总线全是 FD 节点仲裁段也可以提到 1M但先确认线束和终端电阻能撑住。3.2 构造并发送 D_PDU2 格式报文下面这段 C 代码用 socketcan 发一帧 D_PDU2 格式的 J1939-22 报文载荷 32 字节走 29 位 ID#include linux/can.h #include linux/can/raw.h #include sys/socket.h #include sys/ioctl.h #include net/if.h #include string.h #include unistd.h #include stdio.h int main(void) { int s socket(PF_CAN, SOCK_RAW, CAN_RAW); struct ifreq ifr; strcpy(ifr.ifr_name, can0); ioctl(s, SIOCGIFINDEX, ifr); struct sockaddr_can addr {0}; addr.can_family AF_CAN; addr.can_ifindex ifr.ifr_ifindex; bind(s, (struct sockaddr *)addr, sizeof(addr)); // 开启 FD 帧支持 int enable_fd 1; setsockopt(s, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, enable_fd, sizeof(enable_fd)); struct canfd_frame frame {0}; // 29 位 ID优先级 6PF0xEBPS0x00SA0x80 frame.can_id (6 26) | (0xEB 16) | (0x00 8) | 0x80; frame.can_id | CAN_EFF_FLAG; // 扩展帧标志 frame.len 32; // FD 载荷长度 frame.flags CANFD_BRS; // 开启比特率切换 for (int i 0; i frame.len; i) { frame.data[i] i; } int nbytes write(s, frame, sizeof(frame)); printf(sent %d bytes\n, nbytes); close(s); return 0; }逻辑说明can_id按 29 位布局拼出来CAN_EFF_FLAG告诉驱动这是扩展帧。frame.len设成 32表示 FD 帧载荷 32 字节最大可以到 64。CANFD_BRS开启比特率切换让数据段跑在dbitrate上。参数上PF 取 0xEB 是 PDU2 格式PS 作为组扩展如果你要发 PDU1 格式PF 要小于 240PS 填目标地址。3.3 接收端解析与校验接收端用同样的 socketcan 接口读帧先判断can_id是否带CAN_EFF_FLAG再走 2.2 节的解析函数。FD 帧的len字段可能是 0 到 64 之间任意值不要假设固定 8 或 64。校验时重点看 PGN 是否在预期范围内以及源地址是否合法。import socket import struct s socket.socket(socket.AF_CAN, socket.SOCK_RAW, socket.CAN_RAW) s.bind((can0,)) while True: frame, _ s.recvfrom(72) can_id struct.unpack(I, frame[:4])[0] if can_id 0x80000000: # EFF 标志 can_id 0x1FFFFFFF parsed parse_j1939_id(can_id) payload frame[8:8frame[4]] print(parsed, payload.hex())参数说明recvfrom的缓冲区给 72 字节足够容纳canfd_frame结构。frame[4]是长度字段frame[8:]才是数据。注意 Python 的 socketcan 在部分内核上对 FD 帧支持不完整如果读不到换 C 或直接用candump验证。4. 避坑与排查J1939-22 落地时最容易翻车的五个点4.1 现象FD 帧发出去接收端只收到前 8 字节原因接收端控制器没开 FD 模式或者驱动没设置CAN_RAW_FD_FRAMES。经典 CAN 控制器会把 FD 帧当错误帧处理或者截断成 8 字节。解决确认收发两端都执行了ip link set can0 up type can ... fd on并且在 socket 层用setsockopt开启CAN_RAW_FD_FRAMES。用candump -L can0看原始帧如果显示##开头的 FD 标记说明链路层已经识别。4.2 现象Multi-PG 拆包时 PGN 错位原因Multi-PG 里每个 C-PG 的长度字段没对齐或者发送端打包时漏了填充字节。J1939-22 规定 C-PG 按字节对齐长度字段本身占 2 字节。解决在打包函数里强制按 2 字节边界对齐接收端先读长度再读数据不要用固定偏移。我一般会在打包后打印每个 C-PG 的起始偏移和文档 6.5.3 的格式表逐项对。4.3 现象FD.TP 连接建立后数据传输中途超时原因FD.TP.CM 里的超时参数用了默认值但总线负载高时默认值偏短。文档 6.14 给了超时默认值但实际项目里要根据总线负载调整。解决把FD.TP.CM里的Timeout_BS和Timeout_CS适当放大同时检查接收端的缓冲区管理6.13 节提到接收缓冲区不足会丢帧。用 CAN 分析仪的统计功能看总线负载超过 60% 就要考虑调整发送节奏。4.4 现象29 位 ID 解析出的 PGN 和预期不符原因把 PDU1 和 PDU2 的 PGN 计算方式搞混了。PDU1 格式下 PS 是目标地址不参与 PGN 计算PDU2 格式下 PS 是组扩展要参与。解决回到 2.2 节的解析函数先判断 PF 值再算 PGN。如果你从 DBC 文件导入注意 DBC 里对 PGN 的定义可能和 J1939-22 有出入以文档第 6.1.3 节为准。4.5 现象Address Claimed 服务在 FD 模式下不响应原因J1939-22 的地址声明服务沿用了 J1939-21 的机制但 FD 帧的优先级和仲裁段速率可能影响仲裁结果。如果总线上有经典 CAN 节点FD 节点在仲裁段输掉后会重试看起来像不响应。解决确认地址声明报文的优先级设成 6仲裁段速率和总线一致。用示波器看总线波形确认 FD 节点的仲裁段位时间是否匹配。如果总线上混有经典 CAN 节点建议把 FD 节点的仲裁段速率降到 250k 或 500k。5. 进阶技巧用附录 A 的传输序列做协议一致性验证附录 A 给了 FD 传输协议的完整传输序列包括建链、数据传输、断链的每一步报文。我一般拿它当测试用例的预期输出写一个脚本自动比对实际抓包和文档序列。具体做法是先把附录 A 的序列手工录成 CSV每行包含帧序号、方向、ID、载荷摘要再用candump抓实际通信解析成同样格式最后逐行 diff差异行标红。这个验证方法能抓出大部分协议实现错误比如 CM 消息的字节顺序、DT 消息的序列号回绕。文档 6.6.3 和 6.6.4 给了 CM 和 DT 的详细字段定义比对时重点看控制字节和序列号。我还会把附录 B 的 SPN 分配表导入数据库方便查每个 SPN 对应的 PGN 和刷新率。从那以后我每次拿到新的 J1939-22 实现都强制走一遍附录 A 的序列比对哪怕对方说已经测过。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑