.Net Core應用增強型跨平臺串口類庫CustomSerialPort()詳解
摘要
在使用SerialPort進行串口協議解析過程中,經常遇到接收單幀協議數據串口接收事件多次觸發,協議解析麻煩的問題。針對此情況,基於開源跨平臺串口類庫SerialPortStrem進行瞭進一步封裝,實現瞭一種接收超時響應事件機制,簡化串口通訊的使用。
引言
最近,寫瞭一篇博文《.net core跨平臺應用研究-串口篇》得到瞭一些園友的好評,文中介紹瞭在跨平臺應用研究過程中,在dotnet core下使用SerialPort類庫在linux下不能支持的踩坑經歷及解決辦法。
因網上關於SerialPort類庫使用的相關文章較多,在該文中,對串口類庫的使用,一筆帶過。但在實際使用,使用過SerialPort類庫的同學,可能遇到過在數據接收時,由於數據接收事件的觸發具有不確定性,很多時候,一幀通訊協議數據,會多次觸發,造成程序處理協議數據較為麻煩的問題。
為簡化串口通訊類庫的使用,筆者結合自己的相關經驗,封裝瞭一個自定義增強型跨平臺串口類庫,以解決一幀協議數據,多次觸發的問題。
基礎類庫的選擇
由於考慮的是跨平臺應用,SerialPort類庫並不支持linux系統(在前一篇文章中已介紹過踩坑經歷),筆者選用瞭SerialPortStream類庫進行封裝。
該類庫支持windows系統和Linux系統,但在Linux系統下運行,需要額外編譯目標平臺支持庫並進行相關環境配置。
相關編譯配置說明在https://github.com/jcurl/SerialPortStream已有介紹,也可參考本人的拙作《.net core跨平臺應用研究-串口篇》
類庫的實現
創建跨平臺類庫
為瞭支持跨平臺,我們使用Visual Studio 2017創建一個基於.NET Standard的類庫。
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)後循環檢查。如間隔時間大於設定值,觸發外部接收訂閱事件,傳出接收到的數據,退出超時處理線程。
此處應有流程圖。呵呵,懶得畫瞭,大傢自行腦補吧。 ^_^
在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, 0, receivedBytes, receiveDatalen, 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, 0, resultBuffer, 0, i); ReceivedEvent(this, resultBuffer); //System.Diagnostics.Debug.WriteLine("len " + i.ToString() + " " + ByteToHexStr(resultBuffer)); } //Array.Clear (receivedBytes,0,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, 0, returnBytes, 0, receiveDatalen); ReceivedEvent(this, returnBytes); } //Array.Clear (receivedBytes,0,receivedBytes.Length ); receiveDatalen = 0; TimeoutCheckThreadIsWork = false; } else Thread.Sleep(16); } }
創建.net core控制臺程序
為驗證我們的類庫是否能夠正常工作,我們創建一個使用類庫的.net core控制臺程序。
為啥選擇dotnet core,原因很簡單,跨平臺。本程序分別需在windows和linux系統下進行運行測試。
- 顯示系統信息(系統標識、程序標識等)
- 列舉系統可用串口資源
- 選擇串口
- 打開串口/關閉串口
- 串口測試(打開/發送/關閉)
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; } } }
筆者使用類庫是直接引用類庫項目,大傢需要使用的話,可在解決方案資源管理器中,項目的依賴項上點擊右鍵
在NuGet包管理器中,搜索SerialPort或flyfire即可找到並安裝本類庫。
類庫地址
類庫地址:https://www.nuget.org/packages/flyfire.CustomSerialPort
跨平臺測試
Windows測試輸出界面
ubuntu測試輸出界面
源碼地址
類庫源碼與例程地址:https://github.com/flyfire-cn/flyfire.CustomSerialPort
有需要的同學,請自行獲取。
到此這篇關於.Net Core應用增強型跨平臺串口類庫CustomSerialPort()的文章就介紹到這瞭。希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。
推薦閱讀:
- .NET Core使用CZGL.SystemInfo庫獲取主機運行資源
- C#實現簡單學生成績管理系統
- .Net Core跨平臺應用開發串口篇HelloArm
- C#9特性record 類型、模式匹配、init 屬性詳情
- C#跨平臺開發之使用C/C++生成的動態鏈接庫