1. J1939协议帧格式解析与构造

J1939协议帧格式解析与构造 学习卡片

本案例实现一个最小可用的 J1939 帧解析/构造工具:把 29 位扩展 CAN ID 拆成优先级、PGN、源地址、目标地址,并能反向拼回扩展帧 ID。J1939 的 29 位 ID 结构为:3 位优先级、1 位保留、1 位数据页、8 位 PDU 格式、8 位 PDU 特定、8 位源地址。当 PDU 格式为 PDU1(PF < 240)时,PDU 特定字段是目标地址,PGN 的末字节为 0;当为 PDU2(PF ≥ 240)时,PDU 特定字段属于 PGN,目标地址为广播(0xFF)。

实现代码

#include <stdio.h>
#include <stdint.h>

typedef struct {
    uint8_t  priority;   // 0..7
    uint32_t pgn;        // 18-bit PGN
    uint8_t  src;        // source address
    uint8_t  dst;        // destination (0xFF = broadcast)
} J1939Frame;

// 解析 29 位扩展 ID
J1939Frame j1939_parse(uint32_t id29) {
    J1939Frame f;
    f.priority = (id29 >> 26) & 0x7;
    uint8_t dp = (id29 >> 24) & 0x1;
    uint8_t pf = (id29 >> 16) & 0xFF;
    uint8_t ps = (id29 >> 8)  & 0xFF;
    f.src = id29 & 0xFF;

    if (pf < 240) {                 // PDU1: PS 是目标地址
        f.dst = ps;
        f.pgn = ((uint32_t)dp << 16) | ((uint32_t)pf << 8);
    } else {                        // PDU2: PS 属于 PGN
        f.dst = 0xFF;
        f.pgn = ((uint32_t)dp << 16) | ((uint32_t)pf << 8) | ps;
    }
    return f;
}

// 构造 29 位扩展 ID
uint32_t j1939_build(const J1939Frame *f) {
    uint8_t dp = (f->pgn >> 16) & 0x1;
    uint8_t pf = (f->pgn >> 8)  & 0xFF;
    uint8_t ps = (pf < 240) ? f->dst : (f->pgn & 0xFF);

    uint32_t id = ((uint32_t)(f->priority & 0x7) << 26)
                | ((uint32_t)dp << 24)
                | ((uint32_t)pf << 16)
                | ((uint32_t)ps << 8)
                | f->src;
    return id;
}

int main(void) {
    // 例1: PDU1 请求帧, 优先级6, PGN 0xEA00, 目标0x12, 源0x34
    J1939Frame a = {6, 0xEA00, 0x34, 0x12};
    uint32_t ida = j1939_build(&a);
    J1939Frame ra = j1939_parse(ida);
    printf("PDU1 id=0x%08X pri=%u pgn=0x%05X src=0x%02X dst=0x%02X\n",
           ida, ra.priority, ra.pgn, ra.src, ra.dst);

    // 例2: PDU2 广播帧, 优先级3, PGN 0xFEF1, 源0x56
    J1939Frame b = {3, 0xFEF1, 0x56, 0xFF};
    uint32_t idb = j1939_build(&b);
    J1939Frame rb = j1939_parse(idb);
    printf("PDU2 id=0x%08X pri=%u pgn=0x%05X src=0x%02X dst=0x%02X\n",
           idb, rb.priority, rb.pgn, rb.src, rb.dst);
    return 0;
}

运行与验证

gcc j1939.c -o j1939 && ./j1939

预期输出(由代码逻辑唯一确定):

PDU1 id=0x18EA1234 pri=6 pgn=0x0EA00 src=0x34 dst=0x12
PDU2 id=0x18FEF156 pri=3 pgn=0x0FEF1 src=0x56 dst=0xFF

判定通过条件:两行输出中优先级、PGN、源地址、目标地址与构造时输入一致;PDU1 帧目标地址为 0x12,PDU2 帧目标地址为 0xFF。可再自行修改输入值,验证 build→parse 往返结果不变。

29 位扩展 ID 位域布局 Prio R DP PF (8) PS (8) SA (8) 3 位 1 1 bit26-19 bit18-11 bit10-3 PGN 组成规则 PDU1 (PF < 240): PGN = DP·PF·00 PS = 目标地址 (dst) PDU2 (PF ≥ 240): PGN = DP·PF·PS dst = 0xFF (广播)