目錄
一、串口助手界面設計
1.1 串口配置
1.2 接收配置
1.3 發送配置
1.4 接收窗口和發送窗口
1.5 狀態顯示窗口
1.6 串口通訊控件
二、程序編寫
2.1 端口號自動識別并顯示在端口號下拉框
功能說明:
2.2? 波特率下拉框顯示
2.3 數據位下拉框顯示
2.4 校驗位下拉框顯示
2.5 停止位下拉框顯示
2.6 停止位列表數字與serialport1使用的枚舉類型對應
2.7 選擇的串口名稱、波特率、數據位、校驗位、停止位參數賦值給serialport1對應的變量
2.8 編寫串口打開按鈕的點擊事件
2.9 串口數據發送程序編寫
2.10 串口數據接收程序編寫
一、串口助手界面設計
1.1 串口配置
采用groupbox控件,其中包括了串口配置、波特率、數據位、校驗位、停止位、RTS、DTR和打開串口控件。
① 串口配置、波特率、數據位、校驗位、停止位:combobox控件和對應文字描述的label控件
② RTS、DTR:checkbox控件
③ 打開串口:最熟悉的button控件 哈哈哈
1.2 接收配置
同樣采用groupbox控件,支持清空數據、暫停接收、保存數據等功能;可選擇十六進制顯示,便于查看原始數據格式;可指定保存路徑,便于數據。
① 自動清空、16進制:checkbox控件
② 手動清空、暫停、保存數據和選擇路徑:button控件
③ 文件保存路徑:textbox控件
1.3 發送配置
同樣采用 GroupBox 控件,支持手動輸入數據發送、文件發送以及自動循環發送功能;可選擇十六進制格式發送,適用于不同通信協議的調試需求;可設置發送時間間隔,滿足周期性發送場景。
① 自動發送、16進制:CheckBox
控件
② 手動發送、清除數據、發送文件和選擇路徑:Button
控件
③ 文件路徑選擇及發送周期設定:TextBox
控件
1.4 接收窗口和發送窗口
接收窗口和發送窗口采用的是richtextbox控件
1.5 狀態顯示窗口
狀態接收采用的是statustrip控件,主要包括了狀態、接收數量、發送數量和清空計數功能。
1.6 串口通訊控件
使用serialport控件進行串口通訊,本次編寫的串口軟件主要是依托于serialport控件及其對應的類,本次串口軟件中serialport空間名字名為serialport1,后續程序中都是使用該名稱進行編寫程序。
二、程序編寫
2.1 端口號自動識別并顯示在端口號下拉框
private void save_btn_Load(object sender, EventArgs e)
{// 將電腦上的串口資源加載到 port_cbb(端口選擇下拉框)中this.port_cbb.Items.AddRange(SerialPort.GetPortNames());// 初始化發送和接收區域的提示文字this.sent_rtb.Text = "我是發送區域";this.receive_rtb.Text = "我是接收區域";
}
其中的serialport是system.IO.ports命名空間下的serialport類的一個實例對象,在串口軟件頁面加載事件中進行創建的實例對象。
?
功能說明:
-
🔍 自動識別串口:通過
SerialPort.GetPortNames()
獲取當前計算機所有可用串口號,并添加到ComboBox
控件中,供用戶選擇,提升了使用的便捷性。 -
?? 區域提示信息:在發送區(
sent_rtb
)和接收區(receive_rtb
)初始化時分別添加默認提示文字,讓用戶一目了然地了解每個區域的功能。
2.2? 波特率下拉框顯示
直接在波特率下拉框控件中的item屬性中設置常用的波特率數值:19200、9600、4800、2400,程序運行以后直接可以根據通訊要求進行選擇對應波特率。
2.3 數據位下拉框顯示
同上述②,直接在控件的item屬性中設置常用的數據長度:5、6、7、8
2.4 校驗位下拉框顯示
同上述③,直接控件的item屬性設置常用的校驗位類型:Odd、Even、None、Mark、Space
2.5 停止位下拉框顯示
同上述④,直接在控件的item屬性中設置常用的停止位類型:1、2、1.5
注意事項:當數據為6、7、8時,停止位只能配置成1或者2;當數據位為5時,停止位只能設置成1或者1.5位。另外,serialport1中使用的停止位是枚舉類型,需要將停止位列表中的數字于serialport1中使用的停止位枚舉類型進行一一對應。
2.6 停止位列表數字與serialport1使用的枚舉類型對應
停止位下拉列表中的數字實際是字符串,serialport1的停止位設置使用“serialport.stopbits”枚舉類型進行設置,將字符串和stopbits枚舉中的選項進行對應,具體如下所示。
switch (stopbit_cbb.SelectedItem.ToString()){case "1.5":this.serialPort1.StopBits = StopBits.OnePointFive;break;case "1":this.serialPort1.StopBits = StopBits.One;break;case "2":this.serialPort1.StopBits = StopBits.Two;break;}
2.7 選擇的串口名稱、波特率、數據位、校驗位、停止位參數賦值給serialport1對應的變量
this.serialPort1.PortName = this.port_cbb.SelectedItem.ToString();this.serialPort1.BaudRate = Convert.ToInt32(baud_cbb.SelectedItem);this.serialPort1.DataBits = Convert.ToInt32(databit_cbb.SelectedItem);this.serialPort1.Parity = (Parity)Enum.Parse(typeof(Parity), checkbit_cbb.Text.ToString());serialPort1.Encoding = Encoding.UTF8;
?其中,serialport.encoding是編碼格式,本次的串口助手中使用的utf-8,用于后續串口數據的發送和接收;由于控件中item參數默認的數據類型是字符串,需要將字符串轉換成serialport需要的數據,涉及字符串轉數字,字符串轉枚舉。校驗位參數是枚舉類型,其在serialport1中可設置的值如下所示:
2.8 編寫串口打開按鈕的點擊事件
基本流程如下所示
開始|v
[點擊“打開串口”按鈕]|v
{openport_btnisopen == false?}| 是v
[配置串口參數]- 串口號(PortName)- 波特率(BaudRate)- 數據位(DataBits)- 校驗位(Parity)- 停止位(StopBits,根據選擇設置)- 編碼格式(UTF-8)|v
[打開串口 serialPort1.Open()]|v
{串口是否成功打開? serialPort1.IsOpen == true}| 是v
[按鈕背景設為綠色]
[按鈕文字設為“關閉串口”]
[設置 openport_btnisopen = true]|v
結束{openport_btnisopen == false?}| 否v
[關閉串口 serialPort1.Close()]
[按鈕背景設為灰色]
[按鈕文字設為“打開串口”]
[設置 openport_btnisopen = false]|v
結束
注意:其中的openport_btnisopen時一個全局變量,在串口軟件界面初次加載的時候賦值為false。
打開串口按鈕事件具體代碼如下所示:
private void openport_btn_Click(object sender, EventArgs e){try{if (openport_btnisopen==false){//串口名稱、波特率、數據位、校驗位、停止位參數設置this.serialPort1.PortName = this.port_cbb.SelectedItem.ToString();this.serialPort1.BaudRate = Convert.ToInt32(baud_cbb.SelectedItem);this.serialPort1.DataBits = Convert.ToInt32(databit_cbb.SelectedItem);this.serialPort1.Parity = (Parity)Enum.Parse(typeof(Parity), checkbit_cbb.Text.ToString());serialPort1.Encoding = Encoding.UTF8;//當數據位為6、7、8位時,停止位只能配置成1或2位;同樣當數據位為5位時,停止位只能為1或1.5位switch (stopbit_cbb.SelectedItem.ToString()){case "1.5":this.serialPort1.StopBits = StopBits.OnePointFive;break;case "1":this.serialPort1.StopBits = StopBits.One;break;case "2":this.serialPort1.StopBits = StopBits.Two;break;}//打開端口this.serialPort1.Open();if (this.serialPort1.IsOpen ==true){this.openport_btn.BackColor = Color.Green;this.openport_btn.Text = "關閉串口";}openport_btnisopen = true;}else{this.serialPort1.Close();this.openport_btn.BackColor = Color.Gainsboro;this.openport_btn.Text = "打開串口";openport_btnisopen = false;} //關閉端口}catch (Exception xc){}}
2.9 串口數據發送程序編寫
在發送數據之前,需要判斷發送框內的數據是否為空和串口通訊是否打開這兩個條件。如果條件滿足,使用“encoding.Getbytes”函數將發送框內的數據換成字節數據,使用serialport.write函數將轉換得到的字節數據發送給對方。
注意:本次串口助手軟件中沒有補充不滿足條件的執行代碼,小伙伴可以根據自己的要求進行補充完善。
private void hand_send_btn_Click(object sender, EventArgs e){if (sent_rtb.Text !=null && this.serialPort1.IsOpen){Encoding Chinese = System.Text.Encoding.UTF8;//定義一個可以進行中文編碼的變量byte[] writeBytes = Chinese.GetBytes(sent_rtb.Text);this.serialPort1.Write(writeBytes,0, writeBytes.Length);//this.serialPort1.Write(writeBytes,0, writeBytes.Length);}}
2.10 串口數據接收程序編寫
這個是B站上up主編寫的數據接收程序,使用的編碼格式是GB2312,首先在串口數據接收事件中聲明了個字節數組,字節數組長度是串口緩存區中可以讀取字節的數目,使用串口的read函數,將串口緩存區中的可讀字節數據讀取并存到字節數組中。同時將字節數組數據存儲到全局的列表recivebuffer中,使用invoke將讀取的數據在UI界面里的窗口顯示區域進行顯示(涉及到UI線程和非UI線程的問題,有興趣的小伙伴可以查點資料看看)。
2.10.1 VSPD軟件
這里介紹一個虛擬串口軟件Virtual Serial Port Driver,它是 Eltima Software 出品的一款 虛擬串口工具,可以在系統中創建成對的虛擬串口(如 COM3 <-> COM4),廣泛用于串口通信調試、設備模擬和串口軟件開發中。
具體原理如下:
1.串口通訊調試
? 開發者可用它來模擬串口設備之間的通信,實現無需真實硬件的調試環境。
-
例如:開發一款串口通信的軟件,但沒有真實的串口設備,就可以用 COM3 <-> COM4 虛擬一對串口,一端運行你的軟件,另一端用串口助手發送測試數據。
2.串口數據轉發、轉接協議調試
? 可用于協議橋接、數據透傳、協議中間件調試。
-
比如:
-
A 程序向 COM5 發數據;
-
B 程序從 COM6 接收;
-
COM5 和 COM6 通過 VSPD 連接;
-
就可以驗證 A 和 B 之間串口協議是否兼容。
-
具體使用教程可以參考博客:
VSPD工具 - Virtual Serial Port Driver v6.9 激活教程&使用教程 - Citrusliu - 博客園
具體下載鏈接(網上很多資源):
https://gitcode.com/open-source-toolkit/29e06/?utm_source=tools_gitcode&index=top&type=cardhttps://gitcode.com/open-source-toolkit/29e06/?utm_source=tools_gitcode&index=top&type=card
2.10.2 串口通訊實驗
根據上述截圖中的串口數據接收代碼,自己進行了一定的改寫,使得程序能夠滿足自己串口軟件的使用。具體程序如下所示:
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e){//如果暫停按鈕被按下則停止接收數據if (receiveisopen == false){return;}if (serialPort1.BytesToRead==0){return;}byte[] z = new byte[this.serialPort1.BytesToRead];this.serialPort1.Read(z,0,z.Length);datahuanchun.AddRange(z);this.Invoke(new EventHandler(delegate{if (hex_receive_ckb.Checked == true){//編寫數據采用16進制進行顯示}else{//編寫數據采用字符串進行顯示if (serialPort1.BytesToRead == 0){//判斷串口緩存區沒有可以在讀的字節數據string xx = Encoding.UTF8.GetString(datahuanchun.ToArray());receive_rtb.AppendText(xx);//接收完成后清空datahaunchundatahuanchun.Clear();}}}));
具體實驗結果如下圖所示:
這個問題如果你用單步調試可能查不出問題,因為單步調試執行一步,中間存在間隔時間,串口傳輸的線程可以在間隔時間內將全部數據發送給你,當你解碼的時候,數據往往是一段完整的數據。
注意事項:在使用串口方式進行數據收發過程中,需要知道串口數據并不是一次性全部發送,它可能是分多批次地給你發送。這個結果就可能導致中文轉碼時發生亂碼問題,為什么這么說呢,聽我細細道來:
(1)單個字母和數字對應一個字節
(2)一個中文(UTF8編碼)對應三個字節;一個中文(GB2312編碼)對應兩個字節
舉個例子:
舉個例子:
假設你串口接收 "你" 字(UTF-8編碼為 E4 BD A0
):
-
第一次讀取:收到
E4 BD
(2個字節,還差1個字節) -
第二次讀取:收到
A0
程序連續執行時(正常運行):
-
程序執行速度快,串口緩沖區數據還沒完全到齊你就開始處理。
-
比如 UTF-8 中文“你”應為
E4 BD A0
,但你先只收到E4 BD
,立刻嘗試解碼就會報錯或亂碼。
2.10.3 中文亂碼解決方法
以下是程序編寫流程圖:需要用到lock鎖
開始|v
[進入 serialPort1_DataReceived 事件]|v
{是否按下“暫停接收”按鈕? (receiveisopen == false)}| 是v
[直接 return,結束接收]|v
結束|否v
{串口接收緩存是否為空? (BytesToRead == 0)}| 是v
[直接 return,結束接收]|否v
[定義接收字節數組 z,并讀取數據到 z]|v
[將讀取數據加入 datahuanchun 緩存(加鎖 lock)]|v
[Invoke 主線程處理接收顯示邏輯]|v
{是否勾選“16進制顯示”? (hex_receive_ckb.Checked == true)}| 是v
[此處未寫實現邏輯,可擴展為 hex 格式顯示]|否v
[進入文本顯示分支(加鎖 datahuanchun)]|v[UTF8 解碼緩存數據 → charBuffer]|v[將解碼結果追加到接收顯示區 receive_rtb]|v[清空 datahuanchun 緩存]|v[更新接收數據統計 senddata_num + data長度]|v[更新狀態欄 toolStripStatusLabel4]|v
結束
等后面串口軟件寫好后,再上傳下一部分內容