C# 实现 USB 转串口扫描枪数据采集与处理
硬件连接与设备识别
在工业自动化或零售系统中,常通过 USB 接口的条码扫描枪获取商品信息。由于多数扫描枪模拟为串行通信设备,需借助 USB 转串口芯片(如 CH340、CP2102)实现数据传输。
物理连接结构
条码扫描器 → USB转串模块 → PC端COM口
↓
波特率:9600~115200
数据位:8
停止位:1
校验方式:无
驱动配置步骤
- 即插即用模式:插入设备后,Windows 自动识别并分配虚拟 COM 端口号(可在"设备管理器"中查看)。
- 手动安装示例(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());
}
}