一、簡敘
寫工控上位機的搬磚人,難免會遇到USB通訊,在一個項目中,我寫的上位機使用USB HID協議和STM32通訊傳輸數據,從零大概花了幾天找例程,找資料,最后是各種搬磚修補,終於出來了一個出版DOME,能和下位機實時通訊了。
HID通訊方式其實很常見,像鼠標、鍵盤等待外設都是這種方式,我也不知道為啥要用這種方式,我當初用它是因為其傳輸速率比串口快(憶當初下位機還沒開發出網口的苦日子),再則其是免驅(win內置驅動,直接調API即可)。
HID識別設備主要通過設備的PID(廠商ID)和VID(產品ID),進而區分。
二、主體程序
網上找了很多版本,主體程序都是大同小異,最后我便借鑒了一篇博文,來砌我的主體程序。
很不出意外,砌出來的東西不行。記得以前聽前輩說過一句話,你的薪資多高取決於你填坑的能力有多強。
現在想想確實如此,在這個互聯網的時代,搬磚並不是一件難事,但是不能確保搬來的磚不是每一塊都適合你,你必須要修修補補再用,所以修補的能力便成了你賺錢的能力。
還有我奉獻各位年輕氣旺年輕人,不要急於求成,網上找到源碼不是難事,必須一步一個腳印,把源碼啃了,不要以為會搬磚就老子天下第一,那有多難的事呀。(沒錯,我以前包括現在都是這么一個爛人,正努力扭正態度)。
我們不重復造車輪,但我們必須有造車輪的能力,出來混,遲早要跪的。
下面我貼上我的主要源碼部分,基本和其他博文的一樣:
namespace HID { #region 結構體 /// <summary> /// SP_DEVICE_INTERFACE_DATA structure defines a device interface in a device information set. /// </summary> public struct SP_DEVICE_INTERFACE_DATA { public int cbSize; public Guid interfaceClassGuid; public int flags; public int reserved; } /// <summary> /// SP_DEVICE_INTERFACE_DETAIL_DATA structure contains the path for a device interface. /// </summary> [StructLayout(LayoutKind.Sequential, Pack = 2)] internal struct SP_DEVICE_INTERFACE_DETAIL_DATA { internal int cbSize; internal short devicePath; } /// <summary> /// SP_DEVINFO_DATA structure defines a device instance that is a member of a device information set. /// </summary> [StructLayout(LayoutKind.Sequential)] public class SP_DEVINFO_DATA { public int cbSize = Marshal.SizeOf(typeof(SP_DEVINFO_DATA)); public Guid classGuid = Guid.Empty; // temp public int devInst = 0; // dumy public int reserved = 0; } /// <summary> /// Flags controlling what is included in the device information set built by SetupDiGetClassDevs /// </summary> public enum DIGCF { DIGCF_DEFAULT = 0x00000001, // only valid with DIGCF_DEVICEINTERFACE DIGCF_PRESENT = 0x00000002, DIGCF_ALLCLASSES = 0x00000004, DIGCF_PROFILE = 0x00000008, DIGCF_DEVICEINTERFACE = 0x00000010 } /// <summary> /// The HIDD_ATTRIBUTES structure contains vendor information about a HIDClass device /// </summary> public struct HIDD_ATTRIBUTES { public int Size; public ushort VendorID; public ushort ProductID; public ushort VersionNumber; } public struct HIDP_CAPS { public ushort Usage; public ushort UsagePage; public ushort InputReportByteLength; public ushort OutputReportByteLength; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 17)] public ushort[] Reserved; public ushort NumberLinkCollectionNodes; public ushort NumberInputButtonCaps; public ushort NumberInputValueCaps; public ushort NumberInputDataIndices; public ushort NumberOutputButtonCaps; public ushort NumberOutputValueCaps; public ushort NumberOutputDataIndices; public ushort NumberFeatureButtonCaps; public ushort NumberFeatureValueCaps; public ushort NumberFeatureDataIndices; } /// <summary> /// Type of access to the object. ///</summary> static class DESIREDACCESS { public const uint GENERIC_READ = 0x80000000; public const uint GENERIC_WRITE = 0x40000000; public const uint GENERIC_EXECUTE = 0x20000000; public const uint GENERIC_ALL = 0x10000000; } /// <summary> /// Action to take on files that exist, and which action to take when files do not exist. /// </summary> static class CREATIONDISPOSITION { public const uint CREATE_NEW = 1; public const uint CREATE_ALWAYS = 2; public const uint OPEN_EXISTING = 3; public const uint OPEN_ALWAYS = 4; public const uint TRUNCATE_EXISTING = 5; } /// <summary> /// File attributes and flags for the file. /// </summary> static class FLAGSANDATTRIBUTES { public const uint FILE_FLAG_WRITE_THROUGH = 0x80000000; public const uint FILE_FLAG_OVERLAPPED = 0x40000000; public const uint FILE_FLAG_NO_BUFFERING = 0x20000000; public const uint FILE_FLAG_RANDOM_ACCESS = 0x10000000; public const uint FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000; public const uint FILE_FLAG_DELETE_ON_CLOSE = 0x04000000; public const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000; public const uint FILE_FLAG_POSIX_SEMANTICS = 0x01000000; public const uint FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000; public const uint FILE_FLAG_OPEN_NO_RECALL = 0x00100000; public const uint FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000; } /// <summary> /// Serves as a standard header for information related to a device event reported through the WM_DEVICECHANGE message. /// </summary> [StructLayout(LayoutKind.Sequential)] public struct DEV_BROADCAST_HDR { public int dbcc_size; public int dbcc_devicetype; public int dbcc_reserved; } /// <summary> /// Contains information about a class of devices /// </summary> [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct DEV_BROADCAST_DEVICEINTERFACE { public int dbcc_size; public int dbcc_devicetype; public int dbcc_reserved; public Guid dbcc_classguid; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 255)] public string dbcc_name; } static class DEVICE_FLAG { /// <summary>Windows message sent when a device is inserted or removed</summary> public const int WM_DEVICECHANGE = 0x0219; /// <summary>WParam for above : A device was inserted</summary> public const int DEVICE_ARRIVAL = 0x8000; /// <summary>WParam for above : A device was removed</summary> public const int DEVICE_REMOVECOMPLETE = 0x8004; /// <summary>Used when registering for device insert/remove messages : specifies the type of device</summary> public const int DEVTYP_DEVICEINTERFACE = 0x05; /// <summary>Used when registering for device insert/remove messages : we're giving the API call a window handle</summary> public const int DEVICE_NOTIFY_WINDOW_HANDLE = 0; } /// <summary> /// Sharing mode of the file or object ///</summary> static class SHAREMODE { public const uint FILE_SHARE_READ = 0x00000001; public const uint FILE_SHARE_WRITE = 0x00000002; public const uint FILE_SHARE_DELETE = 0x00000004; } #endregion /// <summary> /// 傳入ID和arraybuff(只讀) /// </summary> public class report : EventArgs { public readonly byte reportID; //接收發送的數據(第一幀) public readonly byte[] reportBuff; //接收發送的數據(第二幀及以后) public report(byte id, byte[] arrayBuff) { reportID = id; reportBuff = arrayBuff; } } public class Hid { #region 聲明winAPI // **************************以下是調用windows的API的函數********************************** /// <summary> /// The HidD_GetHidGuid routine returns the device interface GUID for HIDClass devices. /// </summary> /// <param name="HidGuid">a caller-allocated GUID buffer that the routine uses to return the device interface GUID for HIDClass devices.</param> [DllImport("hid.dll")] private static extern void HidD_GetHidGuid(ref Guid HidGuid); /// <summary> /// The SetupDiGetClassDevs function returns a handle to a device information set that contains requested device information elements for a local machine. /// </summary> /// <param name="ClassGuid">GUID for a device setup class or a device interface class. </param> /// <param name="Enumerator">A pointer to a NULL-terminated string that supplies the name of a PnP enumerator or a PnP device instance identifier. </param> /// <param name="HwndParent">A handle of the top-level window to be used for a user interface</param> /// <param name="Flags">A variable that specifies control options that filter the device information elements that are added to the device information set. </param> /// <returns>a handle to a device information set </returns> [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr SetupDiGetClassDevs(ref Guid ClassGuid, uint Enumerator, IntPtr HwndParent, DIGCF Flags); /// <summary> /// The SetupDiDestroyDeviceInfoList function deletes a device information set and frees all associated memory. /// </summary> /// <param name="DeviceInfoSet">A handle to the device information set to delete.</param> /// <returns>returns TRUE if it is successful. Otherwise, it returns FALSE </returns> [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern Boolean SetupDiDestroyDeviceInfoList(IntPtr deviceInfoSet); /// <summary> /// The SetupDiEnumDeviceInterfaces function enumerates the device interfaces that are contained in a device information set. /// </summary> /// <param name="deviceInfoSet">A pointer to a device information set that contains the device interfaces for which to return information</param> /// <param name="deviceInfoData">A pointer to an SP_DEVINFO_DATA structure that specifies a device information element in DeviceInfoSet</param> /// <param name="interfaceClassGuid">a GUID that specifies the device interface class for the requested interface</param> /// <param name="memberIndex">A zero-based index into the list of interfaces in the device information set</param> /// <param name="deviceInterfaceData">a caller-allocated buffer that contains a completed SP_DEVICE_INTERFACE_DATA structure that identifies an interface that meets the search parameters</param> /// <returns></returns> [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = false)] private static extern Boolean SetupDiEnumDeviceInterfaces(IntPtr deviceInfoSet, IntPtr deviceInfoData, ref Guid interfaceClassGuid, uint memberIndex, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData); /// <summary> /// The SetupDiGetDeviceInterfaceDetail function returns details about a device interface. /// </summary> /// <param name="deviceInfoSet">A pointer to the device information set that contains the interface for which to retrieve details</param> /// <param name="deviceInterfaceData">A pointer to an SP_DEVICE_INTERFACE_DATA structure that specifies the interface in DeviceInfoSet for which to retrieve details</param> /// <param name="deviceInterfaceDetailData">A pointer to an SP_DEVICE_INTERFACE_DETAIL_DATA structure to receive information about the specified interface</param> /// <param name="deviceInterfaceDetailDataSize">The size of the DeviceInterfaceDetailData buffer</param> /// <param name="requiredSize">A pointer to a variable that receives the required size of the DeviceInterfaceDetailData buffer</param> /// <param name="deviceInfoData">A pointer buffer to receive information about the device that supports the requested interface</param> /// <returns></returns> [DllImport("setupapi.dll", SetLastError = false, CharSet = CharSet.Auto)] private static extern bool SetupDiGetDeviceInterfaceDetail(IntPtr deviceInfoSet, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData, IntPtr deviceInterfaceDetailData, int deviceInterfaceDetailDataSize, ref int requiredSize, SP_DEVINFO_DATA deviceInfoData); /// <summary> /// The HidD_GetAttributes routine returns the attributes of a specified top-level collection. /// </summary> /// <param name="HidDeviceObject">Specifies an open handle to a top-level collection</param> /// <param name="Attributes">a caller-allocated HIDD_ATTRIBUTES structure that returns the attributes of the collection specified by HidDeviceObject</param> /// <returns></returns> [DllImport("hid.dll")] private static extern Boolean HidD_GetAttributes(IntPtr hidDeviceObject, out HIDD_ATTRIBUTES attributes); /// <summary> /// The HidD_GetSerialNumberString routine returns the embedded string of a top-level collection that identifies the serial number of the collection's physical device. /// </summary> /// <param name="HidDeviceObject">Specifies an open handle to a top-level collection</param> /// <param name="Buffer">a caller-allocated buffer that the routine uses to return the requested serial number string</param> /// <param name="BufferLength">Specifies the length, in bytes, of a caller-allocated buffer provided at Buffer</param> /// <returns></returns> [DllImport("hid.dll")] private static extern Boolean HidD_GetSerialNumberString(IntPtr hidDeviceObject, IntPtr buffer, int bufferLength); /// <summary> /// The HidD_GetPreparsedData routine returns a top-level collection's preparsed data. /// </summary> /// <param name="hidDeviceObject">Specifies an open handle to a top-level collection. </param> /// <param name="PreparsedData">Pointer to the address of a routine-allocated buffer that contains a collection's preparsed data in a _HIDP_PREPARSED_DATA structure.</param> /// <returns>HidD_GetPreparsedData returns TRUE if it succeeds; otherwise, it returns FALSE.</returns> [DllImport("hid.dll")] private static extern Boolean HidD_GetPreparsedData(IntPtr hidDeviceObject, out IntPtr PreparsedData); [DllImport("hid.dll")] private static extern Boolean HidD_FreePreparsedData(IntPtr PreparsedData); [DllImport("hid.dll")] private static extern uint HidP_GetCaps(IntPtr PreparsedData, out HIDP_CAPS Capabilities); /// <summary> /// This function creates, opens, or truncates a file, COM port, device, service, or console. /// </summary> /// <param name="fileName">a null-terminated string that specifies the name of the object</param> /// <param name="desiredAccess">Type of access to the object</param> /// <param name="shareMode">Share mode for object</param> /// <param name="securityAttributes">Ignored; set to NULL</param> /// <param name="creationDisposition">Action to take on files that exist, and which action to take when files do not exist</param> /// <param name="flagsAndAttributes">File attributes and flags for the file</param> /// <param name="templateFile">Ignored</param> /// <returns>An open handle to the specified file indicates success</returns> [DllImport("kernel32.dll", SetLastError = false)] private static extern IntPtr CreateFile(string fileName, uint desiredAccess, uint shareMode, uint securityAttributes, uint creationDisposition, uint flagsAndAttributes, uint templateFile); /// <summary> /// This function closes an open object handle. /// </summary> /// <param name="hObject">Handle to an open object</param> /// <returns></returns> [DllImport("kernel32.dll")] private static extern int CloseHandle(IntPtr hObject); /// <summary> /// This function reads data from a file, starting at the position indicated by the file pointer. /// </summary> /// <param name="file">Handle to the file to be read</param> /// <param name="buffer">Pointer to the buffer that receives the data read from the file </param> /// <param name="numberOfBytesToRead">Number of bytes to be read from the file</param> /// <param name="numberOfBytesRead">Pointer to the number of bytes read</param> /// <param name="lpOverlapped">Unsupported; set to NULL</param> /// <returns></returns> [DllImport("Kernel32.dll", SetLastError = false)] private static extern bool ReadFile(IntPtr file, byte[] buffer, uint numberOfBytesToRead, out uint numberOfBytesRead, IntPtr lpOverlapped); /// <summary> /// This function writes data to a file /// </summary> /// <param name="file">Handle to the file to be written to</param> /// <param name="buffer">Pointer to the buffer containing the data to write to the file</param> /// <param name="numberOfBytesToWrite">Number of bytes to write to the file</param> /// <param name="numberOfBytesWritten">Pointer to the number of bytes written by this function call</param> /// <param name="lpOverlapped">Unsupported; set to NULL</param> /// <returns></returns> [DllImport("Kernel32.dll", SetLastError = false)] private static extern bool WriteFile(IntPtr file, byte[] buffer, uint numberOfBytesToWrite, out uint numberOfBytesWritten, IntPtr lpOverlapped); /// <summary> /// Registers the device or type of device for which a window will receive notifications /// </summary> /// <param name="recipient">A handle to the window or service that will receive device events for the devices specified in the NotificationFilter parameter</param> /// <param name="notificationFilter">A pointer to a block of data that specifies the type of device for which notifications should be sent</param> /// <param name="flags">A Flags that specify the handle type</param> /// <returns>If the function succeeds, the return value is a device notification handle</returns> [DllImport("User32.dll", SetLastError = false)] private static extern IntPtr RegisterDeviceNotification(IntPtr recipient, IntPtr notificationFilter, int flags); /// <summary> /// Closes the specified device notification handle. /// </summary> /// <param name="handle">Device notification handle returned by the RegisterDeviceNotification function</param> /// <returns></returns> [DllImport("user32.dll", SetLastError = false)] private static extern bool UnregisterDeviceNotification(IntPtr handle); #endregion #region 定義字段/屬性 public UInt16 VID = 0x0483; public UInt16 PID = 0x5750; private IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); private const int MAX_USB_DEVICES = 64; private bool deviceOpened = false; private FileStream hidDevice = null; //創建一個usb對象 //public IntPtr[] devices = new IntPtr[10]; //存儲已打開可讀寫的設備 List<string> deviceList = new List<string>(); //存儲所有設備 IntPtr device; //打開文件的設備 private bool IsRead = true; //允許讀取標志 int outputReportLength;//輸出報告長度,包刮一個字節的報告ID public int OutputReportLength { get { return outputReportLength; } } //數據長度 int inputReportLength;//輸入報告長度,包刮一個字節的報告ID public int InputReportLength { get { return inputReportLength; } } private Guid device_class; #endregion /// <summary> /// 構造函數 /// </summary> public Hid() { device_class = HIDGuid; } #region 封裝函數 /// <summary> /// 遍歷所有設備,返回所有可用設備的VID/PID到字符串,以“\r\n”分割 /// </summary> /// <param name="deviceCount">設備數量</param> /// <returns></returns> public String ListHidDevice(ref int deviceCount) { //獲取連接的HID列表 string str = ""; deviceCount = 0; deviceList.Clear(); GetHidDeviceList(ref deviceList);//調用函數獲取所有連接設備並返回給實參列表deviceList //遍歷所有設備 for (int i = 0; i < deviceList.Count; i++) { //打開設備文件(可讀寫)並返回句柄到device變量 device = CreateFile(deviceList[i], DESIREDACCESS.GENERIC_READ | DESIREDACCESS.GENERIC_WRITE, 0, 0, CREATIONDISPOSITION.OPEN_EXISTING, FLAGSANDATTRIBUTES.FILE_FLAG_OVERLAPPED, 0); if (device != INVALID_HANDLE_VALUE) { //devices[j] = device; //j++; HIDD_ATTRIBUTES attributes; HidD_GetAttributes(device, out attributes); str += "VID:" + attributes.VendorID.ToString("X4") + " PID:" + attributes.ProductID.ToString("X4") + "\r\n"; deviceCount++; CloseHandle(device); //釋放打開的句柄 } } if (deviceCount == 0) { return "Unable to find equipment!"; } return str; } /// <summary> /// 打開指定信息的設備 /// </summary> /// <param name="vID">設備的vID</param> /// <param name="pID">設備的pID</param> public HID_RETURN OpenDevice(UInt16 vID, UInt16 pID) { if (deviceOpened == false) { for (int i = 0; i < deviceList.Count; i++) { //打開設備文件(可讀寫)並返回句柄到device變量 device = CreateFile(deviceList[i], DESIREDACCESS.GENERIC_READ | DESIREDACCESS.GENERIC_WRITE, 0, 0, CREATIONDISPOSITION.OPEN_EXISTING, FLAGSANDATTRIBUTES.FILE_FLAG_OVERLAPPED, 0); HIDD_ATTRIBUTES attributes; HidD_GetAttributes(device, out attributes); if (attributes.VendorID == vID && attributes.ProductID == pID) // && deviceStr == serial { IntPtr preparseData; //測試長度 HIDP_CAPS caps; //結構體 HidD_GetPreparsedData(device, out preparseData); //獲取 HidP_GetCaps(preparseData, out caps); //獲取的結果傳給結構體 HidD_FreePreparsedData(preparseData); //釋放 outputReportLength = caps.OutputReportByteLength; //賦值輸出長度 inputReportLength = caps.InputReportByteLength; //賦值輸入長度 hidDevice = new FileStream(new SafeFileHandle(device, false), FileAccess.ReadWrite, inputReportLength, true); //實例化sub對象,對象為字段,所以全局調用同一個,這里已經定了讀數據長度 deviceOpened = true; IsRead = true; //允許讀取 BeginAsyncRead(); //打開成功馬上自動讀取 //返回打開成功 return HID_RETURN.SUCCESS; } else { CloseHandle(device); //釋放打開的句柄 } } //返回打開失敗 return HID_RETURN.DEVICE_NOT_FIND; } //返回已打開 else return HID_RETURN.DEVICE_OPENED; } /// <summary> /// 停止讀取 /// </summary> public void StopRead() { IsRead = false; //禁止讀取 } /// <summary> /// 開始讀取 /// </summary> public void StatiRead() { IsRead = true; //允許讀取 } /// <summary> /// 關閉打開的設備 /// </summary> public void CloseDevice() { IsRead = false; //禁止讀取 System.Threading.Thread.Sleep(100); //線程等待,等待數據讀完在關閉設備 if (deviceOpened == true) { hidDevice.Close(); //關閉usb deviceOpened = false; CloseHandle(device); //釋放打開的句柄 } } /// <summary> /// 開始一次異步讀 /// </summary> private void BeginAsyncRead() { byte[] inputBuff = new byte[InputReportLength]; if (IsRead) { try { //異步讀數據,緩存區、開始讀的幀、最大讀取長度、讀完異步調用的方法、將該特定的異步讀取請求與其他請求區別開來 hidDevice.BeginRead(inputBuff, 0, InputReportLength, new AsyncCallback(ReadCompleted), inputBuff); } catch (Exception) { throw; } } } List<byte[]> buffer = new List<byte[]>(); /// <summary> /// 異步讀取結束,發出有數據到達事件 /// </summary> /// <param name="iResult">這里是輸入報告的數組</param> private void ReadCompleted(IAsyncResult iResult) { byte[] readBuff = (byte[])(iResult.AsyncState); //裝載數據 buffer.Add(readBuff); if (IsRead) { try { try { hidDevice.EndRead(iResult);//讀取結束,如果讀取錯誤就會產生一個異常 byte[] reportData = new byte[readBuff.Length - 1]; //再次從第二幀開始裝載 for (int i = 1; i < readBuff.Length; i++) reportData[i - 1] = readBuff[i]; report e = new report(readBuff[0], reportData); OnDataReceived(e); //觸發事件 } finally { BeginAsyncRead();//啟動下一次讀操作 } } catch (IOException)//讀寫錯誤,設備已經被移除 { EventArgs ex = new EventArgs(); OnDeviceRemoved(ex);//發出設備移除消息 //CloseDevice(); } } } /// <summary> /// 事件:數據到達,處理此事件以接收輸入數據 /// </summary> public event EventHandler<report> DataReceived; /// <summary> /// 調用數據到達事件方法 /// </summary> /// <param name="e"></param> protected virtual void OnDataReceived(report e) { DataReceived?.Invoke(this, e); } /// <summary> /// 事件:設置連接 /// </summary> public event EventHandler DeviceArrived; protected virtual void OnDeviceArrived(EventArgs e) { DeviceArrived?.Invoke(this, e); } /// <summary> /// 事件:設備斷開 /// </summary> public event EventHandler DeviceRemoved; protected virtual void OnDeviceRemoved(EventArgs e) { DeviceRemoved?.Invoke(this, e); } /// <summary> /// 發送數據函數 /// </summary> /// <param name="buffer"></param> /// <returns></returns> public HID_RETURN Write(report r) { if (deviceOpened) { try { byte[] buffer = new byte[outputReportLength]; //存儲數據位 buffer[0] = r.reportID; //數據位第一幀 int maxBufferLength = 0; //循環賦值 //判斷數據長度-1 與 賦值列表長度-1的大小,循環賦值以最小的長度為准 if (r.reportBuff.Length < outputReportLength - 1) maxBufferLength = r.reportBuff.Length; else maxBufferLength = outputReportLength; //遍歷賦值數據位第二幀及以后的數據 for (int i = 1; i < maxBufferLength; i++) buffer[i] = r.reportBuff[i - 1]; hidDevice.Write(buffer, 0, OutputReportLength); //緩存區、開始讀的幀數、最大數據長度 IsRead = true;//允許讀取 BeginAsyncRead(); //繼續讀取 } catch { EventArgs ex = new EventArgs(); OnDeviceRemoved(ex);//發出設備移除消息 CloseDevice(); } } return HID_RETURN.WRITE_FAILD; } /// <summary> /// 獲取所有連接的hid的設備路徑 /// </summary> /// <returns>包含每個設備路徑的字符串數組</returns> public static void GetHidDeviceList(ref List<string> deviceList) { Guid hUSB = Guid.Empty; uint index = 0; deviceList.Clear(); // 取得hid設備全局id HidD_GetHidGuid(ref hUSB); //取得一個包含所有HID接口信息集合的句柄 IntPtr hidInfoSet = SetupDiGetClassDevs(ref hUSB, 0, IntPtr.Zero, DIGCF.DIGCF_PRESENT | DIGCF.DIGCF_DEVICEINTERFACE); if (hidInfoSet != IntPtr.Zero) { SP_DEVICE_INTERFACE_DATA interfaceInfo = new SP_DEVICE_INTERFACE_DATA(); interfaceInfo.cbSize = Marshal.SizeOf(interfaceInfo); //查詢集合中每一個接口 for (index = 0; index < MAX_USB_DEVICES; index++) { //得到第index個接口信息 if (SetupDiEnumDeviceInterfaces(hidInfoSet, IntPtr.Zero, ref hUSB, index, ref interfaceInfo)) { int buffsize = 0; // 取得接口詳細信息:第一次讀取錯誤,但可以取得信息緩沖區的大小 SetupDiGetDeviceInterfaceDetail(hidInfoSet, ref interfaceInfo, IntPtr.Zero, buffsize, ref buffsize, null); //構建接收緩沖 IntPtr pDetail = Marshal.AllocHGlobal(buffsize); SP_DEVICE_INTERFACE_DETAIL_DATA detail = new SP_DEVICE_INTERFACE_DETAIL_DATA(); detail.cbSize = Marshal.SizeOf(typeof(SP_DEVICE_INTERFACE_DETAIL_DATA)); Marshal.StructureToPtr(detail, pDetail, false); if (SetupDiGetDeviceInterfaceDetail(hidInfoSet, ref interfaceInfo, pDetail, buffsize, ref buffsize, null)) { deviceList.Add(Marshal.PtrToStringAuto((IntPtr)((int)pDetail + 4))); } Marshal.FreeHGlobal(pDetail); } } } SetupDiDestroyDeviceInfoList(hidInfoSet); //return deviceList.ToArray(); } ///// <summary> ///// 檢測設備連接狀態 ///// </summary> ///// <param name="m"></param> //public void ParseMessages(ref Message m) //{ // if (m.Msg == DEVICE_FLAG.WM_DEVICECHANGE) // we got a device change message! A USB device was inserted or removed // { // switch (m.WParam.ToInt32()) // Check the W parameter to see if a device was inserted or removed // { // case DEVICE_FLAG.DEVICE_ARRIVAL: // inserted // Console.WriteLine("ParseMessages 設備連接"); // if (DeviceArrived != null) // { // DeviceArrived(this, new EventArgs()); // } // CheckDevicePresent(VID, PID); // break; // case DEVICE_FLAG.DEVICE_REMOVECOMPLETE: // removed // Console.WriteLine("ParseMessages 設備拔除"); // if (DeviceRemoved != null) // { // DeviceRemoved(this, new EventArgs()); // } // CheckDevicePresent(VID, PID); // break; // } // } //} /// <summary> /// 打開指定設備 /// </summary> /// <param name="parameterVID">設備的VID</param> /// <param name="parameterPID">設備的PID</param> /// <returns>打開成功與否</returns> public bool CheckDevicePresent(ushort parameterVID, ushort parameterPID) { this.VID = parameterVID; this.PID = parameterPID; HID_RETURN hid_ret; try { hid_ret = OpenDevice(VID, PID); if (hid_ret == HID_RETURN.SUCCESS) { return true; } else { return false; } } catch (Exception) { throw; } } /// <summary> /// Helper to get the HID guid. /// </summary> public static Guid HIDGuid { get { Guid gHid = Guid.Empty; HidD_GetHidGuid(ref gHid); return gHid; //return new Guid("A5DCBF10-6530-11D2-901F-00C04FB951ED"); //gHid; } } /// <summary> /// btye數組轉換為字符串 /// </summary> /// <param name="bytes"></param> /// <returns></returns> public static string ByteToHexString(byte[] bytes) { string str = string.Empty; if (bytes != null) { for (int i = 0; i < bytes.Length; i++) { str += bytes[i].ToString("X2"); } } return str; } public enum HID_RETURN { SUCCESS = 0, NO_DEVICE_CONECTED, DEVICE_NOT_FIND, DEVICE_OPENED, WRITE_FAILD, READ_FAILD } #endregion } }
三、坑之總結
我簡單地總結了一下,坑有以下幾個:
1.接收數據長度沒對齊,導致接收不成功;
2.Win10調試成功,Win7調試失敗;
3.32程序運行成功,64位程序運行失敗;
4.異步接收數據順序錯亂。
四、談坑
1.坑之接收不成
實話說,我也忘了為什么接收數據不成功,修改了哪里。因為我是在一年前寫的這個程序,而最近同時需要我封的代碼,且他的程序是64位的,我之前是32位,故不兼容,好人做到底,我就幫他稍微改了一下,兼容進去了。然后寫下這篇隨筆記錄一下情況,方便追溯。
下面是我憑借零星的回憶,一頓亂說,我實在忘了原版(接收不成功)長啥樣了。
接收數據不成功主要有兩個原因,一個是接收數據的長度不對,另一個也是接收長度不對。我這里不是學魯迅大師說話,兩種情況是不一樣的。
第一個接收長度不對是因為發送長度超過了64byte,HID幀數據不能超過64Byte,我也不知到為什么,親測確實如此,如果有興趣可以深究一下。
第二個報文的長度不對,
//異步讀數據,緩存區、開始讀的幀、最大讀取長度、讀完異步調用的方法、將該特定的異步讀取請求與其他請求區別開來 hidDevice.BeginRead(inputBuff, 0, InputReportLength, new AsyncCallback(ReadCompleted), inputBuff);
上面的“InputReportLength”必須和下面的“inputReportLength”相同,其是報文的長度。
outputReportLength = caps.OutputReportByteLength; //賦值輸出長度 inputReportLength = caps.InputReportByteLength; //賦值輸入長度
2.坑之win7發送失敗
這個坑確實很坑爹,之前在win10下用得好好的,后面來了一台win7的工控機,然后就發送不了數據了。
后面把第一幀數據改成“0x00”,又可以了,就那么神奇,哎!
3.坑之x64框架枚舉設備失敗
這個坑,其實也不叫坑,只能說明我的技術不行,被虐得完無體膚!網上查了海量資料,遇到我這個問題的不多,並且大部分回答都是把x64改成x86,顯然這並不是我想要的答案。還有少部分人說是winAPI調用錯了,改成x64要改成x64的API,然后又給出了一些修改特性的方式,只能說,統統不行!我理解是,同樣的聲明方式,win系統會更加我們軟件框架不一樣,自動調用相應winAPI,x86的API在C:\Windows\SysWOW64文件夾里,而x64的API在C:\Windows\System32文件夾里。
x86和x64的數據長度是有差異的,包括指針的長度都是不一樣。
我在x86下,調用“SetupDiGetClassDevs”獲取設備信息集的句柄時返回的地址大概是9位數左右,而在x64中,返回的地址大概是13位數。
IntPtr hidInfoSet = SetupDiGetClassDevs(ref hUSB, 0, IntPtr.Zero, DIGCF.DIGCF_PRESENT | DIGCF.DIGCF_DEVICEINTERFACE);
當枚舉列表中的設備時,返回false!
SetupDiEnumDeviceInterfaces(hidInfoSet, IntPtr.Zero, ref hUSB, index, ref interfaceInfo)
我們會想到一個聲明句柄的類型“IntPtr”,平台特定的整數類型。資源的大小取決於使用的硬件和操作系統,即此類型的實例在32位硬件和操作系統中將是32位,在64位硬件和操作系統中將是64位;但其大小總是足以包含系統的指針(因此也可以包含資源的名稱),關心的小伙伴可以深入了解一下。
問題又縮小了,肯定是哪個類型聲明有問題。細分析了一下,
SetupDiEnumDeviceInterfaces中五個參數中,前面四個都沒有問題,問題肯定出在最后一個參數“interfaceInfo”,而最后一個參數是SP_DEVICE_INTERFACE_DATA類型的結構體,那問題肯定在結構體中某個成員,再查了一下這個結構體,最后一個成員是指針類型“reserved”
public struct SP_DEVICE_INTERFACE_DATA { public int cbSize; public Guid interfaceClassGuid; public int flags; public int reserved; }
於是把“reserved”的類型改成IntPtr類型即可,果然能通過枚舉。后面又用同樣的方式修改了其他結構體“reserved”的類型。
這里還有個小問題,上面結構體的其他成員類型不要修改,我之前把int改成uint,報內存溢出的錯誤。
4.坑之接收數據順序錯亂
如前面所言,我用usb主要是考慮傳輸速度,所以下位機發上來的頻率基本是1ms一次,貌似異步接收還是反應不錯的。
但是解析數據就有點和想的不太一樣了,我把數據采集上來,用動態波形圖顯示在上位機,但是顯示時明顯發現數據是不對的,后面監聽發現數據排序的有問題,比如后一幀數據比前一幀數據解析完成早。
后面利用開了三個線程來解決這個問題,一個線程專門接收數據(異步接收),接到立馬放到list里,然后開一個線程解析數據,再開一個線程顯示。這里必須要做多線程同步,不然還是沒有達到我們的要求。我是用線程的信號來處理他們之間同步的,因為這里涉及的東西比較多,后面有空再單獨整理。
五、學不完的新知識,調不完的BUG
雖然解決了幾個HID的坑,目前來說是沒有多大問題,但是並不能保證以后沒有新的需求新的bug出現,革命尚未完成,同志仍需努力!
本人並非計算機科班出身,對於編程也不是說很熱愛,曾經年少時,覺得黑客很牛逼,很酷,於是就踏上了這一條“不歸路”,學不完的新知識,調不完的新bug,應了那句,從入門到入土。
我們必須要放低態度,以無知,無我的狀態修身,才能知之,自我!
