【工业总线-11】DL/T 645-2007 多功能电能表通信协议
前言在物联网、智慧电力项目中DL/T 645协议是多功能电能表远程抄表的核心标准常用版本为2007版兼容1997版。本文提供完整可直接集成的C语言实例基于STM32/通用MCU编写包含帧构建、解析、BCD码转换、串口通信适配附抓包示例和常见坑点新手也能快速上手。注很多同学会混淆DL/T 645和DL/T 654这里明确区分DL/T 645电力行业标准用于电能表与采集器/网关的通信本文重点DL/T 654《火电机组寿命评估技术导则》与通信编程无关。一、DL/T 645-2007 协议帧结构核心简化协议帧采用异步串行通信帧格式固定核心结构如下从左到右[前导码 FE]*N 68 地址(6B) 68 控制码C 数据长度L 数据(DATA) 校验和CS 16关键说明嵌入式编程必看前导码FE字节数量可自定义通常3-4个用于同步通信时序地址域6字节采用BCD码编码低字节在前、高字节在后重点坑点控制码核心指令常用0x11读数据、0x81读数据应答数据域每个字节发送前需0x33接收后需-0x33还原协议加密机制校验和从第一个68字节开始到数据域最后一个字节所有字节累加求和无进位结束符固定为0x16。常用控制码补充实战高频控制码功能描述适用场景0x11读数据读取电能、电压、电流等参数0x81读数据应答电表响应读数据指令返回参数0x13写数据设置电表地址、费率等需权限0x83写数据应答电表响应写数据指令返回执行结果二、C语言实战代码嵌入式通用STM32示例代码采用模块化设计可直接复制到MCU工程只需适配串口收发函数即可使用。包含类型定义、地址设置、帧构建、帧解析、BCD码转换、主流程示例。2.1 头文件与宏定义#include stdint.h #include string.h #include stdio.h // 协议核心宏定义 #define DLT645_68 0x68 // 帧起始符 #define DLT645_16 0x16 // 帧结束符 #define DLT645_ADDR_LEN 6 // 电表地址长度6字节 #define DLT645_MAX_LEN 64 // 最大帧长度适配大多数场景 #define DLT645_PRE_LEN 4 // 前导码数量4个FE // 常用数据标识DI4字节可根据需求扩展 #define DI_FORWARD_ACTIVE_ENERGY {0x00, 0x01, 0x00, 0x00} // 正向有功总电能kWh #define DI_VOLTAGE_PHASE_A {0x00, 0x02, 0x00, 0x00} // A相电压V #define DI_CURRENT_PHASE_A {0x00, 0x03, 0x00, 0x00} // A相电流A // DLT645协议句柄管理收发缓冲区、地址、帧长度 typedef struct { uint8_t addr[DLT645_ADDR_LEN]; // 电表地址BCD码低字节在前 uint8_t tx_buf[DLT645_MAX_LEN]; // 发送缓冲区 uint8_t rx_buf[DLT645_MAX_LEN]; // 接收缓冲区 uint8_t tx_len; // 发送帧长度 uint8_t rx_len; // 接收帧长度 } DLT645_HandleTypeDef; // 串口收发函数声明需根据自身MCU适配此处为示例 void UART_Send(uint8_t *buf, uint8_t len); uint8_t UART_Receive(uint8_t *buf, uint32_t timeout); // 超时时间ms2.2 电表地址设置12位表号→6字节BCD码电表表号通常为12位数字如123456789012需转换为6字节BCD码且遵循“低字节在前”规则。/** * brief 设置电表地址12位表号转换为6字节BCD码低字节在前 * param handle: DLT645协议句柄 * param meter_no: 12位电表表号字符串如123456789012 * retval 无 */ void DLT645_SetAddr(DLT645_HandleTypeDef *handle, const char *meter_no) { uint8_t bcd[6] {0}; // 临时存储BCD码高字节在前 // 12位字符串→6字节BCD码每2位数字对应1字节BCD for (int i 0; i 12; i 2) { uint8_t high meter_no[i] - 0; // 高4位 uint8_t low meter_no[i1] - 0; // 低4位 bcd[i/2] (high 4) | low; // 组合为1字节BCD码 } // 2. 反转BCD码低字节在前DLT645协议要求 for (int i 0; i 6; i) { handle-addr[i] bcd[5 - i]; } }2.3 构建读数据帧核心函数根据协议格式构建读数据指令帧自动处理前导码、地址、校验和、数据域偏移0x33。/** * brief 构建DL/T 645读数据帧 * param handle: DLT645协议句柄 * param di: 数据标识4字节如DI_FORWARD_ACTIVE_ENERGY * retval 发送帧长度 */ uint8_t DLT645_BuildReadFrame(DLT645_HandleTypeDef *handle, const uint8_t *di) { uint8_t *buf handle-tx_buf; uint8_t len 0; // 1. 前导码4个FE用于同步 for (int i 0; i DLT645_PRE_LEN; i) { buf[len] 0xFE; } // 2. 起始符68 buf[len] DLT645_68; // 3. 电表地址6字节低字节在前 memcpy(buf[len], handle-addr, DLT645_ADDR_LEN); len DLT645_ADDR_LEN; // 4. 起始符68协议规定地址后需再跟一个68 buf[len] DLT645_68; // 5. 控制码0x11读数据 buf[len] 0x11; // 6. 数据长度L数据域长度此处为4字节DI buf[len] 0x04; // 7. 数据域DI 0x33协议要求数据字节偏移 for (int i 0; i 4; i) { buf[len] di[i] 0x33; } // 8. 校验和CS从第一个68开始到数据域最后一字节累加 uint8_t cs 0; for (int i DLT645_PRE_LEN; i len; i) { // 跳过前导码从第一个68开始 cs buf[i]; } buf[len] cs; // 9. 结束符16 buf[len] DLT645_16; // 更新发送帧长度 handle-tx_len len; return len; }2.4 解析应答帧核心函数接收电表应答帧验证帧格式、控制码、校验和还原数据域-0x33返回有效数据长度。/** * brief 解析DL/T 645应答帧 * param handle: DLT645协议句柄 * param data_out: 解析后的数据缓冲区输出 * retval 有效数据长度失败返回0 */ uint8_t DLT645_ParseFrame(DLT645_HandleTypeDef *handle, uint8_t *data_out) { uint8_t *buf handle-rx_buf; uint8_t len handle-rx_len; // 1. 最小帧长度检查应答帧最小长度前导468地址668控制1长度1数据n校验1结束1 14 if (len 14) { printf(解析失败帧长度不足\r\n); return 0; } // 2. 查找第一个68起始符 int pos 0; while (pos len buf[pos] ! DLT645_68) { pos; // 跳过前导码找到第一个68 } // 3. 验证帧结构68 6字节地址 68第二个起始符 if (pos 7 len || buf[pos7] ! DLT645_68) { printf(解析失败帧结构错误\r\n); return 0; } // 4. 验证控制码应答帧控制码应为0x81 uint8_t ctr buf[pos8]; if (ctr ! 0x81) { printf(解析失败控制码错误0x%02X\r\n, ctr); return 0; } // 5. 读取数据长度L验证帧完整性 uint8_t L buf[pos9]; if (pos 10 L 2 len) { // 1068地址668控制1长度1L数据域2校验结束 printf(解析失败帧不完整\r\n); return 0; } // 6. 校验和验证 uint8_t cs 0; for (int i pos; i pos 10 L; i) { // 从第一个68到数据域最后一字节 cs buf[i]; } if (cs ! buf[pos10L]) { printf(解析失败校验和错误计算0x%02X接收0x%02X\r\n, cs, buf[pos10L]); return 0; } // 7. 数据域还原-0x33 for (int i 0; i L; i) { data_out[i] buf[pos10i] - 0x33; } return L; // 返回有效数据长度 }2.5 BCD码转浮点数电能/电压/电流解析电表返回的数据为BCD码需转换为浮点数如正向有功电能XX.XXXXX kWh。/** * brief BCD码转浮点数适配4字节BCD对应电能、电压等参数 * param bcd: BCD码缓冲区 * param len: BCD码长度通常4字节 * retval 转换后的浮点数单位kWh/V/A */ float DLT645_BCD2Float(const uint8_t *bcd, uint8_t len) { float val 0.0f; // BCD码转换为十进制数每字节对应2位十进制 for (int i 0; i len; i) { val val * 100 ((bcd[i] 4) 0x0F) * 10 (bcd[i] 0x0F); } // 电能参数4字节BCD对应XX.XXXXX kWh除以100可根据参数调整除数 return val / 100.0f; }2.6 主流程示例完整调用适配STM32串口实现“发送读电能指令→接收应答→解析数据→打印结果”完整流程。/** * brief DL/T 645读电表正向有功总电能示例 * param 无 * retval 无 */ void DLT645_ReadEnergy_Demo(void) { DLT645_HandleTypeDef hdl; memset(hdl, 0, sizeof(DLT645_HandleTypeDef)); // 初始化句柄 // 1. 设置电表表号12位替换为实际电表表号 DLT645_SetAddr(hdl, 123456789012); // 2. 构建读正向有功总电能帧数据标识DI_FORWARD_ACTIVE_ENERGY const uint8_t di[4] DI_FORWARD_ACTIVE_ENERGY; uint8_t tx_len DLT645_BuildReadFrame(hdl, di); printf(发送读电能指令帧长度%d字节\r\n, tx_len); // 3. 串口发送帧数据 UART_Send(hdl.tx_buf, tx_len); // 4. 串口接收应答帧超时时间1000ms hdl.rx_len UART_Receive(hdl.rx_buf, 1000); if (hdl.rx_len 0) { printf(接收失败超时未收到应答\r\n); return; } printf(接收应答帧帧长度%d字节\r\n, hdl.rx_len); // 5. 解析应答帧获取电能数据 uint8_t data[4]; uint8_t data_len DLT645_ParseFrame(hdl, data); if (data_len 0) { // BCD码转浮点数得到正向有功总电能kWh float energy DLT645_BCD2Float(data, data_len); printf(正向有功总电能%.2f kWh\r\n, energy); } else { printf(应答帧解析失败\r\n); } } // 主函数调用示例 int main(void) { // 1. 初始化串口2400 8O1、GPIO等根据自身MCU实现 // UART_Init(); // 2. 循环读取电表数据每隔5秒读一次 while (1) { DLT645_ReadEnergy_Demo(); HAL_Delay(5000); // STM32延时函数其他MCU替换为对应延时 } }三、串口参数与抓包示例实战调试必备3.1 串口参数DL/T 645标准参数电表默认串口参数必配否则无法通信波特率2400bps部分电表可配置为4800/9600需确认电表参数数据位8位校验位偶校验O停止位1位流控无。3.2 抓包示例串口助手/逻辑分析仪以“读正向有功总电能”为例抓包数据参考十六进制发送帧MCU→电表FE FE FE FE 68 12 90 78 56 34 12 68 11 04 33 34 33 33 9A 16解析前导4个FE → 68 → 地址12 90 78 56 34 12对应表号123456789012→ 68 → 控制码11 → 数据长度04 → 数据33 34 33 33DI0x33→ 校验和9A → 结束16。应答帧电表→MCUFE FE FE FE 68 12 90 78 56 34 12 68 81 04 55 66 77 88 E0 16解析控制码81应答→ 数据长度04 → 数据55 66 77 88还原后为22 33 44 55→ 校验和E0 → 结束16。四、常见坑点与调试技巧避坑指南4.1 常见坑点90%的人会踩地址顺序错误必须“低字节在前”比如表号123456789012转换为BCD后需反转否则无法通信数据域偏移遗漏发送时未0x33、接收时未-0x33导致解析出的数据乱码校验和计算错误校验和从“第一个68”开始而非从地址开始遗漏会导致校验失败串口参数不匹配波特率、校验位错误导致接收不到应答或应答乱码前导码数量不足前导码少于3个可能导致电表无法同步帧起始建议用4个FE。4.2 调试技巧用串口助手监听收发数据确认帧格式是否正确若接收不到应答先检查串口接线TX/RX交叉连接、电表电源若解析失败打印接收缓冲区的原始数据逐一核对帧结构、校验和可先固定电表地址用工具发送标准帧验证电表是否正常应答。五、扩展说明实战延伸数据标识扩展本文仅提供3个常用DI可参考DL/T 645-2007标准文档添加更多参数如无功电能、功率因数MCU适配代码可适配STM32、51单片机、ESP32等只需替换串口收发函数异常处理可添加帧重发机制接收超时后重发、错误计数提升通信稳定性写数据功能如需设置电表参数可参考读数据帧修改控制码为0x13补充写数据逻辑需电表权限。下期预告原创不易如果本文对你有帮助欢迎点赞、收藏、关注三连有任何问题都可以在评论区留言我会及时回复。