1. 程式人生 > >C#做一個簡單的進行串列埠通訊的上位機

C#做一個簡單的進行串列埠通訊的上位機

1、上位機與下位機

        上位機相當於一個軟體系統,可以用於接收資料、控制資料。即可以對接收到的資料直接傳送操控命令來操作資料。上位機可以接收下位機的訊號。下位機是一個控制器,是直接控制裝置獲取裝置狀況的計算機。上位機發出的命令首先給下位機,下位機再根據此命令解釋成相應時序訊號直接控制相應裝置。下位機不時讀取裝置狀態資料(一般為模擬量),轉換成數字訊號反饋給上位機。上位機不可以單獨使用,而下位機可以單獨使用。

2、串列埠通訊

        串列埠相當於硬體型別的介面。比如無線感測節點發送訊號到匯聚節點,匯聚節點通過串列埠將資料傳到計算機中的上位機中,上位機接收資訊,並處理。

      串列埠是按位(bit)傳送和接收位元組。串列埠通訊最重要的引數是波特率、資料位、停止位和奇偶校驗。對於兩個進行通訊的埠,這些引數必須匹配。

    a,波特率:這是一個衡量符號傳輸速率的引數。

    b,資料位:這是衡量通訊中實際資料位的引數。

    c,停止位:用於表示單個包的最後一位。典型的值為1,1.5和2位。

    d,奇偶校驗位:在串列埠通訊中一種簡單的檢錯方式。

3、C#程式碼

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;
using System.Diagnostics;
namespace serial2
{
    public partial class Form1 : Form
    {
        SerialPort s = new SerialPort();    //例項化一個串列埠物件,在前端控制元件中可以直接拖過來,但最好是在後端程式碼中寫程式碼,這樣複製到其他地方不會出錯。s是一個串列埠的控制代碼
        public Form1()
        {
            InitializeComponent();
            Control.CheckForIllegalCrossThreadCalls = false;   //防止跨執行緒訪問出錯,好多地方會用到
            button1.Text = "開啟串列埠";
            int[] item = { 9600,115200};    //定義一個Item陣列,遍歷item中每一個變數a,增加到comboBox2的列表中
            foreach (int a in item)
            {
                comboBox2.Items.Add(a.ToString());
            }
           
            comboBox2.SelectedItem = comboBox2.Items[1];    //預設為列表第二個變數
        }
        private void Form1_Load(object sender, EventArgs e)   //窗體事件要先配置埠資訊。
        {
            string[] ports = SerialPort.GetPortNames();
            comboBox1.Items.AddRange(ports);
            comboBox1.SelectedItem=comboBox1.Items[0];
            //Array.Sort(ports);
            
        }
        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 = "關閉串列埠";
                    //MessageBox.Show("串列埠已開啟");
                }
                else
                {
                    s.Close();
                    s.DataReceived -= s_DataReceived;
                    button1.Text = "開啟串列埠";
                }
            }
            catch (Exception ee)
            {
                MessageBox.Show(ee.ToString());
            }
        }
        void s_DataReceived(object sender, SerialDataReceivedEventArgs e)   //資料接收事件,讀到資料的長度賦值給count,如果是8位(節點內部程式設計規定好的),就申請一個byte型別的buff陣列,s控制代碼來讀資料
        {
            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)    //讀取Buff中存的資料,轉換成顯示的十六進位制數
                {
                    str += item.ToString("X2")+" ";
                }
                richTextBox1.Text =System.DateTime.Now.ToString()+": "+ str + "\n" + richTextBox1.Text;      //這是跨執行緒訪問richtextbox,原程式和DataReceived事件是兩個不同的執行緒同時在執行
                if (buff[0] == 0x04)   //如果節點是04發來的資料
                {
                    ID.Text = buff[0].ToString();   //這下面是上位機右邊那一段,用來顯示處理好的資料的溫度、溼度、光照、灰塵、ID資訊的。buff【0】中存的是資料的ID資訊,顯示在ID的Label上面
                    switch (buff[2])   //判斷資料型別  buff【0】和buff【1】代表ID的低位和高位,同理2和3代表資料型別的低位和高位,當2和3的值為1時,4和5代表溫度,6和7代表溼度;
                    
         {  
                      case 0x01:       //當2和3的值為1,4和5是溫度,6和7是溼度
          {
                                Tem.Text = (buff[5] * 4 + buff[4] * 0.05 - 30).ToString();
                                Hum.Text = (buff[6]  + buff[7]).ToString();
                                break;
                            }
                        case 0x02://6和7是光照
                       {
                                Light.Text = (buff[6] + buff[7]).ToString();
                                break;
                            }
                        case 0x04://6和7是灰塵
                        {
                                Dust.Text = (buff[6] + buff[7]).ToString();
                                break;
                            }
                        default:
                            break;
                    }
                }
            
            }
            
        }
        private void button3_Click(object sender, EventArgs e)   //每次發一個位元組
   {
            string[] sendbuff = richTextBox2.Text.Split();  //分割輸入的字串,判斷有多少個位元組需要傳送
        Debug.WriteLine("傳送位元組數:"+sendbuff.Length);
            foreach (string  item in sendbuff)
            {
                int count = 1;
                byte[] buff = new byte[count];
                buff[0] = byte.Parse(item, System.Globalization.NumberStyles.HexNumber);//格式化字串為十六進位制數值
              s.Write(buff, 0, count);
            }
        }
        private void button2_Click(object sender, EventArgs e)//重新整理右邊的數值
      {
            int count = 1;
            byte[] buff = new byte[count];
            buff[0] = byte.Parse("04", System.Globalization.NumberStyles.HexNumber);//這裡只顯示04節點的資訊
        s.Write(buff, 0, count);
        }
    }
}

 (以上規則均是本實驗室節點內部自定義規則,測試的,外面的相應要改)

4、結果

5、補充四點知識

  1)在程式可能會遇到錯誤的地方,用try+兩個Tab鍵,將程式碼寫入try中。比如本例子中的程式碼:

 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 = "關閉串列埠";
                    //MessageBox.Show("串列埠已開啟");
                }
                else
                {
                    s.Close();
                    s.DataReceived -= s_DataReceived;
                    button1.Text = "開啟串列埠";
                }
            }
            catch (Exception ee)
            {
                MessageBox.Show(ee.ToString());
            }
        }

如果程式碼沒有寫入try中,則可能出現的一種情況是比如有兩個上位機,同時佔用同一個串列埠,則就會衝突,會出錯。程式就會終止,整個程序結束。而如果寫入try中,並且把丟擲異常的catch程式碼例項化,即捕獲異常要例項化一個控制代碼,這樣程式遇到error就不會終止,而會出現報錯的原因。如下圖,我的這個上位機和網上下載的一個上位機同時佔用COM3串列埠(網上下載的先佔用COM3),這時我的上位機在開啟串列埠時會出現報錯。

2)就我這個上位機而言,需要有開啟串列埠和關閉串列埠兩個button按鈕,但是考慮到佔地方,當然最重要的還是如果用兩個按鈕來表示,當你按下開啟串列埠,如果忘了是否開啟,則是看不出來是不是開啟的,所以可以合併為一個button控制元件。(程式碼還是用上面那一段的程式碼)。(感覺很神奇啊)。在button1_Click事件中,先點選button,如果串列埠是關閉的,則開啟串列埠,然後把button1.Text的值賦值為“關閉串列埠”,如果串列埠本來是關閉的,則點選按鈕會把button1.Text的值賦值為“開啟串列埠”,同時把接收的資料清空。感覺這個方法真的很不錯!嘿嘿

3)當輸入一個變數或方法什麼的,它所有有的會自動出現在一個列表,這時,“正方體”代表“方法”,“小鉗子”代表“變數”,“閃電”代表“事件”。

4) 產生物件的事件時

比如輸入s.自動會出現DataReceived事件,再輸入“+=”就會有如上圖提示,按Tab鍵。然後又會如下圖提示

再次按tab鍵,就會自動生成DataReceived事件處理函式。