DLT645-2007 协议快速入门

news/2024/10/15 11:06:55

@

目录
  • DLT645-2007 协议快速入门
    • 1. 什么是DLT645-2007 协议
    • 2. 帧格式
      • 2.1 帧起始符
      • 2.2 地址域
      • 2.3 控制码
      • 3.4 数据长度
      • 3.5 数据域
      • 2.6 校验码 CS
      • 2.7 结束符
      • 2.8 传输事项
    • 3. 报文解析
    • 4. 代码实例
    • 5. 报文解析工具

DLT645-2007 协议快速入门

1. 什么是DLT645-2007 协议

DLT645目前主要使用的有两个版本,DLT645-1997 和 DLT645-2007。DLT645协议是一种 问答式(主从式)通信规约 。在这种通信模式下,通常存在一个主站和一个或多个从站。主站负责发起通信请求,从站则根据主站的请求提供相应的响应。

问答式规约的通信过程通常包括以下几个步骤:

  • 主站发起请求:主站发送一个请求报文给特定的从站,请求报文中包含了需要从站执行的操作,如读取点能量、读实时参数等
  • 从站接收请求:从站接收到主站的请求后,根据请求的内容进行处理。
  • 从站准备响应:从站根据请求的内容准备相应的数据,并构造一个响应报文。
  • 从站发送响应:从站将构造好的响应报文发送回主站。
  • 主站接收响应:主站接收到从站的响应后,根据响应内容进行响应处理。

DLT645-2007 是中国电力行业标准,全称为《多功能电能表通信协议》,主要用于电力系统中电能表的通信。这个标准定义了电能表与数据终端设备之间进行数据交换时的物理层、链路层以及应用层的通信协议。DLT645-2007协议采用主-从结构的半双工通信模式,硬件接口通常使用 RS-485.

2. 帧格式

DLT645-2007协议为主-从结构的半双工通信模式。手持单元或其他数据终端为主站,多功能电能表为从站。每个多功能电能表均有各自的地址编码通信链路的建立与接触均由主站发出的信息帧来控制。每帧由起始符、从站地址域、控制码、数据域长度、数据域、帧信息纵向校验码以及帧结束符7个域组成。每部分由若干十六进制码组成。

image-20241015082840566

2.1 帧起始符

DLT645协议的数据帧每帧的开始都固定为 0x68,作为数据的起始符方便接收方做数据解析。

2.2 地址域

地址域为上图的 A0-A5,由6个字节构成。地址域是用来标识电表地址,低位在前,高位在后;在485总线上可能挂多个645设备,要找到指定的设备,必须要根据设备的地址查找。每台设备出厂会有自己的地址,也可以修改设备的通信地址。如下图,设备地址就是 220514030093,在传输时由于低位在前,高位在后,所以实际传输时地址为 930003140522.

1728952772456

需注意:

  • 通信地址 999999999999H 为广播地址,只针对特殊命令有效,如广播校时、广播冻结等。广播命令不要求从站应答
  • 地址域支持缩位寻址,即从若干低位起,剩余高位补AAH作为通配符进行读表操作,从站应答帧的地址域返回实际通信地址
  • 地址域传输时字节低字节在前,高字节在后

2.3 控制码

控制码长度为1个字节,控制码需转成8位的二进制码来解析命令,如0x11,对应的8位二进制码就是 00010001,对应下图则是表示

  • D7(0) :主站发出的命令帧

  • D6(0):总站正确应答

  • D5(0):无后续数据帧

  • D4-D0(10001):读数据

1728953628981

1728953323638

3.4 数据长度

1个字节,表示数据域的字节数。读数据时 L≤200,写数据时L≤50,L--=0表示无数据域

3.5 数据域

数据域包括数据标识、密码、、操作者代码、数据、帧序号等,其结构随控制码的功能而改变。传输时按字节进行加33H处理,接收方按字节进行减33H处理

*数据标识

数据标识编码用四个字节区分不同数据项,四字节分别用DI3、DI2、DI1和DI0代表,每字节采用十六进制编码。数据类型分为七类:电能量、最大需量及发生时间、变量、时间记录、参变量、冻结量、负荷记录

1728961333399

  • DI3

    DI3标识符 对应数据类型
    00 电能量
    01 最大需量及发生时间
    02 变量数据 (遥测等)
    03 事件记录
    04 参变量数据
    05 冻结量
    06 负荷记录

举例

发送端
需要发送            0x02 0x01 0x01 0x00
对应数据标识         DI3  DI2  DI1  DI0
需要发送的数据域     0x00 0x01 0x01 0x02  (发送时,数据标识低位在前,高位在后)
对应数据标识         DI0  DI1  DI2  DI3   (发送时,数据标识低位在前,高位在后)
实际发送数据域:     0x33 0x34 0x34 0x35  (发送端数据域中的数据需作+33H的操作)接收端返回数据: 0x33 0x34 0x34 0x35 0x73 0x55 0x76 0x55 0x78 0x55
实际返回的数据: 0x00 0x01 0x01 0x02 0x40 0x22 0x43 0x22 0x45 0x22 (高位在后,接收端数据域中的数据需作-33H的操作)
拼接好高低位的数据标识  0x02 0x01 0x01 0x00
拼接好高低位的数据(假设数据格式为XXX.X)  ,则返回数数据为
224.5(2245,格式转为XXX.X) 
224.3(2243,格式转为XXX.X) 
224.0(2240,格式转为XXX.X) 

2.6 校验码 CS

占2个字节,从第一个帧起始符开始到校验码之前的所有各字节的模 256 的和,即各字节二进制算术和,不计超过 256 的溢出值。

    /*** @description: 计算校验码* @author WXP* @date 2024/10/11 14:44* @version 1.0*/public static byte calculateChecksum(byte[] data) {int count = 0;int len = data.length - 2;for (int i = 0; i < len; i++) {count += data[i];}byte b = (byte) (count & 0xFF); // 0xFF 等于二进制 1111 1111 只取二进制后8位,既不计超过256的溢出值return b;}

2.7 结束符

占一个字节,固定为 16H,标识一帧信息的结束。

2.8 传输事项

  • 前导字节:在主站发送帧信息前,先发送4个字节 FEH,以唤醒接收方

  • 传输次序:所有数据项均先传送低位字节,后传送高位字节。

  • 传输响应:每次通信都是由主站向按信息帧地址域选择的从站发出请求命令帧开始,被请求的从站接收到命令后做出的响应

  • 差错控制:字节校验为偶校验,帧校验为纵向信息校验和,接收方无论检测到偶校验出错或纵向信息校验和出错,均放弃该信息帧,不予响应

3. 报文解析

发送:68 11 11 11 11 11 11 68 11 04 33 32 34 35 19 16
  • 68:帧起始符,标识这是一帧信息的开始
  • 11 11 11 11 11 11:电表地址,低位在前,高位在后,实际地址为反过来
  • 68:帧起始符,标识前面地址信息结束
  • 11:控制码,对应二进制为 00010001,按前面控制码规则为:主站发送的命令帧(0),从站正确应答(0),无后续数据帧(0),读电表数据(10001)
  • 04:数据域字节数,此处数据域字节数为4
  • 33 32 34 35:数据域,此为数据标识,高位在前,低位在后,传输时都进行了加33H的操作,实际数据为 02 01 FF 00
  • 19:校验码,通过前面算法可以算出
  • 16:结束符,标识一帧信息的结束
接收:68 11 11 11 11 11 11 68 91 0A 33 32 34 35 C8 55 CB 55 33 56 65 16
  • 68:帧起始符,标识这是一帧信息的开始

  • 11 11 11 11 11 11:电表地址,低位在前,高位在后,实际地址为反过来

  • 68:帧起始符,标识前面地址信息结束

  • 91:控制码,对应二进制为 10010001,按前面控制码规则为:从站发送的命令帧(1),从站正确应答(0),无后续数据帧(0),读电表数据(10001)

  • 0A:数据域字节数,此处数据域字节数为10

  • 33 32 34 35 C8 55 CB 55 33 56:数据域,高位在前,低位在后,传输时都进行了加33H的操作

    • 33 32 34 35 为数据标识,-33H并调整高低位后为 02 01 FF 00

    • C8 55 CB 55 33 56 为返回的数据,-33H并调整高低位后为 23 00 22 98 22 95,按(XXX.X)的数据格式则为 230.0 229.8 229.5

      1728959102093

  • 65:校验码,通过前面算法可以算出

  • 16:结束符,标识一帧信息的结束

4. 代码实例

此为Java实现的一个Demo,需导入 jSerialComm 串口通信的包。若接收的信息不完整,则调整接收报文前的线程睡眠时间,或者更改成用while循环监听串口数据流的回复。

<dependency><groupId>com.fazecast</groupId><artifactId>jSerialComm</artifactId><version>2.9.2</version>
</dependency>
package org.xp;import com.fazecast.jSerialComm.SerialPort;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;/*** @author sam* @version 1.0* @description: DLT645Demo* @date 2024/10/11 10:16*/
public class DLT645Demo {// 设备通讯地址private final static String address = "111111111111";// 控制码private static final int controlCode = 0x11;// 数据长度private static final int dataLength = 0x4;// 控制码中从机回复成功的功能码private static final String[] successCode = {"00000", "10001", "10010", "10011", "10100", "10101", "01000", "10110", "10111", "11000", "11001", "11010", "11011", "11100", "11101", "00011"};// 帧起始符private static final int startCharacter = 0x68;// 结束符private static final int endCharacter = 0x16;// 数据加减操作private static final int codeChange = 0x33;public static void main(String[] args) {// 设置通信端口、波特率、数据大小、校验位、停止位SerialPort serialPort = SerialPort.getCommPort("COM3");serialPort.setBaudRate(2400);   // 此电表波特率为2400serialPort.setNumDataBits(8);serialPort.setParity(SerialPort.EVEN_PARITY);   // 偶校验serialPort.setNumStopBits(1);// 初始化发送数据ArrayList<Integer> dataList = new ArrayList<>();dataList.add(0x00);dataList.add(-0x01);dataList.add(0x01);dataList.add(0x02);byte[] request = {};// 打开串口,传输数据if (serialPort.openPort()) {// 发送request = buildDLT645Request(address, controlCode, dataLength, dataList);System.out.println("发送的报文:" + Arrays.toString(request));StringBuilder hexString = new StringBuilder();for (byte b : request) {hexString.append(toHex(b)).append(" ");}System.out.println(hexString);System.out.println(toHex(request[request.length - 2]));serialPort.writeBytes(request, request.length);}// 睡眠 300 毫秒,等待从机响应try {Thread.sleep(300);  // 若传输回来的数据不完整,需调整睡眠时间,回传的报文越长,需要睡眠的时间也要越长} catch (Exception e) {e.printStackTrace();}// 接收byte[] bytes = new byte[255];serialPort.readBytes(bytes, bytes.length);System.out.println("接收的报文:" + Arrays.toString(bytes));// 将 接收的数据放进新的合适大小的byte数组,去掉开头4个FE Integer.parseInt(toHex(bytes[9])) 数据域长度byte[] response = Arrays.copyOfRange(bytes, 4, 4 + 10 + Integer.parseInt(toHex(bytes[13]), 16) + 2);StringBuilder hexString2 = new StringBuilder();for (byte b : response) {hexString2.append(toHex(b)).append(" ");}System.out.println("报文:" + hexString2);if (!hexString2.substring(hexString2.length() - 3, hexString2.length() - 1).equals("16")) {System.out.println("结束符的数据有误,未接收到结束符");}// 处理报文receiveMessage(response);// 关闭串口serialPort.closePort();}/*** @description: byte 字符转换成十六进制字符串* @param: b* @return:* @author WXP* @date: 2024/10/14 12:14*/private static String toHex(byte b) {// Convert byte to unsigned int and then to hex stringreturn String.format("%02X", b & 0xFF);}/*** @description: 构建645请求报文* @param: address* controlCode* dataLength* dataList* @return:* @author WXP* @date: 2024/10/15 10:29*/private static byte[] buildDLT645Request(String address, int controlCode, int dataLength, List<Integer> dataList) {// 数据长度校验if (dataLength < 0) {return new byte[0];}if (dataList == null && dataLength > 0) {return new byte[0];}if (dataList != null && (dataList.isEmpty() && dataLength > 0 || dataList.size() != dataLength)) {return new byte[0];}byte[] bytes = new byte[dataList != null && !dataList.isEmpty() ? 12 + dataList.size() : 12];// 帧起始符bytes[0] = (byte) startCharacter;// 增加地址bytes[1] = (byte) Integer.parseInt(address.substring(0, 2), 16);bytes[2] = (byte) Integer.parseInt(address.substring(2, 4), 16);bytes[3] = (byte) Integer.parseInt(address.substring(4, 6), 16);bytes[4] = (byte) Integer.parseInt(address.substring(6, 8), 16);bytes[5] = (byte) Integer.parseInt(address.substring(8, 10), 16);bytes[6] = (byte) Integer.parseInt(address.substring(10, 12), 16);// 帧起始符bytes[7] = (byte) startCharacter;// 控制符bytes[8] = (byte) controlCode;// 数据长度bytes[9] = (byte) dataLength;// 数据域 发送端需 + 0x33 处理if (dataLength > 0) {for (int i = 0, j = 10; i < dataList.size(); i++, j++) {bytes[j] = (byte) (dataList.get(i) + codeChange);}}// 校验码bytes[bytes.length - 2] = calculateChecksum(bytes);// 结束符bytes[bytes.length - 1] = (byte) endCharacter;return bytes;}/*** @description: 接收从站回传报文* @param: bytes* @return:* @author WXP* @date: 2024/10/15 10:28*/private static byte[] receiveMessage(byte[] bytes) {System.out.println("-----------接收------------------");// 帧起始符System.out.println("帧起始符:" + toHex(bytes[0]));// 地址解析byte[] addressArray = new byte[6];for (int i = 0; i < 6; i++) {addressArray[i] = bytes[6 - i];}StringBuilder address = new StringBuilder();for (byte b : addressArray) {address.append(toHex(b));}System.out.println("地址为:" + address);// 帧起始符System.out.println("帧起始符:" + toHex(bytes[7]));// 控制符parseControl(bytes[8]);// 数据长度// 检验报文中的数据长度和数据长度位是否一致System.out.println("数据长度 :" + Integer.toHexString(bytes[9]));// 数据域 发送端需 + 0x33 处理byte[] dataArray = new byte[bytes.length - 12];StringBuilder sb = new StringBuilder();if (!Byte.valueOf(bytes[8]).equals(Byte.valueOf("0"))) {for (int i = 0, j = 10; j < bytes.length - 2; i++, j++) {// -33HdataArray[i] = (byte) (bytes[j] - codeChange);sb.append(toHex(dataArray[i])).append(" ");}System.out.println("数据域:" + sb);System.out.println("数据标识:" + sb.substring(0, 12));System.out.println("实际数据:" + sb.substring(12, sb.length() - 1));}// 校验码System.out.println("校验码:" + toHex(bytes[bytes.length - 2]));if (toHex(calculateChecksum(bytes)).equals(toHex(calculateChecksum(bytes)))) {System.out.println("校验无误");}// 结束符System.out.println("结束符" + toHex(bytes[bytes.length - 1]));// 提取数据 高位在后 低位在前,根据实际返回值自行写逻辑解析String[] split = sb.substring(12, sb.length() - 1).split(" ");System.out.println("拆分后数据:" + Arrays.toString(split));System.out.println("A相电压:" + (Integer.parseInt(split[1]) * 10 + Float.parseFloat(split[0]) / 10));System.out.println("B相电压:" + (Integer.parseInt(split[3]) * 10 + Float.parseFloat(split[2]) / 10));System.out.println("C相电压:" + (Integer.parseInt(split[5]) * 10 + Float.parseFloat(split[4]) / 10));return bytes;}// 将十六进制字符串转换为二进制字符串public static String hexToBinary(String hex) {StringBuilder binary = new StringBuilder();// 遍历十六进制字符串的每一个字符for (int i = 0; i < hex.length(); i++) {// 获取当前字符的十六进制值(0-9, A-F)char c = hex.charAt(i);int hexValue = Character.digit(c, 16);// 将十六进制值转换为4位二进制字符串,并添加到结果中binary.append(Integer.toBinaryString(hexValue));}return binary.toString();}/*** @description: 计算校验码* @author WXP* @date 2024/10/11 14:44* @version 1.0*/public static byte calculateChecksum(byte[] data) {int count = 0;int len = data.length - 2;for (int i = 0; i < len; i++) {count += data[i];}byte b = (byte) (count & 0xFF);return b;}/*** 解析控制码*/public static void parseControl(byte control) {// 校验控制符是否为从站应答成功String binary = hexToBinary(toHex(control));boolean flag = false;for (String s : successCode) {if (binary.equals(s)) {flag = true;break;}}if (flag) {System.out.println("控制码:" + toHex(control));// D0-D4:功能码String function = Integer.toBinaryString(control & 0x1F);// D5:后续帧标志String next = String.format("%d", control >> 5 & 0x01);// D6:从站应答标志String response = String.format("%d", control >> 6 & 0x01);// D7:传输方向标志String direction = String.format("%d", control >> 7 & 0x01);// 一起解析System.out.println("功能码:" + function + ",后续帧标志:" + next + ",从站应答标志:" + response + ",传输方向标志:" + direction);if ("1".equals(response)) {System.out.println("从站异常应答");return;}} else {System.out.println("控制码错误");}}}

5. 报文解析工具

645报文解析小工具,可以方便提取报文和理解报文。下面是百度网盘连接以及工具界面
链接:https://pan.baidu.com/s/1oMu9lLO5e__HHbYWnzEkqw
提取码:ojzw

主机发送的报文解析:68 11 11 11 11 11 11 68 11 04 33 32 34 35 19 16

1728960704124

从机回复的报文解析:68 11 11 11 11 11 11 68 91 0A 33 32 34 35 B9 55 BC 55 C4 55 D7 16

1728960663809

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/71757.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

Docker部署最新版本EMQX服务,上干货不废话

1.拉取emqx镜像:docker pull emqx/emqx:latest 显示如上即代表拉取成功2.使用docker images 查看镜像 4.启动emqx服务 docker run -d -v /etc/localtime:/etc/localtime:ro -p 18083:18083 -p 1883:1883 -p 8083:8083 emqx/emqx:latest 说明已经成功启动 5.去云服务或者服务器…

Oracle-Plsql-创建一个存储过程

Oracle-Plsql-创建存储过程存储过程 1.plsql中创建存储过程在“窗口列表”中右击鼠标选择“新建”>>>“程序窗口”>>>“Procedure”.创建存储过程界面 输入“Name”确定存储过程的名称,“Parameters”为可选,可以在这个界面输入,也可以在后续界面中输入。…

神经网络之卷积篇:详解残差网络(ResNets)(Residual Networks (ResNets))

详解残差网络 ResNets是由残差块(Residual block)构建的,首先解释一下什么是残差块。这是一个两层神经网络,在\(L\)层进行激活,得到\(a^{\left\lbrack l + 1 \right\rbrack}\),再次进行激活,两层之后得到\(a^{\left\lbrack l + 2 \right\rbrack}\)。计算过程是从\(a^{[l…

拉格朗日插值法

本文通过线性插值和二次插值的形式,介绍了拉格朗日插值算法以及牛顿插值算法的基本形式。两种插值算法的最终函数形式是一致的,但是在不同场景下的参数求解计算量是不一致的,需要根据自己的应用场景选择更加合适的插值算法。技术背景 2024年诺贝尔物理学奖和化学奖的揭幕,正…

TCP的连接与释放

TCP的连接与释放TCP是面向连接的协议,它基于运输连接来传送TCP报文段 TCP运输连接的建立和释放,是每一次面向连接的通信中必不可少的过程 TCP运输连接有以下三个阶段:通过“三报文握手”来建立TCP连接。 基于已建立的TCP连接进行可靠的数据传输。 在数据传输结束后,还要通过…

微信小程序-文件上传功能

WXML文件:<!--pages/picture/picture.wxml--> <text>pages/picture/picture.wxml</text> <button bindtap="ChooseImageFile">选择图片(以File形式存储在39)</button>JS文件:// 39File形式上传ChooseImageFile() {wx.chooseImage({c…

AudioMixer

目录介绍可实现效果使用代码示例 介绍混音器是一种可由音频源 (AudioSource) 引用的资源,能够对通过音频源生成的音频信号进行更复杂的线路规划和混音。这一类混音是通过用户在资源内部构造的音频组层级视图来完成的。 DSP 效果和其他音频母带制作概念可应用于音频信号,因为音…