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

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

  首先創建一個WInfrom窗體應用工程文件,創建過程可參考//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()對不符合長度的數據進行丟棄,實現的效果如下所示:

 

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

  鏈接://pan.baidu.com/s/1Yb1YjdAZfficRtx2srBpzw
  提取碼:7yc2

 

Tags: