1. 程式人生 > 實用技巧 >C# 簡易的串列埠監視上位機實現

C# 簡易的串列埠監視上位機實現

  實現上位機和下位機之間的通訊,通常使用的是串列埠通訊,接下來實現一個通過上位機和串列埠除錯助手來完成串列埠通訊測試。

  首先建立一個WInfrom窗體應用工程檔案,建立過程可參考https://www.cnblogs.com/xionglaichuangyichuang/p/13734179.html;

  在建立好的工程下面,通過工具箱中已有的控制元件完成介面的搭建,如下圖所示,為了方便初學者容易看懂程式,下圖將控制元件的命名一併標註出來:

  直接進入正題,將完整的工程程式碼黏貼出來:

  1 using System;
  2 using System.Collections.Generic;
3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.Linq; 7 using System.Text; 8 using System.Threading.Tasks; 9 using System.Windows.Forms; 10 using System.IO.Ports; 11 using System.Diagnostics; 12 13 namespace Tem_Hum_Monitorring 14 { 15 16
public partial class Form1 : Form 17 { 18 //例項化串列埠 19 SerialPort s = new SerialPort(); 20 21 public Form1() 22 { 23 InitializeComponent(); 24 Control.CheckForIllegalCrossThreadCalls = false; 25 button1.Text = "開啟串列埠"
; 26 int[] item = { 9600,115200}; //遍歷 27 foreach (int a in item) 28 { 29 comboBox2.Items.Add(a.ToString()); 30 } 31 comboBox2.SelectedItem = comboBox2.Items[1]; 32 } 33 34 private void Form1_Load(object sender, EventArgs e) 35 { 36 portInit(); 37 } 38 39 /// <summary> 40 /// 串列埠初始化 41 /// </summary> 42 private void portInit() 43 { 44 string[] ports = SerialPort.GetPortNames(); 45 comboBox1.Items.AddRange(ports); 46 comboBox1.SelectedItem = comboBox1.Items[0]; 47 } 48 49 #region 開關串列埠 50 private void button1_Click(object sender, EventArgs e) 51 { 52 try 53 { 54 if (!s.IsOpen) 55 { 56 s.PortName = comboBox1.SelectedItem.ToString(); 57 s.BaudRate = Convert.ToInt32(comboBox2.SelectedItem.ToString()); 58 s.Open(); 59 s.DataReceived += s_DataReceived; //"+="代表指定響應事件時要呼叫的方法 60 button1.Text = "關閉串列埠"; 61 } 62 else 63 { 64 s.Close(); 65 s.DataReceived -= s_DataReceived; 66 button1.Text = "開啟串列埠"; 67 } 68 } 69 catch(Exception ee) 70 { 71 MessageBox.Show(ee.ToString()); 72 } 73 } 74 #endregion 75 76 #region 串列埠接收 77 void s_DataReceived(object sender, SerialDataReceivedEventArgs e) 78 { 79 int count = s.BytesToRead; 80 string str = null; 81 if (count == 8) 82 { 83 //資料解析 84 byte[] buff = new byte[count]; 85 s.Read(buff, 0, count); 86 foreach (byte item in buff) 87 { 88 str += item.ToString("X2") + " "; 89 } 90 richTextBox1.Text = "[" + System.DateTime.Now.ToString() + "] " + str + "\n" + richTextBox1.Text; 91 if (buff[0] == 0x04) 92 { 93 ID.Text = buff[0].ToString(); 94 switch (buff[2]) 95 { 96 case 0x01: 97 { 98 Tem.Text = (buff[5] * 4 + buff[4] * 0.05 - 30).ToString(); 99 Hum.Text = (buff[6] + buff[7]).ToString(); 100 break; 101 } 102 case 0x02: 103 { 104 Light.Text = (buff[6] + buff[7]).ToString(); 105 break; 106 } 107 case 0x04: 108 { 109 Dust.Text = (buff[6] + buff[7]).ToString(); 110 break; 111 } 112 default: 113 break; 114 } 115 } 116 } 117 else 118 { 119 //當接收資料不在設定的資料位範圍之內時,會出現接受到的資料一直儲存在接收快取區之內,後續每次接手資料都會將上一次的資料進行疊加,造成只能通過關閉串列埠的方法來清除緩衝區的資料 120 s.DiscardInBuffer(); //丟棄來自序列驅動程式的接收緩衝區的資料 121 } 122 } 123 #endregion 124 125 #region 串列埠傳送 126 private void button3_Click(object sender, EventArgs e) 127 { 128 string[] sendbuff = richTextBox2.Text.Split(); 129 Debug.WriteLine("傳送位元組數:" + sendbuff.Length); 130 foreach (string item in sendbuff) 131 { 132 int count = 1; 133 byte[] buff = new byte[count]; 134 buff[0] = byte.Parse(item, System.Globalization.NumberStyles.HexNumber); 135 s.Write(buff,0,count); 136 } 137 } 138 #endregion 139 140 private void button2_Click(object sender, EventArgs e) 141 { 142 int count = 1; 143 byte[] buff = new byte[count]; 144 buff[0] = byte.Parse("04", System.Globalization.NumberStyles.HexNumber); 145 s.Write(buff, 0, count); 146 } 147 } 148 }
View Code

  在Winfrom窗體設計中,實現串列埠可以通過工具箱中的串列埠控制元件來實現,不過一般推薦直接通過程式碼來例項化串列埠,例項化串列埠需使用如下程式碼來實現:

    //例項化串列埠
    SerialPort s = new SerialPort();

  串列埠初始化可以在窗體的Load函式中實現,以下初始化可以自動化取當前裝置中的存在的串列埠,包括真實串列埠和虛擬串列埠:

private void Form1_Load(object sender, EventArgs e)
        {
            portInit();
        }

        /// <summary>
        /// 串列埠初始化
        /// </summary>
        private void portInit()
        {
            string[] ports = SerialPort.GetPortNames();
            comboBox1.Items.AddRange(ports);
            comboBox1.SelectedItem = comboBox1.Items[0];
        }

  通過對開關按鍵button1控制元件的點選事件,實現串列埠的開關,通過對控制元件的文字修改,可以實現一個控制元件機能實現開又能實現關串列埠的作用:

        #region 開關串列埠
        private void button1_Click(object sender, EventArgs e)
        {
            try
            {
                if (!s.IsOpen)
                {
                    s.PortName = comboBox1.SelectedItem.ToString();
                    s.BaudRate = Convert.ToInt32(comboBox2.SelectedItem.ToString());
                    s.Open();
                    s.DataReceived += s_DataReceived; //"+="代表指定響應事件時要呼叫的方法
                    button1.Text = "關閉串列埠";
                }
                else
                {
                    s.Close();
                    s.DataReceived -= s_DataReceived;
                    button1.Text = "開啟串列埠";
                }
            }
            catch(Exception ee)
            {
                MessageBox.Show(ee.ToString());
            }
        }
        #endregion

  串列埠資料接收和資料解析,首先獲取資料接收快取區資料的位元組長度,通過確認長度是否是設定中的長度大小,如果是設定的8位資料長度則對接收的資料進行解析:

        #region 串列埠接收
        void s_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            int count = s.BytesToRead;
            string str = null;
            if (count == 8)
            {
                //資料解析
                byte[] buff = new byte[count];
                s.Read(buff, 0, count);
                foreach (byte item in buff)
                {
                    str += item.ToString("X2") + " ";
                }
                richTextBox1.Text = "[" + System.DateTime.Now.ToString() + "] " + str + "\n" + richTextBox1.Text;
                if (buff[0] == 0x04)
                {
                    ID.Text = buff[0].ToString();
                    switch (buff[2])
                    {
                        case 0x01:
                            {
                                Tem.Text = (buff[5] * 4 + buff[4] * 0.05 - 30).ToString();
                                Hum.Text = (buff[6] + buff[7]).ToString();
                                break;
                            }
                        case 0x02:
                            {
                                Light.Text = (buff[6] + buff[7]).ToString();
                                break;
                            }
                        case 0x04:
                            {
                                Dust.Text = (buff[6] + buff[7]).ToString();
                                break;
                            }
                        default:
                            break;
                    }
                }
            }
            else
            {
                //當接收資料不在設定的資料位範圍之內時,會出現接受到的資料一直儲存在接收快取區之內,後續每次接手資料都會將上一次的資料進行疊加,造成只能通過關閉串列埠的方法來清除緩衝區的資料
                s.DiscardInBuffer(); //丟棄來自序列驅動程式的接收緩衝區的資料
            }
        }
        #endregion

  當接收到的資料長度不等於8的時候,將丟棄來自序列驅動程式的接收緩衝區的資料,接下來通過斷點除錯來分析丟棄緩衝區和不丟棄緩衝區資料兩種情況進行模擬,分析如下幾點。

  • 使用串列埠助手給上位機發送資料資料位長度為8位的資料,串列埠除錯助手和上位機的終端的顯示介面如下,傳送端資料和接收端資料一樣,並未出現異常:

  • 將串列埠除錯助手傳送資料位修改成9位之後,進行傳送,可以發現上位機並未接收到相關的資料:

  • 接著修改串列埠除錯助手的傳送資料位,修改成8位,可以發現上位機尚未能接收到來自串列埠除錯助手發來的資料,這是為什麼呢?

  • 接下來將通過斷點逐步進行除錯,來解釋是為啥上位機沒有接收到除錯助手發來的資料,當串列埠除錯助手發來的資料長度位9位時,通過監視器可以檢視到接收緩衝器中的資料長度長度是9

  • 第一次點選完傳送之後,上位機未能成功接收到資料,我們就會好奇,並且一般都會點選第二次、第三次、甚至一直點下去,觀察是否會出現啥異常現象,當點選第二次時,通過監視視窗,可以觀察到到串列埠緩衝區的資料長度變成了18,這是因為緩衝區將上一次接收的資料給保留了下來並沒有刪除,就算下次傳送的資料長度為8位的時候,也一樣是通過疊加的方式將其儲存到緩衝區,這樣就會造成緩衝區的資料位長度會一直大於8;如果不通過s.DiscardInBuffer()方法丟棄來自序列驅動程式的接收緩衝區的資料,就只能通過關閉串列埠然後重新開啟相應的串列埠來實現緩衝區的資料清除。

  • 使用s.DiscardInBuffer()對不符合長度的資料進行丟棄,實現的效果如下所示:

  需要完整原始碼的朋友可以通過以下連結進行下載,如有大佬有更好的優化意見歡迎一塊進行討論,謝謝!

  連結:https://pan.baidu.com/s/1Yb1YjdAZfficRtx2srBpzw
  提取碼:7yc2