USB HID通讯流程
创建C# USB hid通讯类
下面是应用到WIN32 API:
1. 读取Hid设备全局id
[DllImport("hid.dll")]
private static extern void HidD_GetHidGuid(ref Guid HidGuid);
2. 取得一个包含所有HID接口信息集合的句柄
[DllImport("setupapi.dll", SetLastError = true)]
private static extern IntPtr SetupDiGetClassDevs(ref Guid ClassGuid, uint Enumerator, IntPtr HwndParent, DIGCF Flags);
3.遍历信息集合,得到每个设备的接口信息
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern Boolean SetupDiEnumDeviceInterfaces(IntPtr deviceInfoSet, IntPtr deviceInfoData, ref Guid interfaceClassGuid, UInt32 memberIndex, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData);
4. 取得接口详细信息:第一次读取错误,但可以取得信息缓冲区的大小
SetupDiGetDeviceInterfaceDetail(hidInfoSet, ref interfaceInfo, IntPtr.Zero, buffsize, ref buffsize, null);
5. 取得接口详细信息: 第二次可以读取内容(内容包括VID,PID)
SetupDiGetDeviceInterfaceDetail(hidInfoSet, ref interfaceInfo, IntPtr.Zero, buffsize, ref buffsize, null);
6. 利用上一步读取的设备路径信息,使用CreateFile打开设备
[DllImport("kernel32.dll", SetLastError = true)]
protected static extern SafeFileHandle CreateFile(string strName, uint nAccess, uint nShareMode, uint lpSecurity, uint nCreationFlags, uint nAttributes, uint lpTemplate);
7. 根据文件句柄,读取设备属性信息
[DllImport("hid.dll")]
private static extern Boolean HidD_GetAttributes(SafeFileHandle hidDeviceObject, out HIDD_ATTRIBUTES attributes);
8. 根据属性信息, 判断VID和PID和下位机设备相同,得到此设备
根据得到的匹配设备, 得到准备数据
[DllImport("hid.dll")]
private static extern Boolean HidD_GetPreparsedData(SafeFileHandle 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);
9.创建文件流
hidDevice = new FileStream(device, FileAccess.ReadWrite, inputReportLength, true);
10. 根据文件流读写数据
从流中读取字节块并将该数据写入给定缓冲区中。
public override int Read(byte[] array, int offset, int count);
使用从缓冲区读取的数据将字节块写入该流。
public override void Write(byte[] array, int offset, int count);
注意内容:
发送内容的时候需要发送outputReportLength长度的内容,其中第一个字节是reportid=0x00,发送内容从第二个字节开始,这里的获取到下位机设置的outputReportLength为65
接收下位机内容时,下位机需要发inputReportLength长度的内容, 这里的长度为64
核心源码:
public class Hid : object { protected const Int32 WAIT_TIMEOUT = 0X102; protected const Int32 WAIT_OBJECT_0 = 0; protected static string strDevicePath = ""; 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; int outputReportLength;//输出报告长度,包刮一个字节的报告ID public int OutputReportLength { get { return outputReportLength; } } int inputReportLength;//输入报告长度,包刮一个字节的报告ID public int InputReportLength { get { return inputReportLength; } } private Guid device_class; private IntPtr usb_event_handle; private IntPtr handle; private SafeFileHandle device; private frmMain _main; private Thread _readThread; private int _openDeviceFlag = 0; public int OpenDeviceFlag { get { return _openDeviceFlag; } set { _openDeviceFlag = value; } } public Hid(frmMain main) { device_class = HIDGuid; _main = main; } /// <summary> /// Provides details about a single USB device /// </summary> [StructLayout(LayoutKind.Sequential, Pack = 1)] protected struct DeviceInterfaceData { public int Size; public Guid InterfaceClassGuid; public int Flags; public IntPtr Reserved; // should correspond to ULONG_PTR but was an int } private static string GetDevicePath(IntPtr hInfoSet, ref DeviceInterfaceData oInterface) { DeviceInterfaceDetailData oDetail = new DeviceInterfaceDetailData(); // Size workaround if (IntPtr.Size == 8) oDetail.Size = 8; else oDetail.Size = 5; Console.WriteLine("Size of struct: {0}", Marshal.SizeOf(oDetail)); // 4 + 256 = 260 uint nRequiredSize = 0; // Error 0 if (!SetupDiGetDeviceInterfaceDetail(hInfoSet, ref oInterface, IntPtr.Zero, 0, ref nRequiredSize, IntPtr.Zero)) // Error 122 - ERROR_INSUFFICIENT_BUFFER (not a problem, just used to set nRequiredSize) if (SetupDiGetDeviceInterfaceDetail(hInfoSet, ref oInterface, ref oDetail, nRequiredSize, ref nRequiredSize, IntPtr.Zero)) return oDetail.DevicePath; // Error 1784 - ERROR_INVALID_USER_BUFFER (unless size=5 on 32bit, size=8 on 64bit) return null; } /// <summary> /// 打开指定信息的设备 /// </summary> /// <param name="vID">设备的vID</param> /// <param name="pID">设备的pID</param> /// <param name="serial">设备的serial</param>,string serial /// <returns></returns> public HID_RETURN OpenDevice(UInt16 vID,UInt16 pID) { // IntPtr hInfoSet = SetupDiGetClassDevs(ref gHid, null, IntPtr.Zero, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); if (deviceOpened == false) { //获取连接的HID列表 List<string> deviceList = new List<string>(); GetHidDeviceList(ref deviceList); if (deviceList.Count == 0) return HID_RETURN.NO_DEVICE_CONECTED; for (int i = 0; i < deviceList.Count; i++) { device = CreateFile(deviceList[i], DESIREDACCESS.GENERIC_READ | DESIREDACCESS.GENERIC_WRITE, 0, 0, CREATIONDISPOSITION.OPEN_EXISTING, FLAGSANDATTRIBUTES.FILE_FLAG_OVERLAPPED, 0); if (!device.IsInvalid) { // strDevicePath = GetDevicePath(hInfoSet, ref oInterface); HIDD_ATTRIBUTES attributes; //IntPtr serialBuff = Marshal.AllocHGlobal(512); HidD_GetAttributes(device, out attributes); //HidD_GetSerialNumberString(device, serialBuff, 512); //string deviceStr = Marshal.PtrToStringAuto(serialBuff); //Marshal.FreeHGlobal(serialBuff); 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 (device, FileAccess.ReadWrite, inputReportLength, true); deviceOpened = true; //BeginAsyncRead(); Guid gHid = HIDGuid; IntPtr hInfoSet = SetupDiGetClassDevs(ref gHid, null, IntPtr.Zero, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); DeviceInterfaceData oInterface = new DeviceInterfaceData(); strDevicePath = GetDevicePath(hInfoSet, ref oInterface); return HID_RETURN.SUCCESS; } } } return HID_RETURN.DEVICE_NOT_FIND; } else return HID_RETURN.DEVICE_OPENED; } /// <summary> /// 关闭打开的设备 /// </summary> public void CloseDevice() { if (deviceOpened == true) { hidDevice.Close(); deviceOpened = false; } } /// <summary> /// 开始一次异步读 /// </summary> private void BeginAsyncRead() { byte[] inputBuff = new byte[InputReportLength]; hidDevice.BeginRead(inputBuff, 0, InputReportLength, new AsyncCallback(ReadCompleted), inputBuff); } /// <summary> /// 异步读取结束,发出有数据到达事件 /// </summary> /// <param name="iResult">这里是输入报告的数组</param> private void ReadCompleted(IAsyncResult iResult) { byte[] readBuff = (byte[])(iResult.AsyncState); 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); //发出数据到达消息 Define.cmdRecv.ProcData(reportData,reportData.Length); BeginAsyncRead();//启动下一次读操作 } catch (IOException e)//读写错误,设备已经被移除 { EventArgs ex = new EventArgs(); OnDeviceRemoved(ex);//发出设备移除消息 CloseDevice(); } } /// <summary> /// 事件:数据到达,处理此事件以接收输入数据 /// </summary> public event EventHandler<Report> DataReceived; protected virtual void OnDataReceived(Report e) { if(DataReceived != null) DataReceived(this, e); } /// <summary> /// 事件:设置连接 /// </summary> public event EventHandler DeviceArrived; protected virtual void OnDeviceArrived( EventArgs e ) { if (DeviceArrived != null) DeviceArrived(this, e); } /// <summary> /// 事件:设备断开 /// </summary> public event EventHandler DeviceRemoved; protected virtual void OnDeviceRemoved(EventArgs e) { if (DeviceRemoved != null) DeviceRemoved(this, e); } /// <summary> /// 发送report命令 /// </summary> /// <param name="buffer"></param> /// <returns></returns> public string Write(Report r) { byte[] buffer = null; if (deviceOpened) { try { buffer = new byte[outputReportLength]; buffer[0] = r.reportID; int maxBufferLength = 0; if (r.reportBuff.Length < outputReportLength - 1) maxBufferLength = r.reportBuff.Length; else maxBufferLength = outputReportLength - 1; for (int i = 1; i < maxBufferLength; i++) buffer[i] = r.reportBuff[i - 1]; hidDevice.Write(buffer, 0, OutputReportLength); //buffer= DataRead(); //Hid.ByteToHexString(buffer); } catch { EventArgs ex = new EventArgs(); OnDeviceRemoved(ex);//发出设备移除消息 CloseDevice(); throw; } } return ByteToHexString(buffer); } /// <summary> /// 发送字节数组命令 /// </summary> /// <param name="buffer"></param> /// <returns></returns> public bool Write(Byte[] buf) { if (deviceOpened) { try { hidDevice.Write(buf, 0, OutputReportLength); if (_readThread == null) { _readThread = new Thread(new ThreadStart(DataRead)); _readThread.IsBackground = true; _readThread.Start(); } //buffer = DataRead(); //Hid.ByteToHexString(buffer); return true; } catch { EventArgs ex = new EventArgs(); OnDeviceRemoved(ex);//发出设备移除消息 CloseDevice(); return false; } } else { return false; } } private void DataRead() { try { InputReportViaInterruptTransfer report = new InputReportViaInterruptTransfer(); bool foundMyDevice = false; bool result = false; byte[] readBuffer = new Byte[OutputReportLength]; report.Read(device, ref foundMyDevice, ref readBuffer, ref result); if (result == false) { _main.ProgressBarEnd(); MessageBox.Show("更新超时!"); } _readThread = null; } catch(Exception ex) { _readThread = null; } } internal class InputReportViaInterruptTransfer : ReportIn { internal Boolean readyForOverlappedTransfer; // initialize to false /// <summary> /// closes open handles to a device. /// </summary> /// /// <param name="hidHandle"> the handle for learning about the device and exchanging Feature reports. </param> /// <param name="readHandle"> the handle for reading Input reports from the device. </param> /// <param name="writeHandle"> the handle for writing Output reports to the device. </param> internal void CancelTransfer(SafeFileHandle hidHandle, SafeFileHandle readHandle, SafeFileHandle writeHandle, IntPtr eventObject) { try { // *** // API function: CancelIo // Purpose: Cancels a call to ReadFile // Accepts: the device handle. // Returns: True on success, False on failure. // *** CancelIo(readHandle); //Console.WriteLine("************ReadFile error*************"); //String functionName = "CancelIo"; //Console.WriteLine(MyDebugging.ResultOfAPICall(functionName)); //Console.WriteLine(""); // The failure may have been because the device was removed, // so close any open handles and // set myDeviceDetected=False to cause the application to // look for the device on the next attempt. if ((!(hidHandle.IsInvalid))) { hidHandle.Close(); } if ((!(readHandle.IsInvalid))) { readHandle.Close(); } if ((!(writeHandle.IsInvalid))) { writeHandle.Close(); } } catch (Exception ex) { //DisplayException(MODULE_NAME, ex); throw; } } /// <summary> /// Creates an event object for the overlapped structure used with /// ReadFile. Called before the first call to ReadFile. /// </summary> /// /// <param name="hidOverlapped"> the overlapped structure </param> /// <param name="eventObject"> the event object </param> internal void PrepareForOverlappedTransfer(ref NativeOverlapped hidOverlapped, ref IntPtr eventObject) { try { // *** // API function: CreateEvent // Purpose: Creates an event object for the overlapped structure used with ReadFile. // Accepts: // A security attributes structure or IntPtr.Zero. // Manual Reset = False (The system automatically resets the state to nonsignaled // after a waiting thread has been released.) // Initial state = False (not signaled) // An event object name (optional) // Returns: a handle to the event object // *** eventObject = CreateEvent(IntPtr.Zero, false, false, ""); // Console.WriteLineLine(MyDebugging.ResultOfAPICall("CreateEvent")) // Set the members of the overlapped structure. hidOverlapped.OffsetLow = 0; hidOverlapped.OffsetHigh = 0; hidOverlapped.EventHandle = eventObject; readyForOverlappedTransfer = true; } catch (Exception ex) { //DisplayException(MODULE_NAME, ex); throw; } } /// <summary> /// reads an Input report from the device using interrupt transfers. /// </summary> /// /// <param name="hidHandle"> the handle for learning about the device and exchanging Feature reports. </param> /// <param name="readHandle"> the handle for reading Input reports from the device. </param> /// <param name="writeHandle"> the handle for writing Output reports to the device. </param> /// <param name="myDeviceDetected"> tells whether the device is currently attached. </param> /// <param name="inputReportBuffer"> contains the requested report. </param> /// <param name="success"> read success </param> internal override void Read(SafeFileHandle readHandle, ref Boolean myDeviceDetected, ref Byte[] inputReportBuffer, ref Boolean success) { IntPtr eventObject = CreateEvent(IntPtr.Zero, false, false, ""); //IntPtr.Zero; NativeOverlapped HidOverlapped = new NativeOverlapped(); Int32 numberOfBytesRead = 0; Int32 result = 0; try { // If it's the first attempt to read, set up the overlapped structure for ReadFile. if (readyForOverlappedTransfer == false) { PrepareForOverlappedTransfer(ref HidOverlapped, ref eventObject); } // *** // API function: ReadFile // Purpose: Attempts to read an Input report from the device. // Accepts: // A device handle returned by CreateFile // (for overlapped I/O, CreateFile must have been called with FILE_FLAG_OVERLAPPED), // A pointer to a buffer for storing the report. // The Input report length in bytes returned by HidP_GetCaps, // A pointer to a variable that will hold the number of bytes read. // An overlapped structure whose hEvent member is set to an event object. // Returns: the report in ReadBuffer. // The overlapped call returns immediately, even if the data hasn't been received yet. // To read multiple reports with one ReadFile, increase the size of ReadBuffer // and use NumberOfBytesRead to determine how many reports were returned. // Use a larger buffer if the application can't keep up with reading each report // individually. // *** success = ReadFile(readHandle, inputReportBuffer, inputReportBuffer.Length, ref numberOfBytesRead, ref HidOverlapped); if (!success) { Console.WriteLine("waiting for ReadFile"); // API function: WaitForSingleObject // Purpose: waits for at least one report or a timeout. // Used with overlapped ReadFile. // Accepts: // An event object created with CreateEvent // A timeout value in milliseconds. // Returns: A result code. result = WaitForSingleObject(eventObject, 3000); //eventObject //result = GetOverlappedResult(readHandle.DangerousGetHandle(), ref HidOverlapped, ref numberOfBytesRead, false); //if (result != 0) // success = true; // Find out if ReadFile completed or timeout. switch (result) { case (System.Int32)WAIT_OBJECT_0: // ReadFile has completed success = true; Console.WriteLine("ReadFile completed successfully."); Define.cmdRecv.ProcData(inputReportBuffer, inputReportBuffer.Length); break; case WAIT_TIMEOUT: // Cancel the operation on timeout // CancelTransfer(hidHandle, readHandle, writeHandle, eventObject); Console.WriteLine("Readfile timeout"); success = false; myDeviceDetected = false; break; default: // Cancel the operation on other error. //CancelTransfer(hidHandle, readHandle, writeHandle, eventObject); Console.WriteLine("Readfile undefined error"); success = false; myDeviceDetected = false; break; } } } catch (Exception ex) { ////DisplayException(MODULE_NAME, ex); throw; } } } internal abstract class ReportIn { /// <summary> /// Each class that handles reading reports defines a Read method for reading /// a type of report. Read is declared as a Sub rather /// than as a Function because asynchronous reads use a callback method /// that can access parameters passed by ByRef but not Function return values. /// </summary> internal abstract void Read(SafeFileHandle readHandle, ref Boolean myDeviceDetected, ref Byte[] readBuffer, ref Boolean success); } /// <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(); } 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(); break; case DEVICE_FLAG.DEVICE_REMOVECOMPLETE: // removed Console.WriteLine("ParseMessages 设备拔除"); if (DeviceRemoved != null) { DeviceRemoved(this, new EventArgs()); } CheckDevicePresent(); break; } } } public void RegisterHandle( IntPtr Handle ) { usb_event_handle = RegisterForUsbEvents(Handle, device_class); this.handle = Handle; //Check if the device is already present. CheckDevicePresent(); } public bool CheckDevicePresent() { HID_RETURN hid_ret; string sSerial=""; try { hid_ret = OpenDevice(VID, PID); if (hid_ret == HID_RETURN.SUCCESS) { Console.WriteLine("打开设备成功"); _openDeviceFlag = (int)HID_RETURN.SUCCESS; return true; } else { _openDeviceFlag = (int)hid_ret; return false; } } catch (System.Exception ex) { return false; } } /// <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; } }