串口通信是上位机开发中的核心技能,广泛应用于工业控制、物联网和自动化测试等领域。c# 通过 system.io.ports.serialport 类提供了完整的串口通信支持。本教程从配置参数、数据发送、数据接收、异步操作和错误处理五个方面,系统讲解 serialport 的发送与接收实现。核心逻辑可以概括为三步:配置参数 → 发送 write → 事件接收 datareceived。
1. 引言
在工业控制、嵌入式开发、仪器仪表等场景中,串口通信依然是最基础、最常用的通信方式之一。c# 通过 system.io.ports 命名空间下的 serialport 类,为开发者提供了简洁而强大的串口操作接口。本文将系统讲解 c# 中 serialport 的发送与接收机制,从基础配置到高级应用,帮助你快速掌握串口编程的核心要点。
2. 环境准备与基础配置
2.1 引入命名空间
使用 serialport 前,需要引入 system.io.ports 命名空间:
using system.io.ports;
2.2 创建并配置 serialport 实例
serialport serialport = new serialport(); // 基本配置 serialport.portname = "com3"; // 串口号 serialport.baudrate = 9600; // 波特率 serialport.databits = 8; // 数据位 serialport.parity = parity.none; // 校验位 serialport.stopbits = stopbits.one; // 停止位 // 可选配置 serialport.readtimeout = 500; // 读取超时(毫秒) serialport.writetimeout = 500; // 写入超时(毫秒) serialport.newline = "\r\n"; // 换行符(用于 readline)
2.3 打开与关闭串口
// 打开串口
try
{
serialport.open();
console.writeline("串口已打开");
}
catch (exception ex)
{
console.writeline($"打开失败: {ex.message}");
}
// 关闭串口
serialport.close();3. 数据发送
3.1 发送字符串
// 直接发送字符串
serialport.write("hello, serial!");
// 发送带换行符的字符串
serialport.writeline("at+csq");3.2 发送字节数组
// 构造要发送的字节数据
byte[] data = new byte[] { 0x01, 0x02, 0x03, 0xff, 0x00 };
// 发送字节数组
serialport.write(data, 0, data.length);3.3 发送十六进制字符串
/// <summary>
/// 将十六进制字符串转换为字节数组并发送
/// </summary>
public static void sendhexstring(serialport port, string hexstring)
{
// 去除空格
hexstring = hexstring.replace(" ", "");
// 将十六进制字符串转换为字节数组
byte[] data = new byte[hexstring.length / 2];
for (int i = 0; i < data.length; i++)
{
data[i] = convert.tobyte(hexstring.substring(i * 2, 2), 16);
}
// 发送数据
port.write(data, 0, data.length);
}
// 使用示例
sendhexstring(serialport, "01 03 00 00 00 0a c5 cd");4. 数据接收
4.1 使用 datareceived 事件(推荐)
datareceived 事件是串口接收最常用的方式,它在数据到达时异步触发:
// 订阅事件
serialport.datareceived += serialport_datareceived;
// 事件处理方法
private static void serialport_datareceived(object sender, serialdatareceivedeventargs e)
{
serialport sp = (serialport)sender;
// 读取当前缓冲区中所有可用数据
int bytestoread = sp.bytestoread;
byte[] buffer = new byte[bytestoread];
sp.read(buffer, 0, bytestoread);
// 处理接收到的数据
string receivedtext = system.text.encoding.utf8.getstring(buffer);
console.writeline($"收到数据: {receivedtext}");
// 或者以十六进制显示
string hextext = bitconverter.tostring(buffer);
console.writeline($"收到十六进制: {hextext}");
}4.2 同步读取
// 读取一个字节 int bytevalue = serialport.readbyte(); // 读取到指定缓冲区 byte[] buffer = new byte[1024]; int bytesread = serialport.read(buffer, 0, buffer.length); // 读取一行(直到遇到 newline) string line = serialport.readline();
4.3 完整接收示例:处理不定长数据
private static void serialport_datareceived(object sender, serialdatareceivedeventargs e)
{
serialport sp = (serialport)sender;
// 防止多线程同时访问
lock (sp)
{
// 读取所有可用数据
int bytestoread = sp.bytestoread;
byte[] buffer = new byte[bytestoread];
int bytesread = sp.read(buffer, 0, bytestoread);
if (bytesread > 0)
{
// 将数据追加到接收缓冲区
receivebuffer.addrange(buffer);
// 尝试解析完整的数据帧
processreceiveddata();
}
}
}
private static list<byte> receivebuffer = new list<byte>();
private static void processreceiveddata()
{
// 假设协议帧格式: 帧头(0xaa) + 长度(1字节) + 数据 + 校验(1字节)
while (receivebuffer.count >= 3)
{
// 查找帧头
if (receivebuffer[0] != 0xaa)
{
receivebuffer.removeat(0);
continue;
}
int datalength = receivebuffer[1];
int totallength = 2 + datalength + 1; // 帧头 + 长度 + 数据 + 校验
if (receivebuffer.count < totallength)
{
break; // 数据不完整,等待更多数据
}
// 提取完整帧
byte[] frame = receivebuffer.getrange(0, totallength).toarray();
receivebuffer.removerange(0, totallength);
// 校验并处理帧
if (verifychecksum(frame))
{
console.writeline($"收到完整帧: {bitconverter.tostring(frame)}");
}
}
}
private static bool verifychecksum(byte[] frame)
{
// 简单的校验和验证
byte checksum = 0;
for (int i = 0; i < frame.length - 1; i++)
{
checksum ^= frame[i];
}
return checksum == frame[frame.length - 1];
}5. 完整示例:串口调试助手
下面是一个完整的控制台串口通信示例,包含发送和接收功能:
using system;
using system.io.ports;
using system.text;
using system.threading;
class serialportdemo
{
private static serialport serialport;
private static bool isrunning = true;
static void main(string[] args)
{
// 初始化串口
serialport = new serialport("com3", 9600, parity.none, 8, stopbits.one);
serialport.datareceived += serialport_datareceived;
try
{
serialport.open();
console.writeline("串口已打开,输入 'exit' 退出程序");
console.writeline("输入 'send:内容' 发送字符串");
console.writeline("输入 'hex:01 02 03' 发送十六进制数据");
// 主线程处理用户输入
while (isrunning)
{
string input = console.readline();
if (input == "exit")
{
isrunning = false;
}
else if (input.startswith("send:"))
{
string data = input.substring(5);
serialport.write(data);
console.writeline($"已发送: {data}");
}
else if (input.startswith("hex:"))
{
string hexdata = input.substring(4);
sendhexstring(hexdata);
}
}
}
catch (exception ex)
{
console.writeline($"错误: {ex.message}");
}
finally
{
if (serialport.isopen)
{
serialport.close();
}
console.writeline("串口已关闭");
}
}
private static void serialport_datareceived(object sender, serialdatareceivedeventargs e)
{
serialport sp = (serialport)sender;
int bytestoread = sp.bytestoread;
byte[] buffer = new byte[bytestoread];
sp.read(buffer, 0, bytestoread);
string text = encoding.utf8.getstring(buffer);
string hex = bitconverter.tostring(buffer);
console.writeline($"[接收] 文本: {text}");
console.writeline($"[接收] 十六进制: {hex}");
}
private static void sendhexstring(string hexstring)
{
hexstring = hexstring.replace(" ", "");
byte[] data = new byte[hexstring.length / 2];
for (int i = 0; i < data.length; i++)
{
data[i] = convert.tobyte(hexstring.substring(i * 2, 2), 16);
}
serialport.write(data, 0, data.length);
console.writeline($"已发送十六进制: {bitconverter.tostring(data)}");
}
}6. 常见问题与注意事项
6.1 串口被占用
// 检查串口是否可用
string[] ports = serialport.getportnames();
if (array.indexof(ports, "com3") == -1)
{
console.writeline("com3 不可用");
return;
}6.2 跨线程访问 ui
在 winforms 或 wpf 中,datareceived 事件在后台线程触发,不能直接操作 ui 控件:
// winforms 示例
private void serialport_datareceived(object sender, serialdatareceivedeventargs e)
{
if (this.invokerequired)
{
this.invoke(new action(() => serialport_datareceived(sender, e)));
return;
}
// 现在可以安全地操作 ui 控件
textbox1.appendtext("收到数据");
}6.3 数据粘包与拆包
串口通信中常见的数据粘包问题,解决方案:
- 使用固定帧头帧尾
- 使用长度字段
- 使用超时机制
// 使用超时机制处理粘包
private static void readwithtimeout()
{
datetime starttime = datetime.now;
list<byte> databuffer = new list<byte>();
while ((datetime.now - starttime).totalmilliseconds < 100)
{
if (serialport.bytestoread > 0)
{
int b = serialport.readbyte();
databuffer.add((byte)b);
starttime = datetime.now; // 重置超时
}
thread.sleep(10);
}
if (databuffer.count > 0)
{
console.writeline($"接收完成: {bitconverter.tostring(databuffer.toarray())}");
}
}7. 总结
本文详细介绍了 c# 中 serialport 的发送与接收方法:
- 发送:支持字符串、字节数组、十六进制字符串等多种格式
- 接收:推荐使用
datareceived事件实现异步接收 - 协议处理:通过帧头、长度、校验等机制确保数据完整性
- 注意事项:跨线程访问、数据粘包、串口占用等常见问题
掌握这些核心要点后,你就可以在 c# 项目中灵活实现串口通信功能。建议在实际开发中,根据具体的通信协议设计合适的收发逻辑,并做好异常处理和资源释放。
到此这篇关于c#中serialport 串口通信发送与接收完整指南的文章就介绍到这了,更多相关c# serialport 串口通信内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论