恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

C#上位机实现智能电表远程抄表:DL/T645协议解析与串口通信

  • 首页
  • 资讯中心
  • /
  • C#上位机实现智能电表远程抄表:DL/T645协议解析与串口通信

相关资讯

2018 DeeCamp AI笔试A卷全拆解:从数学到深度学习的算法岗备考指南 2026/8/31 12:43:49
LVGL 9.0移植到STM32F746G全流程与性能优化指南 2026/8/31 12:38:49
花卉识别项目实战:从数据集处理到YOLOv8训练与部署全流程 2026/8/31 12:38:49

最新资讯

等离子体诊断数据解析:C语言与Python混合编程实战
基于C++的XY激光焊接机控制系统源码解析:运动控制与激光时序实战
MIMO-OFDM仿真平台搭建:MATLAB脚本与Simulink联合仿真实战
基于SSM的农村公益活动管理系统:设计与部署全解析
Java Web医院预约挂号系统源码解析:数据库设计与并发控制
STM32标准外设库V3.6.0深度解析:从原理到实践

今日推荐

MCU无DAC如何用定时器+DMA 2D输出高保真任意波形
Cortex-M3 Flash下载失败?从编程错误标志到供电瞬态排查
STM32 TouchGFX屏幕切换Transition优化:原理、配置与排障实战

本周热门

备战数据库管理工程师校招:索引、事务、备份恢复核心考点解析
数字电路时序基石:深入理解建立时间与保持时间
蓝桥杯国赛超声波测距机:从单片机原理到嵌入式系统实战

本月精选

如何用DamaiHelper实现演唱会门票的智能自动化抢购:完整技术解决方案指南
第4篇:59 倍性能差距的索引瓶颈定位——一次教科书级的全表扫描调优
终极歌词批量下载神器:5分钟解决离线音乐库歌词同步难题

C#上位机实现智能电表远程抄表:DL/T645协议解析与串口通信

发布时间:2026/8/31 12:43:49
C#上位机实现智能电表远程抄表:DL/T645协议解析与串口通信 简介这是一份面向C#初学者与物联网数据采集开发者的智能电表自动抄表实践项目聚焦串口通信与协议解析核心技能解决传统人工抄表效率低、易出错的工程痛点适用于能源管理、智慧用电等场景的原型开发与教学实训。压缩包共22个文件含6个C#源码文件如Form1.cs、Program.cs、3个可执行程序exe、1个Visual Studio解决方案sln及配套资源文件resx、settings等完整呈现Windows桌面端串口通信、DL/T645或MODBUS协议解析、电表读数实时显示与存储等关键模块实现。目前已有1406人学习下载。读者可直接编译运行深入理解SerialPort类配置、校验位/波特率匹配、帧结构解析、异常重试机制等实操细节并基于现有架构快速适配不同型号电表是掌握工业现场数据采集全流程的优质入门范例。 做C#上位机的朋友很多都接过智能电表抄表这类活儿。不管是自己家里装的光伏并网表、出租屋里的预付费电表还是工厂配电房的动力表凡是涉及电费结算、能耗统计、设备状态监测的都绕不开“远程抄表”这四个字。前段时间我整理了一套C#写的智能电表抄表源码从串口通信、DL/T645协议解析到数据展示一条龙全齐了。这篇文章就把这套源码背后的事情完整拆一遍数据采集的原理、C#的实现方式、实际踩过的坑一次说透。如果你正在做或打算做电力数据采集相关的项目不管是刚入门的上位机新手还是已经带过几个项目的熟手这篇文章都会对你有实际帮助。读完你不仅能看懂这套抄表源码的逻辑还能自己动手扩展出适合你现场设备的一套采集程序。1. 抄表系统到底在解决什么问题1.1 智能电表数据采集的三种典型形态做抄表系统的第一步不是写代码是搞清楚你的电表是怎么联网的。智能电表的数据采集根据现场条件不同基本有三种形态。第一种是本地串口直采。电表上往往带RS485接口用双绞线把电表连到串口服务器或者USB转485模块上上位机直接通过串口发命令去读数据。这种方案最简单适合电表数量不多、距离不远的小场景比如小型工厂、楼宇配电间、光伏电站。这套源码走的就是这种方案我用一个USB转RS485的小盒子插在工控机上就能跑。第二种是集中器加主站。电表先通过RS485总线接到集中器集中器再通过GPRS或4G网络把数据上传到主站系统。这种方案适合小区、台区这类电表数量上百上千的场景。主站不直接和电表打交道而是和集中器打交道协议变成了Q/GDW 1376或DL/T 698.45复杂度和本地抄表不是一个量级。第三种是物联网无线采集。现在很多新型智能电表本身内置了NB-IoT或WiSUN模组电表直接上云主站通过云平台API或者MQTT订阅就可以拿到数据。这种形态没有本地链路基本上是从“抄表”变成了“订阅数据”。这套源码解决的是第一种场景但通信层、协议层、解析层的代码思路后面两种照样能复用。因为你无论走哪条链路最后要处理的还是那一包数据怎么把电表寄存器里的值读出来、怎么解析成能看的数底层逻辑不变。1.2 为什么上位机选C#而不是别的做上位机采集语言选择无非那么几个C#、LabVIEW、Python、C。我长期用C#做这块原因有三。第一串口通信做起来太方便了。System.IO.Ports.SerialPort类几行代码就能打开一个串口数据接收事件、超时管理、字节读写全都帮你封装好了。相比C里自己撸CreateFile、ReadFile那一套C#的开发效率高好几倍。而Python的pyserial虽然也很简洁但部署到客户的Windows工控机上打包、服务化、跟其它硬件交互体验远不如C#顺手。第二界面开发效率高。抄表上位机不只是后台采集还要给人看。WinForm或者WPF拖一拖控件就出来一个挺像样的界面实时刷新数据、绘制曲线、导出报表都很成熟。做项目交付的时候界面好看不好看直接关系到验收印象分。第三生态完整。C#对接数据库有EF Core和Dapper对接MQTT有MQTTnet对接Modbus有NModbus网口通信用Socket或者TCPClient协议解析自己做也不复杂。一套系统从采集到展示再到上云全链路代码风格统一维护成本低。当然C#也不是没有毛病。比如串口接收底层那一套偶尔会出现丢字节的问题后面我会专门讲这个坑。但总体而言在工业上位机这个领域C#是投入产出比最高的选择。2. 核心协议与关键原理2.1 DL/T645协议是绕不开的门槛国内智能电表最常用的通信规约是DL/T6452007版是目前的主流。这套协议规定了电表怎么响应主站命令、数据怎么编码、校验怎么做。网上很多抄表源码其实核心就是围绕DL/T645展开的。DL/T645的帧结构不算复杂一条完整的读帧长这样68 A0 A1 A2 A3 A4 A5 C L DATA0...DATAn CS 16拆开来看0x68帧起始符固定值。相当于每帧消息的“前导头”。A0到A5电表地址域6个字节。注意这个地址是从表号倒过来排的这一点新手特别容易搞反。比如表号是123456789012那么地址域发出去就是12 90 78 56 34 12低字节在前。C控制码。0x11表示主站请求读数据0x91表示电表正常应答0xD1表示电表异常应答。L数据域长度。只算DATA部分的字节数不包含前面那些固定字段。DATA0到DATAn数据域真正干活的内容。读数据命令里放的是数据标识电表应答里放的是数据标识加实际读数。CS校验和。从0x68开始到数据域结束所有字节累加超过0xFF的溢出部分不要只保留低8位。0x16帧结束符固定值。理解了这帧结构再去看网上的源码就清楚多了。不管代码封得再花哨本质上就是三件事拼帧、发帧、拆帧。2.2 通信参数与地址编码细节DL/T645协议默认的通信参数是波特率2400、偶校验、8个数据位、1个停止位。这个参数一定要设对否则电表根本不理你。现在部分新电表也支持1200或4800波特率但默认值是最稳的。这里额外提醒一句有些电表在RS485接口旁边会标A和B两个接线端子。A接485B接485-。你要是接反了经常出现能收到电表数据但内容乱码的情况因为RS485是差分信号极性反了信号就反相了。地址域还有一个细节全0地址0xAA是广播地址主要用于广播对时。正常抄表时不要用这个地址必须是具体的表号。而且不同厂家的电表表号位数可能不一样有的12位有的8位。地址域只有6个字节最多表示12位十进制数。数据标识这块DL/T645-2007里有一套编码体系。我举一个最常用的例子读取当前正向有功总电能数据标识按DI0 DI1 DI2 DI3的顺序发送常见编码是00 00 01 00或01 00 00 00具体要看电表厂家的规约说明书。电表应答时数据域前4个字节是数据标识后面跟着电能值。电能值通常用4个字节BCD码表示单位是kWh千瓦时有两位小数比如收到00 00 12 34实际值就是1234.56 kWh不对这里要看厂商定义小数位数我遇到的正向有功总电能一般是两位小数读到的BCD字节要自己除以100。说实话DL/T645协议文档写得很晦涩我第一次看的时候对着PDF翻了两天才彻底搞明白。但当你把第一块电表的数据成功读出来的时候那种感觉是相当爽的。3. C#实现抄表采集的完整过程3.1 串口连接与设备发现第一步先把电表连到电脑上。我用的是USB转RS485模块接好电表的A和B端子插上电脑后会在设备管理器里多出一个COM口。打开串口之前先看下波特率和校验位。using System.IO.Ports; SerialPort sp new SerialPort(COM3, 2400, Parity.Even, 8, StopBits.One); sp.ReadTimeout 1000; sp.WriteTimeout 1000; sp.Open();如果Open抛异常要么是COM口号不对要么是串口被别的程序占用了。这种事我在现场遇到太多次了排查顺序永远是先看设备管理器COM口号再看是不是有别的调试助手开着串口最后才怀疑硬件连接问题。打开串口后第一步是探测电表地址。如果你知道表号可以直接把表号转成地址域发命令。如果不知道表号用广播地址是读不到数据的。这时可以先用厂家默认地址或者通过电表铭牌上的表号来组装地址域。地址域倒序逻辑我写了个函数来处理public static byte[] BuildAddressFromMeterNo(string meterNo) { // 电表表号是12位数字例如 123456789012 // DL/T645地址域6字节低字节在前即倒序排列 byte[] addr new byte[6]; string padded meterNo.PadLeft(12, 0); for (int i 0; i 6; i) { string part padded.Substring(i * 2, 2); addr[5 - i] Convert.ToByte(part, 16); } return addr; }这个函数在很多项目里可以直接复用。不过要注意表号里的A-F是十六进制字符如果表号里有非十六进制字符说明你的表号格式不对。3.2 协议帧拼接与校验计算拼帧是抄表源码里最核心的环节。我按DL/T645的帧结构封装了一个通用的读数据命令生成器。public static byte[] BuildReadFrame(byte[] addr, byte[] dataId) { // 固定起始符 byte[] frame new byte[2 6 1 1 dataId.Length 2]; int index 0; frame[index] 0x68; // 地址域 Array.Copy(addr, 0, frame, index, 6); index 6; // 控制码0x11 为读数据 frame[index] 0x11; // 数据域长度 frame[index] (byte)dataId.Length; // 数据域数据标识 Array.Copy(dataId, 0, frame, index, dataId.Length); index dataId.Length; // 校验和从帧起始符到数据域结束累加取低8位 byte cs 0; for (int i 0; i index; i) { cs frame[i]; } frame[index] cs; // 结束符 frame[index] 0x16; return frame; }校验和的计算最容易出错。DL/T645的规定是从0x68这个起始符就开始累加一直到结束符之前的最后一个数据字节。很多网上流传的代码片段漏了起始符结果发出去的电表根本不响应。这个坑我实测过调试了很久才反应过来。拼好帧之后直接写串口sp.Write(frame, 0, frame.Length);然后等电表响应。响应时间一般几十毫秒到几百毫秒不等个别老表会慢一点。读取响应时先读帧起始符0x68再读地址域、控制码、长度、数据域、校验、结束符。如果一上来就读完整一包很容易因为缓冲区里残留字节导致解析错位。这里给一个读取完整响应帧的示例public static byte[] ReadFrame(SerialPort sp) { using (MemoryStream ms new MemoryStream()) { // 先找起始符 int count 0; while (count 100) { int b sp.ReadByte(); if (b 0x68) { ms.WriteByte((byte)b); break; } count; } // 读地址域6字节 控制码1字节 长度1字节 byte[] header new byte[8]; for (int i 0; i header.Length; i) { header[i] (byte)sp.ReadByte(); } ms.Write(header, 0, header.Length); // 长度字节是header的最后一位 int len Convert.ToInt32(header[7]); // 读数据域 校验 结束符 byte[] body new byte[len 2]; for (int i 0; i body.Length; i) { body[i] (byte)sp.ReadByte(); } ms.Write(body, 0, body.Length); return ms.ToArray(); } }这段代码看似简单但有几个地方值得注意。一是先找起始符的逻辑为什么不能直接从头读因为串口缓冲区里可能有上一次残留的数据或者干扰字节如果你默认第一字节就是0x68很容易把脏数据当帧头。二是读取响应期间要维护一个总超时否则电表没响应的时候程序会卡死在ReadByte上。3.3 数据解析与展示电表回帧里的数据域前4个字节是数据标识后面才是实际数据。以读取当前正向有功总电能为例电表应答的数据域通常是这样的01 00 00 00 12 34 56 78前4字节是数据标识后面4字节是BCD码。BCD码的意思是一个字节拆成高低两个十六进制位比如0x12代表数值120x34代表数值34合在一起就是12345678这是带两位小数的数值所以要除以100最终结果是123456.78 kWh。数值解析的代码public static decimal ParseBcd(byte[] data, int startIndex, int length) { decimal result 0; for (int i 0; i length; i) { byte b data[startIndex i]; result result * 100 (b 4) * 10 (b 0x0F); } return result; }这里注意BCD解析出来的值是十进制形式的直接按位计算就行。这个函数在处理电能量数据时很常用电压、电流、功率这些不同数据类型小数位不一样解析的时候要按协议文档中约定的小数位来除。解析完数据界面上可以用DataGridView或ListView把多块电表的读数实时刷新。我习惯用DataGridView绑一个DataTable因为后面要导出Excel或者报表的时候直接就能用。3.4 多表轮询与并发采集现场往往不止一块电表。比如一个小型光伏电站可能有七八块电表一条生产线上也有好几块。这时候就需要轮询采集。最简单的轮询逻辑就是循环遍历所有电表地址依次发命令读数据。但串口是半双工的同一时刻只能往总线上发一帧命令所以严格意义上说多表采集是串行的不存在真正的并发。实际项目中我会用定时器控制轮询节奏每块表之间加一点延时避免命令过于密集导致电表来不及响应。private async Task PollAllMetersAsync() { foreach (var meter in meterList) { try { byte[] cmd BuildReadFrame(meter.Address, dataIdPositiveActiveEnergy); sp.Write(cmd, 0, cmd.Length); byte[] resp await ReadFrameAsync(sp); decimal value ParseBcd(resp, 9, 4); meter.CurrentValue value / 100m; Log($读取成功{meter.Name} {meter.CurrentValue} kWh); } catch (Exception ex) { Log($读取失败{meter.Name}原因{ex.Message}); } await Task.Delay(200); } }轮询间隔怎么定我一般设3秒到5秒一轮。如果电表数量超过20块一轮下来可能就要十几秒这时候要考虑用多个串口分组采集或者改用集中器方案。另外电表内部存储的数据有时效性频繁读取会导致电表内部计量芯片的负荷增加虽然影响不大但要避免无意义的死循环读取。我遇到过一种情况某块电表老是读取超时但用厂家自带的调试软件却能读到。检查发现是电表地址和配置文件里的地址差了最后一位。这种问题就是典型的“程序没问题数据配置错了”排查的时候一定要先确认地址。4. 实战中的坑与排查思路4.1 常见问题速查表我把这几年做电表采集踩过的坑整理成了一张表按问题频率排序现象可能原因排查方法串口打不开COM口号不对、端口被占用设备管理器查看COM号关掉调试助手检查USB转485驱动是否正常完全无响应接线错误、地址不对、波特率不匹配先用串口调试助手发手工帧测试检查A/B接线是否反了有响应但校验失败校验和算法不对、半包数据用串口助手抓原始字节手工计算校验和比对偶尔丢数据串口缓冲溢出、干扰严重加硬件延时、降低波特率、给RS485加终端电阻读出的数值明显不对小数位没对齐、BCD解析错误对照协议文档检查数据标识和数据长度确认小数位数多块表轮询时某块总超时该表地址重复、从站地址冲突单独给那台表发命令测试确认地址唯一性排查的第一步永远是抓包。我说的抓包不是Wireshark那种网络抓包而是用一个串口调试助手把软件发出的字节和电表返回的字节全部以十六进制显示出来。这个工具哪怕是最简单的SSCOM都行。很多问题只要看到原始字节流原因立刻就能判断出来。4.2 日志记录与线上排查技巧写采集程序日志系统一定要从一开始就做上别等出了问题再补。我在这套源码里加了一个简单的日志类按天滚动写入文本文件记录每一帧发送和接收的原始数据。public static void LogFrame(string direction, byte[] frame) { string hex string.Join( , frame.Select(b b.ToString(X2))); Log(${DateTime.Now:yyyy-MM-dd HH:mm:ss} [{direction}] {hex}); }记录原始帧有什么好处有一次客户反馈说数据偶尔不准现场又没人盯着看。我远程拿到日志文件发现电表返回的字节流里偶尔混入了一个0xFF的字节。这个字节一看就不是DL/T645的合法内容估计是485线路上的干扰信号。后来我让客户给RS485总线加上了终端电阻问题就解决了。没有日志这种偶发故障根本无从下手。还有一种情况是程序跑了一整天后突然某块表开始一直超时。我看了日志才发现是某块表没有响应然后定时器就一直在重试这块表把后面所有表都堵住了。后来我把轮询逻辑改成了“连续失败3次就跳过等到下一轮再试”问题才解决。4.3 串口数据错位与字节丢失的深层处理串口接收这块C#的DataReceived事件是工作在线程池线程上的如果你在事件里直接做解析很容易出问题。而且DataReceived事件并不保证数据一次性全到可能分几次触发。这也是很多C#串口程序不稳定的根源。我推荐的做法是用一个缓冲队列把所有收到的字节先丢进队列解析线程再从队列里取数据做帧校验。ConcurrentQueuebyte recvQueue new ConcurrentQueuebyte(); void sp_DataReceived(object sender, SerialDataReceivedEventArgs e) { int count sp.BytesToRead; byte[] buffer new byte[count]; sp.Read(buffer, 0, count); foreach (byte b in buffer) { recvQueue.Enqueue(b); } }然后解析线程专门负责处理队列。这样做的好处是把“接收数据”和“解析数据”解耦哪怕接收到了不完整的一帧只要队列不丢数据下一批字节到了照样能拼出完整帧。还有一个小技巧当帧解析失败时不要立刻丢弃先把缓冲区里已有的数据按“找0x68起始符”的方式重新对齐。因为干扰字节可能让你一开始就找错了帧头直接放弃的话整帧就浪费了。5. 这套源码还能往哪里扩展5.1 从单机采集到云端上报本地抄表只是第一步。现在很多项目要求数据上云让管理人员在手机上就能看到用电情况。数据上云最常用的方式是MQTT协议C#有现成的MQTTnet库使用起来非常简单。思路是轮询程序每采到一条数据就直接通过MQTT发布到云端Broker云端服务订阅之后把数据存库和展示。这样本地程序只负责采集业务逻辑全部上云后期要加多站点集中管理也方便。// 使用MQTTnet发布数据示例 var mqttFactory new MqttFactory(); using (var client mqttFactory.CreateMqttClient()) { var options new MqttClientOptionsBuilder() .WithTcpServer(your-mqtt-broker-address, 1883) .WithCredentials(username, password) .Build(); await client.ConnectAsync(options, CancellationToken.None); string payload JsonSerializer.Serialize(new { meterNo 123456789012, activeEnergy meter.CurrentValue, timestamp DateTime.Now }); await client.PublishAsync(meters/data/123456789012, payload); }这块我踩过一个大坑MQTT的QoS等级。如果用QoS0网络抖动时消息会丢如果用QoS2Broker端压力大而且重传逻辑复杂。做电表数据上报我建议用QoS1正好在可靠性和性能之间取一个平衡。5.2 对接MES或EMS能源管理系统工厂场景里抄表数据通常要对接MES制造执行系统或EMS能源管理系统。C#做这块很顺手你可以用EF Core对接数据库或者用Web API把数据暴露出去。我做过一个项目车间里有六十多块电表每5分钟采集一轮数据存到SQL Server里报表页面用ECharts展示各产线的实时功率曲线和日用电量。整个系统的核心代码就是这套源码里Protocol层的代码换了个调用方式采集服务和Web服务分开部署而已。这种架构下采集服务专注数据采集Web服务专注业务展示数据落到数据库后两边互不干扰。后面你要加告警功能、预测功能都是顺势而为的事情。我个人做抄表项目的经验是协议解析和稳定性是最花时间的部分界面反而是最不重要的。先在串口调试助手里手工把电表调通再写代码成功率能提高很多。千万别一上来就敲代码先手工验证协议。5.3 从C#上位机到跨平台采集如果你以后要考虑跨平台部署C#倒也不是不能做。用. NET 6以上的版本串口编程的API基本是通用的代码稍微改改就能跑在Linux小主机上。我最近一个项目就是把采集程序部署在一块ARM开发板上用. NET运行时跑性能和稳定性都还不错。不过Linux下需要注意串口名称变成了/dev/ttyUSB0、/dev/ttyS0这样的形式而且当前用户需要加入dialout组才有权限访问串口。这些细节在Windows下开发时完全碰不到但换到Linux下就是第一道门槛了。做采集这一行硬件、协议、软件、网络哪个环节都可能出问题。但反过来想也正是因为问题多能把这个领域的东西搞透彻的人到哪都有饭吃。这套C#抄表源码是一个起点把里面的协议层、通信层、解析层真正理解透换到别的设备采集项目上也就是换个协议的事。本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号