C#学习笔记5:Winform简单上位机串口助手的实现

今日尝试使用C#进行 自己编写一款上位机串口助手:

文章提供源码、解释、测试效果、整体工程下载......

以下为串口助手的界面效果:

目录

1、功能设计与摆放控件:

2.遇到的报错解决与基本提示:

C#System.InvalidOperationException:“线程间操作无效: 从不是创建控件“textBox3”的线程访问它。

设置串口端口停止位波特率等时的数据强制类型转换规范:

串口接收触发异常:

 串口对象的一些常用属性赋值实例:

串口发送信息:

串口接收信息:

3、实际功能展示截图:

4、在没有下位机情况下调试上位机串口的方法:

5、整体工程代码:

6、整体测试工程下载:


1、功能设计与摆放控件:

与之前的串口发送不同,我们此次做的是兼顾接收与发送的串口助手,预设功能如下:

1、串口选择

2、波特率

3、接收显示窗口

4、切换十六进制发送接收功能

5、清除接收区

6、必要的交互按钮

最大的接收区我使用的组件是:richTextBox

清除接收区、打开关闭串口、发送等按键使用的组件是:button

16进制显示、发送我使用了可以框选和不选的组件:checkBox

单条发送后的文本输入区我使用的组件是:textBox(相比richTextBox只有一行)

2.遇到的报错解决与基本提示:

C#System.InvalidOperationException:“线程间操作无效: 从不是创建控件“textBox3”的线程访问它。

这项报错是我  试图   将串口接收到的信息打印在richTextBox1组件时遇到的

解决方案 是在创建窗体时增加代码 禁止编译器对跨线程访问做检查:

          Control.CheckForIllegalCrossThreadCalls = false;

查阅资料网址:

C#System.InvalidOperationException:“线程间操作无效: 从不是创建控件“textBox3”的线程访问它。” - 知乎

设置串口端口停止位波特率等时的数据强制类型转换规范:

串口接收触发异常:

 在串口部分,每个属性的数据类型不太相同,所以赋值时需要注意一下

我们一般将combox下拉框中的text属性作为设置的相应值赋值给seriesport的某些属性,但数据类型不一定对应,所以赋值的时候需要作对应的强制类型转换:

 对于停止位设置进入串口服务事件函数的相关问题,在以下代码注释中有所说明:

              serialPort1.PortName = comboBox1.Text;//设置端口号
              serialPort1.BaudRate = Convert.ToInt32(comboBox2.Text);//设置端口波特率
              serialPort1.StopBits = (StopBits)Convert.ToInt32(comboBox3.Text);//设置停止位
              serialPort1.DataBits = Convert.ToInt32(comboBox4.Text);//设置数据位
              /*  一定要加上下面这句话。
               *  意思是接收缓冲区其中假设有一个字节的话就出发接收函数。
                  假设不加上这句话,
                  那就有时候触发接收都发了好多次了也设有触发接收,有时候延时现象等等
              */
              serialPort1.ReceivedBytesThreshold = 1;
              serialPort1.DataReceived += new SerialDataReceivedEventHandler(serialPort1_DataReceived);

              serialPort1.Open();                   //打开串口

 串口对象的一些常用属性赋值实例:

这也是我查阅网上资料获取的笔记摘录,为的是

SerialPort mySerialPort = new SerialPort("COM2");//端口
mySerialPort.BaudRate = 9600;//波特率
mySerialPort.Parity = Parity.None;//校验位
mySerialPort.StopBits = StopBits.One;//停止位
mySerialPort.DataBits = 8;//数据位
mySerialPort.Handshake = Handshake.Non;
mySerialPort.ReadTimeout = 1500;
mySerialPort.DtrEnable = true;//启用数据终端就绪信息
mySerialPort.Encoding = Encoding.UTF8;
mySerialPort.ReceivedBytesThreshold = 1;//DataReceived触发前内部输入缓冲器的字节数
mySerialPort.DataReceived += new SerialDataReceivedEvenHandler(DataReceive_Method);
 
mySerialPort.Open();//打开串口

串口发送信息:

Write(Byte[], Int32, Int32) :将指定数量的字节写入串行端口
Write(Char[], Int32, Int32) :将指定数量的字符写入串行端口
Write(String) :将指定的字符串写入串行端口
WriteLine(String) :将指定的字符串和NewLine值写入输出缓冲区

// Write a string
port.Write("Hello World");
 
// Write a set of bytes
port.Write(new byte[] { 0x0A, 0xE2, 0xFF }, 0, 3);
 
// Close the port关闭串口
port.Close();

串口接收信息:

Read(Byte[], Int32, Int32):从SerialPort输入缓冲区读取一些字节,并将那些字节写入字节数组中指定的偏移量处
ReadByte():从SerialPort输入缓冲区中同步读取一个字节
ReadChar(): 从SerialPort输入缓冲区中同步读取一个字符
ReadExisting() :在编码的基础上,读取SerialPort对象的流和输入缓冲区中所有立即可用的字节
ReadLine() :一直读取到输入缓冲区中的NewLine值
ReadTo(String) :一直读取到输入缓冲区中的指定value的字符串

查阅资料网址:

C#实现串口通信解析_c#串口通讯-CSDN博客

3、实际功能展示截图:

 主要是逻辑正确的弹窗提示,弹窗是调用函数实现的:

                MessageBox.Show("当前串口有设备连接,串口已成功打开", "串口发送提示");

打开串口检测端口有无设备:

关闭串口:

串口接收测试:

使用单片机发送字符串测试接收功能 :

接收测试成功,效果成功在richTextBox组件打印:

勾选16进制显示设置:

是否被勾选的判断语句如下示例:

                    //如果不是16进制发送,就直接string形式发送
                    if (!checkBox2.Checked)

清除接收区:

清除richTextBox的接收区可以直接调用函数:

            richTextBox1.Clear();

4、在没有下位机情况下调试上位机串口的方法

这里我使用虚拟串口软件,给电脑的COM3与COM4搭建了虚拟的串口连接:

软件下载地址如下:

https://download.csdn.net/download/qq_64257614/89044980

如图使用软件对COM3与COM4进行虚拟连接:

 使用SSCOM串口助手软件成功检测到COM3与COM4的虚拟连接:

 发送测试:

5、整体工程代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using static System.Windows.Forms.VisualStyles.VisualStyleElement;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.Button;


namespace Serial_Assistant_YZH
{

    public partial class Form1 : Form
    {
        int CXk1 = 0;//蔡徐坤图片计数器


        public Form1()
        {
            InitializeComponent();
            serialPort1.Encoding = Encoding.GetEncoding("GB2312");     //串口接收编码
            Control.CheckForIllegalCrossThreadCalls = false;
        }

        private void Form1_Load(object sender, EventArgs e)
        {

            for(int i=1;i<10;i++)//初始化串口号下拉框内容
            {
                comboBox1.Items.Add("COM" + i.ToString()); //添加串口
            }

            for (int H = 0; H < 5; H++)//初始化串口波特率下拉框内容
            {
                switch (H)
                {
                    case 0: comboBox2.Items.Add("2400");   break;
                    case 1: comboBox2.Items.Add("4800");   break;
                    case 2: comboBox2.Items.Add("9600");   break;
                    case 3: comboBox2.Items.Add("115200"); break;
                }
            }

            for(int j=0;j<3;j++)
            {
                switch (j)
                {
                    case 0: comboBox3.Items.Add("1"); break;
                    case 1: comboBox3.Items.Add("1.5"); break;
                    case 2: comboBox3.Items.Add("2"); break;
                }
            }

            comboBox1.Text = "COM1";//端口下拉框初始值
            comboBox2.Text = "9600";//波特率下拉框初始值
            comboBox3.Text = "1";//停止位
            comboBox4.Text = "8";//数据位

            serialPort1.Close();   //关闭串行端口连接
        }

        //数据接收显示
        private void richTextBox1_TextChanged(object sender, EventArgs e){}

        //打开串口
        private void button1_Click(object sender, EventArgs e)
        {
            try
            {
                serialPort1.PortName = comboBox1.Text;//设置端口号
                serialPort1.BaudRate = Convert.ToInt32(comboBox2.Text);//设置端口波特率
                serialPort1.StopBits = (StopBits)Convert.ToInt32(comboBox3.Text);//设置停止位
                serialPort1.DataBits = Convert.ToInt32(comboBox4.Text);//设置数据位

                /*  一定要加上下面这句话。
                 *  意思是接收缓冲区其中假设有一个字节的话就出发接收函数。
                    假设不加上这句话,
                    那就有时候触发接收都发了好多次了也设有触发接收,有时候延时现象等等
                */
                serialPort1.ReceivedBytesThreshold = 1;
                serialPort1.DataReceived += new SerialDataReceivedEventHandler(serialPort1_DataReceived);

                serialPort1.Open();                   //打开串口

                MessageBox.Show("当前串口有设备连接,串口已成功打开", "串口发送提示");
                //按键状态置位
                button1.Enabled = false;
                button2.Enabled = true;
            }
            catch
            {
                MessageBox.Show("端口无设备连接", "错误警告");
            }
        }

        //关闭串口
        private void button2_Click(object sender, EventArgs e)
        {
            try
            {
                serialPort1.Close(); //关闭串口        
                //按键状态置位
                button1.Enabled = true;
                button2.Enabled = false;
                MessageBox.Show("已关闭串口", "串口发送提示");
            }
            catch{}
        }

        
        private void label3_Click(object sender, EventArgs e){}

        //单条发送数据
        private void button3_Click(object sender, EventArgs e)
        {
            byte[] Data = new byte[1];                                                         //单字节发数据     
            if (serialPort1.IsOpen)
            {
                if (textBox1.Text != "")
                {
                    //如果不是16进制发送,就直接string形式发送
                    if (!checkBox2.Checked)
                    {
                        try
                        {
                            serialPort1.Write(textBox1.Text);
                            //serialPort1.WriteLine();                             //字符串写入
                        }
                        catch
                        {
                            MessageBox.Show("串口数据写入错误", "错误");
                        }
                    }
                    else                                                                    //数据模式
                    {
                        try                                                                 //如果此时用户输入字符串中含有非法字符(字母,汉字,符号等等,try,catch块可以捕捉并提示)
                        {
                            for (int i = 0; i < (textBox1.Text.Length - textBox1.Text.Length % 2) / 2; i++)//转换偶数个
                            {
                                Data[0] = Convert.ToByte(textBox1.Text.Substring(i * 2, 2), 16);           //转换
                                serialPort1.Write(Data, 0, 1);
                            }
                            if (textBox1.Text.Length % 2 != 0)
                            {
                                //单独处理最后一个字符
                                Data[0] = Convert.ToByte(textBox1.Text.Substring(textBox1.Text.Length - 1, 1), 16);
                                serialPort1.Write(Data, 0, 1);//写入
                            }
                            //Data = Convert.ToByte(textBox2.Text.Substring(textBox2.Text.Length - 1, 1), 16);
                        }
                        catch
                        {
                            MessageBox.Show("数据转换错误,请输入数字。", "错误");
                        }
                    }
                }
            }

        }

        //单条发送输入文本框
        private void textBox1_TextChanged(object sender, EventArgs e)
        {

        }

        //串口接收
        //一个接收数据事件获取串口发送来的数据
        private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {
            //处理事件这块可以加上延时确保不定数的数据可以全部收到缓冲后,才去读缓冲内容--单位: 毫秒
            Thread.Sleep(50);
            //如果16进制转换没被勾选
            if (!checkBox1.Checked)
            {
                richTextBox1.AppendText(serialPort1.ReadExisting());  //串口类会自动处理汉字,所以不需要特别转换
            }
            //如果16进制转换被勾选了
            else
            {
                try
                {
                    //定义缓冲区数组大小为串口缓冲区数据的字节数
                    //因为串口事件触发时有可能收到不止一个字节
                    byte[] data = new byte[serialPort1.BytesToRead];
                    serialPort1.Read(data, 0, data.Length);

                    foreach (byte Member in data)  //遍历用法
                    {
                        string str = Convert.ToString(Member, 16).ToUpper();
                        richTextBox1.AppendText("0x" + (str.Length == 1 ? "0" + str : str) + " ");
                    }
                }
                catch { }
            }
        }

        //蔡徐坤图片
        private void pictureBox1_Click(object sender, EventArgs e)
        {
            int CXK = 0;
            CXk1++;
            CXK = CXk1 % 5;
            switch(CXK)
            {
                case 0: MessageBox.Show("你干嘛~哎呦!", "蔡徐坤");label6.Text = "哎呦!"; break;
                case 1: MessageBox.Show("唱跳、RAP、篮球、Music!", "蔡徐坤"); label6.Text = "篮球"; break;
                case 2: MessageBox.Show("鸡你太美~Baby", "蔡徐坤"); label6.Text = "鸡你太美!"; break;
                case 3: MessageBox.Show("练习时长两年半", "蔡徐坤"); label6.Text = "两年半!"; break;
                case 4: MessageBox.Show("别点了,认真学习", "蔡徐坤"); label6.Text = "认真学习!"; break;
            }

        }

        //清除接收区
        private void button4_Click(object sender, EventArgs e)
        {
            richTextBox1.Clear();
            MessageBox.Show("已清除接收区", "清除接收区");
        }

        //停止位
        private void comboBox3_SelectedIndexChanged(object sender, EventArgs e){}
        //数据位
        private void comboBox4_SelectedIndexChanged(object sender, EventArgs e){}
        //串口选择
        private void comboBox1_SelectedIndexChanged(object sender, EventArgs e){}
        //波特率选择
        private void comboBox2_SelectedIndexChanged(object sender, EventArgs e){}
        //
        private void checkBox1_CheckedChanged(object sender, EventArgs e){}
        //16进制发送按钮
        private void checkBox2_CheckedChanged(object sender, EventArgs e){}
    }
}

6、整体测试工程下载:

https://download.csdn.net/download/qq_64257614/89044991?spm=1001.2014.3001.5503

  • 29
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
是杰杰之前做的 一个参赛小作品 其实在做这个恒温控制系统项目的时候,师弟就问我,什么是上位机。。。。。可能很多师弟师妹都没一个大概的概念。 现在,就来看下什么是上位机上位机是指可以直接发出操控命令的计算机,一般是PC/host computer/master computer/upper computer,屏幕上显示各种信号变化(液压,水位,温度等)。下位机是直接控制设备获取设备状况的计算机,一般是PLC/单片机single chip microcomputer/slave computer/lower computer之类的。上位机发出的命令首先给下位机,下位机再根据此命令解释成相应时序信号直接控制相应设备。下位机不时读取设备状态数据(一般为模拟量),转换成数字信号反馈给上位机。简言之如此,实际情况千差万别,但万变不离其宗:上下位机都需要编程,都有专门的开发系统。 在概念上,控制者和提供服务者是上位机,被控制者和被服务者是下位机,也可以理解为主机和从机的关系,但上位机和下位机是可以转换的。 工作原理 两机如何通讯,一般取决于下位机,TCP/IP一般是支持的,但是下位机一般具有更可靠的独有通讯协议。通常上位机和下位机通讯可以采用不同通讯协议,可以有RS232的串口通讯或者采用RS485串行通讯。采用封装好的程序开发工具就可以实现下位机和上位机的通讯,当然可以自己编写驱动类的接口协议控制上位机和下位机的通讯。 通常工控机,工作站,触摸屏作为上位机,通信控制PLC,单片机等作为下位机,从而控制相关设备元件和驱动装置。 既然差不多知道什么是上位机与下位机,那么,我们做到小喇叭的要求:就得写个上位机,我自己也是学了下C#,用来开发上位机还是可以的,开发环境用visual studio 2015,微软的软件真的是很简单,之前看到有人问为什么微软的软件是最多人用的,答:因为那是傻瓜式操作。。。。。我不得不认同。。。 回归正题:先看看我们的上位机有什么功能: 1)能够实现与下位机的正常通讯。这必须得有,不然算哪门子上位机啊。 2)能够控制我们的恒温系统,通过电脑控制恒温系统的温度。这种应用场景很正常,我在机房就能控制我某一个地方的温度,简单方便。 3)能够实时显示温度与波形。这种应用的场景也是非常常见,实时显示温度我们可以知道温度是否正常,而波形我们能快速看出恒温系统空间温度是否出现异常。 4)数据保存功能,自动将实时的温度保存下来,加入 日期与时间,方便以后排查问题的时候用于检测某段时间的温度是否正常,这也是很使用的功能。 说了那么多,看看我们的上位机。由于我以前用的别人的上位机都是色调比较单一的,而我,又是那种比较活泼开朗的人,觉得单一的灰色调比较死沉,我不喜欢,当然那是别人做的上位机,即使再不喜欢,要用还是要用,没办法, 现在我自己做的就不一样了,得给他做个五颜六色出来。亮瞎眼。。。。。 1.png 2.jpg 3.jpg 当我们的温度达到目标温度的时候,下位机就会控制自己的温度,以保持温度恒定,同时在上位机显示看到温度为一条曲线,由于我设置的温度为整数,没有小数,但是我们通过下位机可以看到实时的温度,精确在0.4范围左右。 4.jpg 当我们把上位机与下位机通讯协议切断,可以看到上位机接受不到数据(在软件右下角粉红色的窗口,没有显示接收到的数据),上位机的波形就会回到默认值(0°),就是上图的样子 5.jpg 6.jpg 当我们需要后期的检测与排除故障的时候,我们可以从我们保存的温度来做简单的排查,上位机自动保存的温度数据,精确到某天某时某分某秒,简单方便。也是一个很实用的功能当没什么故障的时候,我们只需要隔一段时间吧数据清一下就OK了。 附上作品视频:http://v.youku.com/v_show/id_XMzE0Nzg0ODUzMg==.html?spm=a2h0k.8191407.0.0&from=s1.8-1-1.2 本次上位机介绍到此结束 源码文件会可以关注关注获得。 或者CSDN下载连接: 本上位机仅为杰杰个人娱乐,不做商业用途。 如需转载请说明出处。 欢迎关注“创客飞梦空间” 干货不间断 在未来的日子里 创客飞梦空间与你们同在,放飞我们的梦想
以下是一个简单C#串口助手程序的示例代码: ```csharp using System; using System.IO.Ports; namespace SerialPortDemo { class Program { static SerialPort _serialPort; static void Main(string[] args) { try { _serialPort = new SerialPort(); _serialPort.PortName = "COM1"; // 串口名称 _serialPort.BaudRate = 9600; // 波特率 _serialPort.DataBits = 8; // 数据位 _serialPort.Parity = Parity.None; // 校验位 _serialPort.StopBits = StopBits.One; // 停止位 _serialPort.Handshake = Handshake.None; // 握手协议 _serialPort.Open(); // 打开串口 Console.WriteLine("Serial port opened!"); while (true) { string input = Console.ReadLine(); if (input.ToLower() == "exit") break; _serialPort.WriteLine(input); // 发送数据 Console.WriteLine("Sent: " + input); string output = _serialPort.ReadLine(); // 接收数据 Console.WriteLine("Received: " + output); } } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { if (_serialPort != null && _serialPort.IsOpen) _serialPort.Close(); // 关闭串口 } } } } ``` 这个程序使用了C#的`SerialPort`类来实现串口通信。在`Main`方法中,它打开了一个名为COM1的串口,并且设置了波特率为9600,数据位为8,校验位为无,停止位为1,握手协议为无。 程序进入一个无限循环,等待用户从控制台输入数据。当用户输入数据后,程序将其发送到串口,并等待接收到响应。用户可以通过输入"exit"来退出程序。 在`try`块中,我们使用`SerialPort.WriteLine`方法将数据发送到串口,并使用`SerialPort.ReadLine`方法等待串口返回响应。在`catch`块中,如果出现异常,程序将打印错误消息。在`finally`块中,我们使用`SerialPort.Close`方法关闭串口。 请注意,在使用`SerialPort`类之前,您需要将`System.IO.Ports`命名空间添加到您的项目中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NULL指向我

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值