尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
J1939报文解析实战:从CAN ID到PGN/SPN的入门指南
接到第一个商用车对接任务时我面对一屏幕0x0CF00400、0x18FEE300这种 CAN ID 日志确实懵了一段时间。后来把 SAE J1939 协议的关键脉络理清楚才发现它跟普通 CAN 报文解析没有本质区别难点只在于“29 位 ID 里藏着参数组编号”“多字节参数按低字节在前排列”“超过 8 字节的数据要靠传输协议重组”这三道门槛。这篇东西写给所有第一次碰 J1939 的人不管你是做车辆远程监控、农机数据采集还是工程机械电控调试按着我下面的方法走一遍基本就能把日志里 90% 的常见报文拆明白。1. 为什么我一开始会被 J1939 难住1.1 从一段看不懂的 CAN 日志说起那阵子在调一个商用车远程监控项目对方甩过来一份.asc抓包文件里面全是0x0CF00400、0x18FEE300、0x18FEF100这类 29 位扩展 ID。普通乘用车的 CAN 报文 ID 都是 11 位比如0x7E8、0x7DF一眼就能猜个大概但 J1939 的 29 位 ID 太长光看 ID 根本不知道这条报文在说什么。更麻烦的是同一个 ID 在不同车上的含义是一样的可数据字节的排布方式跟 OBD 那套完全不是一回事按着惯性去解析出来的转速能到几千转、车速能到几百公里怎么看都不对。其实现在回头看J1939 并不算复杂它只是把“分段式 CAN 报文”做成了一个体系化的高层协议。理解它需要抓住几条主线29 位 ID 的位分配规则、PGN参数组编号的计算方式、SPN可疑参数编号也就是具体信号的字节位定义以及多包数据的传输机制。把这四件事串起来就能拿着数据手册和抓包日志一步步对照分析了。1.2 J1939 不是新的 CAN而是一套“通信语言”先明确一个基础认知SAE J1939 建立在 CAN 2.0B 之上物理层和数据链路层仍然是标准 CAN波特率通常是 250 kbps。它做的事情是在 CAN 帧的用户数据基础上给 29 位 ID 制定了统一的分配规则给数据字节定义了统一的缩放规则还规定了多帧数据的收发流程。所以抓包之后你看到的仍然是一个一个 CAN 帧只是“怎么解读这些帧”要遵循 J1939 的规范。类比一下CAN 协议相当于邮政系统里的信封格式它保证一封信能寄到但信封上怎么写地址、信纸里怎么排版是邮政总局定的规矩J1939 就是这套规矩。你不需要重新理解 CAN 的仲裁、位填充、CRC 这些机制只需要在拿到一个 CAN 帧之后按 J1939 的规定去拆 ID、按参数定义去解数据。2. 拆解 29 位报文 IDPF、PS、SA 和 PGN 的关系2.1 ID 的位布局先背下来J1939 的 29 位扩展 ID 从高位到低位依次分为这几段位段长度字段名说明bit 28 - 263 bitPriority 优先级0 最高7 最低常见默认值 3 或 6bit 251 bitReserved 保留位一般为 0bit 241 bitData Page 数据页一般为 0扩展时用 DP1bit 23 - 168 bitPF (PDU Format)决定 PDU1 还是 PDU2bit 15 - 88 bitPS (PDU Specific)PDU1 中是目标地址PDU2 中是组扩展bit 7 - 08 bitSA (Source Address)发送节点的源地址实际抓包软件里很多 ID 以十六进制整数显示比如0x0CF00400。取字段的时候不需要去抠二进制位可以直接用位运算can_id 0x0CF00400 pf (can_id 16) 0xFF # 0xF0 ps (can_id 8) 0xFF # 0x04 sa can_id 0xFF # 0x00 priority (can_id 26) 0x7 # 0这里要注意有些接口库返回的 arbitration_id 可能带了标志位稳妥的做法是先can_id 0x1FFFFFFF再解析。2.2 PGN 怎么算SPN 又是什么PGNParameter Group Number是 J1939 里最核心的索引每条报文都有一个 PGN对应一组相关信号。计算方式可以统一记为PGN (PF 8) | PS对于0x0CF00400来说PF 0xF0PS 0x04所以PGN 0xF004换成十进制就是 61444。这就是我们平常说的 EEC1电子发动机控制器 1报文。这里有个细节要区分如果 PF 在 0x00 到 0xEF 之间这种是 PDU1 格式PS 字段表示目标地址报文是点到点通信如果 PF 在 0xF0 到 0xFF 之间这种是 PDU2 格式PS 字段作为组扩展参与 PGN 编号报文是广播。大多数我们常用的车辆状态报文都是 PDU2所以按(PF 8) | PS算 PGN 基本不会错。SPNSuspect Parameter Number则是对“具体参数点”的编号。一个 PGN 里可以包含很多个 SPN比如 EEC1 里既有发动机转速SPN 190也有实际扭矩百分比SPN 513和踏板位置SPN 91。P GN 是“哪一类报文”SPN 是“这一类报文里的哪个信号”两者是包含关系。掌握几个高频 PGN 能省很多翻手册的时间PGN十进制报文名称常见内容0xEB0060160TP.DT多包传输数据包0xEC0060416TP.CM多包传输连接管理0xEE0060928地址声明节点名称与地址0xF00461444EEC1发动机转速、扭矩、踏板0xF00561445EEC2进气温度、燃油压力等0xFECA65226DM1诊断故障码0xFEE365251CCVS车速、刹车状态等0xFEEE65262EBC1制动压力、刹车信号0xFEF165265VH车辆识别信息2.3 优先级和源地址为什么也要看优先级主要影响总线仲裁解析时一般不太管它但如果你做网关转发或者过滤最好保留。源地址 SA 则很有用它表示这条报文是谁发的比如发动机通常是 0x00变速箱可能是 0x03车身控制器可能是 0x21 之类的。同一个 PGN 如果从不同 SA 出来可能代表不同的物理节点做多 ECU 设备的故障定位时会用到。3. 单帧数据解析从 EEC1 和 CCVS 练手3.1 EEC1 的转速与扭矩读取EEC1PGN 61444是 J1939 里最常见的发动机状态报文8 个字节里包含发动机转速、扭矩、踏板位置等信息。拿发动机转速SPN 190举例它的定义是位置数据字节第 4、5 字节按 0 开始计数是 data[4] 和 data[5]分辨率0.125 rpm/bit偏移量0字节序低字节在前也就是说先取原始值raw data[4] | (data[5] 8)再换算成转速rpm raw * 0.125。举个例子假设抓包日志里这条 EEC1 的数据是00 00 00 7D E0 15 00 03raw 0xE0 | (0x15 8) # 0x15E0 5600 rpm 5600 * 0.125 # 700 rpm怠速 700 rpm这个结果就非常合理。如果你按照“高字节在前”去算raw 0xE015 57365换算出来是 7170 rpm显然出了问题。所以 J1939 的多字节参数我习惯先按照低字节在前去解遇到明显不合理的数值再反过来验证。实际扭矩百分比SPN 513的换算要注意偏移是负数分辨率 1%/bit偏移量 -125%。原始值 125 对应 0%原始值 0 对应 -125%。这类带偏移的量在 Excel 或者脚本里很容易漏掉漏掉之后扭矩曲线会整体平移后面做性能分析就全偏了。3.2 CCVS 的车速与刹车状态CCVSPGN 65251是车辆控制与车速报文里面最重要的就是车速SPN 84位置在数据字节第 4、5 字节分辨率 1/256 km/h偏移量 0。假设数据字节为00 00 00 00 00 1E 20 00raw 0x00 | (0x1E 8) # 0x1E00 7680 speed 7680 / 256 # 30 km/h这个原始值其实就是“1/256 km/h”的个数7680 个 1/256 就是 30 km/h。很多解析工具显示 0.0039 之类的小数就是这个原因。CCVS 里还有刹车状态、巡航状态这些 bit 型的 SPN它们在某个字节的某一位上取值只有 0 或 1。解析 bit 型信号时先确定字节索引再确定位序号别把 bit 序号跟字节序号混在一起我见过不少人在这一步因为“从 0 开始还是从 1 开始”产生误判。3.3 搭一张自己的 SPN 解析表刚接触的时候我的做法是建一个表格把项目里遇到的每个 SPN 都登记上PGN、SPN、名称、字节位置、起始位、长度、分辨率、偏移量、单位、字节序。后面解析任何一条日志都先查这个表不依赖记忆。PGNSPN名称字节位长分辨率偏移单位61444190发动机转速4-5160.1250rpm61444513实际扭矩百分比381-125%6525184车速4-5161/2560km/h这张表其实是 J1939 参数手册的“精简本地化版本”。你不需要背下所有 SPN只要把项目中真正要用的十几个信号维护好效率会高非常多。4. 多包传输机制TP.CM / TP.DT 的分包与重组4.1 什么时候数据会超过 8 字节J1939 底层仍然是 CAN 数据帧一帧最多 8 字节但很多参数组的数据量远不止 8 字节比如 DM1 故障码报文、软件识别号、车辆识别信息等都可能要几十甚至几百字节。这时 J1939 使用传输协议Transport Protocol把长报文拆成多个 CAN 帧发送接收端再按顺序组装。传输协议的核心是两个 PGNTP.CMPGN 60416负责连接管理和控制TP.DTPGN 60160负责承载实际数据。抓包时看到一堆0xEB00结尾的 ID通常就是某个多包报文的数据包。4.2 连接管理报文怎么读TP.CM 报文的第一个字节是控制命令最常见的几个值控制字节含义0x20BAM广播公告方式广播给所有节点0x10RTS请求发送用于点对点0x11CTS允许发送0x13EOM消息结束确认0xFFAbort中止传输BAM 方式最简单多用于广播类报文。TP.CM 后面的字段里第 2 字节是总包数第 3 字节是包内有效字节数最后两个字节是被传输的 PGN。对 BAM 来说没有握手确认发送端直接连续把数据包发完接收端只需要按序号把 TP.DT 的数据段拼起来就行。4.3 重组思路与一个可用的脚本框架TP.DT 的每个数据包只有 7 字节有效载荷因为第一个字节是包序号从 1 开始最大 255。接收端需要维护一个缓冲区按序号把数据段填入等所有包到齐后拼出完整字节串。这里给出一个轻量级重组框架class J1939TPReassembly: def __init__(self): self.buffer {} self.pgn None self.total None def handle(self, can_id, data): pf (can_id 16) 0xFF ps (can_id 8) 0xFF pgn (pf 8) | ps if pgn 60416: # TP.CM ctrl data[0] if ctrl 0x20: # BAM self.total data[1] self.pgn data[6] | (data[7] 8) self.buffer {} print(fBAM start: PGN{self.pgn:#x}, packets{self.total}) elif pgn 60160: # TP.DT seq data[0] if self.total and seq self.total: self.buffer[seq] data[1:8] if len(self.buffer) self.total: payload b.join(self.buffer[i] for i in sorted(self.buffer)) print(fReassembled PGN {self.pgn:#x}: {len(payload)} bytes)实际车载总线里除了 BAM还有 RTS/CTS 握手方式用于点对点传输逻辑会多一个状态机接收方收到 RTS 后要回 CTS指定允许发送的包数量发送方再发对应数量的 TP.DT如此反复直到发送完成。做 ECU 刷写或者参数标定的时候会遇到普通报文监控阶段先把 BAM 跑通就够了。重组过程中最常见的异常是丢包可能总线繁忙导致某几个 TP.DT 没被完整捕获也可能是抓包工具本身的缓冲溢出。判断方法很简单看缓冲区里有没有长时间不增长的序号空洞。如果看到 TP.CM 已经宣告了包数TP.DT 却迟迟凑不齐基本上就是丢包了解析程序里要加超时清空逻辑否则旧状态会污染下一条多包报文。5. 三个让我记忆深刻的踩坑现场5.1 字节序高字节在前还是低字节在前第一次解析转速时我随手用大端方式算得到的转速在怠速时显示 7000 多转吓得我以为车辆数据有问题。后来对比仪表盘和个人经验怀疑是字节序弄反了改成data[4] | (data[5] 8)之后数据立刻正常了。这个坑的根源在于J1939 规范里很多参数在文档里用“Motorola 格式”描述有些工具生成的 DBC 文件也会标注 Motorola 字节序导致很多人误以为线缆上传过来的字节就是高字节在前。实际上不少参数在底层传输时是低字节先出现解析工具内部再做转换。所以我的结论是拿到一段日志不要直接套 DBC 里的 Byte Order先用一个你确定会出现的静态值验证比如怠速转速、静止时的车速。如果算出来离谱马上试反过来的字节序。5.2 负值信号补码不能直接当无符号数J1939 里很多信号有负值范围。比如某些扭矩百分比、温度修正量、压力偏移量原始值会落到无符号整数的后半段如果直接乘分辨率和偏移会得到巨大正数。处理方式很简单先看信号是否有符号如果是有符号数在转换成十进制之前做符号扩展。Python 里最直接的办法def to_signed(val, bits): if val (1 (bits - 1)): val - (1 bits) return val有符号之后再乘分辨率和偏移量。这里的教训是解析脚本里统一封装两类函数无符号数和有符号数各走各的路绝不能用同一个转换流程。5.3 同一源地址出现了两个节点有一次做多 ECU 联合调试发现日志里有相同源地址的报文来自不同物理设备导致状态信息互相覆盖。查了很久发现是地址声明PGN 60928环节出了问题某个节点没有正确拿到 0x00 的发动机地址而另一个节点也在用同一个 SA 发数据。J1939 网络里每个节点必须有一个唯一的 SA正常情况下上电时要通过地址声明来确定自己的地址如果地址冲突节点需要进入“不能通信”或“发送地址声明但等待仲裁”的状态。这个坑告诉我们解析数据时不要只看 PGN还得看 SA。同一类报文如果出现了多个不同 SA优先怀疑地址分配问题而不是数据解析问题。6. 工具选择与自动化解析思路6.1 抓包工具和硬件怎么搭解析 J1939首先要有一根能抓 CAN 总线的设备。常用的方案是 USB-CAN 分析仪配合上位机软件或者使用 CANalyzer/CANoe 这类专业的商用车总线分析平台。专业工具可以直接按 J1939 协议解析显示 PGN、SPN、DBC 信号名非常方便但成本高需要授权许可。如果只是做数据监控和简单的信号解析我的建议是用 USB-CAN 设备自带的抓包软件先录原始报文导成.asc或.csv然后用自己的脚本处理。另一个可选方向是开源总线工具支持通用 CAN 数据导入搭配 DBC 文件能实现信号离线解包。重点不在于用什么工具而在于你手里有没有一份正确的 DBC 文件或者一套经过真实数据验证的 SPN 解析表。6.2 用 Python 建一个轻量解析器我习惯用 Python 的can库直接读取总线设备或者读取离线抓包文件。解析时先按 PGN 分类再按 SPN 逐字段转换输出成整洁的表格。import can def parse_pgn(can_id): return ((can_id 16) 0xFF) 8 | ((can_id 8) 0xFF) def parse_speed(pgn, data): raw data[4] | (data[5] 8) return raw / 256.0 bus can.interface.Bus(bustypesocketcan, channelcan0, bitrate250000) for msg in bus: pgn parse_pgn(msg.arbitration_id) if pgn 65251: speed parse_speed(pgn, msg.data) print(fSpeed: {speed:.1f} km/h)这套代码简单但它体现了解析的全部核心逻辑算 PGN、取字节、按倍率换算。真实项目里可以把日志读取、多包重组、DBC 信号映射都做进去形成一个完整的离线解析链。6.3 验证解析结果的三条原则我每次搭好一套解析脚本都会用下面三种方式验证避免把错误结果当成真实数据抓一段车辆静态怠速时的数据转速应该稳定在 600 到 800 rpm车速应该接近 0。对比仪表盘数值。能跟仪表显示对上的解析才是可信的对不上就要怀疑倍率、偏移、字节序或者采样点选择。看数据的连续性。解析完成的物理值曲线应当平滑不应该出现从 0 到 7000 再跳回 0 这种离谱跳变。如果出现跳变先回看字节序和符号处理。我现在拿到一堆陌生报文的第一反应也不再是急着翻完整本标准而是先抓一段现场数据挑一个已知量验证比如把怠速转速解出来。一个值能对上后面一批信号就都顺了一个值对不上就先停下来把所有假设换一遍再继续往下走。J1939 解析是个熟练活多跑几个项目、多积累几张 SPN 表这套流程慢慢就变成肌肉记忆了。
RELATED

相关推荐

Selenium+Pytest+Allure自动化测试环境搭建与报告生成

Selenium+Pytest+Allure自动化测试环境搭建与报告生成

很多测试朋友第一次接触自动化测试时,卡住的地方往往不是测试用例本身,而是“把环境跑起来”这一步。我也见过不少同学照着网上一堆教程敲了半天命令,最后连一个简单登录用例都跑不出报告来。这篇笔记就用我自己实际动手的顺序,完…

📅 2026/10/21 18:57:43
Python爬虫实战:抓取豆瓣Top250并完成数据清洗与可视化分析

Python爬虫实战:抓取豆瓣Top250并完成数据清洗与可视化分析

搞爬虫和数据这块,Top250榜单是个绕不开的经典练手素材。它数据量不大不小,正好够你完整走一遍“请求页面 → 解析内容 → 清洗存储 → 分析可视化”的流程,而且结果天然带有话题性,谁看了都想知道这些高分电影到底分布在哪几年、…

📅 2026/10/21 18:57:43
国产大模型编程能力横评:GLM-5.1与Qwen-3.6首次反超海外标杆

国产大模型编程能力横评:GLM-5.1与Qwen-3.6首次反超海外标杆

过去半年我一直在做一件事:把市面上主流的大模型拉回同一个测试台,用同一批编程任务反复复试。编程能力已经成了大模型最硬核的试金石——代码生成、缺陷修复、工程理解,每一项都比聊天对话更难伪装。这次横评的主角是国产模型的两位代表 GLM…

📅 2026/10/21 18:57:43
MORE NEWS

更多资讯

📰

Java团队用LangChain4j打造医疗智能客服:多轮对话与RAG实战

做医疗智能客服这个项目的时候,我最大的感受是:AI框架的选型真的可以决定一个Java团队的开发节奏。我们内部代号叫"硅谷小智",说白了就是一个面向患者问诊前、问诊后的智能接待机器人,要能回答科室导诊、就诊流程、检查…

📰

多模态大模型训练避坑指南:归一化、激活函数与架构一致性

多模态大模型这几年从论文火到落地,但真正动手做训练和微调的人都知道,它和单模态模型完全是两码事。单模态下好用的很多套路,搬到多模态场景就会出各种奇奇怪怪的问题——loss震荡、收敛慢、某一个模态直接学废。我自己调试了几个开源多模态…

📰

diagram-design:让架构图成为可执行的技术契约

1. “diagram-design”不是画图软件的代名词,而是一套被严重低估的系统化表达方法论 “diagram-design”这个词组在搜索引擎里几乎不返回任何权威定义——它既不是某个知名工具的品牌名,也不属于ISO或IEEE标准术语库。但过去三年里,我在某高校…

📰

远程控制软件怎么选?五款主流远控工具实测对比

作为一个经常要在电脑、手机、平板之间来回切换的人,远程控制软件可以说是我日常工作里的刚需工具。前前后后我把网易UU远程控制、ToDesk、向日葵、TeamViewer、RustDesk这五款主流的专业远控软件都用了个遍,从游戏串流、远程办公到帮家里长辈修电脑&…

📰

系统架构师能力解剖:从截图、做题到目录的实战思维训练

1. 这不是考试指南,而是一份系统架构师实战能力的“解剖图谱” “系统架构设计师”这六个字,在很多人的认知里,还停留在“高级职称考试”“软考高项”“背书刷题拿证”的层面。但我在过去十年带过的几十个模拟项目X中反复验证过一个事实&…

📰

串口不够用?STM32L151+WK2124 SPI转4路串口驱动全记录

串口不够用这个事,做嵌入式的人迟早会撞上。最近我在维护一台低功耗仪表,主控就是标题里的STM32L151,板子上要接的设备却不少:两个传感器、一个显示屏、一个调试口,后端还想留一路备用。这颗芯片原生只带两个UART&…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬