基于WinAPI识别Arduino Uno R3及关联COM句柄的技术问询
Alright, let's break down your problem into two clear parts: verifying if a COM port handle connects to an Arduino, and creating a CreateFile handle from your enumerated USB devices. Here's how to solve both without relying on third-party libraries.
1. Identify Arduino from a COM Port Handle
When you have a CreateFile handle to a COM port, you can map it to the underlying device path using QueryDosDevice, then use SetupAPI functions to check device properties (like hardware IDs or friendly names) to confirm it’s an Arduino.
Implementation Code
#include <windows.h> #include <setupapi.h> #include <initguid.h> #include <devguid.h> #include <string> #include <vector> // Helper to get the raw device path from a COM port name std::string GetDevicePathFromCom(const std::string& comName) { char devicePath[MAX_PATH] = {0}; if (QueryDosDeviceA(comName.c_str(), devicePath, MAX_PATH)) { return std::string(devicePath); } return ""; } // Check if a device path corresponds to an Arduino bool IsArduinoDevice(const std::string& devicePath) { // Open device info set for COM ports HDEVINFO hDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_COMPORT, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); if (hDevInfo == INVALID_HANDLE_VALUE) { return false; } SP_DEVICE_INTERFACE_DATA deviceInterfaceData = {0}; deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); for (DWORD i = 0; SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_COMPORT, i, &deviceInterfaceData); i++) { // Get required buffer size for device details DWORD requiredSize = 0; SetupDiGetDeviceInterfaceDetailA(hDevInfo, &deviceInterfaceData, NULL, 0, &requiredSize, NULL); if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { continue; } // Allocate buffer and retrieve device details std::vector<char> detailBuffer(requiredSize); PSP_DEVICE_INTERFACE_DETAIL_DATA_A pDetailData = reinterpret_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA_A>(detailBuffer.data()); pDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A); SP_DEVINFO_DATA devInfoData = {0}; devInfoData.cbSize = sizeof(SP_DEVINFO_DATA); if (SetupDiGetDeviceInterfaceDetailA(hDevInfo, &deviceInterfaceData, pDetailData, requiredSize, NULL, &devInfoData)) { // Match the device path if (std::string(pDetailData->DevicePath) == devicePath) { // Check hardware ID (official Arduinos use VID_2341) char hardwareId[MAX_PATH] = {0}; if (SetupDiGetDeviceRegistryPropertyA(hDevInfo, &devInfoData, SPDRP_HARDWAREID, NULL, reinterpret_cast<PBYTE>(hardwareId), sizeof(hardwareId), NULL)) { std::string hwId(hardwareId); if (hwId.find("VID_2341") != std::string::npos) { SetupDiDestroyDeviceInfoList(hDevInfo); return true; } } // Fallback to checking friendly name char friendlyName[MAX_PATH] = {0}; if (SetupDiGetDeviceRegistryPropertyA(hDevInfo, &devInfoData, SPDRP_FRIENDLYNAME, NULL, reinterpret_cast<PBYTE>(friendlyName), sizeof(friendlyName), NULL)) { std::string friendly(friendlyName); if (friendly.find("Arduino") != std::string::npos) { SetupDiDestroyDeviceInfoList(hDevInfo); return true; } } } } } SetupDiDestroyDeviceInfoList(hDevInfo); return false; } // Usage example void VerifyArduinoOnCom(const std::string& comName) { std::string devicePath = GetDevicePathFromCom(comName); if (!devicePath.empty() && IsArduinoDevice(devicePath)) { printf("COM port %s is an Arduino device!\n", comName.c_str()); } else { printf("COM port %s is not an Arduino.\n", comName.c_str()); } }
2. Create a CreateFile Handle from Enumerated USB Devices
Your existing USB enumeration code collects device instances, but you need to map these to their associated COM ports. Here’s how to extend your code to get the COM port name and use it to open a handle.
Step 1: Update the device Struct
Add a field to store the COM port name:
struct device { std::string id; std::string busReportedDesc; std::string comPort; // New field for COM port name };
Step 2: Extend the USB Enumeration Code
Modify your ListDevices function to retrieve the COM port for each USB device:
std::vector<device> usbenumerator::ListDevices() { DWORD dwSize; DEVPROPTYPE ulPropertyType; CONFIGRET status; HDEVINFO hDevInfo; SP_DEVINFO_DATA DeviceInfoData; char szDeviceInstanceID[MAX_DEVICE_ID_LEN]; WCHAR szBuffer[4096]; FN_SetupDiGetDeviceProperty GetDeviceProperty = (FN_SetupDiGetDeviceProperty)(GetProcAddress(GetModuleHandle("setupapi.dll"), "SetupDiGetDevicePropertyW")); // Initialize USB device info set (your original code) hDevInfo = SetupDiGetClassDevs(NULL, "USB", NULL, DIGCF_ALLCLASSES | DIGCF_PRESENT); std::vector<device> output; if (hDevInfo == INVALID_HANDLE_VALUE) { return output; } for (int i = 0; ; i++) { device dev; DeviceInfoData.cbSize = sizeof(DeviceInfoData); if (!SetupDiEnumDeviceInfo(hDevInfo, i, &DeviceInfoData)) { break; } // Get device instance ID (your original code) status = CM_Get_Device_ID(DeviceInfoData.DevInst, szDeviceInstanceID, MAX_PATH, 0); if (status != CR_SUCCESS) { continue; } dev.id = std::string(szDeviceInstanceID); // Get bus-reported description (your original code) if (GetDeviceProperty) { if (GetDeviceProperty(hDevInfo, &DeviceInfoData, &DEVPKEY_Device_BusReportedDeviceDesc, &ulPropertyType, (BYTE*) szBuffer, sizeof(szBuffer), &dwSize, 0)) { _bstr_t b(szBuffer); dev.busReportedDesc = std::string(static_cast<const char*>(b)); } } // Find associated COM port for this USB device HDEVINFO hComDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_COMPORT, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); if (hComDevInfo != INVALID_HANDLE_VALUE) { SP_DEVICE_INTERFACE_DATA comInterfaceData = {0}; comInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); for (DWORD j = 0; SetupDiEnumDeviceInterfaces(hComDevInfo, NULL, &GUID_DEVINTERFACE_COMPORT, j, &comInterfaceData); j++) { DWORD requiredSize = 0; SetupDiGetDeviceInterfaceDetailA(hComDevInfo, &comInterfaceData, NULL, 0, &requiredSize, NULL); if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { continue; } std::vector<char> detailBuffer(requiredSize); PSP_DEVICE_INTERFACE_DETAIL_DATA_A pDetailData = reinterpret_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA_A>(detailBuffer.data()); pDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A); SP_DEVINFO_DATA comDevInfoData = {0}; comDevInfoData.cbSize = sizeof(SP_DEVINFO_DATA); if (SetupDiGetDeviceInterfaceDetailA(hComDevInfo, &comInterfaceData, pDetailData, requiredSize, NULL, &comDevInfoData)) { // Get parent device instance ID of the COM port DWORD parentDevInst; status = CM_Get_Parent(&parentDevInst, comDevInfoData.DevInst, 0); if (status == CR_SUCCESS) { char parentID[MAX_PATH] = {0}; status = CM_Get_Device_ID(parentDevInst, parentID, MAX_PATH, 0); if (status == CR_SUCCESS && std::string(parentID) == dev.id) { // Extract COM port name from device path std::string devicePath(pDetailData->DevicePath); size_t comPos = devicePath.find("\\\\.\\COM"); if (comPos != std::string::npos) { dev.comPort = devicePath.substr(comPos + 4); // Extract "COMX" break; } } } } } SetupDiDestroyDeviceInfoList(hComDevInfo); } output.push_back(dev); } SetupDiDestroyDeviceInfoList(hDevInfo); return output; }
Step 3: Open the COM Port with CreateFile
Use the retrieved COM port name to create a handle and configure serial settings:
// Usage example: Open Arduino devices from enumerated list void OpenArduinoDevices() { usbenumerator enumerator; std::vector<device> devices = enumerator.ListDevices(); for (const auto& dev : devices) { // Check if it's an Arduino (match bus description or VID) if (!dev.comPort.empty() && (dev.busReportedDesc.find("Arduino") != std::string::npos || dev.id.find("VID_2341") != std::string::npos)) { std::string comPath = "\\\\.\\" + dev.comPort; // Required prefix for CreateFile HANDLE hSerial = CreateFile(comPath.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hSerial != INVALID_HANDLE_VALUE) { printf("Successfully opened Arduino on %s\n", dev.comPort.c_str()); // Configure serial port to match Arduino's settings (default 9600 baud) DCB dcbSerialParams = {0}; dcbSerialParams.DCBlength = sizeof(dcbSerialParams); if (GetCommState(hSerial, &dcbSerialParams)) { dcbSerialParams.BaudRate = CBR_9600; dcbSerialParams.ByteSize = 8; dcbSerialParams.StopBits = ONESTOPBIT; dcbSerialParams.Parity = NOPARITY; SetCommState(hSerial, &dcbSerialParams); } // Remember to close the handle when done: // CloseHandle(hSerial); } else { printf("Failed to open %s: Error %d\n", dev.comPort.c_str(), GetLastError()); } } } }
Key Notes
- Hardware IDs: Official Arduino boards use VID
2341; clones may use other VIDs (like1A86for CH340-based boards). Adjust the checks to include these if needed. - Serial Configuration: Always match the serial parameters (baud rate, parity, etc.) to your Arduino’s
Serial.begin()call. - Error Handling: The code includes basic error handling, but you should expand it for production use (e.g., logging detailed errors from
GetLastError()).
内容的提问来源于stack exchange,提问作者Tefek

