VS上位机开发——串口助手
VS上位机开发——串口助手
目录
- VS上位机开发——串口助手
- 一、新建项目
- 二、控件布局
- 三、编写程序
- 1、端口更新函数
- 2、启动窗口加载函数
- 3、"打开串口"按键回调函数
- 4、"清除接收"按键回调函数
- 5、"发送"按键回调函数
- 6、串口接收函数
- 7、定时器中断回调函数
- 四、运行
- 五、结束语
第一次接触上位机的开发,单纯是为了玩一下,浅度学习,参考了一下其他文章,做了一个简单的串口助手,算是迈出了第一步。写博客记录一下学习的过程。
一、新建项目
第1步:创建一个Window窗体应用(.NET Framework)
我安装的是vs2019版本。
第2步:配置项目
提示:框架要选.NET Framework 4以上,如果没有,先确认项目选的是不是Window窗体应用,再确认是否安装NET Framework。
二、控件布局
我们先利用控件把串口助手的界面搭建出来。
提示:我们用到的控件都在工具箱里面。
我这里主要用了以下几个控件:
提示:控件名称是一个比较关键的参数,因为后面的代码要根据名称来写。
控件类型 | 控件名称 | Text | 说明 |
---|---|---|---|
TextBox | TextBox1 | TextBox1 | 接收显示窗口 |
TextBox | TextBox2 | TextBox2 | 发送输入窗口 |
label | label1 | 端口号 | 文本提示 |
label | label2 | 波特率 | 文本提示 |
comboBox | comboBox1 | comboBox1 | 端口号下拉菜单 |
comboBox | comboBox2 | comboBox2 | 波特率下拉菜单 |
button | button1 | 打开串口 | 打开串口按键 |
button | button2 | 清除接收 | 清除接收按键 |
button | button3 | 发送 | 发送按键 |
checkBox | checkBox1 | hex发送 | 切换发送格式 |
checkBox | checkBox2 | hex接收 | 切换接收格式 |
serialPort | serialPort1 | serialPort1 | 串口通信控件 |
Time | Tiime1 | Tiime1 | 定时器,用于定时刷新端口 |
先把控件从工具箱里面拉出来,调整好大小和布局。
提示:TextBox要自由调整窗口大小的话需要把属性里面的MultiLine设置为True。
修改控件属性里面的Text,串口助手的界面就出来了。
再添加serialPort和Time控件,这两个是隐藏的控件,在窗口下方,实际运行的时候是看不见的。
选择波特率对应的comboBox控件,在Items属性里面添加常用的波特率。
三、编写程序
提示:可以双击控件窗口打开代码,也可以选中From,右键,选中查看代码
在实际应用中,最常用到的代码在Form1.cs和Form1.Designer.cs两个文件中。
注:Form1是新建窗体默认的名称,实际使用也可能不是这个名字。
Form1.cs是应用部分的代码,也就是我后面要编写的代码所在的文件。
Form1.Designer.cs里面存放各种控件的参数定义,在我们双击某一个控件的时候,实际上这个文件会在相应的生成一行代码,意思是在这个控件里面添加一个事件。
1、端口更新函数
这个函数是自定义的,需要自己添加进去
/* 新增自定义函数:更新可用串口 */
private void Updata_Serialport_Name(ComboBox MycomboBox)
{string[] ArryPort; // 定义字符串数组,数组名为 ArryPortArryPort = SerialPort.GetPortNames(); // SerialPort.GetPortNames()函数功能为获取计算机所有可用串口,以字符串数组形式输出MycomboBox.Items.Clear(); // 清除当前组合框下拉菜单内容 for (int i = 0; i < ArryPort.Length; i++){MycomboBox.Items.Add(ArryPort[i]); // 将所有的可用串口号添加到端口对应的组合框中}
}
引用命名空间System.IO.Ports:
因为上面调用了SerialPort.GetPortNames()函数,需要引用这个命名空间才能使用
using System.IO.Ports;
2、启动窗口加载函数
双击设计界面窗口的空白区域,会自动生成一个Form1_Load空函数。
在Form1_Load函数里面添加以下代码:
Updata_Serialport_Name(comboBox1); // 调用更新可用串口函数,comboBox1为端口号组合框的名称
在默认启动函数里添加以下代码:
System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;//设置该属性 为false
提示:函数名不一定是Form1,这个函数名和自己的使用的窗体名称是一致的
3、"打开串口"按键回调函数
双击“打开串口”按键,会自动生成一个空函数。
在函数里面添加以下代码:
if (button1.Text == "打开串口") // 如果当前是串口设备是关闭状态
{try // try 是尝试部分,如果尝试过程中出现问题,进入catch部分,执行错误处理代码 {serialPort1.PortName = comboBox1.Text; // 将串口设备的串口号属性设置为comboBox1复选框中选择的串口号serialPort1.BaudRate = Convert.ToInt32(comboBox2.Text); // 将串口设备的波特率属性设置为comboBox2复选框中选择的波特率serialPort1.Open(); // 打开串口,如果打开了继续向下执行,如果失败了,跳转至catch部分comboBox1.Enabled = false; // 串口已打开,将comboBox1设置为不可操作comboBox2.Enabled = false; // 串口已打开,将comboBox2设置为不可操作button1.BackColor = Color.Red; // 将串口开关按键的颜色,改为红色button1.Text = "关闭串口"; // 将串口开关按键的文字改为“关闭串口”}catch{MessageBox.Show("打开串口失败,请检查串口", "错误"); // 弹出错误对话框}
}
else // 如果当前串口设备是打开状态
{try{serialPort1.Close(); // 关闭串口comboBox1.Enabled = true; // 串口已关闭,将comboBox1设置为可操作comboBox2.Enabled = true; // 串口已关闭,将comboBox2设置为可操作button1.BackColor = Color.Lime; // 将串口开关按键的颜色,改为青绿色button1.Text = "打开串口"; // 将串口开关按键的文字改为“打开串口”}catch{MessageBox.Show("关闭串口失败,请检查串口", "错误"); // 弹出错误对话框}
}
4、"清除接收"按键回调函数
双击“清除接收”按键,会自动生成一个空函数。
在函数里面添加以下代码:
textBox1.Text = "";
5、"发送"按键回调函数
双击“发送”按键,会自动生成一个空函数。
在函数里面添加以下代码:
if (serialPort1.IsOpen) // 如果串口设备已经打开了
{if (!checkBox1.Checked) // 如果是以字符的形式发送数据{char[] str = new char[1]; // 定义一个字符数组,只有一位try{for (int i = 0; i < textBox2.Text.Length; i++){str[0] = Convert.ToChar(textBox2.Text.Substring(i, 1)); // 取待发送文本框中的第i个字符serialPort1.Write(str, 0, 1); // 写入串口设备进行发送}}catch{MessageBox.Show("串口字符写入错误!", "错误"); // 弹出发送错误对话框serialPort1.Close(); // 关闭串口button1.BackColor = Color.Lime; // 将串口开关按键的颜色,改为青绿色button1.Text = "打开串口"; // 将串口开关按键的文字改为“打开串口”}}else // 如果以数值的形式发送{byte[] Data = new byte[1]; // 定义一个byte类型数据,相当于C语言的unsigned char类型int flag = 0; // 定义一个标志,标志这是第几位try{for (int i = 0; i < textBox2.Text.Length; i++){if (textBox2.Text.Substring(i, 1) == " " && flag == 0) // 如果是第一位,并且为空字符{continue;}if (textBox2.Text.Substring(i, 1) != " " && flag == 0) // 如果是第一位,但不为空字符{flag = 1; // 标志转到第二位数据去if (i == textBox2.Text.Length - 1) // 如果这是文本框字符串的最后一个字符{Data[0] = Convert.ToByte(textBox2.Text.Substring(i, 1), 16); // 转化为byte类型数据,以16进制显示serialPort1.Write(Data, 0, 1); // 通过串口发送flag = 0; // 标志回到第一位数据去}continue;}else if (textBox2.Text.Substring(i, 1) == " " && flag == 1) // 如果是第二位,且第二位字符为空{Data[0] = Convert.ToByte(textBox2.Text.Substring(i - 1, 1), 16); // 只将第一位字符转化为byte类型数据,以十六进制显示serialPort1.Write(Data, 0, 1); // 通过串口发送flag = 0; // 标志回到第一位数据去continue;}else if (textBox2.Text.Substring(i, 1) != " " && flag == 1) // 如果是第二位字符,且第一位字符不为空{Data[0] = Convert.ToByte(textBox2.Text.Substring(i - 1, 2), 16); // 将第一,二位字符转化为byte类型数据,以十六进制显示serialPort1.Write(Data, 0, 1); // 通过串口发送flag = 0; // 标志回到第一位数据去continue;}}}catch{MessageBox.Show("串口数值写入错误!", "错误");serialPort1.Close();button1.BackColor = Color.Lime; // 将串口开关按键的颜色,改为青绿色button1.Text = "打开串口"; // 将串口开关按键的文字改为 “打开串口”}}
}
6、串口接收函数
点击serialPort控件,在该控件的事件里面有一个DataReceived事件,双击它会生成一个数据接收的空函数
在函数里面添加以下代码:
if (!checkBox2.Checked) // 如果以字符串形式读取
{string str = serialPort1.ReadExisting(); // 读取串口接收缓冲区字符串textBox1.AppendText(str + ""); // 在接收文本框中进行显示
}
else // 以数值形式读取
{int length = serialPort1.BytesToRead; // 读取串口接收缓冲区字节数byte[] data = new byte[length]; // 定义相同字节的数组serialPort1.Read(data, 0, length); // 串口读取缓冲区数据到数组中for (int i = 0; i < length; i++){string str = Convert.ToString(data[i], 16).ToUpper(); // 将数据转换为字符串格式textBox1.AppendText("0X" + (str.Length == 1 ? "0" + str + " " : str + " ")); // 添加到串口接收文本框中}
}
7、定时器中断回调函数
在timer控件的属性里面打开使能,设置定时时间为500ms
双击timer控件,会自动生成一个空函数
在函数里面添加以下代码:
Updata_Serialport_Name(comboBox1); // 定时刷新可用串口,可以保证在程序启动之后连接的设备也能被检测到
最后再贴一个完整的代码:
提示:不能直接跳过前面的步骤直接把完整的代码拷贝过去,因为前面双击控件的操作不仅仅是生成空函数,也会在Designer里面添加对应的事件,如果直接拷贝就不会产生事件。当然,如果非要这样操作也不是不行,只要在Form1.Designer.cs文件里面把每个控件对应的事件加上即可。
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;namespace 串口测试工具
{public partial class Form1 : Form{public Form1(){InitializeComponent();System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;//设置该属性 为false}/* 新增自定义函数:更新可用串口 */private void Updata_Serialport_Name(ComboBox MycomboBox){string[] ArryPort; // 定义字符串数组,数组名为 ArryPortArryPort = SerialPort.GetPortNames(); // SerialPort.GetPortNames()函数功能为获取计算机所有可用串口,以字符串数组形式输出MycomboBox.Items.Clear(); // 清除当前组合框下拉菜单内容 for (int i = 0; i < ArryPort.Length; i++){MycomboBox.Items.Add(ArryPort[i]); // 将所有的可用串口号添加到端口对应的组合框中}}/* 启动窗口加载函数 */private void Form1_Load(object sender, EventArgs e){Updata_Serialport_Name(comboBox1); // 调用更新可用串口函数,comboBox1为端口号组合框的名称}/* "打开串口"按键回调函数 */private void button1_Click(object sender, EventArgs e){if (button1.Text == "打开串口") // 如果当前是串口设备是关闭状态{try // try 是尝试部分,如果尝试过程中出现问题,进入catch部分,执行错误处理代码 {serialPort1.PortName = comboBox1.Text; // 将串口设备的串口号属性设置为comboBox1复选框中选择的串口号serialPort1.BaudRate = Convert.ToInt32(comboBox2.Text); // 将串口设备的波特率属性设置为comboBox2复选框中选择的波特率serialPort1.Open(); // 打开串口,如果打开了继续向下执行,如果失败了,跳转至catch部分comboBox1.Enabled = false; // 串口已打开,将comboBox1设置为不可操作comboBox2.Enabled = false; // 串口已打开,将comboBox2设置为不可操作button1.BackColor = Color.Red; // 将串口开关按键的颜色,改为红色button1.Text = "关闭串口"; // 将串口开关按键的文字改为“关闭串口”}catch{MessageBox.Show("打开串口失败,请检查串口", "错误"); // 弹出错误对话框}}else // 如果当前串口设备是打开状态{try{serialPort1.Close(); // 关闭串口comboBox1.Enabled = true; // 串口已关闭,将comboBox1设置为可操作comboBox2.Enabled = true; // 串口已关闭,将comboBox2设置为可操作button1.BackColor = Color.Lime; // 将串口开关按键的颜色,改为青绿色button1.Text = "打开串口"; // 将串口开关按键的文字改为“打开串口”}catch{MessageBox.Show("关闭串口失败,请检查串口", "错误"); // 弹出错误对话框}}}/* "清除接收"按键回调函数 */private void button2_Click(object sender, EventArgs e){textBox1.Text = "";}/* "发送"按键回调函数 */private void button3_Click(object sender, EventArgs e){if (serialPort1.IsOpen) // 如果串口设备已经打开了{if (!checkBox1.Checked) // 如果是以字符的形式发送数据{char[] str = new char[1]; // 定义一个字符数组,只有一位try{for (int i = 0; i < textBox2.Text.Length; i++){str[0] = Convert.ToChar(textBox2.Text.Substring(i, 1)); // 取待发送文本框中的第i个字符serialPort1.Write(str, 0, 1); // 写入串口设备进行发送}}catch{MessageBox.Show("串口字符写入错误!", "错误"); // 弹出发送错误对话框serialPort1.Close(); // 关闭串口button1.BackColor = Color.Lime; // 将串口开关按键的颜色,改为青绿色button1.Text = "打开串口"; // 将串口开关按键的文字改为“打开串口”}}else // 如果以数值的形式发送{byte[] Data = new byte[1]; // 定义一个byte类型数据,相当于C语言的unsigned char类型int flag = 0; // 定义一个标志,标志这是第几位try{for (int i = 0; i < textBox2.Text.Length; i++){if (textBox2.Text.Substring(i, 1) == " " && flag == 0) // 如果是第一位,并且为空字符{continue;}if (textBox2.Text.Substring(i, 1) != " " && flag == 0) // 如果是第一位,但不为空字符{flag = 1; // 标志转到第二位数据去if (i == textBox2.Text.Length - 1) // 如果这是文本框字符串的最后一个字符{Data[0] = Convert.ToByte(textBox2.Text.Substring(i, 1), 16); // 转化为byte类型数据,以16进制显示serialPort1.Write(Data, 0, 1); // 通过串口发送flag = 0; // 标志回到第一位数据去}continue;}else if (textBox2.Text.Substring(i, 1) == " " && flag == 1) // 如果是第二位,且第二位字符为空{Data[0] = Convert.ToByte(textBox2.Text.Substring(i - 1, 1), 16); // 只将第一位字符转化为byte类型数据,以十六进制显示serialPort1.Write(Data, 0, 1); // 通过串口发送flag = 0; // 标志回到第一位数据去continue;}else if (textBox2.Text.Substring(i, 1) != " " && flag == 1) // 如果是第二位字符,且第一位字符不为空{Data[0] = Convert.ToByte(textBox2.Text.Substring(i - 1, 2), 16); // 将第一,二位字符转化为byte类型数据,以十六进制显示serialPort1.Write(Data, 0, 1); // 通过串口发送flag = 0; // 标志回到第一位数据去continue;}}}catch{MessageBox.Show("串口数值写入错误!", "错误");serialPort1.Close();button1.BackColor = Color.Lime; // 将串口开关按键的颜色,改为青绿色button1.Text = "打开串口"; // 将串口开关按键的文字改为 “打开串口”}}}}/* 串口接收函数 */private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e){if (!checkBox2.Checked) // 如果以字符串形式读取{string str = serialPort1.ReadExisting(); // 读取串口接收缓冲区字符串textBox1.AppendText(str + ""); // 在接收文本框中进行显示}else // 以数值形式读取{int length = serialPort1.BytesToRead; // 读取串口接收缓冲区字节数byte[] data = new byte[length]; // 定义相同字节的数组serialPort1.Read(data, 0, length); // 串口读取缓冲区数据到数组中for (int i = 0; i < length; i++){string str = Convert.ToString(data[i], 16).ToUpper(); // 将数据转换为字符串格式textBox1.AppendText("0X" + (str.Length == 1 ? "0" + str + " " : str + " ")); // 添加到串口接收文本框中}}}/* 定时器中断回调函数 */private void timer1_Tick(object sender, EventArgs e){Updata_Serialport_Name(comboBox1); // 定时刷新可用串口,可以保证在程序启动之后连接的设备也能被检测到}}
}
四、运行
在vs里面调试运行结果如下:
我这里连接了一个树莓派,数据收发测试正常
如果需要在其他PC端运行,可以把工程目录下bin文件里面的Debug拷贝出来,运行exe文件即可,不需要再安装vs
五、结束语
简单的做了一个串口助手,总体来说其实不难,不熟悉C#语法也没关系,我也是第一次接触C#,根据C语言的经验去摸索,代码基本都能看的懂,有些语法也是即学即用的。好了,关于这一讲的内容就到这里,如果有什么问题,欢迎在评论区留言讨论,谢谢。
源码下载:https://download.csdn.net/download/ShenZhen_zixian/21712034
VS上位机开发——串口助手相关推荐
- 从零开始编写一个上位机(串口助手)QT Creator + Python
提示:本博客作为学习笔记,有错误的地方希望指正,此文可能会比较长,作为学习笔记的积累,希望对来着有帮助. 绪论:笔者这里使用的是QTCreator和Python来实现一个简单的串口上位机的开发的简 ...
- C#上位机开发串口通信编程——倒计时器开发
C#上位机开发串口通信编程--倒计时器开发 一.介绍 这是我按照B站上的一个上位机开发视频教程开发的倒计时器开发,本来只有开始计时功能,没有停止计时功能,停止计时功能后面我自己添加了. 视频网址:C# ...
- C#上位机开发串口通信
一.创建一个Winform窗体,制作一个5s定时器 using System; using System.Windows.Forms;namespace 计时器 {public partial cla ...
- 从零开始编写一个上位机(串口助手)QT Creator + C++
提示:本博客作为学习笔记,有错误的地方希望指正,此文可能会比较长,作为学习笔记的积累,希望对来着有帮助. 绪论:笔者这里使用的是QTCreator和C++来实现一个简单的串口上位机的开发的简单过程 ...
- 上位机软件-串口助手-带登陆界面
前言: 这是我在大三的时候写的一个串口助手,觉得挺好用的就分享给大家.如果有什么bug的话,希望大家多多包涵,毕竟对于C#语言我只是新手. 制作串口助手 串口助手选用Visual C#开发语言和Vis ...
- 最简单DIY基于ESP32CAM的物联网相机系统⑤(用C#上位机实现串口图传)
第一篇:最简单DIY基于ESP32CAM的物联网相机系统①(用网页实现拍照图传) 第二篇:最简单DIY基于ESP32CAM的物联网相机系统②(在JAVAWEB服务器实现图片查看器) 第三篇:最简单DI ...
- 485通信原理_上位机开发之单片机通信实践
经常会有一些学员会问到上位机与单片机之间通信的问题,而我们经常会讲上位机与PLC之间通信,那么其实对上位机开发来说,不管是和PLC通信,还是和单片机通信,通信原理都是一样的.PLC的本质就是单片机,在 ...
- pythonqt4上位机开发_「新阁教育」自由口通信上位机实战案例
1.引言 组态软件作为一种通用软件,体系结构较为庞大.功能软件包多.价格也比较昂贵,而且对于一些复杂的业务逻辑或自定义的协议,实现起来比较麻烦.近几年,C#/.NET上位机开发应用越来越广泛,相对于传 ...
- 上位机通过串口获取单片机数据
上位机通过串口获取单片机数据(C#) 需求 准备 功能设计 界面设计 关键程序 打开串口 发送数据 接收数据 实现页面 完整源码 后续 需求 我们在平时使用单片机制作项目时,总是希望能够实时显示一些数 ...
- pythonqt4上位机开发_上位机开发之单片机通信实践(一)
经常会有一些学员会问到上位机与单片机之间通信的问题,而我们经常会讲上位机与PLC之间通信,那么其实对上位机开发来说,不管是和PLC通信,还是和单片机通信,通信原理都是一样的.PLC的本质就是单片机,在 ...
最新文章
- 被称为“C#圣经”的权威著作!
- 【Windows 逆向】内存地址分析 ( 内存条 | 虚拟内存 | 内存地址及寻址范围 | 内存地址与数据的关系 )
- HDU 1853 HDU 3488【有向环最小权值覆盖问题 】带权二分图匹配 KM算法
- CodeForces - 1301D Time to Run(构造+模拟)
- 多线程是并行还是并发_并发,并行,线程,进程,异步和同步有相关性吗?
- 嵌入式操作系统内核原理和开发(实时系统中的定时器)
- Microsoft Loopback Adapter : Oracle 安装
- Leetcode每日一题 面试题56 - I.数组中数字出现的次数
- Cadence 16.6快速创建多引脚芯片原理图符号
- Android 和 Java 性能优化最佳实践
- 使用 spring.profiles.active 及 @profile 注解 动态化配置内部及外部配置
- 带你撸一台免费云服务器
- eclipse as 项目编码修改
- 静态网站以及动态网站
- 深圳大学算法实验一——排序算法性能分析
- IDE+vim,提高开发效率
- 【多目标优化】2. 非支配排序遗传算法 —(NSGA、NSGA-II)
- day 86 Vue学习之五DIY脚手架、webpack使用、vue-cli的使用、element-ui
- DirectX游戏开发之代码的框架简析
- 一家两制抗风雨 有得有失看未来(转)
热门文章
- CharNet阅读笔记
- 计算机病毒防治与信息安全知识300问,计算机病毒防治与信息安全知识300问.pdf...
- 【​观察】一部《天龙八部》,缘起“六脉神剑”
- 天龙八部,数据可视化分析虚竹和童姥居然关系非同一般
- lca_trajan
- 【ArcGIS小技巧视频教程】(5):在ArcGIS中挂接其他数据
- 【PS图像处理】PS软件提示不能完成命令,因为暂存盘已满的解决办法
- 离散Hopfield神经网络摘记
- 【Python】Pandas DataFrame 一维表二维表的转换
- 直播视频分辨率码率参考设置