C# Winform使用SerialPort读取USB串口条码数据频繁出错求助
串口读取条码扫描器数据出错问题
问题描述
使用System.IO.Ports.SerialPort通过USB COM端口读取条码扫描器数据时经常出现错误,已确认串口参数(波特率9600、无校验、8位数据位、1位停止位)设置正确。扫描内容为PARTS-00010的条码时程序读取到错误数据,但使用UartAssist.exe测试时,即使快速多次扫描,读取的数据几乎全部正确。
问题代码
using System.IO.Ports; using System.Text; namespace SerialPortTestProject { public partial class Form1: Form { public Form1() { InitializeComponent(); SerialPort sp = new("COM3", 9600, Parity.None, 8, StopBits.One); sp.DataReceived += DataReceived1; sp.Open(); } void DataReceived1(object sender, SerialDataReceivedEventArgs e) { Thread.Sleep(500); SerialPort sp = (SerialPort) sender; byte[] data = new byte[sp.BytesToRead]; sp.Read(data, 0, sp.BytesToRead); string result = Encoding.ASCII.GetString(data); WriteResult($"DataReceived1: {string.Join("-", data)} => {result}\r\n"); } void WriteResult(String str) { BeginInvoke(new(() => { textBox1.AppendText(str); })); } } }
问题原因及解决方案
核心问题分析
Thread.Sleep(500)是致命错误
串口DataReceived事件运行在独立线程中,加500ms休眠会直接阻塞该线程,导致后续的串口数据通知无法及时处理。同时,休眠期间新的扫描数据会持续写入串口缓冲区,一旦缓冲区溢出就会丢失字节,最终读取到残缺或混乱的数据。而UartAssist这类专业工具不会做这种阻塞操作,能实时响应数据通知,所以接收正常。单次读取无法保证拿到完整条码
DataReceived事件触发时,可能只收到了条码的部分字节(比如前几个字符),当前代码直接读取缓冲区现有字节就结束,导致完整的条码被拆分成多次读取,最终拼接错误。
修复方案
方案一:利用内置换行符读取完整数据(推荐)
大部分条码扫描器会以\r\n(回车+换行)作为一条条码的结束标记,直接使用SerialPort.ReadLine()方法可以自动读取到结束符为止,确保拿到完整的条码:
using System.IO.Ports; using System.Text; namespace SerialPortTestProject { public partial class Form1: Form { private SerialPort _serialPort; public Form1() { InitializeComponent(); _serialPort = new SerialPort("COM3", 9600, Parity.None, 8, StopBits.One); _serialPort.DataReceived += SerialPort_DataReceived; // 设置扫描器对应的结束符,可通过UartAssist查看输出结尾确认 _serialPort.NewLine = "\r\n"; // 启用握手信号,提升稳定性 _serialPort.DtrEnable = true; _serialPort.RtsEnable = true; _serialPort.Open(); } private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e) { // 读取完整的一行条码数据(自动处理结束符) string fullBarcode = _serialPort.ReadLine().Trim(); WriteResult($"扫描到完整条码: {fullBarcode}\r\n"); } private void WriteResult(string content) { BeginInvoke(new Action(() => { textBox1.AppendText(content); })); } // 窗体关闭时释放串口资源 protected override void OnFormClosing(FormClosingEventArgs e) { if (_serialPort != null && _serialPort.IsOpen) { _serialPort.Close(); _serialPort.Dispose(); } base.OnFormClosing(e); } } }
方案二:手动拼接数据(自定义结束符场景)
如果扫描器使用非标准结束符(比如单独的\r或特定字节),可以手动拼接缓冲区数据直到检测到结束符:
using System.IO.Ports; using System.Text; namespace SerialPortTestProject { public partial class Form1: Form { private SerialPort _serialPort; private readonly StringBuilder _receivedBuffer = new StringBuilder(); // 自定义结束符,根据实际情况修改 private const string EndMarker = "\r"; public Form1() { InitializeComponent(); _serialPort = new SerialPort("COM3", 9600, Parity.None, 8, StopBits.One); _serialPort.DataReceived += SerialPort_DataReceived; _serialPort.DtrEnable = true; _serialPort.RtsEnable = true; _serialPort.Open(); } private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e) { SerialPort sp = (SerialPort)sender; byte[] buffer = new byte[sp.BytesToRead]; int readCount = sp.Read(buffer, 0, buffer.Length); // 将读取到的字节转换为字符串追加到缓冲区 _receivedBuffer.Append(Encoding.ASCII.GetString(buffer, 0, readCount)); // 检查是否包含结束符 string currentData = _receivedBuffer.ToString(); if (currentData.Contains(EndMarker)) { // 提取完整的条码数据并清空缓冲区 string fullBarcode = currentData.Split(new[] { EndMarker }, StringSplitOptions.RemoveEmptyEntries)[0]; _receivedBuffer.Clear(); WriteResult($"扫描到完整条码: {fullBarcode}\r\n"); } } private void WriteResult(string content) { BeginInvoke(new Action(() => { textBox1.AppendText(content); })); } protected override void OnFormClosing(FormClosingEventArgs e) { if (_serialPort != null && _serialPort.IsOpen) { _serialPort.Close(); _serialPort.Dispose(); } base.OnFormClosing(e); } } }
额外注意事项
- 确认扫描器的输出编码:如果扫描器使用UTF-8编码,需将
Encoding.ASCII改为Encoding.UTF8 - 务必在窗体关闭时关闭并释放串口资源,避免占用COM端口
- 可以通过UartAssist查看扫描器输出的原始数据,确认结束符和编码类型
内容的提问来源于stack exchange,提问作者Streen Jiang
相关产品推荐
相关产品推荐

