文章目录
- 概要
- 整体架构流程
- 1. Winform如何构建
- 2. 串口获取及打开
- 3. 如何注册事件
- 4. 数据发送和清除
- 5. 存储数据
- 6. 总结
概要
串口助手是一个很好熟悉串口上位机的小项目,其中只包含对串口的应用,而不包含太多复杂业务逻辑,本文详细介绍了使用Visual Stuido2010创建一个串口通讯的上位机界面,包括使用groupBox、comboBox、label、button、checkBox和textbox,实现串口参数的设置,串口打开和关闭,数据发送和接收,数据16进制显示和数据清除等功能。
整体架构流程
该图像为串口通讯上位机的Form
1. Winform如何构建
1) 打开vs创建项目,选择Windows窗体应用程序,点击确定。
2) 选择视图中工具箱,按照需求在工具箱中选择。
3) 点击工具,例如groupBox、comboBox等,右键打开属性界面,在Text中改写显示在界面的文字。
2. 串口获取及打开
1)串口获取:添加方法text_Serial_port()来获取电脑的串口,然后在点击Form1中的空白处,跳转到Form1_load函数,在此函数中调用text_Serial_port();
private void text_Serial_port()
{
string[] ports = System.IO.Ports.SerialPort.GetPortNames(); //获取可用的串口
for (int i = 0; i < ports.Length; i++)
{
comboBox1.Items.Add(ports[i]);
}
comboBox1.SelectedIndex = comboBox1.Items.Count > 0 ? 0 : -1;//如果里面有数据,显示第0个
}
2)串口打开:双击“打开串口”这个按钮,跳转到button1_Click函数,在其中添加串口打开代码。
//打开串口
private void button1_Click(object sender, EventArgs e)
{
if (button1.Text == "打开串口")
{
try
{
serialPort1.PortName = comboBox1.Text;//获取要打开的串口
serialPort1.BaudRate = int.Parse(comboBox4.Text);//获取波特率
serialPort1.DataBits = int.Parse(comboBox3.Text);//获取数据位
//设置停止位
if (comboBox2.Text == "1")
{
serialPort1.StopBits = StopBits.One;
}
else if (comboBox2.Text == "1.5")
{
serialPort1.StopBits = StopBits.OnePointFive;
}
else if (comboBox2.Text == "2")
{
serialPort1.StopBits = StopBits.Two;
}
//设置奇偶校验
if (comboBox5.Text == "无")
{
serialPort1.Parity = Parity.None;
}
else if (comboBox5.Text == "奇校验")
{
serialPort1.Parity = Parity.Odd;
}
else if (comboBox5.Text == "偶校验")
{
serialPort1.Parity = Parity.Even;
}
serialPort1.Open();//打开串口
button1.Text = "关闭串口";
//注册DataReceived事件处理
serialPort1.DataReceived += serialPort1_DataReceived;
}
catch (Exception err)
{
MessageBox.Show("打开失败" + err.ToString(), "提示!");
}
}
else
{
//关闭串口
try
{
serialPort1.Close();//关闭串口
}
catch (Exception) { }
button1.Text = "打开串口"; //按钮显示打开
}
}
3. 如何注册事件
1)在工具箱中输入serialPort,将其拖拽到界面中。
2)在C#中,DataReceived事件通常是串口通信相关的类中使用的事件。当接收到数据时,会触发DataReceived事件,从而通知应用程序有新的数据可用。通常的步骤为:
数据到达:当串口接收到新的数据时,会触发底层的数据到达事件。
数据处理:接收到的数据会被处理和解析,以确保其完整性和正确性。
触发DataReceived事件:一旦数据被处理完毕,就会触发DataReceived事件,通知应用程序有新的数据可用。
事件处理:应用程序可以为DataReceived事件添加处理程序,以处理接收到的数据并进行相应的操作。
使用serialPort1_DataReceived处理接收到的数据,并且在serialPort1中添加事件。
//16进制显示字符串
private string byteToHexstr(byte[] buff)
{
string str = "";
try
{
if (buff != null)
{
for (int i = 0; i < buff.Length; i++)
{
str += buff[i].ToString("x2");//转化为小写的16进制
str += " ";//两个之间用空格
}
return str;
}
}
catch
{
return str;
}
return str;
}
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
int len = serialPort1.BytesToRead; //获取可以读取的字节数
byte[] buff = new byte[len];
serialPort1.Read(buff, 0, len);//把数据读取到数组中
string reslut = Encoding.Default.GetString(buff);
//将byte值根据ASCII值转为string
Invoke((new Action(() =>
{
if (checkBox1.Checked)//转化为16进制显示
{
textBox1.AppendText(" " + byteToHexstr(buff));
//将接收到的数据存储到builder3中
builder3.Append(byteToHexstr(buff));
}
else
{
textBox1.AppendText(" " + reslut);
builder3.Append(result);
}
}
)));
}
4. 数据发送和清除
1)字符串转化为16进制的代码,作为信息发送的子函数。
//字符串转为16进制
private byte[] strToHexbytes(string str)
{
str = str.Replace(" ", "");//清除空格
byte[] buff;
if ((str.Length % 2) != 0)
{
buff = new byte[(str.Length + 1) / 2];
try
{
for (int i = 0; i < buff.Length; i++)
{
buff[i] = Convert.ToByte(str.Substring(i * 2, 2), 16);
}
buff[buff.Length - 1] = Convert.ToByte(str.Substring(str.Length - 1, 1).PadLeft(2, '0'), 16);
return buff;
}
catch
{
MessageBox.Show("含有非16进制的字符","提示");
return null;
}
}
else
{
buff = new byte[str.Length / 2];
try
{
for (int i = 0; i < buff.Length; i++)
{
buff[i] = Convert.ToByte(str.Substring(i * 2, 2), 16);
}
}
catch
{
{
MessageBox.Show("含有非16进制的字符","提示");
return null;
}
}
}
return buff;
}
2)首先编写发送数据代码send_,双击“发送信息”这个按钮,跳转到button3_Click函数,在其中调用发送数据代码send_。
string data_;
//发送数据
private void send_()
{
data_ = textBox2.Text.ToString();
try
{
if (data_.Length != 0)
{
data_ += " ";
if (checkBox2.Checked) //16进制发送
{
serialPort1.Write(strToHexbytes(data_), 0, strToHexbytes(data_).Length);
}
else
{
serialPort1.Write(data_);
}
}
}
catch (Exception) { }
}
private void button3_Click(object sender, EventArgs e)
{
Task task = Task.Factory.StartNew(() =>
{
//发送代码
send_();
});
}
3)双击“清除信息”按钮button4打开button4_Click函数,将发送窗口清零,双击“清除数据”按钮button2打开button2_Click_1函数,将接收窗口清零。
private void button4_Click(object sender, EventArgs e)
{
textBox2.Clear();
}
private void button2_Click_1(object sender, EventArgs e)
{
textBox1.Clear();
}
5. 存储数据
主要指的是将通过串口通讯接收到的数据存储到TXT文档中,双击Form1中的“保存数据”按钮,具体实现代码如下,主要包括设置保存路径,然后定义builder,将数据存储在builder中,然后再将其写入到保存路径下的TXT文档中。
private void 保存数据_Click(object sender, EventArgs e)
{
if (保存数据.Text == "保存数据")
{
button2.Enabled = false;
保存数据.Text = "停止存储";
//FileStream用于文件的读写操作,这里设置一个要写入的文件地址,同时使用FileStream时,要在头文件中添加using System.IO;
fs = new FileStream("C:\\Users\\Administrator.USER-20190520UY\\Desktop\\csh\\winform2024910\\data\\" + "Original编号" + "时间" + DateTime.Now.ToString("yyyyMMdd-hhmm") + ".txt", FileMode.Create);
//StreamWriter用于将数据写入流的类
sw = new StreamWriter(fs);
}
else
{
button2.Enabled = true;
保存数据.Text = "保存数据";
//close作用是释放与StreamWriter和FileStream相关联的系统资源。
sw.Close();
fs.Close();
}
if (button1.Text == "关闭串口")
serialPort1.Open();
}
使用定时器进行数据存储,相比在代码中书写UI变化,在定时器中书写UI变化处理数据更佳,当UI刷新数据比较多和比较快的时候,窗口不易被卡死。设置控件可用Enabled为True,设置时间间隔Interval为200ms。
//time控件刷新
//*********************************************
private void timer1_Tick(object sender, EventArgs e)
{
if (button1.Text == "关闭串口")
{
MemoryTxt();//存储进Txt的更新
RenewUI();//数据更新时对应更新UI界面
}
}
//存储进Txt
//为减小IO的开销而设计,十组数据再存储进txt,若数据发送频率提高,则酌情增加textBox1行数
private void MemoryTxt()
{
if (保存数据.Text == "停止存储")
{
sw.Write(builder3);
//下面Flush的作用:当你向一个输出流写入数据时,数据可能会被缓存在内存中,而不是立即写入目标设备。调用Flush方法可以确保缓冲区中的所有数据都被写入目标设备,从而确保数据的完整性。
sw.Flush();
builder3.Clear();
}
}
//数据更新时对应更新UI界面
private void RenewUI()
{
//textbox1响应
if ((builder1.Length > 5))
{
textBox1.AppendText(builder1.ToString());
builder1.Clear();
}
}
6. 总结
总结:
-
串口输入缓冲区获得新数据后,会以DataReceived事件通知SerialPort对象,可以在此时读取串口数据。
-
发送和接收数据包括普通数据发送接收和16进制发送接收,其中16进制的发送和接收通过byte类型的数组将字符串转化为16进制buff[i] = Convert.ToByte(str.Substring(i * 2, 2), 16);
-
在串口通讯中,尽量使用定时器,用于在指定的时间间隔内触发事件或执行代码。它可以用于执行定期的任务,如更新UI、计时等操作。,避免因为数据过多,每个数据来一次,界面就需要刷新一次,这样刷新过多可能会导致窗口卡死。
到此,一个简易的上位机就做好了,有什么不足还请指出,