简介:面向工业自动化现场调试与协议分析人员的Profibus DPV1报文学习材料,聚焦主站与从站之间非周期通信的原始帧及逐条解析。内容按功能组织为一类主站非周期读、二类主站非周期读、一类主站非周期写、二类主站非周期写和子站报警机制等部分,涵盖SRD_LOW、Initiate、Read、Write、Abort、ShortAcknowledge等交互,并标注SAP、Slot、Index、Len、Send_Timeout、Max_Len_Data_Unit、Features_Supported等关键字段,便于对照原始十六进制数据理解请求与响应过程,快速定位读写异常和报警上报流程中的问题。资源包共1个docx文档,大小约33KB,轻量便于随查随用。已有1911人学习,适合需要梳理DPV1通信细节、优化网络参数的技术人员作为报文对照与排错参考。
1. 一串 68 开头的十六进制:DPV1 到底在忙什么
想象两个人在一条很吵的流水线边说话,没法长篇大论,只能喊短句:一个人喊「3 号柜子第 2 个抽屉里是什么」,另一个回「是 5」,然后各自继续干活。Profibus DPV1 的报文就是这么一种对话,只不过说话的是主站和从站两块电子设备,用的是十六进制字节。
DPV1 是 Profibus 在原来那套「地址固定映射、周期刷新」的数据交换之外,额外开出来的一条通道:主站可以按需去读或写从站里任意一条参数,从站也能主动把「我出事了」推上来。抓包工具里那一行68 09 09 68 87 81 7C 33 33 5E 00 02 01 4B 16,看着像天书,其实是几层话叠在一起:谁发给谁、要办什么事、办的是哪一条数据、办成了没有。搞懂它,设备读不出参数、报警反复弹、调试软件连不上这类问题,就不用全靠猜。
2. 报文的骨架:68 和 16 之间到底夹了什么
2.1 一帧的七段:起始符、长度、地址、功能码、数据、校验、结束符
还是拿开头那一帧拆:68 09 09 68 87 81 7C 33 33 5E 00 02 01 4B 16。它一共 15 个字节,但每一段的位置是固定的,像一张印好格子的表格,照着格子填就不会乱。
| 字节位置 | 原始字节 | 字段名 | 这一帧里的含义 |
|---|---|---|---|
| 1 | 68 | 起始定界符 | 可变长帧的开头标记 |
| 2 | 09 | 长度 LE | 从目的地址到数据单元结束的字节数 |
| 3 | 09 | 长度重复 | 与上一字节相同,用来交叉校验 |
| 4 | 68 | 起始定界符 | 再出现一次,把长度夹在中间 |
| 5 | 87 | 目的地址 DA | 低 7 位是站号,这里指向 7 号站 |
| 6 | 81 | 源地址 SA | 低 7 位是站号,这里来自 1 号站 |
| 7 | 7C | 功能码 FC | 这一帧要办哪类事 |
| 8~9 | 33 33 | 目的 / 源 SAP | 服务接入点,等于「找哪个窗口办事」 |
| 10~13 | 5E 00 02 01 | 数据单元 | DPV1 的业务内容:读什么、读多长 |
| 14 | 4B | 校验和 FCS | 前面若干字节求和取低 8 位 |
| 15 | 16 | 结束定界符 | 帧到此为止 |
值得多看一眼的是长度字段出现了两次。这不是冗余设计失误,而是一种很便宜的保险:接收方拿到帧以后,把第 2 字节和第 3 字节比一下,不一致直接丢,不用等后面全部收完再判断。工业现场电磁环境复杂,这种一眼就能看出坏帧的做法,比事后算半天校验要实用得多。
地址字节也有讲究。87不是「第 135 号站」,而是低 7 位 0x07,也就是 7 号站;最高位另有用途,表示地址是不是扩展格式。所以看到0x80以上的地址字节,先把它和0x7F做一次按位与,才是真正的站号。这一步在手工看报文时最容易漏,漏了就会觉得「现场根本没有 135 号站,报文怎么来的」。
2.2 SAP:同一个从站为什么会出现 33、32、31 三个数字
SAP 全称服务接入点,通俗地说,它是一台设备上的「业务窗口编号」。一栋楼只有一个门牌号,但楼里可以开很多窗口:1 号窗口办周期数据交换,33 号窗口办某类非周期读写,31 号窗口办另一类。站地址负责找到这栋楼,SAP 负责找到楼里哪个窗口。
原始资料里,SAP 33、32、31、0x2F、0x30反复出现,而且总是成对出现:33 33、32 31、32 30、32 2F。前一个是目的 SAP,后一个是源 SAP,两个方向各写各的。这不是笔误,而是 Profibus 的规矩:一次对话的两端各自在对方的某一个窗口上登记,源和目的可以是不同的编号。
打个比方,主站给从站打电话,拨的是从站的 33 号分机;从站回话时,拨的是主站这边的 33 号分机。两边分机号碰巧一样,但含义各算各的。理解了这一层,再看报文 4 里的32 ->31、报文 20 里的32 ->2F,就不会误以为「SAP 变了说明从站换了」。
2.3 功能码里的区别:SRD、DL、DataExchange 不是一回事
第 7 个字节的功能码,决定了这一帧是「喊完就走」还是「喊完必须等回话」。原始资料里出现的几类,作用差别很大:
SRD_LOW:发送并请求数据,低优先级通道。一帧里既带了要发的内容,也预约了对方的回答,效率高,非周期读写基本都走它。SRD_HIGH:同样的发送并请求,但走另一条通道。报文 28、29 的周期数据交换和报文 30、31 的诊断读取用的是它。DL Read、DL Write:链路层的数据读写,报文 3、11、17、25 里的响应属于这一类,负责把对方的数据搬回来。Slav_Diag:专门去读从站的诊断信息。Alarm_Ack:专门去确认一条报警。DataExchange:最经典的周期数据交换,主站和从站按约定好的固定长度互相刷数据。
这里有个容易忽略的细节:同一件事往往要几种功能码配合。发请求用SRD_LOW,对方先回一个E5短确认,主站再发一次DPV1 Poll去取货,最后才拿到DL Read的响应。四帧才凑齐一次读操作,看着啰嗦,但每一帧的职责是清楚的。
2.4 校验和与结束符:一个字节算错,整帧作废
校验和 FCS 的计算方式不复杂:把从目的地址开始、到数据单元结束的所有字节相加,取低 8 位。以上面那帧为例,参与计算的字节是87 81 7C 33 33 5E 00 02 01,加起来取低 8 位得到4B,和帧里的一致,这帧才算完整。接收方算出来不一样,直接丢弃,不会去猜「大概是哪个字节错了」。
用一小段代码把这个骨架拆开,比肉眼数位置可靠得多:
def parse_sd2(frame: bytes) -> dict: """解析 Profibus 的 SD2 可变长帧骨架,不做业务层解释""" if frame[0] != 0x68 or frame[3] != 0x68: raise ValueError("起始定界符不符,这可能不是 SD2 帧") le = frame[1] # 长度:从 DA 到数据单元结束 le_r = frame[2] # 长度重复位,必须与 le 相等 if le != le_r: raise ValueError("长度重复位不一致,帧在传输中受损") da = frame[4] & 0x7F # 低 7 位才是站号,最高位是地址扩展标志 sa = frame[5] & 0x7F fc = frame[6] # 功能码:SRD_LOW / SRD_HIGH / DL 等 body = frame[7:7 + le - 3] # 去掉 DA/SA/FC 后的数据单元 fcs = frame[7 + le - 3] # 校验和 ed = frame[8 + le - 3] # 结束定界符,正常应为 0x16 return {"le": le, "da": da, "sa": sa, "fc": fc, "body": body.hex(), "fcs": fcs, "ed": ed} print(parse_sd2(bytes.fromhex("6809096887817C33335E0002014B16")))代码里最关键的一行是body = frame[7:7 + le - 3]。长度字段le统计的是「目的地址 + 源地址 + 功能码 + 数据单元」的总字节数,所以要减掉 3 才是数据单元的长度;换成别的帧,这个减 3 的规则不变。如果你手头抓到的帧解析出来ed不是0x16,多半是长度算错了,或者这压根不是 SD2 帧,而是固定长度的 SD1、SD3。
3. 非周期读写:Slot 与 Index 才是 DPV1 的真正入口
3.1 0x5E 与 0x5F:读一条参数和写一条参数长什么样
周期数据交换只能搬那些组态时约定死的字节,多一个都不行。DPV1 真正多出来的能力,是能按「槽位 + 索引」去点名访问任意一条参数。这个点名动作在数据单元里的第一个字节标出来:0x5E是读,0x5F是写。
报文 8 的数据单元是5E 00 02 01,四个字节拆开看:
| 位置 | 值 | 含义 |
|---|---|---|
| 第 1 字节 | 5E | 读请求 |
| 第 2 字节 | 00 | Slot 槽号 0 |
| 第 3 字节 | 02 | Index 索引 2 |
| 第 4 字节 | 01 | 要读 1 个字节 |
过几帧之后,报文 11 的数据单元是5E 00 02 01 05,前面四字节一模一样,末尾多了一个05。这就是答案:槽 0、索引 2 那条参数,值等于 0x05。整个过程干净利落,没有任何猜测空间。
写操作同理。报文 14 的数据单元是5F 00 02 01 08,5F表示写,后面跟着同样的槽 0、索引 2、长度 1,最后那个08是要写进去的值。报文 17 的写响应是5F 00 02 01,没有数据字节,只回「写完了」。
把 Slot 和 Index 想成一个文件柜就行:Slot 是第几排柜子,对应设备里的某个模块;Index 是这排柜子的第几个抽屉,对应模块里的某条参数。柜子和抽屉都是组态时定好的编号,抓包时照着编号去查对应的参数表,就能把十六进制翻译成人话。
3.2 Initiate 建链:0x57 握手之前,双方要先谈妥几件事
非周期读写不是随时想读就能读的。在正式读写之前,两端要先做一次 Initiate,功能码是0x57。原始资料里报文 4、报文 18 都是这个动作,内容一字不差,说明这是两条不同的连接各自建了一次链。
这次握手谈的东西不少,抓包工具已经替我们翻译成了可读字段:
| 字段 | 建链请求里的值 | 说明 |
|---|---|---|
| Send_Timeout | 200 ms | 我这边的等待上限 |
| Features_Supported1 | 0x01,即 DPV1-RW | 我支持非周期读写 |
| Max_Len_Data_Unit | 0xF0,即 240 字节 | 一帧数据单元最多塞这么大 |
| Profile_Ident_Number | 0x0000 | 设备行规标识 |
| s_type / s_len | 0x00 / 0x02 | 源地址的描述 |
| d_type / d_len | 0x00 / 0x02 | 目的地址的描述 |
建链响应(报文 7、报文 21)里,Max_Len_Data_Unit回的是0xF0,也就是 240 字节。这个数字很重要:它决定了后面一次读写最多能搬多少数据。如果你要读的参数有 300 字节,那就不是一次读得完的,得拆成两段,或者改用别的服务。
Send_Timeout 也值得留意。请求里写 200 ms,另一条连接的管理响应里写 400 ms,两边各报各的上限,取小的那个当实际约束。这意味着如果从站 200 ms 内没把数据准备好,主站就会认为这次对话失败,可能在报文里留下 Abort。调试时看到「读数据偶尔超时」,先别急着怀疑线路,回头看看这个超时值和从站的实际响应时间是否匹配。
3.3 Abort 与 E5:一个负责喊停,一个负责说「收到」
报文 12 和 26 都是 Abort 请求,数据单元58 00 20。58是 Abort 的功能码,后面两个字节是子网和原因码,0x20对应 USER/FDL,意思是用户层或链路层主动中止。这类帧一般出现在连接用完、或者上层决定放弃这次对话的时候。
Abort 本身没什么难懂的,难懂的是它旁边的E5。
E5只有一个字节,叫短应答。它不携带任何数据,含义就是「你刚才那帧我收到了」。报文 9、15、23、27、33 都是它。很多初学者第一次看到E5会以为是错误码,因为在别的地方 E5 常常代表异常。在 Profibus 的链路层里,它恰恰是最正常的一种回应,表示帧已经被正确接收,只是对方暂时没有内容要回。
判断方法很简单:看它有没有后续。E5之后如果主站又发了一次DPV1 Poll,说明对话还在继续,只是分了两步走;如果E5之后直接来了 Abort,那才是真的出问题了。
3.4 一次完整的读操作,为什么需要四帧才闭环
把报文 8 到报文 11 连起来看,一次读参数总共走了四步:
- 报文 8:主站发读请求,
SRD_LOW,数据单元5E 00 02 01。 - 报文 9:从站回
E5,意思是「收到了」。 - 报文 10:主站发
DPV1 Poll,问一句「回话准备好了吗」。 - 报文 11:从站回
DL Read响应,数据单元5E 00 02 01 05,把值带回来。
多出来的第 2、3 步,是 Profibus 的「先确认再取货」机制。从站收到请求后,可能要去内部读一块存储器、算一个值,这需要时间。与其让主站干等,不如先回一个短确认,让主站知道请求没丢;等从站准备好了,主站再来取一次。这就像寄快递:先收到「已揽收」的短信,过一阵再去取件,中间不用一直守在门口。
理解了这四帧的分工,看到「有请求没有响应」的时候,就能判断卡在哪一步:如果连E5都没有,问题在链路层,可能是站号、SAP 或线路;如果有了E5但一直等不到DL Read,问题多半在从站内部,参数不存在、长度不对、或者从站正忙。
4. 一类主站与二类主站:同一件事为什么有两套编号
4.1 一类主站:生产线上那个不停循环的控制器
一类主站是生产运行时一直在场的那台控制器。它的大部分时间花在周期数据交换上,按组态好的固定长度,一轮一轮地和每个从站交换 I/O 数据。除此之外,它还负责两件事:一是按需读写从站的参数,二是接收并确认从站发来的报警。
原始资料里报文 0~3 和 14~17 就是它的非周期读写。这两组报文的共同点是短:读只要四帧,写只要四帧,中间没有多余的建链动作,因为它和从站之间的连接早就建立了,随用随走。这也解释了一个常见困惑:为什么现场抓包时,一类主站的读写报文看起来「没头没尾」,既看不到 Initiate,也看不到 Abort。连接是在更早的时候建好的,抓包开始得晚了而已。
4.2 二类主站:临时接上来的那台调试电脑
二类主站通常是工程站、组态工具或者调试电脑,临时接到总线上的。它不属于生产运行的一部分,来了干活,干完就走。
报文 4~13 和 18~27 完整地展示了这个过程:先 Initiate 建链,然后反复读写,最后 Abort 断链。报文 12 和 26 那两条 Abort 请求,正是「活干完了,把连接还回去」的动作。这一点很关键:二类主站如果不断链,就会一直占着从站的一个连接资源,而连接资源是有限的,占满了别的工具就接不进来。
所以看这两组报文时,重点不在读了什么写了什么,而在建链和断链的节奏。建链参数里协商的超时、缓冲区大小,断链的原因码,都是排查「调试软件连不上」的第一手材料。
4.3 报文里出现的几个 SAP,各自代表什么角色
原始资料这份抓包里,SAP 的数字并不是随便出现的,它们和连接类型绑在一起:
| 出现位置 | SAP 值 | 在报文里的角色 |
|---|---|---|
| 报文 0~3、8~11、14~17 | 33(0x21) | 一类的非周期读写连接 |
| 报文 4~7、18~21 建链 | 32 到 31 | 二类建链时的请求方向 |
| 报文 20、22、24、26 | 32 到 0x2F | 二类在另一个服务窗口上读写 |
| 报文 5、19 的管理响应 | 0x30、0x2F | 从站回告自己这一侧的服务窗口号 |
| 报文 28、29 | nil | 周期数据交换,不走 SAP |
看这张表能得出一个实用结论:SAP 变了,通常意味着换了一条连接或者换了一个服务窗口,而不是设备坏了。调试时如果发现读写报文突然从 SAP 33 跳到 32 到 0x2F,第一反应应该是「是不是另一台主站接进来了」,而不是「从站地址被改了」。
4.4 0~35 这三十多帧,为什么按四段分开编号
原始资料把 36 帧按用途切成几段:0~3 一类非周期读,4~13 二类非周期读,14~17 一类非周期写,18~27 二类非周期写,28~35 报警机制。这种切法的价值在于对照。
同样是读一条槽 0、索引 2 的参数,一类的报文 8 直接发请求,二类的报文 8 之前有建链、之后有断链。把两段并排看,就能一眼看出「连接已经存在」和「连接现场建立」的差别在哪几个帧上。写操作也一样,一类的报文 14 写完就结束,二类的报文 22 写完还有 Abort 收尾。
如果这 36 帧是混在一起按时间顺序排的,这种对照关系就被打散了,看十遍也看不出规律。分组编号看着像是整理者的个人习惯,实际上是给读者省了重新归类的时间。
5. 容易搞混的几组概念:区别到底在哪
5.1 SRD_LOW 与 SRD_HIGH,不是速度高低
易混点:名字里带 LOW 和 HIGH,很多人第一反应是「低速通道和高速通道」,甚至以为 LOW 是降级模式。
为什么混:这两个词在别的技术领域确实常用来形容速率或优先级,而 Profibus 的报文里又看不到显式的速率字段,容易顺着字面联想。
怎么区分:它们描述的是服务原语的通道归属,不是波特率。总线速率对两者是一样的。真正的差别在于用途和排队位置:报文 8、14、22 这些非周期读写走SRD_LOW,报文 28、29 的周期数据交换和报文 30、31 的诊断读取走SRD_HIGH。周期数据是生产运行的生命线,必须优先;参数读写晚几个毫秒没关系。看到SRD_LOW不要以为链路质量差,它只是排在后面。
5.2 短应答 E5 与 DL 应答帧,都叫应答但完全不是一层
易混点:两者都出现在「从站回复主站」的位置上,抓包列表里都标着 Res,看着像同一类东西。
为什么混:抓包工具的列名统一写「响应」,把链路层的确认和应用层的结果混在一列里显示了。
怎么区分:看长度和内容。E5只有一个字节,没有地址、没有 SAP、没有数据单元,它是链路层说「收到了」;DL Read响应是一整帧,有完整的 68 开头、地址、SAP 和带数据的数据单元,它是应用层说「这是你要的东西」。再直白一点:E5之后对话还没完,DL Read响应之后这次读就结束了。
5.3 诊断报文与报警报文,一个像体检报告,一个像急诊呼叫
易混点:两者都带槽号、都带报警类型字段,报文 31 的诊断响应里甚至直接出现了Alarm_Type: 0x01和Specifier: 0x11。
为什么混:DPV1 把报警信息塞进了诊断数据区,同一帧里既有设备状态,又有事件描述,字段还共用。
怎么区分:看是谁发起、要不要确认。诊断是主站主动去读的,报文 30 发Slav_Diag请求,报文 31 拿回一份当前状态的快照,读十次得到十次结果,不改状态。报警是从站主动推的,主站收到后必须回一条Alarm_Ack(报文 32)才算处理完,不回它就一直挂着。快照可以反复看,事件必须回应,这是两者最本质的差别。
5.4 SAP 与 Slot/Index,门牌号和抽屉号别弄反
易混点:都是小整数,都在报文里挨得很近,初学时容易觉得「反正都是编号」。
为什么混:Profibus 的报文把链路层寻址和应用层寻址压缩在了同一帧里,中间只隔一个功能码。
怎么区分:SAP 在地址字节之后、功能码之后,负责「找哪个窗口办事」,属于链路层;Slot 和 Index 在0x5E、0x5F之后,负责「取哪个抽屉里的东西」,属于应用层。报文 8 里,33 33是窗口,00 02是抽屉,位置差得很远,看多了自然分得清。记一句就够:SAP 决定能不能通上话,Slot/Index 决定能不能拿到对的数据。通不上话和拿错数据是两类完全不同的故障。
5.5 报警类型与确认方式,Add_Ack 这一位决定要不要多走一步
易混点:以为所有报警都是回一条Alarm_Ack就完事。
为什么混:报文 32 的Alarm_Ack看起来干净利落,一帧发出去、一个E5回来就结束了,很容易以为什么报警都这样。
怎么区分:看附加信息里的Add_Ack位和Seq_Nr。Add_Ack置位时,从站在告诉主站:这条报警除了标准的确认,还需要一次额外的用户确认,比如再发一次写服务。Seq_Nr则是这条报警的唯一编号,用来区分「同一条报警重复上报」和「又来了一条新报警」。报文 31 里Add_Ack: NO、Seq_Nr: 2,说明这次不需要额外确认,但编号是 2,前面还有过一条。忽略这两个字段,就会出现「报警确认了还在弹」的迷惑现场。
6. 什么时候值得翻这套报文:几种该用和不该用的场景
报文本身不难,难的是判断什么时候该去翻它。我自己的经验是:只要现象里出现「周期数据正常,但某个参数读不出来」,就值得抓一次。周期数据正常说明地址、线路、供电都没问题,剩下的问题一定出在非周期这条通道上,范围一下就缩小了。
下面这几种症状,对应该看哪几帧,可以直接照着对:
| 现场症状 | 重点看的帧 | 想确认什么 |
|---|---|---|
| 调试软件连不上从站 | 建链请求和响应(报文 4~7) | 是没回、回了拒绝、还是超时值不匹配 |
| 参数读出来是默认值 | 读请求和读响应(报文 8~11) | Slot/Index 是否写错,长度是否对上 |
| 写参数提示成功但没生效 | 写请求、写响应、后续读回(报文 14~17) | 写完有没有再读一次验证 |
| 报警反复弹,确认了还弹 | 诊断响应与报警确认(报文 31~35) | Add_Ack是否为 YES,Seq_Nr是否在变 |
| 通信偶尔中断 | Abort 请求(报文 12、26) | 原因码指向用户层还是链路层 |
反过来说,有几种情况不值得在这上面花时间。只是想确认 I/O 通不通,看周期数据交换就够了,报文 28、29 那两帧就是全部答案,没必要去翻非周期部分。手上没有抓包条件、只能靠现场描述猜的时候,也不要硬猜字节,先想办法把报文抓下来,哪怕只抓到十几帧,也比对着现象推演强。还有一种情况是设备本身有厂商提供的诊断工具,能直接显示参数值和状态字,那就先用工具,报文留到工具也说不清的时候再上。
有一次我在一个测试台上折腾了大半天,现象是某条参数怎么读都是 0,换了一台从站还是 0。当时我一直在怀疑从站的存储区,甚至准备去翻固件手册。后来把报文抓下来,只看了三帧就发现问题:读请求里的 Index 写成了0x03,而我一直在查的参数表上写的是0x02。整条链路、整个从站、所有配置都是对的,错的是我自己填的那个数字。从那以后我每次遇到「读不到」,都强制走一遍三步:先看 68 和长度对不对,再看 SAP 是不是这条连接,最后才去看0x5E后面那三个字节到底填了什么。前两步花不到一分钟,却能挡掉大部分方向性错误。
这套 36 帧的报文范例,从建链、读、写、Abort 一直排到报警确认和诊断,正好把这套三步检查法要看的样本都覆盖了,按编号顺序翻一遍,比零散看十篇帖子管用。需要的话可以去下载页取一份慢慢对。希望帮到你。
本文还有配套的精品资源,点击获取