本页深入解析 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_FLAG(0x80000000)标识;若为标准帧则直接返回 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.h 的 can_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_ACCEL 或 CAN_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_vel、get_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,再选择对应的解析后端。