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,整个上下位机串口通信系统运行成功后,显示结果如下:
在这里插入图片描述

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