在C#中使用SerialPort類實現串口通信
下面主要介紹該類的主要屬性(表1)和方法(表.2)。
如果需要了解更多的信息請登錄http://msdn.microsoft.com/zh-cn/library/system.io.ports.serialport(VS.80).aspx查看。
相關文章
《使用System.IO.Ports讀取COM口數據》
http://www.devasp.net/net/articles/display/727.html
表1 SerialPort類的常用屬性
名 稱 |
說 明 |
BaseStream |
獲取 SerialPort 對象的基礎 Stream 對象 |
BaudRate |
獲取或設置串行波特率 |
BreakState |
獲取或設置中斷信號狀態 |
BytesToRead |
獲取接收緩沖區中數據的字節數 |
BytesToWrite |
獲取發送緩沖區中數據的字節數 |
CDHolding |
獲取端口的載波檢測行的狀態 |
CtsHolding |
獲取“可以發送”行的狀態 |
DataBits |
獲取或設置每個字節的標准數據位長度 |
DiscardNull |
獲取或設置一個值,該值指示 Null 字節在端口和接收緩沖區之間傳輸時是否被忽略 |
DsrHolding |
獲取數據設置就緒 (DSR) 信號的狀態 |
DtrEnable |
獲取或設置一個值,該值在串行通信過程中啟用數據終端就緒 (DTR) 信號 |
Encoding |
獲取或設置傳輸前后文本轉換的字節編碼 |
Handshake |
獲取或設置串行端口數據傳輸的握手協議 |
IsOpen |
獲取一個值,該值指示 SerialPort 對象的打開或關閉狀態 |
NewLine |
獲取或設置用於解釋 ReadLine( )和WriteLine( )方法調用結束的值 |
Parity |
獲取或設置奇偶校驗檢查協議 |
續表
名 稱 |
說 明 |
ParityReplace |
獲取或設置一個字節,該字節在發生奇偶校驗錯誤時替換數據流中的無效字節 |
PortName |
獲取或設置通信端口,包括但不限於所有可用的 COM 端口 |
ReadBufferSize |
獲取或設置 SerialPort 輸入緩沖區的大小 |
ReadTimeout |
獲取或設置讀取操作未完成時發生超時之前的毫秒數 |
ReceivedBytesThreshold |
獲取或設置 DataReceived 事件發生前內部輸入緩沖區中的字節數 |
RtsEnable |
獲取或設置一個值,該值指示在串行通信中是否啟用請求發送 (RTS) 信號 |
StopBits |
獲取或設置每個字節的標准停止位數 |
WriteBufferSize |
獲取或設置串行端口輸出緩沖區的大小 |
WriteTimeout |
獲取或設置寫入操作未完成時發生超時之前的毫秒數 |
表2 SerialPort類的常用方法
方 法 名 稱 |
說 明 |
Close |
關閉端口連接,將 IsOpen 屬性設置為False,並釋放內部 Stream 對象 |
Open |
打開一個新的串行端口連接 |
Read |
從 SerialPort 輸入緩沖區中讀取 |
ReadByte |
從 SerialPort 輸入緩沖區中同步讀取一個字節 |
ReadChar |
從 SerialPort 輸入緩沖區中同步讀取一個字符 |
ReadLine |
一直讀取到輸入緩沖區中的 NewLine 值 |
ReadTo |
一直讀取到輸入緩沖區中指定 value 的字符串 |
Write |
已重載。將數據寫入串行端口輸出緩沖區 |
WriteLine |
將指定的字符串和 NewLine 值寫入輸出緩沖區 |
使用SerialPort類的方法:
方法一:
首先要添加
using System.IO; using System.IO.Ports;
1...在類的內部定義SerialPort com;
2...打開串口
com = new SerialPort();
com.BaudRate = 115200;
com.PortName = "COM1";
com.DataBits = 8;
com.Open();//打開串口
3...發送數據
Byte[] TxData ={1,2,3,4,5,6,7,8 };
com.Write(TxData, 0, 8);
4...接收數據
4.1使用事件接收
this.com.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(this.OnDataReceived);
private void OnDataReceived(object sender, SerialDataReceivedEventArgs e)
4.2使用線程接收
接收數據啟動一個線程,使其接收。
在類的內部定義
Thread _readThread;
bool _keepReading;
打開串口后啟動線程
_keepReading = true;
_readThread = new Thread(ReadPort);
_readThread.Start();
線程函數
- privatevoid ReadPort()
- {
- while (_keepReading)
- {
- if (com.IsOpen)
- {
- byte[] readBuffer = newbyte[com.ReadBufferSize + 1];
- try
- {
- // If there are bytes available on the serial port,
- // Read returns up to "count" bytes, but will not block (wait)
- // for the remaining bytes. If there are no bytes available
- // on the serial port, Read will block until at least one byte
- // is available on the port, up until the ReadTimeout milliseconds
- // have elapsed, at which time a TimeoutException will be thrown.
- int count = com.Read(readBuffer, 0, com.ReadBufferSize);
- String SerialIn = System.Text.Encoding.ASCII.GetString(readBuffer, 0, count);
- if (count != 0)
- //byteToHexStr(readBuffer);
- ThreadFunction(byteToHexStr(readBuffer,count));
- }
- catch (TimeoutException) { }
- }
- else
- {
- TimeSpan waitTime = new TimeSpan(0, 0, 0, 0, 50);
- Thread.Sleep(waitTime);
- }
- }
- }
方法二:使用C#自帶的SerialPor控件。
1...在“工具箱”的“組件”中選擇SerialPor控件添加。
2...設置串口並打開
serialPort1.PortName = "COM1";
serialPort1.BaudRate = 9600;
serialPort1.Open();
3...寫入數據可以使用Write或者下面的函數
serialPort1.WriteLine(str);
4...添加數據接收的事件
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
使用中的一些常見問題
C#中SerialPort類中DataReceived事件GUI實時處理方法(來自wanglei_wan@yahoo.com.cn 的看法)
MSDN: 從 SerialPort 對象接收數據時,將在輔助線程上引發 DataReceived 事件。由於此事件在輔助線程而非主線程上引發,因此嘗試修改主線程中的一些元素(如 UI 元素)時會引發線程異常。如果有必要修改主 Form 或 Control 中的元素,必須使用 Invoke 回發更改請求,這將在正確的線程上執行.進而要想將輔助線程中所讀到的數據顯示到主線程的Form控件上時,只有通過Invoke方法來實現 下面是代碼實例:
privatevoid serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
int SDateTemp = this.serialPort1.ReadByte();
//讀取串口中一個字節的數據
this.tB_ReceiveDate.Invoke(
//在擁有此控件的基礎窗口句柄的線程上執行委托Invoke(Delegate)
//即在textBox_ReceiveDate控件的父窗口form中執行委托.
new MethodInvoker(
/*表示一個委托,該委托可執行托管代碼中聲明為 void 且不接受任何參數的任何方法。 在對控件的 Invoke 方法進行調用時或需要一個簡單委托又不想自己定義時可以使用該委托。*/
delegate{
/* 匿名方法,C#2.0的新功能,這是一種允許程序員將一段完整代碼區塊當成參數傳遞的程序代碼編寫技術,通過此種方法可 以直接使用委托來設計事件響應 程序以下就是你要在主線程上實現的功能但是有一點要注意,這里不適宜處理過多的方法,因為C#消息機制是消息流水線響應機制,如果這里在主線程上處理語句 的時間過長會導致主UI線程阻塞,停止響應或響應不順暢,這時你的主form界面會延遲或卡死 */
this.tB_ReceiveDate.AppendText(SDateTemp.ToString());//輸出到主窗口文本控件
this.tB_ReceiveDate.Text += " ";}
)
);
}
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
int SDateTemp = this.serialPort1.ReadByte(); //讀取串口中一個字節的數據
this.tB_ReceiveDate.Invoke( //在擁有此控件的基礎窗口句柄的線程上執行委托
Invoke(Delegate) //即在textBox_ReceiveDate控件的父窗口form中執行委托.
new MethodInvoker( /*表示一個委托,該委托可執行托管代碼中聲明為 void 且不接受任何參數的任何方法。 在對控件的 Invoke 方法進行調用時或需要一個簡單委托又不想自己定義時可以使用該委托。*/
delegate{ /*匿名方法,C#2.0的新功能,這是一種允許程序員將一段完整代碼區塊當成參數傳遞的程序代碼編寫技術,通過此種方法可 以直接使用委托來設計事件響應程序以下就是你要在主線程上實現的功能但是有一點要注意,這里不適宜處理過多的方法,因為C#消息機制是消息流水線響應機 制,如果這里在主線程上處理語句的時間過長會導致主UI線程阻塞,停止響應或響應不順暢,這時你的主form界面會延遲或卡死 */
this.tB_ReceiveDate.AppendText(SDateTemp.ToString());//輸出到主窗口文本控件
this.tB_ReceiveDate.Text += " ";} ) );
}
如何知道當前電腦有哪個串口
在窗體上添加一個comboBox控件。
然后使用comboBox1.Items.AddRange(System.IO.Ports.SerialPort.GetPortNames()); 或者
string[] portList = System.IO.Ports.SerialPort.GetPortNames();
for (int i = 0; i < portList.Length; ++i)
{
string name = portList[i];
comboBox1.Items.Add(name);
}
SerialPort中串口數據的讀取與寫入有較大的不同。由於串口不知道數據何時到達,因此有兩種方法可以實現串口數據的讀取。一、線程實時讀串口;二、事件觸發方式實現。 由 於線程實時讀串口的效率不是十分高效,因此比較好的方法是事件觸發的方式。在SerialPort類中有DataReceived事件,當串口的讀緩存有 數據到達時則觸發DataReceived事件,其中SerialPort.ReceivedBytesThreshold屬性決定了當串口讀緩存中數據 多少個時才觸發DataReceived事件,默認為1。 另外,SerialPort.DataReceived事件運行比較特殊,其運行在輔線程,不能與主線程中的顯示數據控件直接進行數據傳輸,必須用間接的方式實現。如下:
SerialPort spSend; //spSend,spReceive用虛擬串口連接,它們之間可以相互傳輸數據。spSend發送數據 SerialPort spReceive; //spReceive接受數據 TextBox txtSend; //發送區 TextBox txtReceive; //接受區 Button btnSend; //數據發送按鈕 delegate void HandleInterfaceUpdateDelegate(string text); //委托,此為重點 HandleInterfaceUpdateDelegate interfaceUpdateHandle;
public void InitClient() //窗體控件已在初始化 { interfaceUpdateHandle = new HandleInterfaceUpdateDelegate(UpdateTextBox); //實例化委托對象 spSend.Open(); //SerialPort對象在程序結束前必須關閉,在此說明 spReceive.DataReceived += Ports.SerialDataReceivedEventHandler(spReceive_DataReceived); spReceive.ReceivedBytesThreshold = 1; spReceive.Open(); }
public void btnSend_Click(object sender,EventArgs e) { spSend.WriteLine(txtSend.Text); }
public void spReceive_DataReceived(object sender,Ports.SerialDataReceivedEventArgs e) { byte[] readBuffer = new byte[spReceive.ReadBufferSize]; spReceive.Read(readBuffer, 0, readBuffer.Length); this.Invoke(interfaceUpdateHandle, new string[] { Encoding.Unicode.GetString(readBuffer) }); }
private void UpdateTextBox(string text) { txtReceive.Text = text; }
private string StringToHexString(string s,Encoding encode) { byte[] b = encode.GetBytes(s);//按照指定編碼將string編程字節數組 string result = string.Empty; for (int i = 0; i < b.Length; i++)//逐字節變為16進制字符,以%隔開 { result += "%"+Convert.ToString(b[i], 16); } return result; } private string HexStringToString(string hs, Encoding encode) { //以%分割字符串,並去掉空字符 string[] chars = hs.Split(new char[]{'%'},StringSplitOptions.RemoveEmptyEntries); byte[] b = new byte[chars.Length]; //逐個字符變為16進制字節數據 for (int i = 0; i < chars.Length; i++) { b[i] = Convert.ToByte(chars[i], 16); } //按照指定編碼將字節數組變為字符串 return encode.GetString(b); }