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 控制码:告诉电表“你想干嘛”

控制码是帧里最关键的字节之一。它决定了这次通信是读还是写,是主站发还是从站回。

控制码的格式是这样的:

D7D6D5D4D3D2D1D0
含义传输方向从站异常后续帧功能码

咱们挑几个最常用的说说:

  • 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%。下一章我们接着聊具体的通信流程和异常处理,到时候你会看到这些知识点怎么串起来用。