Visual C# (1) 使用windows窗體應用程序設計上下位機串口通信系統的上位機界面

前言

前一篇文章講了利用Arduino mega2560來設計上下位機串口通信系統的下位機,這一章則要講如何設計上位機界面軟件,這裏我們先選用了VS2013自帶的Visual C#。這是微軟公司開發的C#編程語言規格之集成開發環境使用者接口,可以幫助開發者快速地設計出一款適合自己的界面軟件。

創建窗體應用程序

  • 首先打開VS2013,選擇工具欄中的"文件"->“新建”->“項目”->“Visual C#”->“Windows桌面”->“Windows窗體應用程序”,重新命名後,點擊確定就可以了。
    在這裏插入圖片描述
  • 生成項目文件之後,最主要的是Form1.cs文件和Form1.cs[設計],其中前者是代碼編寫的區域,已經定義了公有類Form1(也可以根據需要修改類名),我們後續只需要在這個類下加進去自己需要的按鈕操作;後者是界面的圖形設計,可以從左側工具箱中選擇公共控件,加入到界面當中去,包括Button、CheckBox等一系列操作。其他的操作項目框架已經幫我們做好了,我們只需要設計這兩個文件即可。
    在這裏插入圖片描述

創建一個串口

  • 首先我們在Form1.cs[設計]裏選擇左邊工具箱內的ComboBox,拖動到圖形界面上,並且在前面給它一個Label叫“串口選擇”,點擊這個ComboBox的屬性,找到"設計"->(Name),將這個ComboBox命名爲“SerialChoose”,之後在代碼區就可以用“SerialChoose”來指代這個ComboBox,這一步的作用是爲了生成界面後選擇插入到PC上不同的串口;接着同樣做一個波特率的ComboBox,命名爲"BaudRate";最後再從工具箱內選擇一個Button按鈕,給它命名爲"SerialSwitch",並在屬性->“外觀”->“Text”,給這個Button賦上"串口打開"字符。
    在這裏插入圖片描述
  • 先在代碼區最上面引用Ports類using System.IO.Ports; 接着在代碼區public Form1()這個函數裏編寫串口相關代碼,因爲串口操作需要界面軟件初始化就開始使用,所以直接將這部分代碼寫在InitializeComponent()之後即可。當然如果有需求的,可以在打開串口操作成功後加上狀態按鈕,顯示更加清楚。
namespace SimpleHostPrint
{
	public partial class BOTDR : Form
	{
		//通過System.IO.Ports實例化一個串口對象s,這一步要定義爲全局變量,後面各個函數才能使用
 		SerialPort ss = new SerialPort();   
  
 		public BOTDR()    //這裏我把圖形界面名Form1改名成了自己需要的名字,相應的類名也會跟着變化
        {
            InitializeComponent();

            Control.CheckForIllegalCrossThreadCalls = false;  //放置跨線程訪問出錯
            SerialSwitch.Text = "打開串口";    // "SerialSwitch"Button的初始狀態
            //定義一個Item數組,遍歷item中每一個波特率變量a,增加到"BaudRate"ComboBox的列表中
            int[] item = { 9600, 115200 };   
            foreach (int a in item)
            {
                BaudRate.Items.Add(a.ToString());
            }
            BaudRate.SelectedItem = BaudRate.Items[0];  //默認爲列表第1個變量
        }
		
		//這個函數界面軟件啓動時會載入
        private void Form1_Load(object sender, EventArgs e)
        {
            //獲取當前的所有的串口名字,存入字符串數組
            String[] ports = SerialPort.GetPortNames();
            //把這個串口名字符串數組全都加載進"SerialChoose"ComboBox裏
            SerialChoose.Items.AddRange(ports);
            //"SerialChoose"ComboBox默認選擇第一個串口名
            SerialChoose.SelectedItem = SerialChoose.Items[0];
        }
		
		//"SerialSwitch"Button對應的代碼,點擊該Button即觸發此段代碼
        private void SerialSwitch_Click(object sender, EventArgs e)
        {
            try
            {
                if (!ss.IsOpen)   //檢查串口是否打開
                {
                    //將選擇的串口名賦給實例化的串口ss
                    ss.PortName = SerialChoose.SelectedItem.ToString();
                    //將選擇的波特率賦給實例化的串口ss
                    ss.BaudRate = Convert.ToInt32(BaudRate.SelectedItem.ToString());
                    //串口打開
                    ss.Open();
                    //串口接收數據的函數
                    ss.DataReceived += ss_DataReceived;
                    SerialSwitch.Text = "關閉串口";

                }
                else
                {
                    ss.Close();
                    ss.DataReceived -= ss_DataReceived;
                    SerialSwitch.Text = "打開串口";
                }
            }
            catch (Exception ee)
            {
                MessageBox.Show(ee.ToString());
            }
        }
        
         //異或校驗函數,用於命令字當中
        private byte CalBIP8(byte[] data)       
        {
            byte bip16 = Convert.ToByte(data[0] ^ (data[1] & 0x00) ^ data[2] ^ data[3] ^ data[4] ^ data[5] ^ data[6] ^ data[7]);
            byte bip8 = Convert.ToByte(((bip16 & 0xf0) >> 4) ^ (bip16 & 0x0f));
            return bip8;
        }
 	}
}

界面軟件啓動後串口的初始狀態:
在這裏插入圖片描述
選擇好串口名和波特率,點擊"打開串口"按鈕後的狀態:
在這裏插入圖片描述

串口發送數據

  • 我這裏串口發送數據是由界面發送參數下來,再把數據幀通過串口發送到下位機。以光開關爲例,這裏我加了"SetChannelNumber"和"ChannelNumber"兩個TextBox,分別用來設置光通道號和顯示光通道號,同時加了"OpticalSwitchSetting" Button來觸發設置。將數據輸入到"SetChannelNumber" TextBox裏,點擊"設置"Button,即可觸發相應代碼,發送數據幀到下位機。下位機設置好之後,將當前通道號的參數返回給上位機,上位機串口讀取後顯示在"ChannelNumber" TextBox裏。"設置"Button對應的代碼如下所示。
    ![在這裏插入圖片描述](https://img-blog.csdnimg.cn/20190809173952324.png
        private void OpticalSwitchSetting_Click(object sender, EventArgs e)    //設置光開關通道號
        {
            if (ss.IsOpen)
            {
                if (SetChannelNumber.Text.Trim() != "")  //當輸入框有數據輸入時
                {
                    //自定義的帶有命令字和數值的數據幀
                    byte [] txdata = {0xfb, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0xfe};
                    //將設置的光開關通道號數值寫入數據幀對應位置
                    txdata[4] = Convert.ToByte(SetChannelNumber.Text.Trim());
                    //生成異或校驗碼
                    txdata[1] = CalBIP8(txdata);
                    int IsCompleted = 0;
                    for(int i=0;i<8;i++)
                    {
                        //串口發送函數
                        ss.Write(txdata, i, 1);
                        IsCompleted += 1;
                    }
                    //驗證發送的字節數
                    if (IsCompleted == txdata.Length)
                    {
                        MessageBox.Show("下發數據成功"); 
                    }
                    else
                    {
                        MessageBox.Show("下發數據失敗"); return;
                    }
                }
                else
                {
                    MessageBox.Show("未設置通道號"); return;
                }
            }
            else
            {
                MessageBox.Show("串口未打開"); return;
            }
        }

串口接收數據

  • C#窗體應用程序也提供了串口接收函數,可以自動監聽串口,獲取串口緩衝區內的數據,我們要做的就是編寫數據處理部分的代碼。這個串口接收函數在串口打開後,可由實例化的串口ss類辦法實現,語句就是SerialSwitch_Click()裏所寫的,ss.DataReceived += ss_DataReceived;寫到ss.DataReceoved += 時,可以根據提示按“TAB”鍵,自動鏈接串口接收函數 ss_DataReceived(),即ss這個串口的數據接收應當由函數ss_DataReceived()來處理,剩下的就是編寫這個函數內的代碼,如下所示。
void s_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            //讀取串口緩衝區的數據長度
            int count = ss.BytesToRead;
            string str = null;
            //將串口緩衝區的數據全部存入buff數組
            byte[] buff = new byte[count];
            ss.Read(buff, 0, count);
            
            //這部分代碼是做了一個RichTextBox並以十六進制顯示,並提供清除數據功能
            foreach (byte item in buff)        
            {
                //讀取buff中存的數據,轉換成顯示的十六進制數
                if (HEX2.Checked)
                {
                    str += item.ToString("X2") + " ";
                }
                else
                {
                    str += item.ToString() + " ";
                    //Convert.ToByte(buff[0]);
                }                   
            }
            //這是跨線程訪問RichTextBox,原程序和DataReceived事件是兩個不同的線程同時在執行
            ReceivedDataTextBox.Text += System.DateTime.Now.ToString() + ": " + Encoding.ASCII.GetString(buff,0,1) + str.Remove(0, 2) + "\n";
            //統計接收的數據字節數
            ReceivedDataCount.Invoke(new MethodInvoker(delegate 
                { ReceivedDataCount.Text = (int.Parse(ReceivedDataCount.Text) + count).ToString(); }));
			
			//返回的數據幀自定義,根據各自需要自行處理
            if (buff[0] == 'D')
            {
                if (buff[1] == OpticalSwitch)
                {
                    //"ChannelNumber" TextBox裏顯示收到的參數值
                    ChannelNumber.Text = buff[2].ToString();
                }
            }
            else if (buff[0] == 'S')
            {
                MessageBox.Show("下位機設置成功!");
            }
            else if (buff[0] == 'W')
            {
                MessageBox.Show("警告!模塊電流或溫度超過閾值!");
            }
            else if (buff[0] == 'E')
            {
                MessageBox.Show("模塊設置錯誤!");
            } 
        }
		
		//點擊"清除接收區"Button按鈕清空串口接收緩衝區的數據
		private void ClearReceivedData_Click(object sender, EventArgs e)  
        {
            ReceivedDataTextBox.Clear();
            ReceivedDataCount.Invoke(new MethodInvoker(delegate { ReceivedDataCount.Text = "0"; }));
        }

在這裏插入圖片描述

運行程序

當所有的工作做完之後,點擊工具欄的"啓動"按鈕,排除錯誤和報警後,即可運行一個Debug下的界面程序(也可以在配置管理器裏選擇生成Release版本的),在"設置通道號"裏輸入數值3,整個上下位機串口通信系統運行成功後,顯示結果如下:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章