当前位置:首页 > 技术 > 正文内容

C# 实现 USB 转串口扫描枪数据采集与处理

访客 技术 2026年7月4日 1

硬件连接与设备识别

在工业自动化或零售系统中,常通过 USB 接口的条码扫描枪获取商品信息。由于多数扫描枪模拟为串行通信设备,需借助 USB 转串口芯片(如 CH340、CP2102)实现数据传输。

物理连接结构

条码扫描器 → USB转串模块 → PC端COM口
                    ↓
              波特率:9600~115200  
              数据位:8  
              停止位:1  
              校验方式:无

驱动配置步骤

  1. 即插即用模式:插入设备后,Windows 自动识别并分配虚拟 COM 端口号(可在"设备管理器"中查看)。
  2. 手动安装示例(CH340芯片)
    • 从官网下载 CH341SER 驱动程序包
    • 在设备管理器中定位未知设备,选择"更新驱动程序"
    • 指定解压后的驱动路径完成安装
    • 确认新生成的串口号(如 COM4)

核心通信模块设计

使用 .NET 的 System.IO.Ports.SerialPort 类构建稳定的数据监听机制,确保实时接收扫描结果。

串口封装类(CommunicationPort.cs)

using System;
using System.IO.Ports;
using System.Threading;

public class CommunicationPort : IDisposable
{
    private SerialPort _port;
    private Thread _listener;
    private volatile bool _isActive;
    private readonly object _syncRoot = new object();

    public event Action<string> Received;

    public CommunicationPort(string portName, int rate = 9600)
    {
        _port = new SerialPort(portName, rate)
        {
            Parity = Parity.None,
            DataBits = 8,
            StopBits = StopBits.One,
            ReadTimeout = 500
        };
    }

    public bool Start()
    {
        try
        {
            if (!_port.IsOpen)
                _port.Open();

            _isActive = true;
            _listener = new Thread(Listen) { IsBackground = true };
            _listener.Start();
            return true;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"端口启动失败: {ex.Message}");
            return false;
        }
    }

    private void Listen()
    {
        while (_isActive && _port.IsOpen)
        {
            try
            {
                string input = _port.ReadLine();
                string cleanData = input?.Trim('\r', '\n');
                lock (_syncRoot)
                {
                    Received?.Invoke(cleanData);
                }
            }
            catch (TimeoutException) { continue; }
            catch (IOException) when (!_port.IsOpen) { break; }
        }
    }

    public void Stop()
    {
        _isActive = false;
        _port?.Close();
        _listener?.Join(1000);
    }

    public void Dispose()
    {
        Stop();
        _port?.Dispose();
    }
}

主界面逻辑(ScannerForm.cs)

public partial class ScannerForm : Form
{
    private CommunicationPort _scanner;
    private readonly StringBuilder _logBuffer = new StringBuilder();

    public ScannerForm()
    {
        InitializeComponent();
        SetupScanner();
    }

    private void SetupScanner()
    {
        // 可动态检测可用COM端口
        _scanner = new CommunicationPort("COM3", 9600);
        _scanner.Received += HandleBarcode;
        
        if (!(_scanner.Start()))
        {
            MessageBox.Show(this, "无法连接扫描设备,请检查硬件连接", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }

    private void HandleBarcode(string code)
    {
        if (this.InvokeRequired)
        {
            this.Invoke(new Action<string>(HandleBarcode), code);
            return;
        }

        // 清洗数据(例如去除起始/结束符)
        string content = code.TrimStart('[').TrimEnd(']');

        // 更新UI显示
        txtOutput.AppendText($"[{DateTime.Now:HH:mm:ss}] {content}{Environment.NewLine}");
        txtOutput.SelectionStart = txtOutput.TextLength;
        txtOutput.ScrollToCaret();

        // 异步处理业务逻辑
        Task.Run(() => ProcessScannedCode(content));
    }

    private void ProcessScannedCode(string barcode)
    {
        // 示例:记录日志、上传服务端、写入数据库等
        LogToDatabase(barcode);
        UploadToApi(barcode);
    }

    protected override void OnFormClosing(FormClosingEventArgs e)
    {
        _scanner?.Dispose();
        base.OnFormClosing(e);
    }
}

高级功能增强

数据解析与校验

针对特定协议格式进行解码,提升数据可靠性。

public class BarcodeDecoder
{
    public static DecodedResult Decode(string raw)
    {
        if (string.IsNullOrEmpty(raw) || raw.Length < 8) return null;

        // 协议假设:前两位表示类型,四位长度,中间为内容,最后两位校验
        var typeCode = Convert.ToByte(raw.Substring(0, 2), 16);
        var length = Convert.ToUInt16(raw.Substring(2, 4), 16);
        var payload = raw.Substring(6, (int)length * 2);
        var checkSum = Convert.ToByte(raw.Substring(6 + (int)length * 2, 2), 16);

        return new DecodedResult
        {
            Category = (BarcodeCategory)typeCode,
            Value = HexToString(payload),
            Integrity = ComputeChecksum(payload) == checkSum
        };
    }

    private static string HexToString(string hex) => 
        string.Join("", Enumerable.Range(0, hex.Length / 2)
            .Select(i => ((char)Convert.ToByte(hex.Substring(i * 2, 2), 16))));

    private static byte ComputeChecksum(string data)
    {
        return (byte)data.Sum(c => c);
    }
}

public enum BarcodeCategory : byte
{
    UPC_A = 0x01,
    QR = 0x02,
    CODE_128 = 0x03
}

public class DecodedResult
{
    public BarcodeCategory Category { get; set; }
    public string Value { get; set; }
    public bool Integrity { get; set; }
}

异常恢复机制

建立健壮的容错策略以应对设备断开或通信中断。

private async Task ReconnectAsync()
{
    for (int i = 0; i < 3; i++)
    {
        try
        {
            _scanner.Stop();
            await Task.Delay(1500);
            if (_scanner.Start()) return;
        }
        catch { /* 忽略重试过程中的异常 */ }
    }
    MessageBox.Show("连续三次尝试重连失败,请检查设备");
}

多设备支持管理器

public class MultiDeviceHub
{
    private Dictionary<string, CommunicationPort> _devices = new();

    public void Attach(string portName)
    {
        if (_devices.ContainsKey(portName)) return;

        var device = new CommunicationPort(portName);
        device.Received += msg => OnGlobalReceive(portName, msg);
        
        if (device.Start())
        {
            _devices[portName] = device;
        }
    }

    public void Detach(string portName)
    {
        if (_devices.TryGetValue(portName, out var dev))
        {
            dev.Dispose();
            _devices.Remove(portName);
        }
    }

    private void OnGlobalReceive(string source, string message)
    {
        // 分发至全局事件或日志系统
        GlobalMessageBus.Publish(source, message);
    }
}

部署与维护建议

权限声明

确保应用程序具备访问串口所需的管理员权限,在项目清单文件中添加:

<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />

依赖项配置

推荐使用 NuGet 包管理外部功能扩展:

<PackageReference Include="System.IO.Ports" Version="7.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />

安装包集成

  • 采用 Inno Setup 打包发布版本
  • 捆绑 .NET 运行时环境(如 .NET 6 Desktop Runtime)
  • 嵌入常用 USB 芯片驱动程序(CH340/CP210x)

调试辅助工具

开发阶段可启用原始数据捕获功能,便于协议分析和问题排查。

public static class DiagnosticTool
{
    public static void DumpRawStream(string comPort, string outputPath)
    {
        using var sp = new SerialPort(comPort, 9600);
        sp.Open();
        byte[] buffer = new byte[1024];
        int read = sp.Read(buffer, 0, buffer.Length);
        File.WriteAllBytes(outputPath, buffer.Take(read).ToArray());
    }
}

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。