C# 实现简易的串口监视上位机功能附源码下载
作者:熊来闯一闯 发布时间:2023-10-12 11:12:19
实现上位机和下位机之间的通信,通常使用的是串口通信,接下来实现一个通过上位机和串口调试助手来完成串口通信测试。
首先创建一个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();
}
/// <summary>
/// 串口初始化
/// </summary>
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();
}
/// <summary>
/// 串口初始化
/// </summary>
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()对不符合长度的数据进行丢弃,实现的效果如下所示:
需要完整源码的朋友可以通过以下链接进行下载,如有大佬有更好的优化意见欢迎一块进行讨论,谢谢!
链接: https://pan.baidu.com/s/1MXVIFQHHsEmx4p28Pz-wcQ 提取码: ibu9
来源:https://www.cnblogs.com/xionglaichuangyichuang/p/14011154.html


猜你喜欢
- 今天碰到一个非常奇怪的问题: 在Android中ImageView无法显示加载的本地SDCard图片。 具体过程是:先调用本地照相机程序摄像
- ADB全称Android Debug Bridge, 是android sdk里的一个工具, 用这个工具可以直接操作管理android模拟器
- 查看JDK1.8 ArrayList的源代码1、默认初始容量为10 /** * Default i
- 最近公司要求开发工具要用Idea,作为一个eclipse的老员工,记录一下Idea中遇到的坑刚开始用Idea从Git上导入一个项目时,遇到了
- Thread dumps(线程转储)能帮助我们判断 CPU 峰值、死锁、内存异常、应用反应迟钝、响应时间变长和其他系统问题。一些在线的分析工
- 0.背景简介微软在 .NET 框架中提供了多种实用的线程同步手段,其中包括 monitor 类及 reader-writer锁。
- 在Android中使用ImageView显示图片的时候发现图片显示不正,方向偏了或者倒过来了。 解决这个问题很自然想到的分两步走: 1、自动
- 今天老师留的作业,使用俩个Fragment来实现3D翻转效果,遇到了一点点的问题,于是在网上进行了查找,但是发现有些博主的代码不正确,对其他
- 目录不含return的执行顺序finally子句含return的执行顺序返回类型是对象类型时值的变化结论不含return的执行顺序执行顺序为
- 本文实例讲述了C#通过WIN32 API实现嵌入程序窗体的方法,分享给大家供大家参考。具体如下:这是一个不使用COM,而是通过WIN32 A
- Android仿通话来电界面,供大家参考,具体内容如下简介:开发中需要模拟来电时的通话界面,仿照来电界面实现来电时播放铃声,界面通过动画模拟
- 本文实例为大家分享了java实现简单快递系统的具体代码,供大家参考,具体内容如下创建四个类Express,Locker, User, Adm
- 思想利用栈和队列都可以实现树的迭代遍历。递归的写法将这个遍历的过程交给系统的堆栈去实现了,所以思想都是一样的、无非就是插入值的时机不一样。利
- 前言:Android Studio中把项目的lib库提交到Jcenter仓库中,需要使用到Bintray,Bintray是jCenter的提
- 本文实例为大家分享了Java实现二分查找的变种,供大家参考,具体内容如下普通二分查找:先回顾一下普通的二分查找注意:二分查找有这样一个问题:
- 我们在上网的过程中经常看到各种图片,那你知道它是如何实现的吗?接下来就让我们一块探讨一下。 网络图片的浏览可以分为俩部分,基本的页面布局与界
- 本文大纲本文章将要介绍的内容有以下几点,读者朋友也可先自行思考一下相关问题:线程中断 interrupt 方法怎么理解,意思就是线程中断了吗
- C# 中的委托(Delegate)类似于 C 或 C++ 中函数的指针。委托(Delegate) 是存有对某个方法的引用的一种引用类型变量。
- 本文实例讲述了java实现mp3合并的方法。分享给大家供大家参考。具体实现方法如下:package test;import java.io.
- 定义:封装某些作用于某种数据结构中各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。类型:行为类模式类图:例子:例如