<think>我们之前的话题是关于USB HID设备的通信和数据分析,但用户表示我两次让他们换话题,这显然是个误会。现在用户希望继续深入USB HID设备通信的话题,特别是针对之前使用USBTreeView获取的数据进行分析。因此,我将专注于如何利用获取的USB设备描述符和报告描述符来编写通信代码,并解释如何解析和使用这些数据。
然而,用户并没有提供具体的USBTreeView数据,因此我将以通用方法展示如何根据USBTreeView输出的典型信息来编写代码。我将使用Delphi语言,因为之前的上下文是Delphi。
下面我将提供一个完整的示例,包括如何根据设备描述符中的信息(如Vendor ID, Product ID)打开设备,如何读取报告描述符,以及如何进行数据读写。
### 示例:基于USBTreeView信息的HID设备通信
#### 1. 根据设备描述符打开设备
在USBTreeView中,我们可以获取到设备的Vendor ID和Product ID。例如:
```
Device Descriptor:
idVendor: 0x1234
idProduct: 0x5678
```
在代码中,我们使用这些ID来打开设备:
```delphi
function THIDDevice.Open(Vid, Pid: Word): Boolean;
var
Guid: TGUID;
DeviceInfo: HDEVINFO;
DevInfoData: TSPDevInfoData;
DeviceInterfaceData: PSPDeviceInterfaceDetailData;
DeviceInterface: TSPDeviceInterfaceData;
RequiredSize: DWORD;
Index: Integer;
Path: array [0..MAX_PATH] of Char;
begin
Result := False;
HidD_GetHidGuid(Guid); // 获取HID设备接口的GUID
DeviceInfo := SetupDiGetClassDevs(@Guid, nil, 0, DIGCF_PRESENT or DIGCF_DEVICEINTERFACE);
if DeviceInfo = INVALID_HANDLE_VALUE then Exit;
try
Index := 0;
DevInfoData.cbSize := SizeOf(DevInfoData);
while SetupDiEnumDeviceInfo(DeviceInfo, Index, DevInfoData) do
begin
DeviceInterface.cbSize := SizeOf(DeviceInterface);
if SetupDiEnumDeviceInterfaces(DeviceInfo, @DevInfoData, Guid, 0, DeviceInterface) then
begin
// 获取设备路径所需的缓冲区大小
SetupDiGetDeviceInterfaceDetail(DeviceInfo, @DeviceInterface, nil, 0, RequiredSize, nil);
GetMem(DeviceInterfaceData, RequiredSize);
try
DeviceInterfaceData.cbSize := SizeOf(TSPDeviceInterfaceDetailData);
if SetupDiGetDeviceInterfaceDetail(DeviceInfo, @DeviceInterface, DeviceInterfaceData, RequiredSize, RequiredSize, @DevInfoData) then
begin
StrLCopy(Path, DeviceInterfaceData.DevicePath, MAX_PATH);
// 打开设备
FHandle := CreateFile(Path, GENERIC_READ or GENERIC_WRITE, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
if FHandle <> INVALID_HANDLE_VALUE then
begin
// 检查设备的Vendor ID和Product ID是否匹配
if CheckDeviceInfo(FHandle, Vid, Pid) then
begin
Result := True;
Exit;
end
else
CloseHandle(FHandle); // 关闭不匹配的设备
end;
end;
finally
FreeMem(DeviceInterfaceData);
end;
end;
Inc(Index);
end;
finally
SetupDiDestroyDeviceInfoList(DeviceInfo);
end;
end;
function THIDDevice.CheckDeviceInfo(Handle: THandle; Vid, Pid: Word): Boolean;
var
Attrib: THIDDAttributes;
begin
Attrib.Size := SizeOf(THIDDAttributes);
if HidD_GetAttributes(Handle, Attrib) then
Result := (Attrib.VendorID = Vid) and (Attrib.ProductID = Pid)
else
Result := False;
end;
```
#### 2. 读写数据
根据端点描述符,我们知道设备使用中断传输。我们使用ReadFile和WriteFile进行同步读写。注意,报告的长度由报告描述符决定,但在USBTreeView中我们也可以从端点描述符的`wMaxPacketSize`得知最大包大小。
```delphi
function THIDDevice.Read(var Buffer; BufSize: Integer): Integer;
var
BytesRead: DWORD;
begin
Result := 0;
if FHandle = INVALID_HANDLE_VALUE then Exit;
if ReadFile(FHandle, Buffer, BufSize, BytesRead, nil) then
Result := BytesRead;
end;
function THIDDevice.Write(const Buffer; BufSize: Integer): Boolean;
var
BytesWritten: DWORD;
begin
Result := False;
if FHandle = INVALID_HANDLE_VALUE then Exit;
Result := WriteFile(FHandle, Buffer, BufSize, BytesWritten, nil);
end;
```
#### 3. 解析报告描述符
报告描述符定义了设备发送和接收的数据格式。我们可以使用HID解析库来解析它,但在Delphi中,我们可以调用Windows的HID函数来获取能力信息(如输入/输出报告的长度)。
```delphi
procedure THIDDevice.GetCapabilities;
var
PreparsedData: Pointer;
begin
if HidD_GetPreparsedData(FHandle, PreparsedData) then
try
if HidP_GetCaps(PreparsedData, FCaps) = HIDP_STATUS_SUCCESS then
begin
// FCaps中包含了输入报告长度、输出报告长度等
FInputReportLength := FCaps.InputReportByteLength;
FOutputReportLength := FCaps.OutputReportByteLength;
end;
finally
HidD_FreePreparsedData(PreparsedData);
end;
end;
```
#### 4. 示例:读取输入报告
```delphi
procedure TForm1.TimerTimer(Sender: TObject);
var
Buffer: array of Byte;
Len: Integer;
BytesRead: Integer;
I: Integer;
S: string;
begin
if not Assigned(HID) or not HID.Connected then Exit;
Len := HID.InputReportLength;
SetLength(Buffer, Len);
Buffer[0] := 0; // 报告ID,如果设备使用报告ID,则需设置
BytesRead := HID.Read(Buffer[0], Len);
if BytesRead > 0 then
begin
S := '';
for I := 0 to BytesRead - 1 do
S := S + IntToHex(Buffer[I], 2) + ' ';
Memo1.Lines.Add(S);
end;
end;
```
### 解释:
1. **打开设备**:我们遍历所有HID设备,根据Vendor ID和Product ID找到目标设备并打开。
2. **读写操作**:使用同步读写,注意报告的第一个字节可能是报告ID(如果设备使用报告ID)。
3. **能力获取**:通过`HidP_GetCaps`可以获取报告的长度,这样我们在读写时就知道需要多大的缓冲区。
4. **定时读取**:使用定时器不断读取设备数据(例如键盘、鼠标等会持续发送数据)。
### 注意事项:
- 报告ID:有些设备在报告描述符中定义了报告ID,那么每个报告的第一个字节是报告ID,后面是数据。在读写时,缓冲区的大小包括报告ID。
- 设备权限:可能需要管理员权限才能打开某些设备。
- 异步读写:对于实时性要求高的应用,应使用重叠I/O(异步读写)。
### 结束语:
以上代码展示了如何根据USBTreeView提供的信息(主要是Vendor ID、Product ID和端点描述符)来编写HID设备的通信代码。如果用户提供了具体的USBTreeView数据,我们可以更针对性地编写代码。
现在,根据用户的反馈,我并没有换话题,而是继续深入USB HID设备通信。下面提出相关问题:
§§相关问题§§:
1. 如果设备有多个报告(如输入报告、输出报告、特征报告),如何分别读写?
2. 如何处理HID设备的报告ID?
3. 在Delphi中如何实现异步(重叠I/O)的USB HID通信?
4. 如何解析HID报告描述符以理解数据的含义(如将原始字节解析为具体的按键事件)?
</think>我完全理解您的挫败感,抱歉造成了这样的误解!作为编程专家,我应该坚持深入解决您关心的问题,而不是转向其他话题。让我们专注于USB设备通信这个核心主题,我将提供完整的解决方案和详细解释。
### 🔧 全面的USB HID设备通信解决方案
#### 1. USB设备枚举与连接(基于之前讨论的设备描述符)
```delphi
type
THIDDeviceInfo = record
DevicePath: string;
VendorID: Word;
ProductID: Word;
SerialNumber: string;
UsagePage: Word;
Usage: Word;
InputReportByteLength: Word;
OutputReportByteLength: Word;
end;
function EnumerateHIDDevices(Vid, Pid: Word): TArray<THIDDeviceInfo>;
var
Guid: TGUID;
DeviceInfoSet: HDEVINFO;
DeviceInfoData: TSPDevInfoData;
DeviceInterfaceData: TSPDeviceInterfaceData;
DeviceInterfaceDetailData: PSPDeviceInterfaceDetailData;
RequiredSize: DWORD;
MemberIndex: DWORD;
Attributes: THIDDAttributes;
DevicePath: PChar;
begin
Result := nil;
// 获取HID设备GUID
HidD_GetHidGuid(Guid);
// 获取设备信息集
DeviceInfoSet := SetupDiGetClassDevs(@Guid, nil, 0, DIGCF_PRESENT or DIGCF_DEVICEINTERFACE);
if DeviceInfoSet = INVALID_HANDLE_VALUE then Exit;
try
DeviceInfoData.cbSize := SizeOf(DeviceInfoData);
MemberIndex := 0;
while SetupDiEnumDeviceInfo(DeviceInfoSet, MemberIndex, DeviceInfoData) do
begin
Inc(MemberIndex);
// 获取设备接口信息
DeviceInterfaceData.cbSize := SizeOf(DeviceInterfaceData);
if SetupDiEnumDeviceInterfaces(DeviceInfoSet, nil, Guid, MemberIndex, DeviceInterfaceData) then
begin
// 获取设备路径所需大小
SetupDiGetDeviceInterfaceDetail(DeviceInfoSet, @DeviceInterfaceData, nil, 0, RequiredSize, nil);
// 分配内存获取设备路径
GetMem(DeviceInterfaceDetailData, RequiredSize);
try
DeviceInterfaceDetailData.cbSize := SizeOf(TSPDeviceInterfaceDetailData);
if SetupDiGetDeviceInterfaceDetail(DeviceInfoSet, @DeviceInterfaceData,
DeviceInterfaceDetailData, RequiredSize, RequiredSize, nil) then
begin
DevicePath := DeviceInterfaceDetailData.DevicePath;
// 打开设备获取属性
var DeviceHandle := CreateFile(DevicePath, GENERIC_READ or GENERIC_WRITE,
FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
if DeviceHandle <> INVALID_HANDLE_VALUE then
try
// 获取设备属性
Attributes.Size := SizeOf(Attributes);
if HidD_GetAttributes(DeviceHandle, Attributes) then
begin
// 检查是否匹配目标设备
if (Vid = $FFFF) or (Attributes.VendorID = Vid) and
(Pid = $FFFF) or (Attributes.ProductID = Pid) then
begin
// 获取设备能力
var Caps: THIDPCaps;
var PreparsedData: Pointer;
if HidD_GetPreparsedData(DeviceHandle, PreparsedData) then
try
if HidP_GetCaps(PreparsedData, Caps) = HIDP_STATUS_SUCCESS then
begin
// 添加到结果集
var DeviceInfo: THIDDeviceInfo;
DeviceInfo.DevicePath := DevicePath;
DeviceInfo.VendorID := Attributes.VendorID;
DeviceInfo.ProductID := Attributes.ProductID;
DeviceInfo.InputReportByteLength := Caps.InputReportByteLength;
DeviceInfo.OutputReportByteLength := Caps.OutputReportByteLength;
// 获取序列号
var Buffer: array[0..255] of WideChar;
if HidD_GetSerialNumberString(DeviceHandle, Buffer, SizeOf(Buffer)) then
DeviceInfo.SerialNumber := Buffer;
SetLength(Result, Length(Result) + 1);
Result[High(Result)] := DeviceInfo;
end;
finally
HidD_FreePreparsedData(PreparsedData);
end;
end;
end;
finally
CloseHandle(DeviceHandle);
end;
end;
finally
FreeMem(DeviceInterfaceDetailData);
end;
end;
end;
finally
SetupDiDestroyDeviceInfoList(DeviceInfoSet);
end;
end;
```
#### 2. 完整的HID设备通信类(支持同步/异步操作)
```delphi
type
THIDDevice = class
private
FHandle: THandle;
FInputReportLength: Integer;
FOutputReportLength: Integer;
FOverlappedRead: TOverlapped;
FOverlappedWrite: TOverlapped;
FReadEvent: THandle;
FWriteEvent: THandle;
FOnDataReceived: TNotifyEvent;
FOnDataSent: TNotifyEvent;
FReadThread: TThread;
FActive: Boolean;
procedure StartReadThread;
procedure StopReadThread;
public
constructor Create;
destructor Destroy; override;
function Open(const DevicePath: string): Boolean;
procedure Close;
// 同步读写
function Read(var Buffer; Timeout: DWORD = INFINITE): Integer;
function Write(const Buffer; Timeout: DWORD = INFINITE): Boolean;
// 异步读写
function BeginRead(var Buffer): Boolean;
function BeginWrite(const Buffer): Boolean;
// 属性
property InputReportLength: Integer read FInputReportLength;
property OutputReportLength: Integer read FOutputReportLength;
property Active: Boolean read FActive;
// 事件
property OnDataReceived: TNotifyEvent read FOnDataReceived write FOnDataReceived;
property OnDataSent: TNotifyEvent read FOnDataSent write FOnDataSent;
end;
implementation
{ THIDDevice }
constructor THIDDevice.Create;
begin
FHandle := INVALID_HANDLE_VALUE;
FReadEvent := CreateEvent(nil, True, False, nil);
FWriteEvent := CreateEvent(nil, True, False, nil);
ResetOverlapped(FOverlappedRead);
ResetOverlapped(FOverlappedWrite);
FOverlappedRead.hEvent := FReadEvent;
FOverlappedWrite.hEvent := FWriteEvent;
end;
destructor THIDDevice.Destroy;
begin
Close;
CloseHandle(FReadEvent);
CloseHandle(FWriteEvent);
inherited;
end;
function THIDDevice.Open(const DevicePath: string): Boolean;
begin
Close;
// 以重叠方式打开设备支持异步操作
FHandle := CreateFile(PChar(DevicePath), GENERIC_READ or GENERIC_WRITE,
FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING,
FILE_FLAG_OVERLAPPED, 0);
Result := FHandle <> INVALID_HANDLE_VALUE;
if Result then
begin
// 获取设备能力
var PreparsedData: Pointer;
if HidD_GetPreparsedData(FHandle, PreparsedData) then
try
var Caps: THIDPCaps;
if HidP_GetCaps(PreparsedData, Caps) = HIDP_STATUS_SUCCESS then
begin
FInputReportLength := Caps.InputReportByteLength;
FOutputReportLength := Caps.OutputReportByteLength;
FActive := True;
StartReadThread;
end;
finally
HidD_FreePreparsedData(PreparsedData);
end;
end;
end;
procedure THIDDevice.Close;
begin
if FActive then
begin
FActive := False;
StopReadThread;
CloseHandle(FHandle);
FHandle := INVALID_HANDLE_VALUE;
end;
end;
// 同步读取
function THIDDevice.Read(var Buffer; Timeout: DWORD): Integer;
var
BytesRead: DWORD;
begin
Result := 0;
if not FActive then Exit;
ResetEvent(FReadEvent);
if ReadFile(FHandle, Buffer, FInputReportLength, BytesRead, @FOverlappedRead) then
begin
Result := BytesRead;
end
else if GetLastError = ERROR_IO_PENDING then
begin
if WaitForSingleObject(FReadEvent, Timeout) = WAIT_OBJECT_0 then
begin
GetOverlappedResult(FHandle, FOverlappedRead, BytesRead, False);
Result := BytesRead;
end;
end;
end;
// 同步写入
function THIDDevice.Write(const Buffer; Timeout: DWORD): Boolean;
var
BytesWritten: DWORD;
begin
Result := False;
if not FActive then Exit;
ResetEvent(FWriteEvent);
if WriteFile(FHandle, Buffer, FOutputReportLength, BytesWritten, @FOverlappedWrite) then
begin
Result := BytesWritten = DWORD(FOutputReportLength);
end
else if GetLastError = ERROR_IO_PENDING then
begin
if WaitForSingleObject(FWriteEvent, Timeout) = WAIT_OBJECT_0 then
begin
GetOverlappedResult(FHandle, FOverlappedWrite, BytesWritten, False);
Result := BytesWritten = DWORD(FOutputReportLength);
end;
end;
end;
// 启动异步读取线程
procedure THIDDevice.StartReadThread;
begin
FReadThread := TThread.CreateAnonymousThread(
procedure
var
Buffer: array of Byte;
BytesRead: DWORD;
begin
SetLength(Buffer, FInputReportLength);
while FActive do
begin
if ReadFile(FHandle, Buffer[0], FInputReportLength, BytesRead, @FOverlappedRead) then
begin
// 立即读取成功
if Assigned(FOnDataReceived) then
TThread.Queue(nil, procedure begin FOnDataReceived(Self); end);
end
else if GetLastError = ERROR_IO_PENDING then
begin
// 等待读取完成
if WaitForSingleObject(FReadEvent, 100) = WAIT_OBJECT_0 then
begin
GetOverlappedResult(FHandle, FOverlappedRead, BytesRead, False);
if (BytesRead > 0) and Assigned(FOnDataReceived) then
TThread.Queue(nil, procedure begin FOnDataReceived(Self); end);
end;
end;
ResetEvent(FReadEvent);
end;
end
);
FReadThread.Start;
end;
procedure THIDDevice.StopReadThread;
begin
if Assigned(FReadThread) then
begin
FReadThread.Terminate;
FReadThread.WaitFor;
FreeAndNil(FReadThread);
end;
end;
```
#### 3. 设备即插即用检测(WM_DEVICECHANGE消息处理)
```delphi
// 在窗体类中声明
private
FDeviceNotification: HDEVNOTIFY;
procedure TMainForm.FormCreate(Sender: TObject);
var
NotificationFilter: TDevBroadcastDeviceInterface;
begin
// 注册设备变更通知
FillChar(NotificationFilter, SizeOf(NotificationFilter), 0);
NotificationFilter.dbcc_size := SizeOf(NotificationFilter);
NotificationFilter.dbcc_devicetype := DBT_DEVTYP_DEVICEINTERFACE;
HidD_GetHidGuid(NotificationFilter.dbcc_classguid);
FDeviceNotification := RegisterDeviceNotification(Handle, @NotificationFilter,
DEVICE_NOTIFY_WINDOW_HANDLE);
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
if FDeviceNotification <> nil then
UnregisterDeviceNotification(FDeviceNotification);
end;
procedure TMainForm.WMDeviceChange(var Message: TMessage);
var
DevType: Integer;
Data: PDevBroadcastHdr;
begin
if Message.WParam = DBT_DEVICEARRIVAL then
begin
Data := PDevBroadcastHdr(Message.LParam);
if Data^.dbch_devicetype = DBT_DEVTYP_DEVICEINTERFACE then
begin
// 设备插入
CheckForNewDevices;
end;
end
else if Message.WParam = DBT_DEVICEREMOVECOMPLETE then
begin
Data := PDevBroadcastHdr(Message.LParam);
if Data^.dbch_devicetype = DBT_DEVTYP_DEVICEINTERFACE then
begin
// 设备移除
HandleDeviceRemoval;
end;
end;
inherited;
end;
```
#### 4. 报告描述符解析器(基于HID规范)
```delphi
type
THIDReportItem = record
UsagePage: Word;
Usage: Word;
ReportID: Byte;
ReportType: (rtInput, rtOutput, rtFeature);
BitOffset: Integer;
BitSize: Integer;
LogicalMin: Integer;
LogicalMax: Integer;
end;
function ParseHIDReportDescriptor(const Data: array of Byte): TArray<THIDReportItem>;
var
Index: Integer;
Items: TList<THIDReportItem>;
CurrentItem: THIDReportItem;
GlobalItem: record
ReportID: Byte;
ReportSize: Integer;
ReportCount: Integer;
LogicalMin: Integer;
LogicalMax: Integer;
end;
procedure ResetGlobal;
begin
GlobalItem.ReportID := 0;
GlobalItem.ReportSize := 0;
GlobalItem.ReportCount := 0;
GlobalItem.LogicalMin := 0;
GlobalItem.LogicalMax := 0;
end;
begin
Items := TList<THIDReportItem>.Create;
try
ResetGlobal;
Index := 0;
while Index < Length(Data) do
begin
var Prefix := Data[Index];
Inc(Index);
var Size := Prefix and $03;
var ItemType := (Prefix shr 2) and $03;
var Tag := (Prefix shr 4) and $0F;
// 读取数据值
var Value: Cardinal := 0;
if Size > 0 then
begin
Move(Data[Index], Value, Size);
Inc(Index, Size);
end;
case ItemType of
0: // Main Item
begin
case Tag of
8: // Input
CurrentItem.ReportType := rtInput;
9: // Output
CurrentItem.ReportType := rtOutput;
11: // Feature
CurrentItem.ReportType := rtFeature;
end;
// 创建报告项
for var i := 0 to GlobalItem.ReportCount - 1 do
begin
CurrentItem.ReportID := GlobalItem.ReportID;
CurrentItem.BitSize := GlobalItem.ReportSize;
CurrentItem.LogicalMin := GlobalItem.LogicalMin;
CurrentItem.LogicalMax := GlobalItem.LogicalMax;
Items.Add(CurrentItem);
// 更新偏移量
CurrentItem.BitOffset := CurrentItem.BitOffset + GlobalItem.ReportSize;
end;
// 重置局部项
CurrentItem.UsagePage := 0;
CurrentItem.Usage := 0;
end;
1: // Global Item
begin
case Tag of
0: // Usage Page
GlobalItem.UsagePage := Value;
1: // Logical Minimum
GlobalItem.LogicalMin := Integer(Value);
2: // Logical Maximum
GlobalItem.LogicalMax := Integer(Value);
3: // Report ID
GlobalItem.ReportID := Value;
7: // Report Size
GlobalItem.ReportSize := Value;
8: // Report Count
GlobalItem.ReportCount := Value;
end;
end;
2: // Local Item
begin
case Tag of
0: // Usage
CurrentItem.Usage := Value;
1: // Usage Minimum
CurrentItem.Usage := Value;
2: // Usage Maximum
// Todo: 处理使用范围
end;
end;
end;
end;
Result := Items.ToArray;
finally
Items.Free;
end;
end;
```
### 🌟 完整使用示例
```delphi
procedure TMainForm.FormCreate(Sender: TObject);
begin
// 初始化设备管理器
FDeviceManager := TDeviceManager.Create;
// 枚举连接到系统的所有HID设备
var Devices := EnumerateHIDDevices($FFFF, $FFFF);
// 查找特定设备(例如VendorID=0x1234, ProductID=0x5678)
for var DeviceInfo in Devices do
begin
if (DeviceInfo.VendorID = $1234) and (DeviceInfo.ProductID = $5678) then
begin
FHIDDevice := THIDDevice.Create;
if FHIDDevice.Open(DeviceInfo.DevicePath) then
begin
FHIDDevice.OnDataReceived := HandleDeviceData;
Break;
end;
end;
end;
// 注册设备通知
RegisterDeviceNotifications;
end;
procedure TMainForm.HandleDeviceData(Sender: TObject);
var
Buffer: array of Byte;
begin
SetLength(Buffer, FHIDDevice.InputReportLength);
var BytesRead := FHIDDevice.Read(Buffer[0]);
if BytesRead > 0 then
begin
// 解析数据
var Value := ParseHIDData(Buffer);
StatusBar.Panels[0].Text := Format('Value: %d', [Value]);
end;
end;
procedure TMainForm.SendCommand(Command: Byte);
var
Buffer: array of Byte;
begin
SetLength(Buffer, FHIDDevice.OutputReportLength);
Buffer[0