从过去的历史走来,往USB端口读写设备相较于通过串口或并口都复杂了一个层次。
要开通USB端口(host主机那端),先枚举连接端口的可见设备,指定了相关的端口(连接了需要操作设备的那个端口),才能向它连接的设备作读写的操作。刚接触USB操作的时候真的是难倒了。
本文主要是描述第一个步骤(获取相关USB端口信息后,至于对它的读写操作,将在本文末端需要补充)。
枚举USB设备的方式其中一种是使用GUID,个人认为使用这种方式所开发的应用程序有个不方便的地方,它是定制了host的环境情况,一旦目标设备改插到另一个host上或host的系统有变更,GUID将有所改变,导致应用程序对相关目标设备无法识别。
本文例子是使用VID和PID两个信息来识别设备。VID和PID这两个信息容易获取,例如用 bus hound 可以读取插在host上的设备,再读取该设备的这个信息。另外,设备买回来的时候,也可以向厂家处取得。
当然写应用程序的时候,可以使用较普及的设计,就是显示在host上所有可见的USB设备,然后再选取目标设备,(这种做法比较适合同类型设备但VIP&PID较有可能变动的情况)。如果将VIP&PID 固化在程序里面,一旦VIP&PID改变了,就要变更应用程序才能继续操作。
例程如下:
/**
* @file main.cpp
* @author your name (you@domain.com)
* @brief
* @version 0.1
*
* @copyright Copyright (c) 2023
*
*/
#include <Windows.h>
#include <SetupAPI.h>
#include <stdio.h>
// 定义要枚举目标设备的 VID and PID
#define MY_VID 0x1234 //这里我随便举例
#define MY_PID 0x5678
int main()
{
// Define variables
HDEVINFO hDevInfo;
SP_DEVINFO_DATA devInfoData;
DWORD index;
DWORD dataType;
DWORD dataSize;
BYTE propertyBuffer[1024];
DWORD requiredSize;
HANDLE hDevice;
BOOL result;
DWORD bytesReturned;
// Initialize the device information set
hDevInfo = SetupDiGetClassDevs(NULL, "USB", NULL, DIGCF_ALLCLASSES | DIGCF_PRESENT);
if (hDevInfo == INVALID_HANDLE_VALUE) {
printf("Failed to get device information set. Error code: %d\n", GetLastError());
return 1;
}
// 枚举插在host的各可见设备
devInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
index = 0;
while (SetupDiEnumDeviceInfo(hDevInfo, index, &devInfoData)) {
// Get the device instance ID
result = SetupDiGetDeviceInstanceId(hDevInfo, &devInfoData, propertyBuffer, sizeof(propertyBuffer), &requiredSize);
if (!result) {
printf("Failed to get device instance ID. Error code: %d\n", GetLastError());
SetupDiDestroyDeviceInfoList(hDevInfo);
return 1;
}
// 检查相关设备是否匹配指定的 VID and PID
result = SetupDiGetDeviceRegistryProperty(hDevInfo, &devInfoData, SPDRP_HARDWAREID, &dataType, propertyBuffer, sizeof(propertyBuffer), &requiredSize);
if (result && (dataType == REG_MULTI_SZ)) {
char* ptr = (char*)propertyBuffer;
while (*ptr) {
if (sscanf(ptr, "USB\\VID_%x&PID_%x", &dataType, &dataSize) == 2) {
if ((dataType == MY_VID) && (dataSize == MY_PID)) {
// 如果匹配VID&PID成功,就创建句柄以便读写该设备
hDevice = CreateFileA((LPCSTR)propertyBuffer, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if (hDevice == INVALID_HANDLE_VALUE) {
printf("打开设备失败. Error code: %d\n", GetLastError());
SetupDiDestroyDeviceInfoList(hDevInfo);
return 1;
}
// Send a request to the device
result = DeviceIoControl(hDevice, /* ... */);
if (!result) {
printf("向目标设备发生请求失败. Error code: %d\n", GetLastError());
CloseHandle(hDevice);
SetupDiDestroyDeviceInfoList(hDevInfo);
return 1;
}
// Close the device handle
CloseHandle(hDevice);
}
}
ptr += strlen(ptr) + 1;
}
}
index++;
}
// Clean up the device information set
SetupDiDestroyDeviceInfoList(hDevInfo);
return 0;
}
补充一点,虽然环境变量不一定很重要,但我使用的tasks.json是:
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"type": "shell",
"command": "\\编译器的路径\\g++.exe",
"args": [
"${fileDirname}\\*.cpp",
"-o",
"${fileBasenameNoExtension}.exe",
"-std=c++11",
"-g"
],
"presentation": {
"echo": true,
"reveal": "always",
"panel": "new"
},
"problemMatcher": "$msCompile",
"group": "build"
},
{
"label": "run",
"type": "shell",
"dependsOn": "build",
"command": "${fileDirname}\\${fileBasenameNoExtension}.exe",
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": true,
"panel": "new"
}
},
{
"type": "cppbuild",
"label": "C/C++: g++.exe build active file",
"command": "\\编译器的路径\\g++.exe",
"args": [
"-fdiagnostics-color=always",
"-std=c++11",
"-g",
"${fileDirname}\\*.cpp",
"-o",
"${fileDirname}\\${fileBasenameNoExtension}.exe"
],
"options": {
"cwd": "\\放编译器的文件夹路径\\bin"
},
"problemMatcher": [
"$gcc"
],
"group": {
"kind": "build",
"isDefault": true
},
"detail": "Task generated by Debugger."
}
]
}
【本文完】
本文讲述了如何通过USB端口进行设备操作,介绍了使用VID和PID识别设备的方法,以及如何通过枚举设备和硬件ID来确保跨平台兼容性。作者还提供了一个C++示例代码,展示了如何在Windows环境中检测和操作特定VID/PID的USB设备。





