开篇碎碎念
今天要要写的是 C# 中可以用来实现串口通信的 SerialPort 类。这个 demo 在很久之前就写了,当时是需要调试串口间发送和接收的消息,后面用到了 sscom 工具,心血来潮模仿这个工具写了一个pdd版的串口调试工具。
串口通信在各领域都有广泛的应用,下面我将使用 System.IO.Ports 命名空间提供的 SerialPort 类来简单演示如何在 C# 中使用串口通信。
基本认识
在使用 SerialPort 类进行串口通信时,可以配置的属性有:
- PortName: 串口名称,如"COM1"
- BaudRate:波特率,表示每秒发送的位数,常见的波特率有9600、115200等
- Parity:奇偶校验位。可以选择 None(无校验)、Even(偶校验)和Odd(奇校验)
- DataBits:数据位的位数,常见值为8
- StopBits:停止位的位数,常见值由None(无停止位)、One(1位停止位)、Two(2位停止位)
- readTimeout:读取操作的超时时间
- writeTimeout:写入操作的超时时间
常用方法:
public static string[] GetPortNames()
获取当前计算机上可用的串口名称数组
public void Open()
打开串口以建立连接
public int Read(byte[] buffer, int offset, int count)
从串口读取指定数量的字节数据,并存储到缓冲区中
public int ReadChar()
从串口读取一个字符
BytesToRead()
获取当前串口接收缓冲区中可用的字节数
public int Read(char[] buffer, int offset, int count)
从串口读取指定数量的字符,并存储到指定的缓冲区中
BytesToWrite()
获取当前串口发送缓冲区中待发送的bytes
public void Write(string text)
向串口写入字符串
public void Write(char[] buffer, int offset, int count)
向串口写入指定数量的字符数据
public void WriteLine(string text)
向串口写入字符串并在末尾添加换行符
public void DiscardInBuffer()
清空串口接收缓冲区中的数据
public void DiscardOutBuffer()
清空串口发送缓冲区中的数据
public void Close()
关闭串口连接
正餐开始
UI界面
新建窗体应用,创建 SerialPort 控件,并在工具箱中拉出button等相关控件,创建如下 WinForm 应用界面

串口扫描
先写一个活动串口扫描,实现自动扫描COM口,并设置 comboBox 初始值。由于本机上只有一个串口COM3,这里我使用虚拟串口工具Virtual Serial Port Driver 创建了4个串口。
ini
private void getAvailablePort()
{
try
{
string[] serialPorts = SerialPort.GetPortNames();
if(serialPorts.Length == 0)
{
throw (new System.IO.IOException("this device doesn't have any port!"));
}
if(serialPorts != null && serialPorts.Length != 0)
{
Array.Sort<String>(serialPorts);
foreach(String port in serialPorts)
{
if(port != null && port.Length != 0)
{
comboBox1.Items.Add(port);
}
Console.WriteLine(port);
}
comboBox1.SelectedIndex = 0;
}
}
catch(System.IO.IOException e)
{
MessageBox.Show(e.Message);
}
}
private void portInit()
{
getAvailablePort();
//设置初始显示的值
comboBox2.SelectedIndex = 6;
comboBox3.SelectedIndex = 2;
comboBox4.SelectedIndex = 0;
comboBox5.SelectedIndex = 0;
}

打开串口
扫描到COM口后,选择一个打开,下面选择COM1,并写好波特率、数据位等对应的配置,进行配置,最后调用Open()方法打开串口

数据发送和接收
接下来写发送按钮click事件处理程序,获取到发送框输入的数据并存储在str中,再使用String.Split方法将输入字符串按空格拆分为十六进制数,并将所有的十六进制数对存储在一个List中。后面在转换过程中,使用了byte.Parse方法,用来接收一个字符串和一个指定的十六进制格式的参数来解析十六进制数对。
接收框获取数据这里,由于想减少UI刷新,便先将接收到的数据先存储在一个缓冲区中,并在合适时机使用BeginInvoke方法将UI刷新放到UI线程执行。
csharp
private List<string> GetHexPairs(string str)
{
List<string> hexPairs = new List<string>();
string[] strArray = str.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
foreach(string hexPair in strArray)
{
hexPairs.Add(hexPair);
}
return hexPairs;
}
private byte ConvertHexPairToByte(string hexPair)
{
return Convert.ToByte(hexPair, 16);
}
private void button1_Click(object sender, EventArgs e)
{
string str = textBox7.Text.Trim();
if (serialPort1.IsOpen)
{
try
{
foreach(var hexPair in GetHexPairs(str))
{
byte data = ConvertHexPairToByte(hexPair);
serialPort1.Write(new byte[] { data }, 0, 1);
}
}
catch (FormatException)
{
MessageBox.Show("输入的16进制数数据格式错误!");
}
catch (Exception ex)
{
MessageBox.Show($"串口发送失败,系统将关闭当前串口错误!\n {ex.Message}");
serialPort1.Close();
}
}
}
private StringBuilder receivedDataBuffer = new StringBuilder();
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
int bufferSize = serialPort1.BytesToRead;
byte[] buffer = new byte[bufferSize];
serialPort1.Read(buffer, 0, bufferSize);
foreach(byte data in buffer)
{
string hex = data.ToString("X2");
// 以空格分割并添加到缓冲区,避免频繁UI更新
receivedDataBuffer.Append(hex).Append(' ');
}
// 将UI更新操作放入UI线程执行
BeginInvoke(new Action(() =>
{
textBox6.AppendText(receivedDataBuffer.ToString());
textBox6.AppendText(Environment.NewLine);
}));
serialPort1.DiscardInBuffer();
}
最终效果如下:

最后
本示例只是简单介绍了 C# 中 SerialPort 串口控件的扫描、打开、数据发送和接收。对这方面有兴趣的伙伴可以参考一下,对于文中出现的不足或可优化之处,欢迎大家评论吐槽!最后,祝大家新年快乐,工作顺心,同事有趣~