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

也可以像我這樣,不是很嚴謹,僅供參考

        /// <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里面還是有好多第三方編寫的串口類可用的,也可以嘗試一下

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM