本节旨在记录工作中遇到的一些通信协议。
CAN 总线原理与通信机制
CAN(Controller Area Network)总线是一种专为汽车电子系统设计的串行通信协议,主要用于不同 ECU(电子控制单元)之间的数据传输。其最大特点是具有高可靠性、实时性强、抗干扰能力强,现已广泛应用于汽车、工业自动化、医疗设备等领域。
物理结构
CAN 总线通常由两根双绞线构成,分别为:
CAN_H
(高电平线)CAN_L
(低电平线)
它们以差分信号的形式工作,通过比较两者之间的电压差来进行数据传输,差分电压为0,称作隐性电平,表示逻辑1,差分电压为2V,称作显性电平,逻辑表达为0。
状态 CAN_H CAN_L 电压差 逻辑意义 电平类型
──────────────────────────────────────────────────────────────────────
显性电平(Dominant) ≈3.5V ≈1.5V ≈2V 逻辑 0 主导电平
隐性电平(Recessive) ≈2.5V ≈2.5V ≈0V 逻辑 1 被动电平
这种差分传输方式大大提高了抗干扰能力,即使在恶劣电磁环境中也能稳定工作。
节点结构

一个连接到 CAN 总线的设备都被视为一个节点,每个节点至少包含以下两部分:
- CAN 控制器:负责协议层的处理,如帧格式解析、仲裁、错误处理;
- CAN 收发器:负责物理层信号的收发与电平转换。
发送过程:
CPU → CAN控制器 → 逻辑电平信号 → CAN收发器 → 差分电平 → CAN总线
接收过程:
CAN总线差分电平 → CAN收发器 → 逻辑电平 → CAN控制器 → CPU
多节点通信与仲裁机制
CAN 总线支持多主机制,即所有节点都有发送权限。 那么当有多个节点时,如何确保数据不会混乱?
多主工作方式:最先向总线发送信息的节点获得总线的发送权。
非破坏性仲裁机制: 通过报文 ID 的优先级来决定哪个节点获得总线控制权。 具体流程:所有节点监听总线; 从SOF位开始,按位计算,显性电平会覆盖隐性点平,被覆盖的节点退出竞争,转为接收节点。
帧结构
CAN2.0B包括两种消息帧格式:标准帧和扩展帧。扩展帧格式如图。

根据用途可分为四种:
- 数据帧:传输有效数据内容(最多 8 字节)。
- 远程帧:请求远程节点发送特定 ID 的数据帧。
- 错误帧:检测到错误时发出,用于错误恢复。
- 过载帧:用于请求延迟下一个帧的发送(暂缓)。
J1939 协议
SAE J1939 是基于 CAN2.0B(扩展帧格式)的高层通信协议,专为商用车、工程机械、农用设备等车辆领域设计。它在 CAN 总线基础上,扩展了帧结构,并定义了一套固定的 PGN(Parameter Group Number,参数组编号) 和 SPN(Suspect Parameter Number,疑似参数编号) 编码体系,用于组织和传输各类车辆参数与诊断信息。
消息帧格式

J1939 只适用于扩展帧格式,其标识符(Identifier)被严格划分为以下几部分:
- 优先级 (Priority):3 位,数值越小,优先级越高(0~7)
- 保留位 (Reserved) :1 位,固定为 0,保留。
- 数据页 (Data Page):1 位,表示页码(扩展 PGN 用)
- PDU 格式 (PDU Format, PF):8 位,确定是点对点还是广播。
- PDU 特定 (PDU Specific, PS):8 位,若为点对点,表示目标地址;否则为 PGN 的一部分。
- 源地址 (Source Address, SA):8 位,当前帧的发送节点地址(0~253)
- 数据场 (Data Field):0~8 字节,实际传输的参数(如速度、电压等)
参数群编号 PGN 与 SPN
PGN 是 J1939 报文的核心,用于定义一类参数组(Parameter Group),描述“这是一类什么类型的报文”。PGN 是由标识符中的以下字段拼接而成的:
PGN = DP(1位)+ PF(8位)+ PS(8位,仅限广播帧)
如果 PF ≥ 240(全局广播),PGN = DP + PF + PS(完整 18 位)。
如果 PF < 240(点对点),PGN = DP + PF(PS 被视为目标地址,不算入 PGN)。
PGN 在表示时通常以十进制表示,如:
61444
→ 发动机状态(EEC1)65262
→ 冷却液温度、油温等。
SPN 是 PGN 中的“子参数”,用于定义 PGN 报文中每个字段(字节或字节组合)的具体含义。例如:
PGN | 报文名称 | 包含的 SPN(部分示例) |
---|---|---|
61444 | EEC1(发动机控制器1) | SPN 190: 发动机转速,SPN 512: 发动机-扭矩百分比 |
65262 | ET1(发动机温度 1) | SPN 110: 冷却液温度,SPN 174: 燃油温度 |
具体 PGN 与 SPN 参数含义可以参考:https://www.canfd.net/j1939pgn.html
应用示例
此示例主在通过设备连接车辆 CAN 总线,获取车辆参数信息。默认设备连接口为 can0。
首先测试连接、设置波特率。
candump can0 # 观察can0有没有数据传过来
ip -details link show can0 # 查看can0口波特率
ip link set can0 down # 关闭can0
ip link set can0 up type can bitrate 250000 # 设置波特率为250kbps
ip link set can0 up # 开启can0
CanRander.hpp 实现连接车辆CAN总线,解析发动机转速数据。
#pragma once
#include <linux/can.h>
#include <string>
#include "MessageQueue.hpp"
#include <atomic>
class CanReader {
public:
CanReader(const std::string& iface, const std::string &device_id, MessageQueue& queue);
~CanReader();
void run();
private:
bool connectCanSocket();
bool parseFrame(const struct can_frame& frame);
void closeCANSocket();
private:
std::string iface_;
std::string device_id_;
int can_socket_ = -1;
MessageQueue& queue_;
std::atomic<bool> running_{true};
};
CanRander.cpp 实现
#include "CanReader.hpp"
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <linux/can/raw.h>
#include <net/if.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <cstring>
#include <iostream>
#include "utils.hpp"
#include "Logger.hpp"
CanReader::CanReader(const std::string& iface, const std::string &device_id, MessageQueue& queue)
: iface_(iface), device_id_(device_id), queue_(queue){}
CanReader::~CanReader() {
running_ = false;
closeCANSocket();
}
bool CanReader::connectCanSocket() {
if (can_socket_ >= 0) return true; // 已连接
// 初始化 SocketCAN
struct ifreq ifr{};
struct sockaddr_can addr{};
can_socket_ = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if (can_socket_ < 0) {
LOG_ERR("[CAN] Socket creation failed.", can_socket_);
return false;
}
strncpy(ifr.ifr_name, iface_.c_str(), IFNAMSIZ);
if (ioctl(can_socket_, SIOCGIFINDEX, &ifr) < 0) {
LOG_ERR("[CAN] ioctl failed.");
return false;
}
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
if (bind(can_socket_, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
LOG_ERR("[CAN] Bind failed.");
return false;
}
return true;
}
void CanReader::run() {
struct can_frame frame{};
while (running_) {
if(!connectCanSocket()) {
LOG_ERR("[CAN] read data error:", errno, ", message: ", strerror(errno));
std::this_thread::sleep_for(std::chrono::milliseconds(2));
continue;
}
int nbytes = read(can_socket_, &frame, sizeof(frame));
if (nbytes < 0) {
closeCANSocket();
LOG_ERR("[CAN] read data error: ", nbytes, ", errno: ", errno, ", message: ", strerror(errno));
continue;
}
if (!parseFrame(frame)) {
LOG_INFO("[CAN] other data.");
}
}
}
bool CanReader::parseFrame(const struct can_frame& frame) {
uint32_t pgn = (frame.can_id >> 8) & 0xFFFF;
if (pgn == 0xF004) {
float rpm = ((frame.data[4] << 8) | frame.data[3]) * 0.125f;
LOG_INFO("[CAN] can rpm data: ", rpm);
return true;
}
return false;
}
void CanReader::closeCANSocket() {
if (can_socket_ >= 0) {
close(can_socket_);
can_socket_ = -1;
}
}
Ntrip 协议
Ntrip(Networked Transport of RTCM via Internet Protocol)是基于互联网协议的 RTCM 网络传输协议,通过互联网传输和共享差分改正数据来实现精确定位和导航。
NTRIP 组成

Ntrip 有四个核心角色:
Ntrip Source:系统的数据源,提供差分数据和原始卫星观测数据。
Ntrip Server(Caster Source):从本地 GNSS 基站获取 RTCM 数据,上传给 Caster。
Ntrip Caster:类似于转发服务器,管理多个 Source 与 Client。
Ntrip Client(Rover端): 通过网络从 Caster 请求 RTCM 差分数据,并用于解算高精度位置。
Ntrip Server 将本地采集的差分数据(如RTCM)上传给 Ntrip Caster,是 Ntrip Client 接收到的差分数据的源头,每个 Ntrip Server 负责维护一个 Mountpoint,Server 不直接与 Client 通信,而是通过 Caster 中转。
Ntrip Caster 是整个系统的”中控”,就像一台差分数据 “交换机”,负责:管理多个差分源(Mountpoint)和多个客户端之间的连接与分发。
传统模式
对于传统固定基站,Ntrip Client 连接上 Ntrip Caster 后,无需发送任何位置信息,只需选择一个固定的差分基站(即指定 mountpoint)并接收其广播的 RTCM 差分数据,与自身接收到的 GNSS 原始观测数据(载波相位、伪距等)进行融合,得到厘米级高精度定位结果。
// Client 流程
1. 建立与 Ntrip Caster 的连接
2. 接收差分站下发的RTCM数据
3. 将RTCM数据与自身观测数据融合得到GPS。
优点:实现简单,无需额外逻辑;固定测站、工程测量场景适用。
缺点:离基站太远(>10~20km),RTK 精度下降,甚至无法解算;误差来源大(基线过长、对流层误差等)
VRS 模式
VRS(Virtual Reference Station)虚拟参考站,是一种基于用户当前位置动态生成差分数据的高精度定位服务方式。Client 在连接 Caster 后,主动上传自身的粗略位置(GGA语句),差分服务系统根据 Client 的位置动态生成一个虚拟基站,返回该虚拟基站点的定制化差分数据(RTCM 格式)。这使得移动站始终能够接收到来自“最近基站”的最优差分修正,提高解算精度和稳定性。
// Client 流程
1. 建立与 Ntrip Caster 的连接
2. 向 Caster 主动发送自身的 GGA 位置语句
3. 接收 Caster 下发的针对当前位置的 RTCM 差分数据流
4. 将 RTCM 数据与自身接收的 GNSS 原始观测数据融合,输出GPS。
相比于传统差分站,VRS 多了上传 GGA 数据的步骤。
优点:精度高,收敛快;适合流动站不断移动的场景(如无人车、测绘无人机);减少了对实际物理基站的建设和维护压力。
缺点:必须周期性注入 GGA;部分 VRS 服务为商业服务,需要账户认证、订阅许可等。
RTCM3.2
RTCM(Radio Technical Commission for Maritime Services) 是由美国海事无线电技术委员会发布的标准,广泛应用于高精度 GNSS(如 GPS/北斗/GLONASS/Galileo)定位中,用于传输差分修正数据。
数据结构
┌──────────┬──────────┬─────────────────┬──────────────┬────────┐
│ Preamble │ Reserved │ Message Length │ Payload │ CRC │
│ (8bit) │ (6bit) │ (10bit) │ (N bits) │ 24bit │
同步码 保留位 信息长度 数据信息
└──────────┴──────────┴─────────────────┴──────────────┴────────┘
相关资料: