USB 设备通信(LibUsbDotNet)
大约 8 分钟约 2466 字
USB 设备通信(LibUsbDotNet)
简介
USB 通信是上位机与硬件设备交互的重要方式。LibUsbDotNet 是 .NET 平台最常用的 USB 通信库,支持控制传输、批量传输、中断传输和等时传输。通过 VID/PID 识别设备,配合端点配置,可以实现高速可靠的数据传输。
特点
设备发现
查找和打开设备
/// <summary>
/// USB 设备发现和连接
/// </summary>
using LibUsbDotNet;
using LibUsbDotNet.Main;
using LibUsbDotNet.LibUsb;
public class UsbDeviceService
{
private UsbDevice? _device;
private UsbEndpointReader? _reader;
private UsbEndpointWriter? _writer;
// 设备标识(需要替换为实际设备的 VID/PID)
private const int VendorId = 0x1234;
private const int ProductId = 0x5678;
public event Action<byte[]>? DataReceived;
// 查找设备
public UsbDevice? FindDevice()
{
var finder = new UsbDeviceFinder(VendorId, ProductId);
_device = UsbDevice.OpenUsbDevice(finder);
if (_device == null)
{
Console.WriteLine($"未找到 USB 设备 VID={VendorId:X4} PID={ProductId:X4}");
return null;
}
Console.WriteLine($"已找到设备:{_device.Info.ProductString}");
return _device;
}
// 打开并配置设备
public bool Open()
{
if (_device == null) return false;
// 打开设备
if (_device.TryOpen())
{
// 获取配置
var config = _device.Configs[0];
// 获取接口
var iface = config.Interfaces[0];
_device.ClaimInterface(iface);
// 获取端点
foreach (var endpoint in iface.Endpoints)
{
if (endpoint.Direction == EndpointDirection.In)
_reader = _device.OpenEndpointReader(endpoint);
else
_writer = _device.OpenEndpointWriter(endpoint);
}
return true;
}
return false;
}
}数据读写
收发数据
/// <summary>
/// USB 数据读写
/// </summary>
public class UsbCommunication
{
private UsbEndpointReader? _reader;
private UsbEndpointWriter? _writer;
// 发送数据
public bool SendData(byte[] data)
{
if (_writer == null) return false;
int bytesWritten;
var result = _writer.Write(data, 2000, out bytesWritten);
if (result)
{
Console.WriteLine($"已发送 {bytesWritten} 字节");
return true;
}
Console.WriteLine($"发送失败:{result}");
return false;
}
// 接收数据(同步)
public byte[]? ReceiveData(int timeoutMs = 2000)
{
if (_reader == null) return null;
var buffer = new byte[64];
int bytesRead;
var result = _reader.Read(buffer, timeoutMs, out bytesRead);
if (result && bytesRead > 0)
{
var data = new byte[bytesRead];
Array.Copy(buffer, data, bytesRead);
return data;
}
return null;
}
// 异步持续接收
public void StartListening()
{
if (_reader == null) return;
_reader.DataReceived += (sender, args) =>
{
if (args.Count > 0)
{
var data = new byte[args.Count];
Array.Copy(args.Buffer, data, args.Count);
DataReceived?.Invoke(data);
}
};
_reader.DataReceivedEnabled = true;
}
public event Action<byte[]>? DataReceived;
// 控制传输 — 发送命令
public bool SendControlCommand(byte request, ushort value, ushort index)
{
var setupPacket = new UsbSetupPacket(
requestType: 0x40, // Vendor, Host-to-Device
request: request,
value: value,
index: index,
length: 0);
int transferred;
return _device!.ControlTransfer(ref setupPacket, null, 0, out transferred);
}
}热插拔监听
设备插拔事件
/// <summary>
/// USB 热插拔监听
/// </summary>
public class UsbHotplugService : IDisposable
{
private readonly int _vid;
private readonly int _pid;
private Timer? _pollTimer;
private bool _lastConnected;
public event Action? DeviceArrived;
public event Action? DeviceRemoved;
public UsbHotplugService(int vid, int pid)
{
_vid = vid;
_pid = pid;
}
public void StartMonitoring(int pollIntervalMs = 1000)
{
_pollTimer = new Timer(_ =>
{
var connected = IsDeviceConnected();
if (connected && !_lastConnected)
DeviceArrived?.Invoke();
else if (!connected && _lastConnected)
DeviceRemoved?.Invoke();
_lastConnected = connected;
}, null, 0, pollIntervalMs);
}
private bool IsDeviceConnected()
{
var finder = new UsbDeviceFinder(_vid, _pid);
using var device = UsbDevice.OpenUsbDevice(finder);
return device != null;
}
// 使用 WMI 监听(Windows)
public void StartWmiMonitoring()
{
var watcher = new ManagementEventWatcher(
"SELECT * FROM __InstanceCreationEvent WITHIN 2 WHERE TargetInstance ISA 'Win32_USBControllerDevice'");
watcher.EventArrived += (s, e) =>
{
// 检查是否为目标设备
DeviceArrived?.Invoke();
};
watcher.Start();
}
public void Dispose()
{
_pollTimer?.Dispose();
}
}异步通信
异步收发与缓冲管理
/// <summary>
/// USB 异步通信服务
/// </summary>
public class UsbAsyncCommunication : IDisposable
{
private UsbEndpointReader? _reader;
private UsbEndpointWriter? _writer;
private readonly CancellationTokenSource _cts = new();
private readonly BlockingCollection<byte[]> _sendQueue = new(100);
private readonly ILogger<UsbAsyncCommunication> _logger;
// 接收事件
public event Action<byte[]>? DataReceived;
// 发送完成事件
public event Action<bool, string>? SendCompleted;
// 错误事件
public event Action<string>? ErrorOccurred;
public UsbAsyncCommunication(ILogger<UsbAsyncCommunication> logger)
{
_logger = logger;
}
// 启动异步接收线程
public void StartReceiving()
{
if (_reader == null) return;
Task.Run(async () =>
{
while (!_cts.IsCancellationRequested)
{
try
{
var buffer = new byte[4096];
int bytesRead;
var ec = _reader.Read(buffer, 5000, out bytesRead);
if (ec == ErrorCode.None && bytesRead > 0)
{
var data = new byte[bytesRead];
Array.Copy(buffer, data, bytesRead);
DataReceived?.Invoke(data);
}
}
catch (Exception ex)
{
if (!_cts.IsCancellationRequested)
{
_logger.LogError(ex, "USB 接收异常");
ErrorOccurred?.Invoke($"接收异常:{ex.Message}");
}
}
}
}, _cts.Token);
}
// 启动异步发送线程
public void StartSending()
{
if (_writer == null) return;
Task.Run(async () =>
{
foreach (var data in _sendQueue.GetConsumingEnumerable(_cts.Token))
{
try
{
int bytesWritten;
var ec = _writer.Write(data, 3000, out bytesWritten);
SendCompleted?.Invoke(ec == ErrorCode.None,
ec == ErrorCode.None
? $"发送成功 ({bytesWritten} 字节)"
: $"发送失败:{ec}");
}
catch (Exception ex)
{
SendCompleted?.Invoke(false, $"发送异常:{ex.Message}");
}
}
}, _cts.Token);
}
// 队列发送(非阻塞)
public bool EnqueueSend(byte[] data)
{
if (_sendQueue.Count >= 100)
{
_logger.LogWarning("USB 发送队列已满,丢弃数据");
return false;
}
return _sendQueue.TryAdd(data);
}
// 同步发送(带重试)
public async Task<bool> SendWithRetryAsync(byte[] data, int maxRetries = 3, int delayMs = 100)
{
for (int i = 0; i < maxRetries; i++)
{
if (_writer == null) return false;
int bytesWritten;
var ec = _writer.Write(data, 3000, out bytesWritten);
if (ec == ErrorCode.None)
return true;
_logger.LogWarning("USB 发送失败(第 {Attempt} 次):{Error}", i + 1, ec);
if (i < maxRetries - 1)
await Task.Delay(delayMs);
}
return false;
}
public void Dispose()
{
_cts.Cancel();
_sendQueue.CompleteAdding();
}
}设备信息读取
USB 设备描述符
/// <summary>
/// USB 设备信息读取
/// </summary>
public class UsbDeviceInfoReader
{
public UsbDevice? Device { get; private set; }
// 读取设备描述符信息
public UsbDeviceDescriptor GetDeviceDescriptor()
{
if (Device == null)
throw new InvalidOperationException("设备未连接");
var info = Device.Info;
return new UsbDeviceDescriptor
{
VendorId = info.VendorId,
ProductId = info.ProductId,
ProductString = info.ProductString ?? "",
ManufacturerString = info.ManufacturerString ?? "",
SerialString = info.SerialString ?? "",
DeviceType = info.DeviceType,
ConfigurationCount = Device.Configs.Length
};
}
// 读取配置描述符
public UsbConfigDescriptor GetConfigDescriptor(int configIndex = 0)
{
if (Device?.Configs.Length <= configIndex)
throw new ArgumentOutOfRangeException(nameof(configIndex));
var config = Device.Configs[configIndex];
var endpoints = new List<UsbEndpointDescriptor>();
foreach (var iface in config.Interfaces)
{
foreach (var ep in iface.Endpoints)
{
endpoints.Add(new UsbEndpointDescriptor
{
Address = ep.Address,
Direction = ep.Direction,
EndpointType = ep.EndpointType,
MaxPacketSize = ep.MaxPacketSize,
Interval = ep.Interval
});
}
}
return new UsbConfigDescriptor
{
ConfigurationValue = config.ConfigValue,
InterfaceCount = config.Interfaces.Length,
Endpoints = endpoints
};
}
}
public class UsbDeviceDescriptor
{
public int VendorId { get; set; }
public int ProductId { get; set; }
public string ProductString { get; set; }
public string ManufacturerString { get; set; }
public string SerialString { get; set; }
public UsbDeviceType DeviceType { get; set; }
public int ConfigurationCount { get; set; }
public override string ToString() =>
$"{ManufacturerString} - {ProductString} (VID={VendorId:X4}, PID={ProductId:X4})";
}
public class UsbConfigDescriptor
{
public byte ConfigurationValue { get; set; }
public int InterfaceCount { get; set; }
public List<UsbEndpointDescriptor> Endpoints { get; set; } = new();
}
public class UsbEndpointDescriptor
{
public byte Address { get; set; }
public EndpointDirection Direction { get; set; }
public EndpointType EndpointType { get; set; }
public int MaxPacketSize { get; set; }
public int Interval { get; set; }
}协议封装
自定义通信协议
/// <summary>
/// USB 通信协议封装
/// </summary>
public class UsbProtocol
{
private readonly UsbCommunication _comm;
// 协议帧格式:[帧头 0xAA][命令 1B][数据长度 2B][数据 NB][校验 1B][帧尾 0x55]
private const byte Header = 0xAA;
private const byte Footer = 0x55;
public UsbProtocol(UsbCommunication comm)
{
_comm = comm;
}
// 发送命令帧
public bool SendFrame(byte command, byte[] data)
{
var frame = new byte[6 + data.Length];
frame[0] = Header;
frame[1] = command;
frame[2] = (byte)(data.Length >> 8);
frame[3] = (byte)(data.Length & 0xFF);
Array.Copy(data, 0, frame, 4, data.Length);
frame[4 + data.Length] = CalculateChecksum(frame, 1, 3 + data.Length);
frame[5 + data.Length] = Footer;
return _comm.SendData(frame);
}
// 解析接收帧
public (byte command, byte[] data)? ParseFrame(byte[] buffer)
{
if (buffer.Length < 6) return null;
if (buffer[0] != Header || buffer[^1] != Footer) return null;
int dataLen = (buffer[2] << 8) | buffer[3];
if (buffer.Length != 6 + dataLen) return null;
byte expectedChecksum = CalculateChecksum(buffer, 1, 3 + dataLen);
if (buffer[4 + dataLen] != expectedChecksum) return null;
var data = new byte[dataLen];
Array.Copy(buffer, 4, data, 0, dataLen);
return (buffer[1], data);
}
// 读取设备固件版本
public string? ReadFirmwareVersion()
{
_comm.SendFrame(0x01, Array.Empty<byte>());
var response = _comm.ReceiveData(2000);
if (response != null)
{
var parsed = ParseFrame(response);
if (parsed != null)
return Encoding.ASCII.GetString(parsed.Value.data);
}
return null;
}
private byte CalculateChecksum(byte[] data, int start, int length)
{
byte sum = 0;
for (int i = start; i < start + length; i++)
sum ^= data[i];
return sum;
}
}优点
缺点
总结
USB 通信推荐使用 LibUsbDotNet 库。核心流程:通过 VID/PID 查找设备 → Claim 接口 → 打开端点读写器 → 收发数据。协议设计建议帧头+命令+长度+数据+校验+帧尾格式。热插拔用轮询或 WMI 监听。USB 适合数据量大、实时性要求高的场景。
关键知识点
- 先分清主题属于界面层、ViewModel 层、线程模型还是设备接入层。
- WPF 文章真正的价值在于把 UI、数据、命令、线程和资源关系讲清楚。
- 上位机场景里,稳定性和异常恢复常常比界面花哨更重要。
- WPF 主题往往要同时理解依赖属性、绑定、可视树和命令系统。
项目落地视角
- 优先明确 DataContext、绑定路径、命令源和线程切换位置。
- 涉及设备时,补齐超时、重连、日志、告警和资源释放策略。
- 复杂控件最好提供最小可运行页面,便于后续复用和排障。
- 优先确认 DataContext、绑定路径、命令触发点和资源引用来源。
常见误区
- 把大量逻辑堆进 code-behind,导致页面难测难维护。
- 在后台线程直接操作 UI,或忽略 Dispatcher 切换。
- 只验证正常路径,不验证设备掉线、权限缺失和资源泄漏。
- 在 code-behind 塞太多状态与业务逻辑。
进阶路线
- 继续向 MVVM 工具链、控件可复用性、性能优化和视觉树诊断深入。
- 把主题放回真实设备流程,思考启动、连接、采集、显示、告警和恢复。
- 沉淀成控件库、通信层和诊断工具,提高整套客户端的复用度。
- 继续补齐控件库、主题系统、诊断工具和启动性能优化。
适用场景
- 当你准备把《USB 设备通信(LibUsbDotNet)》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合桌面业务系统、监控看板、工控上位机和设备交互界面。
- 当系统需要同时处理复杂 UI、后台任务和硬件通信时,这类主题尤为关键。
落地建议
- 优先明确 View、ViewModel、服务层和设备层边界,避免代码隐藏过重。
- 涉及实时刷新时,提前设计 UI 线程切换、节流和资源释放。
- 对硬件通信、日志、告警和异常恢复建立标准流程。
排错清单
- 先检查 DataContext、绑定路径、INotifyPropertyChanged 和命令状态是否正常。
- 排查 Dispatcher 调用、死锁、后台线程直接操作 UI 的问题。
- 出现内存增长时,优先检查事件订阅、图像资源和窗口生命周期。
复盘问题
- 如果把《USB 设备通信(LibUsbDotNet)》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《USB 设备通信(LibUsbDotNet)》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《USB 设备通信(LibUsbDotNet)》最大的收益和代价分别是什么?
