c# 模拟串口通信 SerialPort的实现示例
作者:hey_lie 发布时间:2023-09-03 22:19:50
一、前导知识
串行口是计算机的标准接口,现在的PC机(个人电脑)一般至少有两个串行口COM1和COM2。串行口应用广泛,在数据通信、计算机网络以及分布式工业控制系统中,经常采用串行通信来交换数据和信息
电气标准及协议来分包括RS-232-C、RS-422、RS485、USB(Universal Serial Bus)等
实现串口通信的必要设置
串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。
对于两个进行通行的端口,这些参数必须匹配:
波特率
这是一个衡量通信速度的参数。它表示**每秒钟传送的bit的个数**。例如300波特表示每秒钟发送300个bit,波特率和距离成反比。高波特率常常用于放置的很近的仪器间的通信,典型的例子就是GPIB设备的通信
数据位
这是衡量通信中实际数据位的参数。当计算机发送一个信息包,实际的数据不会是8位的,标准的值是5、7和8位,如何设置取决于你想传送的信息。比如,标准的ASCII码是0~127(7位)。扩展的ASCII码是0~255(8位)。如果数据使用简单的文本(标准 ASCII码),那么每个数据包使用7位数据。每个包是指一个字节,包括开始/停止位,数据位和奇偶校验位。由于实际数据位取决于通信协议的选取,术语“包”指任何通信的情况。
停止位
用于表示单个包的最后一位。典型的值为1,1.5和2位。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢
奇偶校验位
在串口通信中一种简单的检错方式。有四种检错方式:偶、奇、高和低。当然没有校验位也是可以的。对于偶和奇校验的情况,串口会设置校验位(数据位后面的一位),用一个值确保传输的数据有偶个或者奇个逻辑高位。例如,如果数据是011,那么对于偶校验,校验位为0,保证逻辑高的位数是偶数个。如果是奇校验,校验位位1,这样就有3个逻辑高位。高位和低位不真正的检查数据,简单置位逻辑高或者逻辑低校验。这样使得接收设备能够知道一个位的状态,有机会判断是否有噪声干扰了通信或者是否传输和接收数据是否不同步
二、实验
我们将通过模拟串口通信,在pc机上进行两个串口(COM1、COM2)的交互
需要用到的软件:
Launch Virtual Serial Port Driver Pro:虚拟串口。使用它来模拟两个串口的连接
绘制窗口
代码实现
1.使用SerialPort控制串口
private SerialPort sp1 = new SerialPort();
2.打开串口
private void button2_Click(object sender, EventArgs e)
{
if (!sp1.IsOpen)
{
try
{
//串口号
sp1.PortName = "COM1";
//波特率
sp1.BaudRate = 115200;
//数据位
sp1.DataBits = 8;
//停止位
sp1.StopBits = StopBits.One;
//奇偶校验位
sp1.Parity = Parity.Even;
//DataReceived事件发送前,内部缓冲区里的字符数
sp1.ReceivedBytesThreshold = 1;
sp1.RtsEnable = true; sp1.DtrEnable = true; sp1.ReadTimeout = 3000;
// Control.CheckForIllegalCrossThreadCalls = false;
//表示将处理 System.IO.Ports.SerialPort 对象的数据接收事件的方法。
sp1.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(sp1_DataReceived_1);
//打开串口
sp1.Open();
MessageBox.Show("COM1打开成功!");
}
catch (Exception ex)
{
MessageBox.Show("COM1打开失败!");
}
}
else
{
MessageBox.Show("COM1打开成功!");
}
}
3.关闭串口
private void button3_Click(object sender, EventArgs e)
{
if (sp1.IsOpen)
{
sp1.Close();
MessageBox.Show("COM1关闭成功!");
}
}
串口2的打开和关闭同理串口1实现
4.发送
private void button1_Click(object sender, EventArgs e)
{
if (sp1.IsOpen)
{
if (!string.IsNullOrEmpty(this.textBox1.Text))
{
sp1.WriteLine(this.textBox1.Text+"\r\n");
}
else
{
MessageBox.Show("发送数据为空");
}
}
else
{
MessageBox.Show("COM1未打开!");
}
}
5.接收
StringBuilder builder1 = new StringBuilder();
//在接收到了ReceivedBytesThreshold设置的字符个数或接收到了文件结束字符并将其放入了输入缓冲区时被触发
public void sp1_DataReceived_1(object sender, SerialDataReceivedEventArgs e)
{
Console.WriteLine("接收中...");
int n = sp1.BytesToRead; //先记录下来,避免某种原因,人为的原因,操作几次之间时间长,缓存不一致
byte[] buf = new byte[n]; //声明一个临时数组存储当前来的串口数据
sp1.Read(buf, 0, n); //读取缓冲数据
builder1.Remove(0, builder1.Length); //清除字符串构造器的内容
builder1.Append(Encoding.ASCII.GetString(buf));
string comdata = builder1.ToString();
Console.WriteLine("data: + " + comdata);
this.Invoke(settextevent,comdata);
}
这里仅仅实现了一般的接收方式,并不严谨和健壮
测试
使用软件模拟串口连接
打开两个程序
在一程序中打开串口1,在二程序中打开串口2,发送消息
在一程序中输入字符"hello,HanHanCheng!",发现在二程序中接收到,同样,在二程序中输入,在一中也能收到
三、总结
1.由于是异步线程接收,在接收中需要使用委托来跨线程调用组件
public delegate void settext(string text);
public event settext settextevent;
public void set(string text)
{
this.textBox2.Text = text;
}
//再注册
settextevent += set;
2.DataReceived事件触发条件需要注意,可能在实现时,无法触发导致无法接收。
触发条件是:在接收到了ReceivedBytesThreshold设置的字符个数或接收到了文件结束字符并将其放入了输入缓冲区时被触发
四、附件完整代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO.Ports;
namespace Training_USBCOM
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
settextevent += set;
}
private SerialPort sp1 = new SerialPort();
StringBuilder builder = new StringBuilder();
private void button1_Click(object sender, EventArgs e)
{
if (sp1.IsOpen)
{
if (!string.IsNullOrEmpty(this.textBox1.Text))
{
sp1.WriteLine(this.textBox1.Text+"\r\n");
}
else
{
MessageBox.Show("发送数据为空");
}
}
else
{
MessageBox.Show("COM1未打开!");
}
}
public delegate void settext(string text);
public event settext settextevent;
public void set(string text)
{
this.textBox2.Text = text;
}
StringBuilder builder1 = new StringBuilder();
//在接收到了ReceivedBytesThreshold设置的字符个数或接收到了文件结束字符并将其放入了输入缓冲区时被触发
public void sp1_DataReceived_1(object sender, SerialDataReceivedEventArgs e)
{
Console.WriteLine("接收中...");
int n = sp1.BytesToRead; //先记录下来,避免某种原因,人为的原因,操作几次之间时间长,缓存不一致
byte[] buf = new byte[n]; //声明一个临时数组存储当前来的串口数据
sp1.Read(buf, 0, n); //读取缓冲数据
builder1.Remove(0, builder1.Length); //清除字符串构造器的内容
builder1.Append(Encoding.ASCII.GetString(buf));
string comdata = builder1.ToString();
Console.WriteLine("data: + " + comdata);
this.Invoke(settextevent,comdata);
}
private void button2_Click(object sender, EventArgs e)
{
if (!sp1.IsOpen)
{
try
{
//串口号
sp1.PortName = "COM1";
//波特率
sp1.BaudRate = 115200;
//数据位
sp1.DataBits = 8;
//停止位
sp1.StopBits = StopBits.One;
//奇偶校验位
sp1.Parity = Parity.Even;
//DataReceived事件发送前,内部缓冲区里的字符数
sp1.ReceivedBytesThreshold = 1;
sp1.RtsEnable = true; sp1.DtrEnable = true; sp1.ReadTimeout = 3000;
// Control.CheckForIllegalCrossThreadCalls = false;
//表示将处理 System.IO.Ports.SerialPort 对象的数据接收事件的方法。
sp1.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(sp1_DataReceived_1);
//打开串口
sp1.Open();
MessageBox.Show("COM1打开成功!");
}
catch (Exception ex)
{
MessageBox.Show("COM1打开失败!");
}
}
else
{
MessageBox.Show("COM1打开成功!");
}
}
private void button3_Click(object sender, EventArgs e)
{
if (sp1.IsOpen)
{
sp1.Close();
MessageBox.Show("COM1关闭成功!");
}
}
private void button5_Click(object sender, EventArgs e)
{
if (!sp1.IsOpen)
{
try
{
//串口号
sp1.PortName = "COM2";
//波特率
sp1.BaudRate = 115200;
//数据位
sp1.DataBits = 8;
//停止位
sp1.StopBits = StopBits.One;
//奇偶校验位
sp1.Parity = Parity.Even;
sp1.ReceivedBytesThreshold = 1;
sp1.RtsEnable = true; sp1.DtrEnable = true; sp1.ReadTimeout = 3000;
Control.CheckForIllegalCrossThreadCalls = false;
//表示将处理 System.IO.Ports.SerialPort 对象的数据接收事件的方法。
sp1.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(sp1_DataReceived_1);
//打开串口
sp1.Open();
MessageBox.Show("COM2打开成功!");
}
catch (Exception ex)
{
MessageBox.Show("COM2打开失败!");
}
}
else
{
MessageBox.Show("COM2打开成功!");
}
}
private void button4_Click(object sender, EventArgs e)
{
if (sp1.IsOpen)
{
sp1.Close();
MessageBox.Show("COM2关闭成功!");
}
}
}
}
来源:https://blog.csdn.net/hey_lie/article/details/120194909


猜你喜欢
- Android 判断SIM卡属于哪个移动运营商第一种方法:获取手机的IMSI码,并判断是中国移动\中国联通\中国电信TelephonyMan
- 目录题目及要求:提示:原创代码:代码思路:题目及要求:给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。提示:0 <
- 在你的jar文件当前目录中建立一个bat文件:内容是:注意文件名要对应@echo offSTART "commandServer&
- 本文会先介绍通用 Mapper 的简单原理,然后使用最简单的代码来实现这个过程。基本原理通用 Mapper 提供了一些通用的方法,这些通用方
- 什么是自动装箱,拆箱先抛出定义,Java中基础数据类型与它们的包装类进行运算时,编译器会自动帮我们进行转换,转换过程对程序员是透明的,这就是
- Spring多数据源实现的方式大概有2中,一种是新建多个MapperScan扫描不同包,另外一种则是通过继承AbstractRoutingD
- 先给大家展示下效果图,对第三方开源 android tickplusdrawable相关知识感兴趣的朋友一起学习吧。Android tick
- Spring Boot 项目之热部署配置前言所谓热部署,简单来说,就是代码修改后不需重启项目就可自动加载出新的内容。注意:热部署在 debu
- 今天就是国赛的第一天直接开摆打国赛不如玩羊了个羊玩羊了个羊不如玩MATLAB版写作不易留个赞叭(比赛之余放松一下也行,反正MATLAB版我设
- 基本的SpringMVC的搭建在我的上一篇文章里已经写过了,这篇文章主要说明一下如何使用SpringMVC进行表单上的文件上传以及多个文件同
- 在java程序开发中,ftp用的比较多,经常打交道,比如说向FTP服务器上传文件、下载文件,本文给大家介绍如何利用jakarta commo
- 注:底色透明是否生效与android版本有关,版本过低设置无效1.在main.dart内设置void main(){ runApp(new
- 1、前言Android Studio对模块化开发提供的一个很有用的功能就是可以在主项目下新建库项目(Module),但是在使用库项目时却有一
- 声明一个可变数量的参数: Static int Add(params int[] values) { int sum = 0; if(val
- 示例 1 :使用搜索表单创建全屏模式我们要构建的小应用程序有一个应用程序栏,右侧有一个搜索按钮。按下此按钮时,将出现一个全屏模式对话框。它不
- 目录前言概念什么是循环依赖?报错信息通俗版理解两人对峙必须有一人妥协Spring版理解实例化和初始化什么区别? * 缓存创建过程(简易版)创建
- 为了避免直接进入项目中存在的页面,使用filter过滤器新建一个类loginFilter:package com.tjcu.filter;i
- 今天带大家实现滑动返回效果.,具体内容如下所示:先看看效果图:因为没有具体内容,也没有简书的图片资源,所以稍微简陋了点.但是依然不妨碍我们的
- MojoUnityJson 是使用C#实现的JSON解析器 ,算法思路来自于游戏引擎Mojoc的C语言实现 Json.h 。借助C#的类库,
- 前言最近参加了21天打卡活动,希望可以让自己养成写博客的习惯…ArrayList和LinkedListArrayLis