2. J1939协议核心概念:PGN、SA、DA、PF、PS、PDU格式详解
说实话,刚接触J1939的工程师,十有八九都会被那一堆缩写搞晕。PGN、SA、DA、PF、PS……这些到底在说什么?
我当年第一次调J1939栈的时候,看着协议文档里的PDU格式图,愣是盯了半小时没动。后来踩了几个坑才明白——这些概念其实就干一件事:告诉总线上的节点,谁在说话、在说什么、说给谁听。
这一章,咱们就把这些核心概念掰开揉碎,一个一个讲清楚。
2.1 从CAN ID到J1939:29位标识符的战场
J1939用的是CAN 2.0B扩展帧,29位标识符。这29位里,藏着所有秘密。
| 位域 | 位数 | 含义 | 取值范围 |
|---|---|---|---|
| SOF | 1 | 帧起始 | 固定0 |
| SRR | 1 | 替代远程请求 | 固定1 |
| IDE | 1 | 扩展标识符标志 | 固定1 |
| Priority | 3 | 优先级 | 0-7(0最高) |
| EDP | 1 | 扩展数据页 | 0-1 |
| DP | 1 | 数据页 | 0-1 |
| PF | 8 | PDU格式 | 0-255 |
| PS | 8 | PDU特定域 | 0-255 |
| SA | 8 | 源地址 | 0-255 |
你看,真正干活的就是最后那三个8位域:PF、PS、SA。Priority、DP、EDP这些,说白了就是给报文排队的优先级和路由信息。
关键点:J1939的29位ID不是随便拼出来的。PF和PS的组合,决定了这个报文是发个所有人(广播),还是发给特定节点(点对点)。
2.2 PDU格式:报文到底长什么样?
PDU,全称Protocol Data Unit,协议数据单元。J1939里,一个PDU由以下部分组成:
- 优先级(P):3位,0最高,7最低。我习惯把关键控制报文设成3,参数信息设成6。
- 数据页(DP):1位,用来扩展PGN空间。大部分时候都是0。
- PDU格式(PF):8位,这是核心中的核心。
- PDU特定域(PS):8位,它的含义取决于PF的值。
- 源地址(SA):8位,谁发的这个报文。
这里有个关键分水岭——PF的值决定了PS是干什么的。
2.3 PF与PS:广播还是点对点?
J1939把报文分成两大类,分界线就是PF=240(0xF0)。
| PF范围 | 类型 | PS含义 | 例子 |
|---|---|---|---|
| 0-239 | PDU1格式(点对点) | 目标地址(DA) | 请求配置信息给特定ECU |
| 240-255 | PDU2格式(广播) | 组扩展(GE) | 发动机速度、温度等广播 |
为什么会这样设计?我个人的理解是:PF小于240时,PS里放的是目标地址,报文是定向发送的;PF大于等于240时,PS里放的是组扩展,用来进一步细分PGN空间。
举个例子你就明白了:
- 点对点:PF=0xEF(239),PS=0x03,表示发给地址为3的ECU。
- 广播:PF=0xF0(240),PS=0x01,表示这是一个广播报文,组扩展为1。
我的经验:调试的时候,看到PF值大于等于0xF0,就别指望它只发给一个节点了。我曾经在项目里把广播报文当点对点解析,结果丢了一堆数据——嗯,那感觉挺酸爽的。
2.4 PGN:参数组编号,报文的身份证
PGN,Parameter Group Number,参数组编号。它是J1939里最重要的概念之一。
PGN由以下部分组成:
- 保留位(R):1位,目前固定为0。
- 数据页(DP):1位。
- PDU格式(PF):8位。
- PDU特定域(PS):8位。
计算PGN的公式很简单:
PGN = (DP << 16) | (PF << 8) | PS
但注意——当PF小于240时,PS被当作DA(目标地址),此时PS固定为0。也就是说,点对点报文的PGN里,PS那8位是0。
举个例子:
- 发动机速度(EEC1):PGN = 0xF004,PF=0xF0,PS=0x04,广播报文。
- 请求PGN:PGN = 0xEA00,PF=0xEA,PS=0x00,点对点报文。
避坑指南:我曾经在解析报文时,直接把29位ID里的PS字段当作PGN的低8位,结果点对点报文全算错了。记住:点对点报文的PGN,PS部分强制为0!
2.5 SA与DA:谁在说,说给谁?
SA(源地址)是每个ECU在总线上的唯一标识。J1939有一个官方地址分配表,比如:
| 地址 | 设备 |
|---|---|
| 0x00 | 发动机控制器 #1 |
| 0x03 | 变速箱控制器 |
| 0x21 | ABS控制器 |
| 0xF9 | 诊断工具 |
DA(目标地址)只在PDU1格式(PF<240)时存在,它告诉总线这个报文是发给谁的。
你想想看,如果总线上有10个ECU,一个报文广播出去,所有节点都得判断是不是发给自己的。这就是DA的作用——硬件过滤+软件过滤,双重保险。
2.6 一张图看懂所有关系
说了这么多,咱们用一张SVG图把整个PDU到PGN的映射关系串起来。
2.7 实战:从CAN报文里提取PGN
假设你在总线上抓到一帧CAN数据:
CAN ID: 0x18F00400
数据: 08 7F 00 00 00 00 00 00
咱们来拆解一下:
- 二进制展开:0x18F00400 = 0001 1000 1111 0000 0000 0100 0000 0000
- 提取字段:
- Priority = 011(3位)= 3
- EDP = 0
- DP = 0
- PF = 0xF0(240)
- PS = 0x04
- SA = 0x00(发动机控制器)
- 计算PGN:PF=0xF0 ≥ 240,所以是广播报文。PGN = (0 << 16) | (0xF0 << 8) | 0x04 = 0xF004
- 查表:PGN 0xF004 = 发动机速度/转速(EEC1)
你看,是不是很清晰?
我的习惯:写解析代码的时候,我一般先判断PF是否大于等于0xF0,再决定PS怎么处理。这样逻辑分支清晰,不容易出错。
2.8 常见误区总结
这些年带团队,我发现新手最容易犯这几个错:
- 误区一:直接把29位ID的低16位当PGN。错!PGN里不包含SA。
- 误区二:点对点报文把PS当PGN低8位。错!点对点报文的PGN低8位是0。
- 误区三:混淆PF和PGN的高字节。PF只是PGN的一部分,PGN还包含DP和PS。
- 误区四:忽略优先级对总线仲裁的影响。优先级设太高会抢占关键报文,设太低可能被淹没。
我曾经踩过的坑:有一次调试发动机和变速箱的通信,变速箱一直收不到发动机的转速报文。抓了半天,发现是发动机的PGN算错了——我把PS直接塞进了PGN,而那个报文是点对点的,PS是目标地址,不是PGN的一部分。改过来之后,一切正常。
好了,这一章的核心概念就这些。PGN、SA、DA、PF、PS,说白了就是一套寻址和分类的规则。理解了它们,J1939的报文结构你就掌握了八成。