1. 程式人生 > 程式設計 >.Net Core應用增強型跨平臺串列埠類庫CustomSerialPort()詳解

.Net Core應用增強型跨平臺串列埠類庫CustomSerialPort()詳解

目錄
  • 摘要
  • 引言
  • 基礎類庫的選擇
  • 類庫的實現
    • 建立跨平臺類庫
    • 實現機制/條件
    • .net core跨平臺實現
    • 主要程式碼
  • 建立.net core控制檯程式
    • 類庫地址
      • 跨平臺測試
        • Windows測試輸出介面
        • ubuntu測試輸出介面
      • 原始碼地址

        摘要

        在使用SerialPort進行串列埠協議解析過程中,經常遇到接收單幀協議資料串列埠接收事件多次觸發,協議解析麻煩的問題。針對此情況,基於開源跨平臺串列埠類庫SerialPortStrem進行了進一步封裝,實現了一種接收超時響應事件機制,簡化串列埠通訊的使用。

        引言

        最近,寫了一篇博文《.net core跨平臺應用研究-串列埠篇》得到了一些園友的好評,文中介紹了在跨平臺應用研究過程中,在dotnet core下使用SerialPort類庫在下不能支援的踩坑經歷及解決辦法。

        因網上關於SerialPort類庫使用的相關文章較多,在該文中,對串列埠類庫的使用,一筆帶過。但在實際使用,使用過SerialPort類庫的同學,可能遇到過在資料接收時,由於資料接收事件的觸發具有不確定性,很多時候,一幀通訊協議資料,會多次觸發,造成程式處理協議資料較為麻煩的問題。

        為簡化串列埠通訊類庫的使用,筆者結合自己的相關經驗,封裝了一個自定義增強型跨平臺串列埠類庫,以解決一幀協議資料,多次觸發的問題。

        基礎類庫的選擇

        由於考慮的是跨平臺應用,SerialPort類庫並不支援linux系統(在前一篇文章中已介紹過踩坑經歷),筆者選用了SerialPortStream類庫進行封裝。

        該類庫支援windows系統和Linux系統,但在Linux系統下執行,需要額外編譯目標平臺支援庫並進行相關環境配置。

        相關編譯配置說明在https://.com/jcurl/SerialPortStream已有介紹,也可參考本人的拙作《.net core跨平臺應用研究-串列埠篇》

        類庫的實現

        建立跨平臺類庫

        為了支援跨平臺,我們使用Visual Studio 2017建立一個基於.NET Standard的類庫。

        .NetCore應用增強型跨平臺串列埠類庫CustomSerialPort()詳解

        NET Standard是一項API規範,每一個特定的版本,都定義了必須實現的基類庫。

        .NET Core是一個託管框架,針對構建控制檯、雲、ASP.NET Core和UWP應用程式進行了優化。

        每一種託管實現(如.NET Core、.NET Framework或Xamarin)都必須遵循.NET Standard實現基類庫(BCL)。

        關於NET Standard和跨平臺的詳細說明在此:

        //www.jb51.net/article/234699.htm

        筆者也不再囉嗦呵。

        實現機制/條件

        通常串列埠通訊中,傳送資料後,會有一段時間用於等待接收方應答,如此一來,兩次資料傳送之間,必然會有一定的時間間隔。如ModbusRTU協議就規定,兩次資料報文傳送之間,需要等待超過傳送4個位元組以上的間隔時間。

        筆者在微控制器以及實時性較高的嵌入式系統中,為處理串列埠接收與協議的無關性,通常採用資料幀接收超時來處理資料幀的接收。根據串列埠通訊的速率計算出兩次通訊之間所需要超時間隔,取兩倍超時間隔時間作為超時引數,每接收到一個位元組,將資料放入緩衝區並進行計時,當最後一個位元組的接收時間超過超時時間,返回接收資料並清空快取,一次完整接收完成(DMA接收方式不在此討論)。

        .net core跨平臺實現

        在自定義的串列埠類中,訂閱基礎串列埠類資料接收事件,在接收事件每次觸發後,讀出當前可用的緩衝資料到自定義緩衝區,同時,標記最後接收時間Tick為當前系統Tick。判斷是否開啟了接收超時處理執行緒,如未開啟,則開啟一個接收超時處理執行緒。

        接收超時處理執行緒中,以一個較小的時間間隔進行判斷,如果最後接收時間與當前時間之間的間隔小於設定值(預設128ms),休眠一段時間(預設16ms)後迴圈檢查。如MeUVKKTim間隔時間大於設定值,觸發外部接收訂閱事件,傳出接收到的資料,退出超時處理執行緒。

        此處應有流程圖。呵呵,懶得畫了,大家自行腦補吧。 ^_^

        在windows系統或linux系統中,因系統的多工處理的特性,系統實時性較差,通常50ms以下時間間隔的定時任務,較大程度會出現不可靠的情況(任務執行時間都有可能超過呼叫間隔時間)。

        因此,預設超時時間間隔設定為128ms。也可根據實際使用情況調整,但最小間隔不宜低於64ms。

        注:此處為個人經驗和理解,如不認同,請直接忽視。

        主要程式碼

        串列埠接收事件程式碼:

                 protected void Sp_DataReceived(object sender,SerialDataReceivedEventArgs e)
                 {
                     int canReadBytesLen = 0;
                     if (ReceiveTimeoutEnable)
                     {
                         while (sp.BytesToRead > 0)
                         {
                             canReadBytesLen = sp.BytesToRead;
                             if (receiveDatalen + canReadBytesLen > BufSize)
                             {
                                 receiveDatalen = 0;
                                 throw new Exception("Serial port receives buffer overflow!");
                             }
                             var receiveLen = sp.Read(recviceBuffer,receiveDatalen,canReadBytesLen);
                             if (receiveLen != canReadBytesLen)
                             {
                                 receiveDatalen = 0;
                                 throw new Exception("Serial port receives exception!");
                             }
                             //Array.Copy(recviceBuffer,receivedBytes,receiveLen);
                             receiveDatalen += receiveLen;
                             lastReceiveTick = Environment.TickCount;
                             if (!TimeoutCheckThreadIsWork)
                             {
                                 TimeoutCheckThreadIsWork = true;
                                 Thread thread = new Thread(ReceiveTimeoutCheckFunc)
                                 {
                                     Name = "ComReceiveTimeoutCheckThread"
                                 };
                                 thread.Start();
                             }
                         }
                     }
                     else
                     {
                         if (ReceivedEvent != null)
                         {
                             // 獲取位元組長度
                             int bytesNum = sp.BytesToRead;
                             if (bytesNum == 0)
                                 return;
                             // 建立位元組陣列
                             byte[] resultBuffer = new byte[bytesNum];
         
                             int i = 0;
                             while (i < bytesNum)
                             {
                                 // 讀取資料到緩衝區
                                 int j = sp.Read(recviceBuffer,i,bytesNum - i);
                                 i += j;
                             }
                             Array.Copy(recviceBuffer,resultBuffer,i);
                             ReceivedEvent(this,resultBuffer);
                             //System.Diagnostics.Debug.WriteLine("len " + i.ToString() + " " + ByteToHexStr(resultBuffer));
                         }
                         //Array.Clear (receivedBytes,receivedBytes.Length );
                         receiveDatalen = 0;
                     }
                 }

        接收超時處理執行緒程式碼:

                 /// <summary>
                 /// 超時返回資料處理執行緒方法
                 /// </summary>
                 protected void ReceiveTimeoutCheckFunc()
                 {
                     while (TimeoutCheckThreadIsWork)
                     {
                         if (Environment.TickCount - lastReceiveTick > ReceiveTimeout)
                         {
                             if (ReceivedEvent != null)
                             {
                                 byte[] returnBytes = new byte[receiveDatalen];
                                 Array.Copy(recviceBuffer,returnBytes,receiveDatalen);
                                 ReceivedEvent(this,returnBytes);
                             }
                             //Array.Clear (receivedByhttp://www.cppcns.comtes,receivedBytes.Length );
                             receiveDatalen = 0;
                             TimeoutCheckThreadIsWork = false;
                         }
                         else
                             Thread.Sleep(16);
                     }
                 }

        建立.net core控制檯程式

        為驗證我們的類庫是否能夠正常工作,我們建立一個使用類庫的.net core控制檯程式。

        為啥選擇dotnet core,原因很簡單,跨平臺。本程式分別需在windows和linux系統下進行執行測試。

        • 顯示系統資訊(系統標識、程式標識等)
        • 列舉系統可用串MeUVKKTim口資源
        • 選擇串列埠
        • 開啟串列埠/關閉串列埠
        • 串列埠測試(開啟/傳送/關閉)
                 static void Main(string[] args)
                 {
                     SetLibPath();
                     ShowWelcome();
         
                     GetPortNames();
                     ShowPortNames();
         
                     if (serailports.Length == 0)
                     {
                         Console.WriteLine($"Press any key to exit");
                         Console.ReadKey();
         
                         return;
                     }
         #if RunIsService
                     RunService();
         #endif
         
                     bool quit = false;
                     while (!quit)
                     {
                         Console.WriteLine("\r\nPlease Input command Key\r\n");
                         Console.WriteLine("p:Show SerialPort List");
                         Console.WriteLine($"t:Test Uart:\"{selectedComPort}\"");
                         Console.WriteLine($"o:Open Uart:\"{selectedComPort}\"");
                         Console.WriteLine($"c:Close Uart:\"{selectedComPort}\"");
                         Console.WriteLine("n:select next serial port");
                         Console.WriteLine("q:exit app");
                         Console.WriteLine();
                         var key = Console.ReadKey().KeyChar;
                         Console.WriteLine();
         
                         switch (key)
                         {
                             case (Char)27:
                             case 'q':
                             case 'Q':
                                 quit = true;
                                 break;
                             case 's':
                                 ShowWelcome();
                                 break;
                             case 'p':
                                 ShowPortNames();
                                 break;
                             case 'n':
                                 SelectSerialPort();
                                 break;
                             case 't':
                                 TestUart(selectedComPort);
                                 break;
                             case 'w':
                                 TestWinUart(selectedComPort);
                                 break;
                             case 'o':
                                 OpenUart(selectedComPort);
                                 break;
                             case 'c':
                                 CloseUart();
                                 break;
                         }
                     }
                 }

        筆者使用類庫是直接引用類庫專案,大家需要使用的話,可在解決方案資源管理器中,專案的依賴項上點選右鍵

        .NetCore應用增強型跨平臺串列埠類庫CustomSerialPort()詳解

        在NuGet包管理器中,搜尋SerialPort或flyfire即可找到並安裝本類庫。

        .NetCore應用增強型跨平臺串列埠類庫CustomSerialPort()詳解

        類庫地址

        類庫地址:https://www.nuget.org/packages/flyfire.CustomSerialPort

        .NetCore應用增強型跨平臺串列埠類庫CustomSerialPort()詳解

        跨平臺測試

        Windows測試輸出介面

        .NetCore應用增強型跨平臺串列埠類庫CustomSerialPort()詳解

        .NetCore應用增強型跨平臺串列埠類庫CustomSerialPort()詳解

        ubuntu測試輸出介面

        .NetCore應用增強型跨平臺串列埠類庫CustomSerialPort()詳解

        原始碼地址

        類庫原始碼與例程地址:https://github.com/flyfire-cn/flyfire.CustomSerialPort

        有需要的同學,請自行獲取。

        到此這篇關於.Net Core應用增強型跨平臺串列埠類庫CustomSerialPort()的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支援我們。