782 字
4 分钟
C# 进阶指南:构建稳健的 Windows HID 通信框架

1. 为什么选择 HID 通信?#

在嵌入式与上位机数据交换场景中,HID (Human Interface Device) 协议相比串口(UART)具有显著优势:

  • 免驱设计: Windows 系统原生支持,用户插即用,无需安装复杂的 USB 转串口驱动。
  • 身份识别: 通过 VID (Vendor ID)PID (Product ID) 精确锁定设备,避免了串口号(COM1, COM2…)随机分配带来的困扰。
  • 稳定性: 协议层自带纠错与握手机制,适合传输控制指令和传感器数据。

2. 系统架构设计#

为了实现代码的高复用性和健壮性,本方案将 HID 通信抽象为三层架构,确保 UI 逻辑与底层驱动完全解耦。

  1. 底层驱动层 (HID.cs): 负责直接调用 Win32 API(CreateFile, WriteFile, ReadFile),处理非托管内存。
  2. 接口中转层 (HIDInterface.cs): 负责自动连接逻辑、线程管理以及数据的打包/拆包。
  3. 应用业务层 (App): 只需关注 DataReceived 事件和 Send() 方法,完全屏蔽底层细节。

3. 核心实现:底层驱动 (HID.cs)#

底层采用异步读取模式,确保上位机界面在大数据量传输时不会出现卡死现象。

// 关键点:使用异步 BeginRead 维持接收流
private void BeginAsyncRead()
{
var inputBuff = new byte[InputReportLength];
// 使用 FileStream 的异步读取功能
_hidDevice.BeginRead(inputBuff, 0, InputReportLength, ReadCompleted, inputBuff);
}
private void ReadCompleted(IAsyncResult asyncResult)
{
var readBuff = (byte[])asyncResult.AsyncState;
try
{
_hidDevice.EndRead(asyncResult);
// 处理 Report ID (readBuff[0]) 和 数据载荷 (Payload)
OnDataReceived(new ReportEventArgs(readBuff[0], ExtractPayload(readBuff)));
if (_deviceOpened) BeginAsyncRead(); // 递归开启下一轮监听
}
catch (IOException) // 捕获设备拔出异常
{
OnDeviceRemoved(EventArgs.Empty);
CloseDevice();
}
}

4. 核心实现:中转与自动重连 (HIDInterface.cs)#

在工业级应用中,设备可能会因干扰掉线。自动重连(Auto-Reconnect) 是提升用户体验的核心功能。

// 内部使用 BackgroundWorker 维持心跳检测
private void ReadWriteThread_DoWork(object sender, DoWorkEventArgs e)
{
while (ContinueConnectFlag)
{
if (!bConnected)
{
// 尝试根据 VID/PID 重新建立句柄
Connect(lowHidDevice);
}
Thread.Sleep(500); // 轮询周期 500ms
}
}

5. 报文协议约定#

本实例设定单包大小为 64 Bytes(HID 全速设备的标准)。

  • 发送协议: 第 1 字节定义为后续有效数据的长度,方便固件解析。
  • Report ID: 默认使用 0x00。如果你的硬件定义了特定的 Report ID,请修改 Write 方法中的 buffer[0]

6. 应用示例:快速集成#

在你的 UI 逻辑(如 WPF 或 WinForms)中,只需几行代码即可完成初始化:

HIDInterface hid = new HIDInterface();
public void Init()
{
// 1. 订阅事件
hid.StatusConnected += (s, isConnect) => UpdateUIStatus(isConnect);
hid.DataReceived += (s, data) => ProcessData(data);
// 2. 配置参数并启动
var config = new HIDInterface.HidDevice {
vID = 0x04D8,
pID = 0x003F
};
hid.AutoConnect(config); // 开启自动扫描
}
public void SendCommand(byte[] cmd)
{
if(hid.Send(cmd)) {
// 发送成功
}
}

7. 总结与注意事项#

  1. 权限问题: 某些系统下访问 HID 设备可能需要管理员权限,或者需要将设备在 HIDClass 下配置为非独占模式。
  2. 数据对齐: 确保上位机的 OutputReportLength 与单片机端定义的 REPORT_COUNT 严格一致,否则 WriteFile 会返回错误。
  3. 垃圾回收:Dispose 方法中务必停止心跳线程并释放 SafeFileHandle,防止内存泄漏。
C# 进阶指南:构建稳健的 Windows HID 通信框架
https://hw.rscclub.website/posts/csharpusbhid/
作者
杨月昌
发布于
2017-03-23
许可协议
CC BY-NC-SA 4.0