实现上位机和下位机之间的通信,通常使用的是串口通信,接下来实现一个通过上位机和串口调试助手来完成串口通信测试。
首先创建一个WInfrom窗体应用工程文件,创建过程可参考 https://www.jb51.net/article/150973.htm
在创建好的工程下面,通过工具箱中已有的控件完成界面的搭建,如下图所示,为了方便初学者容易看懂程序,下图将控件的命名一并标注出来:
直接进入正题,将完整的工程代码黏贴出来:
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;
using System.Diagnostics;
namespace Tem_Hum_Monitorring
{
public partial class Form1 : Form
{
//实例化串口
SerialPort s = new SerialPort();
public Form1()
{
InitializeComponent();
Control.CheckForIllegalCrossThreadCalls = false;
button1.Text = "打开串口";
int[] item = { 9600,115200}; //遍历
foreach (int a in item)
{
comboBox2.Items.Add(a.ToString());
}
comboBox2.SelectedItem = comboBox2.Items[1];
}
private void Form1_Load(object sender, EventArgs e)
{
portInit();
}
///
/// 串口初始化
///
private void portInit()
{
string[] ports = SerialPort.GetPortNames();
comboBox1.Items.AddRange(ports);
comboBox1.SelectedItem = comboBox1.Items[0];
}
#region 开关串口
private void button1_Click(object sender, EventArgs e)
{
try
{
if (!s.IsOpen)
{
s.PortName = comboBox1.SelectedItem.ToString();
s.BaudRate = Convert.ToInt32(comboBox2.SelectedItem.ToString());
s.Open();
s.DataReceived += s_DataReceived; //"+="代表指定响应事件时要调用的方法
button1.Text = "关闭串口";
}
else
{
s.Close();
s.DataReceived -= s_DataReceived;
button1.Text = "打开串口";
}
}
catch(Exception ee)
{
MessageBox.Show(ee.ToString());
}
}
#endregion
#region 串口接收
void s_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
int count = s.BytesToRead;
string str = null;
if (count == 8)
{
//数据解析
byte[] buff = new byte[count];
s.Read(buff, 0, count);
foreach (byte item in buff)
{
str += item.ToString("X2") + " ";
}
richTextBox1.Text = "[" + System.DateTime.Now.ToString() + "] " + str + "\n" + richTextBox1.Text;
if (buff[0] == 0x04)
{
ID.Text = buff[0].ToString();
switch (buff[2])
{
case 0x01:
{
Tem.Text = (buff[5] * 4 + buff[4] * 0.05 - 30).ToString();
Hum.Text = (buff[6] + buff[7]).ToString();
break;
}
case 0x02:
{
Light.Text = (buff[6] + buff[7]).ToString();
break;
}
case 0x04:
{
Dust.Text = (buff[6] + buff[7]).ToString();
break;
}
default:
break;
}
}
}
else
{
//当接收数据不在设定的数据位范围之内时,会出现接受到的数据一直保存在接收缓存区之内,后续每次接手数据都会将上一次的数据进行叠加,造成只能通过关闭串口的方法来清除缓冲区的数据
s.DiscardInBuffer(); //丢弃来自串行驱动程序的接收缓冲区的数据
}
}
#endregion
#region 串口发送
private void button3_Click(object sender, EventArgs e)
{
string[] sendbuff = richTextBox2.Text.Split();
Debug.WriteLine("发送字节数:" + sendbuff.Length);
foreach (string item in sendbuff)
{
int count = 1;
byte[] buff = new byte[count];
buff[0] = byte.Parse(item, System.Globalization.NumberStyles.HexNumber);
s.Write(buff,0,count);
}
}
#endregion
private void button2_Click(object sender, EventArgs e)
{
int count = 1;
byte[] buff = new byte[count];
buff[0] = byte.Parse("04", System.Globalization.NumberStyles.HexNumber);
s.Write(buff, 0, count);
}
}
}
在Winfrom窗体设计中,实现串口可以通过工具箱中的串口控件来实现,不过一般推荐直接通过代码来实例化串口,实例化串口需使用如下代码来实现:
//实例化串口
SerialPort s = new SerialPort();
串口初始化可以在窗体的Load函数中实现,以下初始化可以自动化取当前设备中的存在的串口,包括真实串口和虚拟串口:
private void Form1_Load(object sender, EventArgs e)
{
portInit();
}
///
/// 串口初始化
///
private void portInit()
{
string[] ports = SerialPort.GetPortNames();
comboBox1.Items.AddRange(ports);
comboBox1.SelectedItem = comboBox1.Items[0];
}
通过对开关按键button1控件的点击事件,实现串口的开关,通过对控件的文字修改,可以实现一个控件机能实现开又能实现关串口的作用:
#region 开关串口
private void button1_Click(object sender, EventArgs e)
{
try
{
if (!s.IsOpen)
{
s.PortName = comboBox1.SelectedItem.ToString();
s.BaudRate = Convert.ToInt32(comboBox2.SelectedItem.ToString());
s.Open();
s.DataReceived += s_DataReceived; //"+="代表指定响应事件时要调用的方法
button1.Text = "关闭串口";
}
else
{
s.Close();
s.DataReceived -= s_DataReceived;
button1.Text = "打开串口";
}
}
catch(Exception ee)
{
MessageBox.Show(ee.ToString());
}
}
#endregion
串口数据接收和数据解析,首先获取数据接收缓存区数据的字节长度,通过确认长度是否是设定中的长度大小,如果是设定的8位数据长度则对接收的数据进行解析:
#region 串口接收
void s_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
int count = s.BytesToRead;
string str = null;
if (count == 8)
{
//数据解析
byte[] buff = new byte[count];
s.Read(buff, 0, count);
foreach (byte item in buff)
{
str += item.ToString("X2") + " ";
}
richTextBox1.Text = "[" + System.DateTime.Now.ToString() + "] " + str + "\n" + richTextBox1.Text;
if (buff[0] == 0x04)
{
ID.Text = buff[0].ToString();
switch (buff[2])
{
case 0x01:
{
Tem.Text = (buff[5] * 4 + buff[4] * 0.05 - 30).ToString();
Hum.Text = (buff[6] + buff[7]).ToString();
break;
}
case 0x02:
{
Light.Text = (buff[6] + buff[7]).ToString();
break;
}
case 0x04:
{
Dust.Text = (buff[6] + buff[7]).ToString();
break;
}
default:
break;
}
}
}
else
{
//当接收数据不在设定的数据位范围之内时,会出现接受到的数据一直保存在接收缓存区之内,后续每次接手数据都会将上一次的数据进行叠加,造成只能通过关闭串口的方法来清除缓冲区的数据
s.DiscardInBuffer(); //丢弃来自串行驱动程序的接收缓冲区的数据
}
}
#endregion
当接收到的数据长度不等于8的时候,将丢弃来自串行驱动程序的接收缓冲区的数据,接下来通过断点调试来分析丢弃缓冲区和不丢弃缓冲区数据两种情况进行仿真,分析如下几点。
使用串口助手给上位机发送数据数据位长度为8位的数据,串口调试助手和上位机的终端的显示界面如下,发送端数据和接收端数据一样,并未出现异常:
将串口调试助手发送数据位修改成9位之后,进行发送,可以发现上位机并未接收到相关的数据:
接着修改串口调试助手的发送数据位,修改成8位,可以发现上位机尚未能接收到来自串口调试助手发来的数据,这是为什么呢?
接下来将通过断点逐步进行调试,来解释是为啥上位机没有接收到调试助手发来的数据,当串口调试助手发来的数据长度位9位时,通过监视器可以查看到接收缓冲器中的数据长度长度是9
第一次点击完发送之后,上位机未能成功接收到数据,我们就会好奇,并且一般都会点击第二次、第三次、甚至一直点下去,观察是否会出现啥异常现象,当点击第二次时,通过监视窗口,可以观察到到串口缓冲区的数据长度变成了18,这是因为缓冲区将上一次接收的数据给保留了下来并没有删除,就算下次发送的数据长度为8位的时候,也一样是通过叠加的方式将其保存到缓冲区,这样就会造成缓冲区的数据位长度会一直大于8;如果不通过s.DiscardInBuffer()方法丢弃来自串行驱动程序的接收缓冲区的数据,就只能通过关闭串口然后重新打开相应的串口来实现缓冲区的数据清除。
使用s.DiscardInBuffer()对不符合长度的数据进行丢弃,实现的vb.net教程效果如下所示:
需要完整源码的朋友可以通过以下链接进行下载,如有大佬有更好的优化意见欢迎一块进行讨论,谢谢!
链接: https://pan.baidu.com/s/1MXVIFQHHsEmx4p28Pz-wcQ 提取码: ibu9
到此这篇关于C# 实现简易的串口监视上位机功能的文章就介绍到这了