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;
        }

        /// 
        /// Provides details about a single USB device
        /// 
        [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;
        }

        /// 
        /// 打开指定信息的设备
        /// 
        /// 设备的vID
        /// 设备的pID
        /// 设备的serial,string serial
        /// 
        
        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 deviceList = new List();
                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;
        }

        /// 
        /// 关闭打开的设备
        /// 
        public void CloseDevice()
        {
            if (deviceOpened == true)
            {
                hidDevice.Close();
                deviceOpened = false;
            }
        }

        /// 
        /// 开始一次异步读
        /// 
        private void BeginAsyncRead()
        {
            byte[] inputBuff = new byte[InputReportLength];
            hidDevice.BeginRead(inputBuff, 0, InputReportLength, new AsyncCallback(ReadCompleted), inputBuff);            
        }

        /// 
        /// 异步读取结束,发出有数据到达事件
        /// 
        /// 这里是输入报告的数组
        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();

            }
        }

        /// 
        /// 事件:数据到达,处理此事件以接收输入数据
        /// 
        public event EventHandler DataReceived;
        protected virtual void OnDataReceived(Report e)
        {
            if(DataReceived != null) DataReceived(this, e);
        }

        /// 
        /// 事件:设置连接
        /// 
        public event EventHandler DeviceArrived;
        protected virtual void OnDeviceArrived( EventArgs e )
        {
            if (DeviceArrived != null) DeviceArrived(this, e);
        }

        /// 
        /// 事件:设备断开
        /// 
        public event EventHandler DeviceRemoved;
        protected virtual void OnDeviceRemoved(EventArgs e)
        {
            if (DeviceRemoved != null) DeviceRemoved(this, e);
        }

        /// 
        /// 发送report命令
        /// 
        /// 
        /// 
        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);
        }

        /// 
        /// 发送字节数组命令
        /// 
        /// 
        /// 
        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

            ///  
            ///  closes open handles to a device.
            ///  
            ///  
            ///   the handle for learning about the device and exchanging Feature reports. 
            ///   the handle for reading Input reports from the device. 
            ///   the handle for writing Output reports to the device. 

            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;
                }
            }

            ///  
            ///  Creates an event object for the overlapped structure used with 
            ///  ReadFile. Called before the first call to ReadFile.
            ///  
            ///  
            ///   the overlapped structure 
            ///   the event object 

            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;
                }
            }

            ///  
            ///  reads an Input report from the device using interrupt transfers.
            ///  
            ///  
            ///   the handle for learning about the device and exchanging Feature reports. 
            ///   the handle for reading Input reports from the device. 
            ///   the handle for writing Output reports to the device. 
            ///   tells whether the device is currently attached. 
            ///   contains the requested report. 
            ///   read success 
           
           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
        {
            ///  
            ///  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.
            ///  

            internal abstract void Read(SafeFileHandle readHandle,  ref Boolean myDeviceDetected, ref Byte[] readBuffer, ref Boolean success);
        }


        /// 
        /// 获取所有连接的hid的设备路径
        /// 
        /// 包含每个设备路径的字符串数组
        public static void GetHidDeviceList(ref List 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;
            }
        }

        /// 
        /// Helper to get the HID guid.
        /// 
        public static Guid HIDGuid
        {
            get
            {
                Guid gHid = Guid.Empty;
                HidD_GetHidGuid(ref gHid);
                return gHid;
                //return new Guid("A5DCBF10-6530-11D2-901F-00C04FB951ED"); //gHid;
            }
        }

 

你可能感兴趣的:(C#)