第一次接触上位机的开发,单纯是为了玩一下,浅度学习,参考了一下其他文章,做了一个简单的串口助手,算是迈出了第一步。写博客记录一下学习的过程。
一、新建项目
第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; // 定义字符串数组,数组名为 ArryPort
ArryPort = 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; // 定义字符串数组,数组名为 ArryPort
ArryPort = 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