4、用电信息采集协议(1):DL/T 645-2007协议帧结构、控制码与数据标识
各位同学,今天我们聊聊用电信息采集里最基础、也最绕不开的一个东西——DL/T 645-2007协议。
说实话,我刚入行那会儿,第一次看到这个协议的名字,还以为是哪个标准编号。后来真正上手做TTU(配变终端)的采集程序,才发现这玩意儿就是咱们跟电表打交道的“通用语言”。你想想看,一个台区下面几十上百块表,要是每家的通信格式都不一样,那TTU得累死。
所以,国网出了这个标准。2007版是目前最主流的版本。今天我就带大家把它的帧结构、控制码和数据标识这三个核心概念彻底搞明白。
4.1 帧结构:数据怎么“打包”发送?
协议说白了就是约定。约定好数据怎么开头、怎么结尾、中间放什么。DL/T 645-2007的帧结构,我习惯把它想象成一个“信封”。
一个完整的报文帧,长这样:
起始符 | 地址域 | 起始符 | 控制码 | 数据长度 | 数据域 | 校验和 | 结束符
68H | A0...A5 | 68H | C | L | DATA | CS | 16H
咱们拆开来看:
- 起始符(68H):固定值0x68。告诉接收方“嘿,我要开始发数据了”。开头和中间各有一个,这是协议的特点。
- 地址域(A0-A5):6个字节。这是电表的“身份证号”。我遇到过很多新手搞不清地址顺序——注意了,低字节在前。比如表号是123456789012,在报文里是倒着放的。
- 控制码(C):1个字节。这是今天的主角之一,后面细讲。
- 数据长度(L):1个字节。表示后面数据域有多少个字节。注意,这个长度不包括校验和和结束符。
- 数据域(DATA):变长。具体内容由控制码和数据标识决定。
- 校验和(CS):1个字节。从起始符到数据域最后一个字节,所有字节相加,取低8位。嗯,这个我吃过亏——有一次校验和算错了,调试了一下午才发现是加法溢出了没处理。
- 结束符(16H):固定值0x16。表示“我说完了”。
重点记忆: 一个完整的645帧,最少有12个字节(地址域全0的情况),最多呢?数据域最长255字节,所以帧最长也就12+255=267字节。实际项目中,抄读数据一般也就几十个字节。
4.2 控制码:告诉电表“你想干嘛”
控制码是帧里最关键的字节之一。它决定了这次通信是读还是写,是主站发还是从站回。
控制码的格式是这样的:
| 位 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
|---|---|---|---|---|---|---|---|---|
| 含义 | 传输方向 | 从站异常 | 后续帧 | 功能码 | ||||
咱们挑几个最常用的说说:
- D7位(传输方向):0表示主站→从站(咱们发命令给电表),1表示从站→主站(电表回复咱们)。
- D6位(从站异常):正常回复时是0。如果电表发现命令有问题,比如数据标识不存在,这个位会置1。我调试时经常看这个位——如果收到回复D6=1,那基本就是命令格式错了。
- D5位(后续帧):0表示没有后续帧,1表示还有后续数据。这个在抄读大量数据时有用。
- 功能码(D0-D4):具体操作类型。常用的有:
- 0x01:读数据。最常用,比如抄表读数。
- 0x04:写数据。比如设置电表参数。
- 0x0A:广播校时。这个我提醒一下——广播校时不需要地址域匹配,所有电表都会响应。
举个例子:主站发一个读数据的命令,控制码就是0x01(D7=0,D6=0,D5=0,功能码=00001)。电表正常回复时,控制码是0x81(D7=1,D6=0,D5=0,功能码=00001)。
我的小技巧: 记控制码不用死记。你只要记住“主站发是0x01,电表回是0x81”这个规律。其他功能码同理,主站发就是0x0X,电表回就是0x8X。X是功能码的低4位。
4.3 数据标识:找到电表里的“抽屉”
控制码告诉电表“我要读数据”,但读哪个数据呢?这就需要数据标识(DI)了。
数据标识由4个字节组成:DI3、DI2、DI1、DI0。每个字节代表不同的分类层级。我习惯把它想象成一个“文件路径”:
- DI0:最细的粒度,比如具体哪个费率、哪个时区。
- DI1:数据类型,比如电量、电压、电流。
- DI2:数据属性,比如正向有功、反向无功。
- DI3:数据块编号,一般固定为0x00或0x01。
举个例子,咱们最常抄的正向有功总电能,数据标识是:
DI3 = 0x00
DI2 = 0x00
DI1 = 0x01
DI0 = 0x00
组合起来就是00 00 01 00。注意,在报文里也是低字节在前,所以实际发送顺序是:00 01 00 00。
再比如A相电压:
DI3 = 0x00
DI2 = 0x02
DI1 = 0x01
DI0 = 0x01
发送顺序:01 01 02 00。
我曾经踩过的坑: 数据标识的字节顺序特别容易搞反。有一次我写抄读程序,明明数据标识查对了,但电表就是不回数据。后来用串口抓包一看,原来我把DI0和DI3的顺序写反了。记住:报文里永远是DI0在前,DI3在后。
4.4 实战:拼一个完整的读数据帧
光说不练假把式。咱们现在拼一个完整的读数据帧,抄读正向有功总电能。
假设电表地址是123456789012(12位十进制数)。
第一步:地址域
地址是12位BCD码,每两位占一个字节。所以:
- 12 → 0x12
- 34 → 0x34
- 56 → 0x56
- 78 → 0x78
- 90 → 0x90
- 12 → 0x12
注意低字节在前,所以地址域是:12 34 56 78 90 12(实际上在报文里是12 34 56 78 90 12,因为BCD码本身已经按字节排列了)。
第二步:控制码
读数据,主站发,所以是0x01。
第三步:数据长度
数据域只有数据标识4个字节,所以L=0x04。
第四步:数据域
正向有功总电能的数据标识是00 00 01 00,低字节在前发送:00 01 00 00。
第五步:校验和
从起始符到数据域最后一个字节,全部相加:68 + 12 + 34 + 56 + 78 + 90 + 12 + 68 + 01 + 04 + 00 + 01 + 00 + 00 = ?
我算一下:68+12=7A,7A+34=AE,AE+56=104,104+78=17C,17C+90=20C,20C+12=21E,21E+68=286,286+01=287,287+04=28B,28B+00=28B,28B+01=28C,28C+00=28C,28C+00=28C。取低8位:0x8C。
第六步:结束符
0x16。
所以完整的报文是:
68 12 34 56 78 90 12 68 01 04 00 01 00 00 8C 16
验证一下: 把这个报文通过串口发给电表,如果电表正常,会回复一个类似结构的帧,控制码变成0x81,数据域里会带上电能量值。嗯,那种收到正确回复的感觉,比喝咖啡还提神。
4.5 避坑指南
最后,分享几个我这些年积累的经验:
- 地址域别搞错:电表地址是12位BCD码,不是16进制数。比如地址是123456789012,在报文里就是12 34 56 78 90 12,不是0x12 0x34 ... 这个我见过太多人搞混了。
- 校验和一定要算对:包括起始符(第一个68H),但不包括结束符。我习惯写个小函数自动算,手算太容易出错了。
- 数据标识查标准:DL/T 645-2007的附录里有完整的数据标识表。别自己猜,我刚开始就自己猜了一个电压的数据标识,结果电表根本不认。
- 注意帧间隔:发送完一帧后,至少要等33个字节的时间才能发下一帧。这是协议规定的,太快了电表反应不过来。
好了,这一章的内容就到这儿。帧结构、控制码、数据标识,这三个东西搞明白了,DL/T 645-2007协议你就掌握了80%。下一章我们接着聊具体的通信流程和异常处理,到时候你会看到这些知识点怎么串起来用。