串行通訊之.NET SerialPort


1串行通訊之.NET SerialPort    2

1 枚舉串口    2

2 打開/關閉串口    2

3 寫數據    3

3.1 寫二進制數據    3

3.2 寫文本數據    4

4 讀數據    5

4.1 讀二進制數據    6

4.2 讀一個字節    7

4.3 讀一個字符    7

4.4 讀全部文本    7

4.5 讀文本到某個字符串    8

4.6 讀一行文本    8

4.7 DataReceived事件    8

5 流控制    9

5.1 軟件流控制(XON/XOFF    10

5.2 硬件流控制(RTS/CTS    10

6 輸入信號    11

 

 

1串行通訊之.NET SerialPort

.NET 庫中類System.IO.Ports.SerialPort用於串行通訊,本文對其使用進行簡要說明。

1 枚舉串口

函數System.IO.Ports.SerialPort.GetPortNames將獲得系統所有的串口名稱。C#代碼如下:

string[] arrPort = System.IO.Ports.SerialPort.GetPortNames();

foreach (string s in arrPort)

{

}

2 打開/關閉串口

下面的 C# 代碼將打開 COM100:1200,N,8,1

System.IO.Ports.SerialPort m_sp = new System.IO.Ports.SerialPort();

private void btnOpen_Click(object sender, EventArgs e)

{

m_sp.PortName = "COM100";            //串口

m_sp.BaudRate = 1200;                 //波特率:1200

m_sp.Parity = System.IO.Ports.Parity.None;    //校驗法:無

m_sp.DataBits = 8;                     //數據位:8

m_sp.StopBits = System.IO.Ports.StopBits.One;     //停止位:1

try

{

m_sp.Open();        //打開串口

m_sp.DtrEnable = true;     //設置DTR為高電平(含義見下文)

m_sp.RtsEnable = true;     //設置RTS為高電平(含義見下文)

}

catch (System.Exception ex)

{//打開串口出錯,顯示錯誤信息

MessageBox.Show(ex.Message);

}

} 

下面的 C# 代碼將關閉打開的串口

if(m_sp.IsOpen) //判斷串口是否已經被打開

{

m_sp.Close(); //關閉串口

}

3 寫數據

System.IO.Ports.SerialPort用於寫串口數據的成員函數有四個,如下所示:

void Write(byte[] buffer, int offset, int count);

void Write(char[] buffer, int offset, int count);

寫二進制數據

void Write(string text);

寫文本數據

void WriteLine(string text); 

寫一行文本

3.1 寫二進制數據

void Write(byte[] buffer, int offset, int count);void Write(char[] buffer, int offset, int count);用於寫二進制數據。它們的區別僅僅在於第一個參數不同:byte[]是無符號的,char[]是有符號的。對於二進制數據而言,bytechar沒有實質的區別。

下面的C#代碼,將寫102400H

try

{

if(m_sp.IsOpen)

{

byte[] bt = new byte[1024];

m_sp.Write(bt,0,bt.Length); // 1024 00H

}

}

catch (System.Exception ex)

{//顯示錯誤信息

MessageBox.Show(ex.Message);

}

注意:

1Write函數是同步的。以上面的代碼為例,102400H在發送完之前,Write函數是不會返回的。波特率1200,發送1024個字節大概要耗時9秒。如果這段代碼在主線程里,那么這9秒內整個程序將處於假死狀態:無法響應用戶的鍵盤、鼠標輸入;

2WriteTimeout屬性用於控制Write函數的最長耗時。它的默認值為System.IO.Ports.SerialPort.InfiniteTimeout,也就是-1。其含義為:Write函數不將所有數據寫完絕不返回。可以修改此屬性,如下面的代碼:

try

{

if(m_sp.IsOpen)

{

byte[] bt = new byte[1024];

m_sp.WriteTimeout = 5000; //Write 函數最多耗時 5000 毫秒

m_sp.Write(bt,0,bt.Length); // 1024 00H

}

}

catch (System.Exception ex)

{//顯示錯誤信息

MessageBox.Show(ex.Message);

}

上面的代碼中,設置WriteTimeout屬性為5秒。所以Write寫數據時最多耗時5秒,超過這個時間未發的數據將被舍棄,Write函數拋出異常TimeoutException后立即返回。

3.2 寫文本數據

下面是void Write(string text)的示例代碼

try

{

if(m_sp.IsOpen)

{

m_sp.Encoding = System.Text.Encoding.GetEncoding(936);

m_sp.Write("串行通訊");

}

}

catch (System.Exception ex)

{//顯示錯誤信息

MessageBox.Show(ex.Message);

}

首先設置代碼頁為936(即GBK碼),Write(string text)函數根據代碼頁把字符串"串行通訊"轉換為二進制數據,如下所示:

字符串

內碼

B4 AE

D0 D0

CD A8

D1 B6

然后把二進制數據B4 AE D0 D0 CD A8 D1 B6發送出去。

函數void WriteLine(string text);等價於void Write(text + NewLine)。參考下面的代碼:

try

{

if(m_sp.IsOpen)

{

m_sp.Encoding = System.Text.Encoding.GetEncoding(936);

m_sp.NewLine = "\r\n";

m_sp.WriteLine("串行通訊");

}

}

catch (System.Exception ex)

{//顯示錯誤信息

MessageBox.Show(ex.Message);

}

代碼m_sp.NewLine = "\r\n";設置行結束符為回車(0DH)換行(0AH)。m_sp.WriteLine("串行通訊");等價於m_sp.Write("串行通訊"+m_sp.NewLine);也就是m_sp.Write("串行通訊\r\n");

最終,發送出去的二進制數據為B4 AE D0 D0 CD A8 D1 B6 0D 0A

4 讀數據

System.IO.Ports.SerialPort用於讀串口數據的成員函數有七個,如下所示:

int ReadByte();

讀取一個字節

int ReadChar(); 

讀取一個字符

int Read(byte[] buffer, int offset, int count);

int Read(char[] buffer, int offset, int count);

讀取二進制數據

string ReadExisting();

讀取全部文本

string ReadTo(string value); 

讀取文本到某個字符串

string ReadLine(); 

讀取一行文本

4.1 讀二進制數據

下面的C#代碼,將讀取 3 個字節的串口數據

if(m_sp.IsOpen)

{

try

{

byte[] b = new byte[3];

int n = m_sp.Read(b,0,3); //返回值是讀取到的字節數

}

catch (System.Exception ex)

{

MessageBox.Show(ex.Message);

}

}

注意:

1Read函數是同步的。以上面的代碼為例,3個字節的數據被讀取之前,Read函數是不會返回的。如果這段代碼在主線程里,那么整個程序將處於假死狀態;

2ReadTimeout屬性用於控制Read函數的最長耗時。它的默認值為System.IO.Ports.SerialPort.InfiniteTimeout,也就是-1。其含義為:Read函數未讀取到串口數據之前是不會返回的。可以修改此屬性,如下面的代碼:

if(m_sp.IsOpen)

{

try

{

byte[] b = new byte[3];

m_sp.ReadTimeout = 2000;

int n = m_sp.Read(b,0,3); //返回值是讀取到的字節數

}

catch (System.Exception ex)

{

MessageBox.Show(ex.Message);

}

}

上面的代碼中,設置ReadTimeout屬性為2秒。所以Read函數讀數據時最多耗時2秒。超過這個時間未讀取到數據,Read函數將拋出異常TimeoutException,然后返回。

4.2 讀一個字節

int ReadByte();int Read(byte[] buffer, int offset, int count);類似,它的特點就是只讀取一個字節的串口數據。

4.3 讀一個字符

int ReadChar();是讀取一個字符,這個稍微復雜些。它可能讀取1~3個字節的數據,然后合為一個字符。

如:m_sp.Encoding = System.Text.Encoding.GetEncoding(936);即字符串編碼為GBK。給m_sp發送"串"的GBK編碼B4 AEReadChar首先讀取一個字節得到B4H。這是一個漢字的區碼,還得讀取一個字節得到位碼。最終ReadChar讀取的是B4 AEReadChar的返回值是Unicode編碼,即返回前會把GBK編碼B4 AE轉換為Unicode編碼0x4E32

再如:m_sp.Encoding = System.Text.Encoding.UTF8;即字符串編碼為UTF8。給m_sp發送"串"的UTF8編碼E4 B8 B2ReadChar會讀取三個字節的串口數據E4 B8 B2,然后將其轉換為Unicode編碼0x4E32,並返回這個數值。

4.4 讀全部文本

函數string ReadExisting();讀取串口輸入緩沖區中的所有二進制數據,然后將其轉換為字符串,最后返回字符串。

注意:

1ReadExisting會立即返回。如果輸入緩沖區內沒有數據,直接返回長度為零的空字符串;

2ReadExisting讀取輸入緩沖區后,有時會留幾個字節。參考下面的代碼:

m_sp.Encoding = System.Text.Encoding.GetEncoding(936);

string s = m_sp.ReadExisting();

int n = m_sp.BytesToRead; //輸入緩沖區剩余的字節數

"串"、"行"的GBK編碼分別為 B4 AED0 D0

首先發送 B4 AE D0 m_sp,運行上述代碼。ReadExisting將獲得B4 AE D0,"B4 AE"會被解釋為"串",D0是漢字的區碼,所以ReadExisting會將D0保留在輸入緩沖區內。上述代碼的運行結果就是:s""n1

然后發送D0 m_sp,運行上述代碼。ReadExisting將獲得D0 D0,"D0 D0"會被解釋為"行"。上述代碼的運行結果就是:s""n0

4.5 讀文本到某個字符串

函數string ReadTo(string value);將在串口輸入緩沖區內查找字符串value。找到了,就返回value之前的字符串,同時清除緩沖區內value及其之前的數據;未找到,就一直等待,直至超時。

4.6 讀一行文本

函數string ReadLine();等價於ReadTo(NewLine)。使用前,請設置NewLine屬性,指定行結束符。

4.7 DataReceived事件

串口輸入緩沖區獲得新數據后,會以DataReceived事件通知System.IO.Ports.SerialPort對象,可以在此時讀取串口數據。請參考下面兩段代碼:

m_sp.ReceivedBytesThreshold = 1;

m_sp.DataReceived+=new System.IO.Ports.SerialDataReceivedEventHandler(m_sp_DataReceived);

void m_sp_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)

{

int nRead = m_sp.BytesToRead;

if (nRead > 0)

{

byte[] data = new byte[nRead];

m_sp.Read(data, 0, nRead);

}

} 

m_sp.ReceivedBytesThreshold = 1;的含義:串口輸入緩沖區獲得新數據后,將檢查緩沖區內已有的字節數,大於等於ReceivedBytesThreshold就會觸發DataReceived事件。這里設置為1,顯然就是一旦獲得新數據后,立即觸發DataReceived事件。

m_sp.DataReceived+=new System.IO.Ports.SerialDataReceivedEventHandler(m_sp_DataReceived);的含義:對於DataReceived事件,用函數m_sp_DataReceived進行處理。

回調函數m_sp_DataReceived用於響應DataReceived事件,通常在這個函數里讀取串口數據。它的第一個參數sender就是事件的發起者。上面的代碼中,sender其實就是m_sp。也就是說:多個串口對象可以共用一個回調函數,通過sender可以區分是哪個串口對象。

回調函數是被一個多線程調用的,它不在主線程內。所以,不要在這個回調函數里直接訪問界面控件。如下面的代碼將將讀取到的串口數據轉換為字符串,然后顯示在按鈕Open上。紅色代碼處將產生異常。

void m_sp_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)

{

int nRead = m_sp.BytesToRead;

if (nRead > 0)

{

byte[] data = new byte[nRead];

m_sp.Read(data, 0, nRead);

btnOpen.Text = System.Text.Encoding.Default.GetString(data);

}

} 

可使用InvokeBeginInvoke改進上面的紅色代碼:

BeginInvoke(new Action<string>((x)=>{btnOpen.Text = x;})

,new Object[] {System.Text.Encoding.Default.GetString(data)});

5 流控制

串行通訊的雙方,如果有一方反應較慢,另一方不管不顧的不停發送數據,就可能造成數據丟失。為了防止這種情況發生,需要使用流控制。

流控制也叫握手,System.IO.Ports.SerialPortHandshake屬性用於設置流控制。它有四種取值:

System.IO.Ports.Handshake.None

System.IO.Ports.Handshake.XOnXOff

軟件

System.IO.Ports.Handshake.RequestToSend

硬件

System.IO.Ports.Handshake.RequestToSendXOnXOff

硬件和軟件

5.1 軟件流控制(XON/XOFF

串口設備A給串口設備B發送數據。B忙不過來時(B的串口輸入緩沖區快滿了)會給A發送字符XOFF(一般為13H),A將暫停發送數據;B的串口輸入緩沖區快空時,會給A發送字符XON(一般為11H),A將繼續發送數據。

軟件流控制最大的問題在於:通訊雙方不能傳輸字符XONXOFF

5.2 硬件流控制(RTS/CTS

RTS/CTS流控制是硬件流控制的一種,需要按下圖連線:

圖1

串口設備A給串口設備B發送數據。B忙不過來時(B的串口輸入緩沖區快滿了)會設置自己的RTS為低電平,這樣ACTS也變為低電平。A發現自己的CTS為低電平后,會停止發送數據;B的串口輸入緩沖區快空時,會設置自己的RTS為高電平,這樣ACTS也變為高電平。A發現自己的CTS為高電平后,會繼續發送數據。

相同的道理,DTR/DSR也可以做硬件流控制。

現在再來看看如下代碼:

m_sp.Open();

m_sp.DtrEnable = true;

m_sp.RtsEnable = true;

為什么打開串口時需要設置DTRRTS為高電平呢?原因就在於:如果對方設置了硬件流控制,而這邊的DTRRTS為低電平,那么對方就不會給這邊發送數據。

需要注意的是:RTS/CTS硬件流控制下,RTS的電平由系統自行調整。調用m_sp.RtsEnable = true;改變RTS的電平將會導致異常。

6 輸入信號

上一節中,屬性DtrEnableRtsEnable可以控制輸出信號DTRRTS。與之相應的,屬性CDHoldingCtsHoldingDsrHolding可讀取輸入信號。

CtsHolding true,說明對方的RTS為高電平(請按圖1所示連線)。

DsrHolding true,說明對方的DTR為高電平(請按圖1所示連線)。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM