文章目录
1、创建虚拟串口
虚拟串口工具获取及使用可参考:虚拟串口工具VSPD简单使用
2、创建WinForm应用程序
我这里使用的是vs2019


3、绘制WinForm窗体

界面确实不好看,不过这些都不重要
在这里说明一下用到了哪些控件及控件命名
1、接收数据及发送两个模块分别使用GroupBox容器控件包裹,发送部分容器命名gb_SendPanel,接口部分命名gb_ReceivePanel
2、接收栏内只有一个TextBox控件,命名为txt_Received,并设置属性允许多行,只读,显示垂直滚动条Multiline=true,ReadOnly = true,ScrollBars =Vertical
3、发送部分由6个文本Label,6个下拉框ComboBox,3个按钮Button,1个发送内容框TextBox组成
4、发送部分6个Label命名改不改都无所谓,后续不会再有用到
5、6个下拉框ComboBox分别命名为:串口下拉框cb_SelectComPort、波特率下拉框cb_baudSelect、停止位下拉框cb_StopSelect、数据位下拉框cb_DataSelect、检验位下拉框cb_CheckSelect、显示下拉框cb_DataShow
6、3个按钮Button命名分别为:检测串口btn_CheckPort、打开\关闭串口btn_OpenOrrEndCom、发送按钮btn_Send
7、发送内容文本框命名为txt_Send,设置属性允许多行Multiline = true
8、在这个窗体中字体上我这里是设置的宋体, 12pt(无关紧要)
9、窗体数据相关都是后台通过代码实现的,详情继续往下看
4、属性初始化
#region 初始话属性
/// <summary>
/// 实例化串口资源类
/// </summary>
private readonly System.IO.Ports.SerialPort serialPort = new System.IO.Ports.SerialPort();
/// <summary>
/// 波特率数值范围
/// </summary>
private readonly List<string> baudList = new List<string>() {
"600","1200", "2400", "4800", "9600","14400", "19200", "28800","38400", "43000", "57600",
"76800","115200","128000","230400","256000","460800","921600","1382400", "自定义"
};
/// <summary>
/// 停止位
/// </summary>
private readonly List<string> stopList = new List<string> { "1", "1.5", "2" };
/// <summary>
/// 数据位
/// </summary>
private readonly List<string> dataList = new List<string> { "5", "6", "7", "8" };
/// <summary>
/// 校验位
/// </summary>
private readonly List<string> checkList = new List<string>() { "NONE", "ODD", "EVEN", "MARK", "SPACE" };
/// <summary>
/// 数据显示
/// </summary>
private readonly List<string> showList = new List<string>() { "16进制(HEX)", "字符显示(ASCII)" };
#endregion
5、界面加载事件与初始化控件
#region 界面加载
/// <summary>
/// 界面加载事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Form1_Load(object sender, EventArgs e)
{
//串口选择
InitPorts();
//波特率
InitBaud();
//停止位
InitStop();
//数据位
InitData();
//校验位
InitCheck();
//初始化数据显示
InitDataShow();
//实例化串口控件
InitSerialPort();
}
#endregion
#region 初始化控件
/// <summary>
/// 初始化串口控件
/// </summary>
public void InitSerialPort()
{
serialPort.DataReceived += Sp1_DataReceived;//指示已通过由 System.IO.Ports.SerialPort 对象表示的端口接收了数据
serialPort.DtrEnable = true;//获取或设置一个值,该值在串行通信过程中启用数据终端就绪 (DTR) 信号。
serialPort.RtsEnable = true;//获取或设置一个值,该值指示在串行通信中是否启用请求发送 (RTS) 信号
serialPort.ReadTimeout = 1000;//设置数据读取超时为1s
serialPort.Close();
}
/// <summary>
/// 扫描可用串口,初始化串口选择
/// </summary>
public void InitPorts()
{
this.cb_SelectComPort.DropDownStyle = ComboBoxStyle.DropDownList;//禁用输入功能
this.cb_SelectComPort.Items.Clear();//清除原有串口
var comPostList = System.IO.Ports.SerialPort.GetPortNames().ToList();//读取串口
if (comPostList != null && comPostList.Any())
{
comPostList = comPostList.OrderBy(x => x).ToList();
foreach (var item in comPostList)
{
this.cb_SelectComPort.Items.Add(item);
}
this.cb_SelectComPort.Text = comPostList.FirstOrDefault();//默认选择第一个串口
this.cb_SelectComPort.Enabled = true;//打开选择框
}
else
{
MessageBox.Show("本机未找到串口!!!", "Error");
}
}
/// <summary>
/// 初始化校验位
/// </summary>
public void InitCheck()
{
this.cb_CheckSelect.DropDownStyle = ComboBoxStyle.DropDownList;
foreach (var item in checkList)
{
this.cb_CheckSelect.Items.Add(item);
}
this.cb_CheckSelect.SelectedItem = "NONE";
}
/// <summary>
/// 初始化数据位
/// </summary>
public void InitData()
{
this.cb_DataSelect.DropDownStyle = ComboBoxStyle.DropDownList;
foreach (var item in dataList)
{
this.cb_DataSelect.Items.Add(item);
}
this.cb_DataSelect.SelectedItem = "8";
}
/// <summary>
/// 初始化停止位
/// </summary>
public void InitStop()
{
this.cb_StopSelect.DropDownStyle = ComboBoxStyle.DropDownList;
foreach (var item in stopList)
{
this.cb_StopSelect.Items.Add(item);
}
this.cb_StopSelect.SelectedItem = "1";
}
/// <summary>
/// 初始化波特率
/// </summary>
public void InitBaud()
{
this.cb_baudSelect.DropDownStyle = ComboBoxStyle.DropDownList;//不可手动输入
foreach (var item in baudList)
{
this.cb_baudSelect.Items.Add(item);
}
this.cb_baudSelect.SelectedItem = "9600";
}
/// <summary>
/// 初始化数据显示
/// </summary>
public void InitDataShow()
{
this.cb_DataShow.DropDownStyle = ComboBoxStyle.DropDownList;//不可手动输入
foreach (var item in showList)
{
this.cb_DataShow.Items.Add(item);
}
this.cb_DataShow.SelectedItem = "字符显示(ASCII)";
}
#endregion
6、波特率选择事件
/// <summary>
/// 波特率选择,自定义时允许输入
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void cb_baudSelect_SelectedValueChanged(object sender, EventArgs e)
{
if (this.cb_baudSelect.SelectedItem?.ToString() == "自定义")
{
this.cb_baudSelect.DropDownStyle = ComboBoxStyle.DropDown;
}
else
{
this.cb_baudSelect.DropDownStyle = ComboBoxStyle.DropDownList;
}
}
7、检测串口
/// <summary>
/// 检测串口
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btn_CheckPort_Click(object sender, EventArgs e)
{
this.cb_SelectComPort.Items.Clear();//清除原有串口选择数据
var comPostList = System.IO.Ports.SerialPort.GetPortNames().ToList();//读取串口
if (comPostList != null && comPostList.Any())
{
comPostList = comPostList.OrderBy(x => x).ToList();//排序
foreach (var item in comPostList)
{
this.cb_SelectComPort.Items.Add(item);
}
this.cb_SelectComPort.Text = comPostList.FirstOrDefault();//默认选择第一个串口
}
else
{
MessageBox.Show("本机未找到串口!!!", "Error");
}
}
8、串口设置
#region 设置串口
/// <summary>
/// 设置串口信息
/// </summary>
private void SetSerialPort()
{
string strBuadRate = this.cb_baudSelect.Text.Trim();
string strDataRate = this.cb_DataSelect.Text.Trim();
string strCheckRate = this.cb_CheckSelect.Text.Trim();
string strPortRate = this.cb_SelectComPort.Text.Trim();
string strStopRate = this.cb_StopSelect.Text.Trim();
int IBaudBits = Convert.ToInt32(strBuadRate);
int IDataBits = Convert.ToInt32(strDataRate);
serialPort.PortName = strPortRate;//设置串口号
serialPort.BaudRate = IBaudBits;//设置波特率
serialPort.DataBits = IDataBits;//设置数据位
serialPort.Encoding = Encoding.UTF8;
//设置校验位
switch (strCheckRate)
{
case "NONE":
serialPort.Parity = Parity.None;
break;
case "ODD":
serialPort.Parity = Parity.Odd;
break;
case "EVEN":
serialPort.Parity = Parity.Even;
break;
case "MARK":
serialPort.Parity = Parity.Mark;
break;
case "SPACE":
serialPort.Parity = Parity.Space;
break;
default:
serialPort.Parity = Parity.None;
break;
}
//设置停止位
switch (strStopRate)
{
case "1":
serialPort.StopBits = StopBits.One;
break;
case "1.5":
serialPort.StopBits = StopBits.OnePointFive;
break;
case "2":
serialPort.StopBits = StopBits.Two;
break;
default:
serialPort.StopBits = StopBits.One;
break;
}
serialPort.ReadTimeout = 5000;//读取超时时间5s
}
#endregion
9、打开\关闭串口
/// <summary>
/// 打开\关闭串口按钮点击事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btn_OpenOrrEndCom_Click(object sender, EventArgs e)
{
//串口是否打开
if (!serialPort.IsOpen)
{
try
{
this.btn_OpenOrrEndCom.Text = "关闭串口";
//如果打开状态,则先关闭一下
if (serialPort.IsOpen)
{
serialPort.Close();
}
//设置串口
SetSerialPort();
//打开串口后不允许更改连接属性
this.cb_baudSelect.Enabled = false;
this.cb_CheckSelect.Enabled = false;
this.cb_SelectComPort.Enabled = false;
this.cb_StopSelect.Enabled = false;
this.cb_DataSelect.Enabled = false;
//打开
serialPort.Open();
}
catch (Exception ex)
{
MessageBox.Show("打开串口失败:" + ex.Message);
throw;
}
}
else
{
this.cb_baudSelect.Enabled = true;
this.cb_CheckSelect.Enabled = true;
this.cb_SelectComPort.Enabled = true;
this.cb_StopSelect.Enabled = true;
this.cb_DataSelect.Enabled = true;
this.btn_OpenOrrEndCom.Text = "打开串口";
serialPort.Close();//关闭
}
}
10、发送与接收数据
/// <summary>
/// 发送数据
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btn_Send_Click(object sender, EventArgs e)
{
if (serialPort.IsOpen)
{
try
{
byte[] sendData = null;
string send = this.txt_Send.Text.Trim();
var showText = this.cb_DataShow.Text;
//按照指定编码将string编程字节数组
byte[] b = Encoding.GetEncoding("GBK").GetBytes(send);
if (showText == "16进制(HEX)")
{
string result = string.Empty;
//逐字节变为16进制字符
for (int i = 0; i < b.Length; i++)
{
result += Convert.ToString(b[i], 16).ToUpper() + " ";
}
sendData = Encoding.GetEncoding("GBK").GetBytes(result);
}
else
{
sendData = b;
}
serialPort.Write(sendData, 0, sendData.Length);
}
catch (Exception ex)
{
MessageBox.Show("发送失败:" + ex.Message, "Error");
}
}
else
{
MessageBox.Show("请先打开串口", "Error");
}
}
/// <summary>
/// 接收数据
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Sp1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
//异步委托一个线程,不然接收数据时会出现数据线程错误
this.Invoke((EventHandler)(delegate
{
var showText = this.cb_DataShow.Text;
if (serialPort.IsOpen)
{
try
{
byte[] receivedData = new byte[serialPort.BytesToRead];//创建接收数据数组
serialPort.Read(receivedData, 0, receivedData.Length);//读取数据
var content = string.Empty;
//显示形式
switch (showText)
{
case "16进制(HEX)":
for (int i = 0; i < receivedData.Length; i++)
{
//ToString("X2") 为C#中的字符串格式控制符
//X为 十六进制
//2为 每次都是两位数
content += (receivedData[i].ToString("X2") + " ");
}
break;
case "字符显示(ASCII)":
content = Encoding.GetEncoding("GB2312").GetString(receivedData);//防止乱码
break;
}
//接收文本框
this.txt_Received.AppendText($"{DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss:ffff")}:{content} \r\n");
serialPort.DiscardInBuffer();//丢弃缓存区数据
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error");
}
}
else
{
MessageBox.Show("请打开串口", "Error");
}
}));
}
11、完整代码
using System;
using System.Collections.Generic;
using System.Data;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace 虚拟串口通信
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
#region 初始话属性
/// <summary>
/// 实例化串口资源类
/// </summary>
private readonly System.IO.Ports.SerialPort serialPort = new System.IO.Ports.SerialPort();
/// <summary>
/// 波特率数值范围
/// </summary>
private readonly List<string> baudList = new List<string>() {
"600","1200", "2400", "4800", "9600","14400", "19200", "28800","38400", "43000", "57600",
"76800","115200","128000","230400","256000","460800","921600","1382400","自定义"
};
/// <summary>
/// 停止位
/// </summary>
private readonly List<string> stopList = new List<string> { "1", "1.5", "2" };
/// <summary>
/// 数据位
/// </summary>
private readonly List<string> dataList = new List<string> { "5", "6", "7", "8" };
/// <summary>
/// 校验位
/// </summary>
private readonly List<string> checkList = new List<string>() { "NONE", "ODD", "EVEN", "MARK", "SPACE" };
/// <summary>
/// 数据显示
/// </summary>
private readonly List<string> showList = new List<string>() { "16进制(HEX)", "字符显示(ASCII)" };
#endregion
#region 界面加载
/// <summary>
/// 界面加载事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Form1_Load(object sender, EventArgs e)
{
//串口选择
InitPorts();
//波特率
InitBaud();
//停止位
InitStop();
//数据位
InitData();
//校验位
InitCheck();
//初始化数据显示
InitDataShow();
//实例化串口控件
InitSerialPort();
}
#endregion
#region 初始化
/// <summary>
/// 初始化串口控件
/// </summary>
public void InitSerialPort()
{
serialPort.DataReceived += Sp1_DataReceived;//指示已通过由 System.IO.Ports.SerialPort 对象表示的端口接收了数据
serialPort.DtrEnable = true;//获取或设置一个值,该值在串行通信过程中启用数据终端就绪 (DTR) 信号。
serialPort.RtsEnable = true;//获取或设置一个值,该值指示在串行通信中是否启用请求发送 (RTS) 信号
serialPort.ReadTimeout = 1000;//设置数据读取超时为1s
serialPort.Close();
}
/// <summary>
/// 扫描可用串口,初始化串口选择
/// </summary>
public void InitPorts()
{
this.cb_SelectComPort.DropDownStyle = ComboBoxStyle.DropDownList;//禁用输入功能
this.cb_SelectComPort.Items.Clear();//清除原有串口
var comPostList = System.IO.Ports.SerialPort.GetPortNames().ToList();//读取串口
if (comPostList != null && comPostList.Any())
{
comPostList = comPostList.OrderBy(x => x).ToList();
foreach (var item in comPostList)
{
this.cb_SelectComPort.Items.Add(item);
}
this.cb_SelectComPort.Text = comPostList.FirstOrDefault();//默认选择第一个串口
this.cb_SelectComPort.Enabled = true;//打开选择框
}
else
{
MessageBox.Show("本机未找到串口!!!", "Error");
}
}
/// <summary>
/// 初始化校验位
/// </summary>
public void InitCheck()
{
this.cb_CheckSelect.DropDownStyle = ComboBoxStyle.DropDownList;
foreach (var item in checkList)
{
this.cb_CheckSelect.Items.Add(item);
}
this.cb_CheckSelect.SelectedItem = "NONE";
}
/// <summary>
/// 初始化数据位
/// </summary>
public void InitData()
{
this.cb_DataSelect.DropDownStyle = ComboBoxStyle.DropDownList;
foreach (var item in dataList)
{
this.cb_DataSelect.Items.Add(item);
}
this.cb_DataSelect.SelectedItem = "8";
}
/// <summary>
/// 初始化停止位
/// </summary>
public void InitStop()
{
this.cb_StopSelect.DropDownStyle = ComboBoxStyle.DropDownList;
foreach (var item in stopList)
{
this.cb_StopSelect.Items.Add(item);
}
this.cb_StopSelect.SelectedItem = "1";
}
/// <summary>
/// 初始化波特率
/// </summary>
public void InitBaud()
{
this.cb_baudSelect.DropDownStyle = ComboBoxStyle.DropDownList;//不可手动输入
foreach (var item in baudList)
{
this.cb_baudSelect.Items.Add(item);
}
this.cb_baudSelect.SelectedItem = "9600";
}
/// <summary>
/// 初始化数据显示
/// </summary>
public void InitDataShow()
{
this.cb_DataShow.DropDownStyle = ComboBoxStyle.DropDownList;//不可手动输入
foreach (var item in showList)
{
this.cb_DataShow.Items.Add(item);
}
this.cb_DataShow.SelectedItem = "字符显示(ASCII)";
}
#endregion
#region 设置串口
/// <summary>
/// 设置串口信息
/// </summary>
private void SetSerialPort()
{
string strBuadRate = this.cb_baudSelect.Text.Trim();
string strDataRate = this.cb_DataSelect.Text.Trim();
string strCheckRate = this.cb_CheckSelect.Text.Trim();
string strPortRate = this.cb_SelectComPort.Text.Trim();
string strStopRate = this.cb_StopSelect.Text.Trim();
int IBaudBits = Convert.ToInt32(strBuadRate);
int IDataBits = Convert.ToInt32(strDataRate);
serialPort.PortName = strPortRate;//设置串口号
serialPort.BaudRate = IBaudBits;//设置波特率
serialPort.DataBits = IDataBits;//设置数据位
serialPort.Encoding = Encoding.UTF8;
//设置校验位
switch (strCheckRate)
{
case "NONE":
serialPort.Parity = Parity.None;
break;
case "ODD":
serialPort.Parity = Parity.Odd;
break;
case "EVEN":
serialPort.Parity = Parity.Even;
break;
case "MARK":
serialPort.Parity = Parity.Mark;
break;
case "SPACE":
serialPort.Parity = Parity.Space;
break;
default:
serialPort.Parity = Parity.None;
break;
}
//设置停止位
switch (strStopRate)
{
case "1":
serialPort.StopBits = StopBits.One;
break;
case "1.5":
serialPort.StopBits = StopBits.OnePointFive;
break;
case "2":
serialPort.StopBits = StopBits.Two;
break;
default:
serialPort.StopBits = StopBits.One;
break;
}
serialPort.ReadTimeout = 5000;//读取超时时间5s
}
#endregion
#region 波特率选择
/// <summary>
/// 波特率选择,自定义时允许输入
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void cb_baudSelect_SelectedValueChanged(object sender, EventArgs e)
{
if (this.cb_baudSelect.SelectedItem?.ToString() == "自定义")
{
this.cb_baudSelect.DropDownStyle = ComboBoxStyle.DropDown;
}
else
{
this.cb_baudSelect.DropDownStyle = ComboBoxStyle.DropDownList;
}
}
#endregion
#region 检测串口
/// <summary>
/// 检测串口
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btn_CheckPort_Click(object sender, EventArgs e)
{
this.cb_SelectComPort.Items.Clear();//清除原有串口选择数据
var comPostList = System.IO.Ports.SerialPort.GetPortNames().ToList();//读取串口
if (comPostList != null && comPostList.Any())
{
comPostList = comPostList.OrderBy(x => x).ToList();
foreach (var item in comPostList)
{
this.cb_SelectComPort.Items.Add(item);
}
this.cb_SelectComPort.Text = comPostList.FirstOrDefault();//默认选择第一个串口
}
else
{
MessageBox.Show("本机未找到串口!!!", "Error");
}
}
#endregion
#region 打开\关闭串口
/// <summary>
/// 打开\关闭串口
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btn_OpenOrrEndCom_Click(object sender, EventArgs e)
{
//串口是否打开
if (!serialPort.IsOpen)
{
try
{
this.btn_OpenOrrEndCom.Text = "关闭串口";
//如果打开状态,则先关闭一下
if (serialPort.IsOpen)
{
serialPort.Close();
}
//设置串口
SetSerialPort();
//打开串口后不允许更改
this.cb_baudSelect.Enabled = false;
this.cb_CheckSelect.Enabled = false;
this.cb_SelectComPort.Enabled = false;
this.cb_StopSelect.Enabled = false;
this.cb_DataSelect.Enabled = false;
//打开
serialPort.Open();
}
catch (Exception ex)
{
MessageBox.Show("打开串口失败:" + ex.Message);
throw;
}
}
else
{
this.cb_baudSelect.Enabled = true;
this.cb_CheckSelect.Enabled = true;
this.cb_SelectComPort.Enabled = true;
this.cb_StopSelect.Enabled = true;
this.cb_DataSelect.Enabled = true;
this.btn_OpenOrrEndCom.Text = "打开串口";
serialPort.Close();//关闭
}
}
#endregion
#region 发送与接收数据
/// <summary>
/// 发送数据
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btn_Send_Click(object sender, EventArgs e)
{
if (serialPort.IsOpen)
{
try
{
byte[] sendData = null;
string send = this.txt_Send.Text.Trim();
var showText = this.cb_DataShow.Text;
//按照指定编码将string编程字节数组
byte[] b = Encoding.GetEncoding("GBK").GetBytes(send);
if (showText == "16进制(HEX)")
{
string result = string.Empty;
//逐字节变为16进制字符
for (int i = 0; i < b.Length; i++)
{
result += Convert.ToString(b[i], 16).ToUpper() + " ";
}
sendData = Encoding.GetEncoding("GBK").GetBytes(result);
}
else
{
sendData = b;
}
serialPort.Write(sendData, 0, sendData.Length);
}
catch (Exception ex)
{
MessageBox.Show("发送失败:" + ex.Message, "Error");
}
}
else
{
MessageBox.Show("请先打开串口", "Error");
}
}
/// <summary>
/// 接收数据
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Sp1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
//异步委托一个线程,不然接收数据时会出现数据线程错误
this.Invoke((EventHandler)(delegate
{
var showText = this.cb_DataShow.Text;
if (serialPort.IsOpen)
{
try
{
byte[] receivedData = new byte[serialPort.BytesToRead];//创建接收数据数组
serialPort.Read(receivedData, 0, receivedData.Length);//读取数据
var content = string.Empty;
//显示形式
switch (showText)
{
case "16进制(HEX)":
for (int i = 0; i < receivedData.Length; i++)
{
//ToString("X2") 为C#中的字符串格式控制符
//X为 十六进制
//2为 每次都是两位数
content += (receivedData[i].ToString("X2") + " ");
}
break;
case "字符显示(ASCII)":
content = Encoding.GetEncoding("GB2312").GetString(receivedData);//防止乱码
break;
}
//接收文本框
this.txt_Received.AppendText($"{DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss:ffff")}:{content} \r\n");
serialPort.DiscardInBuffer();//丢弃缓存区数据
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error");
}
}
else
{
MessageBox.Show("请打开串口", "Error");
}
}));
}
#endregion
}
}
12、测试结果




就以上测试结果来看需要的效果以达成。
13、Demo下载
Demo下载地址:C# WinForm虚拟串口通信

780





