🤖 roboto_origin_03 Wiki
首页 / IMU 子模块 / J1939 协议解析与 PGN 映射

本页深入解析 HiPNUC 系列 IMU 在 CAN 总线上采用的 J1939 扩展帧私有协议 实现。与标准 SAE 定义的 J1939 参数组不同,HiPNUC 使用制造商保留段 PGN(0xFF00–0xFFFF)将传感器原始数据、融合姿态、GNSS 定位及环境信息拆分为多条 8 字节短帧广播。理解 PGN 提取公式、载荷字节序与比例因子体系,是阅读 SocketCAN 单例与实时接收线程工厂模式与抽象驱动设计 的底层前提。

Sources: hipnuc_j1939_parser.h, hipnuc_can_common.h

J1939 扩展帧 ID 结构与 PGN 提取

本协议仅支持 29 位扩展帧(EFF)。在 hipnuc_j1939_parse_frame 的入口处,首先检查 HIPNUC_CAN_EFF_FLAG0x80000000)标识;若为标准帧则直接返回 CAN_MSG_UNKNOWN。对于有效扩展帧,PGN 的提取采用精简位运算:

uint32_t id  = frame->can_id & HIPNUC_CAN_EFF_MASK;  // 保留低 29 位
uint32_t pgn = (id >> 8) & 0xFFFF;                   // 提取 PDU Format + PDU Specific

在标准 J1939 广播语义中(PF ≥ 240),Data Page 为 0,因此 PGN = (PF << 8) | PS,恰好等价于 (can_id >> 8) & 0xFFFF。源地址(Source Address, SA)位于低 8 位,可直接通过 can_id & 0xFF 提取。在驱动注册回调时,HipnucIMUDriver 正是以 SA 作为设备区分键,实现同一 CAN 总线上多节点的并行解析。

Sources: hipnuc_j1939_parser.c, hipnuc_imu_driver.cpp

PGN 映射总表与数据语义

HiPNUC J1939 协议共定义 13 条专有 PGN,覆盖惯性测量、组合导航及环境量。所有载荷均采用 小端字节序(Little-Endian),并映射为 16 位或 32 位有符号整数后乘以固定比例因子得到物理量。

PGN (Hex) 语义 载荷类型 字节布局 比例因子 物理单位 返回值常量
0xFF10 经纬度 int32[2] 0–3: lat, 4–7: lon 1e-7 deg CAN_MSG_GNSS_POS
0xFF14 高度与差分龄期 int32 + int16[2] 0–3: MSL, 4–5: undulation, 6–7: diff_age 0.01 m CAN_MSG_GNSS_POS
0xFF18 定位/定向状态 uint8[5] 0: solq_pos, 1: solq_heading, 2: nv_pos, 3: nv_heading, 4: ins_status 1 CAN_MSG_GNSS_STATUS
0xFF26 地速与航速 int16[4] 0–1: vel_e, 2–3: vel_n, 4–5: vel_u, 6–7: speed 0.01 m/s CAN_MSG_GNSS_VEL
0xFF2F UTC 时间 uint8[6] + uint16 0: year, 1: month, 2: day, 3: hour, 4: min, 5: sec, 6–7: ms 1 CAN_MSG_TIME
0xFF34 加速度 int16[3] 0–1: acc_x, 2–3: acc_y, 4–5: acc_z 0.00048828 g CAN_MSG_ACCEL
0xFF37 角速度 int16[3] 0–1: gyr_x, 2–3: gyr_y, 4–5: gyr_z 0.061035 °/s CAN_MSG_GYRO
0xFF3A 磁场强度 int16[3] 0–1: mag_x, 2–3: mag_y, 4–5: mag_z 0.030517 µT 级 CAN_MSG_MAG
0xFF3D 横滚与俯仰 int32[2] 0–3: roll, 4–7: pitch 0.001 ° CAN_MSG_PITCH_ROLL
0xFF41 航向角 int32 0–3: yaw 0.001 ° CAN_MSG_YAW
0xFF43 环境量 int16 + 填充 + int32 0–1: temperature, 4–7: pressure 0.01 °C / Pa 级 CAN_MSG_PRESSURE
0xFF46 四元数 int16[4] 0–1: w, 2–3: x, 4–5: y, 6–7: z 0.0001 CAN_MSG_QUAT
0xFF4A 倾角 int32[2] 0–3: incli_x, 4–7: incli_y 0.001 ° CAN_MSG_INCLI

上述比例因子的选取与传感器量程直接相关:加速度比例 0.00048828 ≈ 1/2048,对应 16 位有符号整数的满量程 ±16 g;角速度比例 0.061035 ≈ 1/16.384,对应 ±2000 °/s 量程。这种“整数原始值 + 固定比例”的编码策略在车载嵌入式场景中兼具计算效率与总线带宽利用率。

Sources: hipnuc_j1939_parser.c, hipnuc_can_common.h

帧解析控制流

当 SocketCAN 接收线程通过 can_rx_cbk 将一帧 linux/can.hcan_frame 传入后,解析器执行严格的类型分派。以下 Mermaid 图展示了从原始帧到结构化数据的完整决策路径:

flowchart TD
    A[SocketCAN 接收线程<br/>can_frame] --> B[HipnucIMUDriver::can_rx_cbk]
    B --> C[封装为 hipnuc_can_frame_t]
    C --> D[hipnuc_j1939_parse_frame]
    D --> E{检查 EFF 标志<br/>0x80000000}
    E -->|缺失| F[返回 CAN_MSG_UNKNOWN]
    E -->|存在| G[提取 PGN<br/>(can_id >> 8) & 0xFFFF]
    G --> H{Switch PGN}
    H -->|0xFF34| I[parse_j1939_accel]
    H -->|0xFF37| J[parse_j1939_gyro]
    H -->|0xFF46| K[parse_j1939_quat]
    H -->|...| L[其余专用解析函数]
    I --> M[按小端解码 int16/int32<br/>写入 can_sensor_data_t]
    J --> M
    K --> M
    L --> M
    M --> N[返回 CAN_MSG_* 类型码]
    N --> O{Driver 单位转换层}
    O -->|CAN_MSG_ACCEL| P[× 9.8 → m/s²]
    O -->|CAN_MSG_GYRO| Q[× 0.01745329 → rad/s]
    O -->|其他| R[直接写入共享状态]
    P --> S[std::shared_mutex 保护下的<br/>sensor_data_]
    Q --> S
    R --> S

值得注意的设计细节是:解析器本身不执行任何单位换算,始终输出厂家原始物理单位(g、°/s、° 等)。SI 单位转换被显式推迟到 C++ 驱动层的 can_rx_cbk 中,仅在返回码为 CAN_MSG_ACCELCAN_MSG_GYRO 时触发。这一分层策略使得底层 C 解析模块保持对硬件语义的原子性,而上层驱动可按应用需求灵活调整单位体系。

Sources: hipnuc_j1939_parser.c, hipnuc_imu_driver.cpp

配置帧协议:PGN 0x0CEF

除传感器广播帧外,HiPNUC J1939 栈还实现了基于 PGN 0x0CEF 的点对点配置读写协议。该协议用于在运行时访问设备内部寄存器,支持功能码 0x03(读)与 0x06(写)。其 CAN ID 构造遵循标准 J1939 点对点寻址:

uint32_t hipnuc_j1939_make_cfg_id(uint8_t da, uint8_t sa) {
    return 0x0CEF0000U | ((uint32_t)da << 8) | (uint32_t)sa;
}

位域解读为:Priority = 3,EDP = 0,DP = 0,PF = 0xEF(点对点),PS = 目的地址(DA),SA = 源地址。配置帧的 8 字节载荷统一采用小端编码:字节 0–1 为寄存器地址,字节 2 为功能码,字节 3 保留,字节 4–7 为写入值或读取长度。解析端通过校验 DLC == 8、EFF 标志及 ((can_id & EFF_MASK) >> 16) == 0x0CEF 三重条件确保帧合法性,随后回提地址、功能码、状态与数值。

Sources: hipnuc_can_common.h, hipnuc_can_common.c

驱动层集成与线程安全

J1939 解析模块以纯 C 实现,不依赖 C++ 标准库或 ROS 2 基础设施,从而与 工厂模式与抽象驱动设计 中的 IMUDriver 基类解耦。集成点集中在 HipnucIMUDriver::can_rx_cbk:该回调运行于 SocketCAN 接收子线程内部,在调用 hipnuc_j1939_parse_frame 之前通过 std::unique_lock<std::shared_mutex> 独占锁定 sensor_data_。用户接口(get_ang_velget_quat 等)则使用 std::shared_lock 实现并发读安全。

由于 J1939 广播天然将不同物理量拆分到独立 PGN 帧中,解析器对 can_sensor_data_t 的更新属于部分写模型:一帧 0xFF34 仅覆写加速度三轴,而四元数、温度等字段保持上一次有效值。这种设计在 1 kHz 高频总线场景下不会引入数据撕裂,因为每条 PGN 的写入粒度恰好落在 32 位或 64 位对齐边界,且 C 结构体成员为独立标量;但跨 PGN 的“同一时刻采样一致性”需由应用层通过时间戳或硬件同步机制保证。

Sources: hipnuc_imu_driver.cpp, hipnuc_imu_driver.hpp, imu_driver.hpp

与 CANopen TPDO 的边界

HiPNUC 驱动源码目录中同时存在 canopen_parser.c,说明同一硬件平台可能支持 CANopen 与 J1939 两种协议 personality。与 CANopen TPDO 解析 不同,本页的 J1939 实现不依赖对象字典(OD)或 PDO 映射表,而是采用硬编码的 switch-case PGN 路由。这使得 J1939 路径的代码体积更小、解析延迟更确定(无哈希或树查找),代价是扩展新 PGN 时必须修改源码并重新编译。开发者在适配新型号传感器时,应首先确认固件输出的是 J1939 专有帧还是 CANopen TPDO,再选择对应的解析后端。