.Net Core跨平臺應用研究-CustomSerialPort(加強型跨平臺串口類庫)

.Net Core跨平臺應用研究-CustomSerialPort

-加強型跨平臺串口類庫

摘要

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

引言

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

      因網上關於SerialPort類庫使用的相關文章較多,在該文中,對串口類庫的使用,一筆帶過。但在實際使用,使用過SerialPort類庫的同窗,可能遇到過在數據接收時,因爲數據接收事件的觸發具備不肯定性,不少時候,一幀通信協議數據,會屢次觸發,形成程序處理協議數據較爲麻煩的問題。git

      爲簡化串口通信類庫的使用,筆者結合本身的相關經驗,封裝了一個自定義加強型跨平臺串口類庫,以解決一幀協議數據,屢次觸發的問題。github

 基礎類庫的選擇

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

 

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

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

類庫的實現

建立跨平臺類庫

     爲了支持跨平臺,咱們使用Visual Studio017建立一個基於.NET Standard的類庫。緩存

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

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

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

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

     https://zhuanlan.zhihu.com/p/30081607

     筆者也再也不囉嗦呵。

實現機制/條件

     一般串口通信中,發送數據後,會有一段時間用於等待接收方應答,如此一來,兩次數據發送之間,必然會有必定的時間間隔。如ModbusRTU協議就規定,兩次數據報文發送之間,須要等待超過發送4個字節以上的間隔時間。

     筆者在單片機以及實時性較高的嵌入式系統中,爲處理串口接收與協議的無關性,一般採用數據幀接收超時來處理數據幀的接收。根據串口通信的速率計算出兩次通信之間所須要超時間隔,取兩倍超時間隔時間做爲超時參數,每接收到一個字節,將數據放入緩衝區並進行計時,當最後一個字節的接收時間超過超時時間,返回接收數據並清空緩存,一次完整接收完成(DMA接收方式不在此討論)。

.net core跨平臺實現

     在自定義的串口類中,訂閱基礎串口類數據接收事件,在接收事件每次觸發後,讀出當前可用的緩衝數據到自定義緩衝區,同時,標記最後接收時間Tick爲當前系統Tick。判斷是否開啓了接收超時處理線程,如未開啓,則開啓一個接收超時處理線程。

     接收超時處理線程中,以一個較小的時間間隔進行判斷,若是最後接收時間與當前時間之間的間隔小於設置值(默認128ms),休眠一段時間(默認16ms)後循環檢查。如間隔時間大於設定值,觸發外部接收訂閱事件,傳出接收到的數據,退出超時處理線程。

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

     在windows系統或linux系統中,因系統的多任務處理的特性,系統實時性較差,一般50ms如下時間間隔的定時任務,較大程度會出現不可靠的狀況(任務執行時間都有可能超過調用間隔時間)。

     所以,默認超時時間間隔設置爲128ms。也可根據實際使用狀況調整,但最小間隔不宜低於64ms。

     注:此處爲我的經驗和理解,如不認同,請直接忽視。

 

主要代碼

       串口接收事件代碼:    

 1         protected void Sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
 2         {
 3             int canReadBytesLen = 0;
 4             if (ReceiveTimeoutEnable)
 5             {
 6                 while (sp.BytesToRead > 0)
 7                 {
 8                     canReadBytesLen = sp.BytesToRead;
 9                     if (receiveDatalen + canReadBytesLen > BufSize)
10                     {
11                         receiveDatalen = 0;
12                         throw new Exception("Serial port receives buffer overflow!");
13                     }
14                     var receiveLen = sp.Read(recviceBuffer, receiveDatalen, canReadBytesLen);
15                     if (receiveLen != canReadBytesLen)
16                     {
17                         receiveDatalen = 0;
18                         throw new Exception("Serial port receives exception!");
19                     }
20                     //Array.Copy(recviceBuffer, 0, receivedBytes, receiveDatalen, receiveLen);
21                     receiveDatalen += receiveLen;
22                     lastReceiveTick = Environment.TickCount;
23                     if (!TimeoutCheckThreadIsWork)
24                     {
25                         TimeoutCheckThreadIsWork = true;
26                         Thread thread = new Thread(ReceiveTimeoutCheckFunc)
27                         {
28                             Name = "ComReceiveTimeoutCheckThread"
29                         };
30                         thread.Start();
31                     }
32                 }
33             }
34             else
35             {
36                 if (ReceivedEvent != null)
37                 {
38                     // 獲取字節長度
39                     int bytesNum = sp.BytesToRead;
40                     if (bytesNum == 0)
41                         return;
42                     // 建立字節數組
43                     byte[] resultBuffer = new byte[bytesNum];
44 
45                     int i = 0;
46                     while (i < bytesNum)
47                     {
48                         // 讀取數據到緩衝區
49                         int j = sp.Read(recviceBuffer, i, bytesNum - i);
50                         i += j;
51                     }
52                     Array.Copy(recviceBuffer, 0, resultBuffer, 0, i);
53                     ReceivedEvent(this, resultBuffer);
54                     //System.Diagnostics.Debug.WriteLine("len " + i.ToString() + " " + ByteToHexStr(resultBuffer));
55                 }
56                 //Array.Clear (receivedBytes,0,receivedBytes.Length );
57                 receiveDatalen = 0;
58             }
59         }
View Code

 

      接收超時處理線程代碼:

 1         /// <summary>
 2         /// 超時返回數據處理線程方法
 3         /// </summary>
 4         protected void ReceiveTimeoutCheckFunc()
 5         {
 6             while (TimeoutCheckThreadIsWork)
 7             {
 8                 if (Environment.TickCount - lastReceiveTick > ReceiveTimeout)
 9                 {
10                     if (ReceivedEvent != null)
11                     {
12                         byte[] returnBytes = new byte[receiveDatalen];
13                         Array.Copy(recviceBuffer, 0, returnBytes, 0, receiveDatalen);
14                         ReceivedEvent(this, returnBytes);
15                     }
16                     //Array.Clear (receivedBytes,0,receivedBytes.Length );
17                     receiveDatalen = 0;
18                     TimeoutCheckThreadIsWork = false;
19                 }
20                 else
21                     Thread.Sleep(16);
22             }
23         }
View Code

 

建立.net core控制檯程序

    爲驗證咱們的類庫是否可以正常工做,咱們建立一個使用類庫的.net core控制檯程序。

    爲啥選擇dotnet core,緣由很簡單,跨平臺。本程序分別需在windows和linux系統下進行運行測試。

    控制檯程序主要實現如下功能:

  •     顯示系統信息(系統標識、程序標識等)
  •     列舉系統可用串口資源
  •     選擇串口
  •     打開串口/關閉串口
  •     串口測試(打開/發送/關閉)
 1         static void Main(string[] args)
 2         {
 3             SetLibPath();
 4             ShowWelcome();
 5 
 6             GetPortNames();
 7             ShowPortNames();
 8 
 9             if (serailports.Length == 0)
10             {
11                 Console.WriteLine($"Press any key to exit");
12                 Console.ReadKey();
13 
14                 return;
15             }
16 #if RunIsService
17             RunService();
18 #endif
19 
20             bool quit = false;
21             while (!quit)
22             {
23                 Console.WriteLine("\r\nPlease Input command Key\r\n");
24                 Console.WriteLine("p:Show SerialPort List");
25                 Console.WriteLine($"t:Test Uart:\"{selectedComPort}\"");
26                 Console.WriteLine($"o:Open Uart:\"{selectedComPort}\"");
27                 Console.WriteLine($"c:Close Uart:\"{selectedComPort}\"");
28                 Console.WriteLine("n:select next serial port");
29                 Console.WriteLine("q:exit app");
30                 Console.WriteLine();
31                 var key = Console.ReadKey().KeyChar;
32                 Console.WriteLine();
33 
34                 switch (key)
35                 {
36                     case (Char)27:
37                     case 'q':
38                     case 'Q':
39                         quit = true;
40                         break;
41                     case 's':
42                         ShowWelcome();
43                         break;
44                     case 'p':
45                         ShowPortNames();
46                         break;
47                     case 'n':
48                         SelectSerialPort();
49                         break;
50                     case 't':
51                         TestUart(selectedComPort);
52                         break;
53                     case 'w':
54                         TestWinUart(selectedComPort);
55                         break;
56                     case 'o':
57                         OpenUart(selectedComPort);
58                         break;
59                     case 'c':
60                         CloseUart();
61                         break;
62                 }
63             }
64         }
View Code

 

     筆者使用類庫是直接引用類庫項目,你們須要使用的話,可在解決方案資源管理器中,項目的依賴項上點擊右鍵

 

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

 

類庫地址

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

 

 

 

跨平臺測試

Windows測試輸出界面

 

ubuntu測試輸出界面

 

源碼地址

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

     有須要的同窗,請自行獲取。

相關文章
相關標籤/搜索