C#中SerialPort 的使用


 

最近在學習C#的SerialPort ,關於SerialPort 的使用,做如下總結:

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

string[] sPorts = SerialPort.GetPortNames();
foreach(string port in sPorts)
{
   var serialPort = new SerialPort();
   serialPort.PortName =  port;
   serialPort.Open();
   serialPort.WriteLine("ATI"); // this will ask the port to issue an ident string which you can match against 
}

2.列出所有的串口:

        private void comboBox1_Click(object sender, EventArgs e)
        {
            string[] portNamesArray = SerialPort.GetPortNames();
            this.comboBox1.Items.Clear();
            foreach (var item in portNamesArray)
            {
                this.comboBox1.Items.Add(item);
            }
            this.comboBox1.Items.Add("");

        }



        private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
        {
            selectedPortName = this.comboBox1.SelectedItem.ToString();//獲取選中的port

        }

 

 

 

3. 打開/關閉串口:

            SerialPort port = new SerialPort();
            port.BaudRate = 1200;//波特率
            port.PortName = "COM1";
            port.Parity = Parity.None;//校驗法:無
            port.DataBits = 8;//數據位:8
            port.StopBits = StopBits.One;//停止位:1
            try
            {
                port.Open();//打開串口
                port.DtrEnable = true;//設置DTR為高電平
                port.RtsEnable = true;//設置RTS位高電平

            }
            catch (Exception ex)
            {
                //打開串口出錯,顯示錯誤信息
                MessageBox.Show(ex.Message);
            }

            if (port.IsOpen)
            {
                port.Close();//關閉串口
            }
           

 

 

4.寫數據:

函數 說明

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

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

寫二進制數據
void Write(string text); 寫文本數據
void WriteLine(string text);  寫一行數據

 

 

(1)寫二進制數據:

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

下面的C#代碼,將寫1024個00H:

            if (port.IsOpen)
            {
                byte[] bt = new byte[1024];
                port.Write(bt, 0, bt.Length);//寫1024個00H                
            }

注意:

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

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

            if (port.IsOpen)
            {
                byte[] bt = new byte[1024];
                port.WriteTimeout = 5000;//Write 函數最多耗時 5秒
                port.Write(bt, 0, bt.Length);//寫1024個00H                
            }

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

(2)寫文本數據

void Write(string text)的示例:

            if (port.IsOpen)
            {
                port.Encoding = System.Text.Encoding.GetEncoding(936);
                port.Write("串行通訊");
            }

 

首先設置代碼頁為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)。參考下面的代碼:

            if (port.IsOpen)
            {
                port.Encoding = System.Text.Encoding.GetEncoding(936);
                port.NewLine = "\r\n";
                port.WriteLine("串行通訊");
            }

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

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

5.讀數據:

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(); 

讀取一行文本

 

(1)讀二級制

讀取 3 個字節的串口數據:

 

                try
                {
                    byte[] b = new byte[3];
                    int n = port.Read(b, 0, 3); //返回值是讀取到的字節數
                }
                catch (Exception ex)
                {
                    MessageBox.Show(ex.Message);
                }

 

 

注意:

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

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

                    byte[] b = new byte[3];
                    port.ReadTimeout = 2000;
                    int n = port.Read(b, 0, 3); //返回值是讀取到的字節數

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

(2) 讀一個字節

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

(3) 讀一個字符

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

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

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

(4) 讀全部文本

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

注意:

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

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

      
                    port.Encoding = System.Text.Encoding.GetEncoding(936);
                    string s = port.ReadExisting();
                    int nn = port.BytesToRead; //輸入緩沖區剩余的字節數

"串"、"行"的GBK編碼分別為 B4 AE和D0 D0。

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

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

(5) 讀文本到某個字符串

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

(6) 讀一行文本

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

(7) DataReceived事件

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

                    port.ReceivedBytesThreshold = 1;
                    port.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(port_DataReceived);
        void port_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {
            int nRead = port.BytesToRead;
            if (nRead > 0)
            {
                byte[] data = new byte[nRead];
                port.Read(data, 0, nRead);
            }
        }

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

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

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

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

        void port_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {
            int nRead = port.BytesToRead;
            if (nRead > 0)
            {
                byte[] data = new byte[nRead];
                port.Read(data, 0, nRead);
                btnOpen.Text = System.Text.Encoding.Default.GetString(data);
            }
        }

可使用Invoke或BeginInvoke改進上面的紅色代碼:

  this.Invoke(new MethodInvoker(() =>
                {
                    btnOpen.Text = System.Text.Encoding.Default.GetString(data);
                }));
  BeginInvoke(new Action<string>((x) => { btnOpen.Text = x; }), new Object[] { System.Text.Encoding.Default.GetString(data) });

6.流控制

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

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

取值

說明

System.IO.Ports.Handshake.None

System.IO.Ports.Handshake.XOnXOff

軟件

System.IO.Ports.Handshake.RequestToSend

硬件

System.IO.Ports.Handshake.RequestToSendXOnXOff

硬件和軟件

(1) 軟件流控制(XON/XOFF)

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

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

(2)硬件流控制(RTS/CTS)

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

 

 

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

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


免責聲明!

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



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