C#串口通信SeriPort 电表DLT645 RS234/RS485

难受,三个多月前有一个电表电量监控的项目。做完了就没再管了。今天有需求需要改一些地方,但是....我想不起来干了啥,怎么干的啦。真的完全忘了.....项目名称叫啥都忘了.找了半天

不知道有没有和我一样的贵人程序员......

 

首先回顾一下大致的网络结构如下,每个电表通过USB的总线,连接到PC上,可能302车间的所有电表,划分为组1,连接到C0M0串口,303车间,划分位组2,连接到COM1串口

 首先是串口通信的一些基础知识https://blog.csdn.net/xiaobaixiongxiong/article/details/83998436

 大致来说,电表是通过串口,经一个USB转串口来连接到电脑的,这里我一直不大明白,明明是通过USB,为啥叫串口通讯那。其实,早一点的电脑,笔记本,都是有一个9针的真正的串口插座的。后来笔记本越来越薄,那个插口基本都没了,换成了

现在我们看到的通过USB转串口,但是串口通信的基本概念仍然是那一套

 

 首先是串口通过高低电平来传输0和1的比特流的理论基础

起始位,数据位,奇偶校验位,停止位,波特率。这些,为一条线(其实表示高低电平可以是1条线如RS232,也可能是多条线如RS485)通过高低电平变化来传输数据,提供理论基础。

 

 波特率其实就是每秒钟,电平的变化频率。比如波特率1200.那么每一个0(低电平)1(高电平)持续的时间是1/1200秒。如果波特率是9600.那么持续时间是1/9600.那么 波特率越高,每秒能传输的数据就越多。

 在这个基础上,诞生了一些不同的实现,形成了常见的标准 RS232 RS485等。 

 

 我个人的不是很准确的理解,理论基础都是这一套,都是用高低电平来表示0和1达到传输比特流的目的

但是实现有所区别,

比如RS232的高低电平,使用3-15v有效电平,使用一根线,有电压表示1,无电压表示0,使用一根线就能完成传输0和1的比特流

而RS485使用差分电平。它的高低电平需要两根线的电压差值来表示,这样的话,虽然都是通过高低电平来传输0和1,但是实现方式是不一样的,特性也不一样。比如RS485的传输距离更长,抗干扰性更好等

(或者将来也可能诞生了一种新的标准RS200,使用100V-200V来表示高低电平)。这样是否对RS232和RS485到底是啥有更明白一点的理解

 

那么串口通信的基础,我们了解了大概,其实这部分都是别人给我们做好的啦,不需要自己去实现。

真正到了与我们使用息息相关的部分,则是更上层的电表的通讯协议 DLT645-1997  DLT645-2007,通信协议可以参考https://blog.csdn.net/u013184273/article/details/98083050

 

如果说RS232,RS485为我们提供了与电表通讯的基础,那么DLT645-1997  DLT645-2007则是明确了与电表通讯的交互方式,就像规定我给你发 HOW are you  , 你给我回复 fine and you

这里其实没有啥好讲的,总的来讲就是数据格式,按下图的数据格式,拼装出一个byte[]  往串口写数据,那么电表就会按照我们的请求,返回对应的响应结果。

 

返回数据:68 78 56 34 12 00 00 68 91 08 33 33 34 33 A4 56 79 38 F5 16  

注意,这里面没有前导字节FE,并不代表所有的电表厂家都没有,而且还是不固定的,所以一定小心写程序,因为不同厂家电表回的前导字节个数不一样。

其中:78 56 34 12 00 00 是表地址,传输次序是低在前,高在后,而且是十六进制。

  91-为从返回命令

 08-共8个字节

33 33 34 33-数据块,可以理解成寄存器地址,表示读电表总电流。

A4 56 79 38-具体数据,分析时,应减33,所以为:

A4-33=71
56-33=23
79-33=46
38-33=5

实际的电表数为:54623.71度

 

下面我以C# 为例,来讲讲编程中的一些坑

1 对于返回的数据,FE FE FE FE这四个前导字节可能有,也可能没有,也可能1个,2个 3个 。。。。。。就是说看电表厂家的心情。所以我们编码要对他做特殊处理

2 C# 的SerialPort  串口,比较坑,怎么个坑法那。

我们以同步读写为列。假设我发送 12345678 ,返回 abcdefgh。那我SerialPort.write(12345678)后,去读SerialPort.read(out result) 按我们的理解,result 应该是 abcdefgh了把?

 事实上,它可能是abcdefgh,也可能是abcde  也可能是abc .  就是说,我们read一次,它不一定完全返回,如果只返回了一部分,那么我们还要再读,第一次返回abc ,第二次有可能返回剩下的defgh,也可能还是只返回部分def。

就是说write一次,我可能要读N次,而且N还不固定,才能接收到完整的数据。

 

而SerialPort 的异步读接口(其实就是回调函数),也是问题多多

            SerialPort .ReceivedBytesThreshold =15;  //表示接收到15 byte的数据后触发    SerialPort .DataReceived .      但其实有问题,接收到1个,两个,3个都有可能触发,就是说等15个接受完,可能会触发N次
            SerialPort .DataReceived +=new System.IO.Ports.SerialDataReceivedEventHandler(spReceive_DataReceived);   

可能这是C#封装串口的一些问题,不知道是我用的不对还是大家都是这样,有懂的人可以指教一下。

好在知道有这种问题,解决的方法并不难。可以参考这个博客https://www.cnblogs.com/royenhome/archive/2010/03/23/1692440.html

也可以像我这样,不是很严谨,仅供参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/// <summary>
/// 暂时没考虑返回可能包含的FEFE前缀,1秒超时
/// </summary>
/// <param name="RetLength">完整返回帧的长度</param>
/// <param name="buffer"></param>
/// <returns></returns>
private int ReadCicle(int RetLength, ref byte[] buffer)
{
    TimeSpan overtime = new TimeSpan(TimeSpan.TicksPerSecond);
    DateTime dt = DateTime.Now;
    int length = 0;
    while (true)
    {
        try
        {
            length += mySerialPort.Read(buffer, length, buffer.Length - length);
        }
        catch (TimeoutException e)
        {
            //超时不处理                                     
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
        if (length >= RetLength || DateTime.Now - dt > overtime)
            break;
    }
 
   return length;
}

  

 如果不想用SeriPort接口,nuget里面还是有好多第三方编写的串口类可用的,也可以尝试一下

 

posted on   陈傻傻周笨笨  阅读(3724)  评论(0编辑  收藏  举报

编辑推荐:
· 对象命名为何需要避免'-er'和'-or'后缀
· SQL Server如何跟踪自动统计信息更新?
· AI与.NET技术实操系列:使用Catalyst进行自然语言处理
· 分享一个我遇到过的“量子力学”级别的BUG。
· Linux系列:如何调试 malloc 的底层源码
阅读排行:
· C# 中比较实用的关键字,基础高频面试题!
· .NET 10 Preview 2 增强了 Blazor 和.NET MAUI
· Ollama系列05:Ollama API 使用指南
· 为什么AI教师难以实现
· 如何让低于1B参数的小型语言模型实现 100% 的准确率

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

统计

点击右上角即可分享
微信分享提示