You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
            }));
        }
    }
}

问题原因及解决方案

核心问题分析

  1. Thread.Sleep(500)是致命错误
    串口DataReceived事件运行在独立线程中,加500ms休眠会直接阻塞该线程,导致后续的串口数据通知无法及时处理。同时,休眠期间新的扫描数据会持续写入串口缓冲区,一旦缓冲区溢出就会丢失字节,最终读取到残缺或混乱的数据。而UartAssist这类专业工具不会做这种阻塞操作,能实时响应数据通知,所以接收正常。

  2. 单次读取无法保证拿到完整条码
    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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 12:13:17