c#收发数据给USB HID

C#和USB HID进行通讯,实现发送、接收数据主要是通过两个函数实现的FileStream.Write(...)(发送数据)FileStream.Read(...)(接收数据)Write和Read是同步,BeginWrite和BeginRead是异步。

或者是c++的库函数WriteFile()、ReadFile(),在库kernel32.dll中。(但是我的项目发现WriteFile()、ReadFile()不能通讯)

1、准备工作

c#中一定要先申明c++库操作HID设备的函数:

[DllImport("hid.dll")]
        private static extern void HidD_GetHidGuid(ref Guid HidGuid);


[DllImport("setupapi.dll", SetLastError = true)]
        private static extern IntPtr SetupDiGetClassDevs(ref Guid ClassGuid, uint Enumerator, IntPtr HwndParent, USBHIDEnum.DIGCF Flags);


[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);
        

        public struct SP_DEVICE_INTERFACE_DATA
        {
            public int cbSize;
            public Guid interfaceClassGuid;
            public int flags;
            public int reserved;
        }


[DllImport("setupapi.dll", SetLastError = true, 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);
        
        [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;
        }


 [StructLayout(LayoutKind.Sequential, Pack = 2)]
        internal struct SP_DEVICE_INTERFACE_DETAIL_DATA
        {
            internal int cbSize;
            internal short devicePath;
        }

       

[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern Boolean SetupDiDestroyDeviceInfoList(IntPtr deviceInfoSet);


[DllImport("kernel32.dll", SetLastError = true)]
        static extern IntPtr CreateFile(
       string FileName,                // 文件名
       uint DesiredAccess,             // 访问模式
       uint ShareMode,                 // 共享模式
       uint SecurityAttributes,        // 安全属性
       uint CreationDisposition,       // 如何创建
       uint FlagsAndAttributes,        // 文件属性
       int hTemplateFile               // 模板文件的句柄
       );


        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern Boolean WriteFile(
        IntPtr hFile,
        byte[] lpBuffer,
        uint nNumberOfBytesToWrite,
        ref uint nNumberOfBytesWrite,
        IntPtr lpOverlapped
        );


        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern Boolean ReadFile(
       IntPtr hFile,
       byte[] lpBuffer,
       uint nNumberOfBytesToRead,
       ref uint nNumberOfBytesRead,
       IntPtr lpOverlapped
       );


[DllImport("hid.dll")]
        private static extern Boolean HidD_GetAttributes(IntPtr hidDeviceObject, out HIDD_ATTRIBUTES attributes);

       

[DllImport("hid.dll")]
        private static extern Boolean HidD_GetPreparsedData(IntPtr hidDeviceObject, out IntPtr PreparsedData);

        [DllImport("hid.dll")]
        private static extern uint HidP_GetCaps(IntPtr PreparsedData, out HIDP_CAPS Capabilities);

        [DllImport("hid.dll")]
        private static extern Boolean HidD_FreePreparsedData(IntPtr PreparsedData);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern int CloseHandle(int hObject);


以上这些函数申明网上有别人封装好的类,可以自己找一找。我用的把这些函数套了一层,类似下面这样,(在同一个类中)

internal void GetDeviceGuid(ref Guid HIDGuid)
        {
            HidD_GetHidGuid(ref HIDGuid);
        }

internal IntPtr GetClassDevOfHandle(Guid HIDGuid)
        {
            return SetupDiGetClassDevs(ref HIDGuid,0,IntPtr.Zero,USBHIDEnum.DIGCF.DIGCF_PRESENT|USBHIDEnum.DIGCF.DIGCF_DEVICEINTERFACE);
        }

注意,c++有有些类型c#没有,要自己转换一下。


2、获取HID设备的名称

List deviceList = new List();//保存HID设备的名称。开始是列出所有的HID设备,后来要求只连接固定的VID和PID

private void GetDeviceList(ref List deviceList, string vid, string pid)
        {
            Guid HIDGuid = Guid.Empty;
            windowsApi.GetDeviceGuid(ref HIDGuid);//获取HID的全局GUID
            IntPtr HIDInfoSet = windowsApi.GetClassDevOfHandle(HIDGuid);//获取包含所有HID接口信息集合的句柄

//这里的windowsApi是我声明hid操作和封装这些函数的类

            if (HIDInfoSet != IntPtr.Zero)
            {
                SP_DEVICE_INTERFACE_DATA interfaceInfo = new SP_DEVICE_INTERFACE_DATA();
                interfaceInfo.cbSize = Marshal.SizeOf(interfaceInfo);

                //检测集合的每个接口
               
for (uint index = 0; index < MAX_USB_DEVICES; index++)
                {
                    //获取接口信息
                   
if (!windowsApi.GetEnumDeviceInterfaces(HIDInfoSet, ref HIDGuid, index, ref interfaceInfo))
                        continue;

                    int buffsize = 0;
                    //获取接口详细信息;第一次读取错误,但可取得信息缓冲区的大小
                   
windowsApi.GetDeviceInterfaceDetail(HIDInfoSet, ref interfaceInfo, IntPtr.Zero, ref buffsize);

                    //接受缓冲
                   
IntPtr pDetail = Marshal.AllocHGlobal(buffsize);
                    SP_DEVICE_INTERFACE_DETAIL_DATA detail = new WindowsAPI.SP_DEVICE_INTERFACE_DETAIL_DATA();
                    detail.cbSize = Marshal.SizeOf(typeof(USBHIDControl.WindowsAPI.SP_DEVICE_INTERFACE_DETAIL_DATA));
                    Marshal.StructureToPtr(detail, pDetail, false);
                    if (windowsApi.GetDeviceInterfaceDetail(HIDInfoSet, ref interfaceInfo, pDetail, ref buffsize))//第二次读取接口详细信息
                   
{
                        string deviceVIDPID = "vid_" + vid + "&pid_" + pid;
                        string str = Marshal.PtrToStringAuto((IntPtr)((int)pDetail + 4));//如果要获取所有的设备(包括HID以外的设备),去掉下面的if语句,直接deviceList.Add(str);
                        if (str.IndexOf(deviceVIDPID) >= 0)
                        {
                            deviceList.Add(str);//获取带有固定vid或pid的设备字符串句柄
                        }
                    }
                    Marshal.FreeHGlobal(pDetail);
                }
            }

            //删除设备信息并释放内存
           
windowsApi.DestroyDeviceInfoList(HIDInfoSet);
        }


3、打开HID设备

private int outputReportLength;
        private int inputReportLength;

        private FileStream hidDevice;
        private IntPtr device;
        private const int MAX_USB_DEVICES = 64;

public bool OpenUSBHid(string deviceStr)
        {
            //创建,打开设备文件
           
device = windowsApi.CreateDeviceFile(deviceStr);
            if (device == new IntPtr(-1))
                return false;

            HIDD_ATTRIBUTES attributes;
            windowsApi.GETDeviceAttribute(device, out attributes);

            //找到相对应的HID设备信息
           
IntPtr preparseData;
            HIDP_CAPS caps;
            windowsApi.GetPreparseData(device, out preparseData);
            windowsApi.GetCaps(preparseData, out caps);
            windowsApi.FreePreparseData(preparseData);
            outputReportLength = caps.OutputReportByteLength;
            inputReportLength = caps.InputReportByteLength;

            hidDevice = new FileStream(new SafeFileHandle(device, false), FileAccess.ReadWrite, inputReportLength, true);
            return true;
        }


4、以上操作都没问题就可以发送数据给HID设备了

(有的是只有先发送数据才有回传数据;有的不用发送直接有回传数据的,可以跳过此步,直接读取数据)

internal string WriteHID(string sendValue)
        {
            try
            {
                byte[] array = System.Text.ASCIIEncoding.Default.GetBytes(sendValue);
                byte[] arrays=new byte[array.Length+1];
                arrays[0] = 0;
                for (int i = 1; i <= array.Length; i++)
                {
                    arrays[i]=array[i-1];
                }
                hidDevice.Write(arrays, 0, arrays.Length);
               
//uint size = 0;
                //windowsApi.WriteDeviceFile(device, arrays, (uint)inputReportLength, ref size, IntPtr.Zero);

                //注释掉的是调用的c++的库函数bool WriteFile(hFile, lpBuffer, nNumberOfBytesToRead, ref nNumberOfBytesRead, lpOverlapped),发现返回值一直是0,就是没有发送成功。所以用的FileStream.Write()函数
               
return sendValue; 
            }
            catch (Exception e)
            {
                return e.Message;
            }
        }


5、读取HID设备回传回来的数据

public void BeginAsyncRead(string sendValue)
        {
            byte[] inputBuff = new byte[inputReportLength];
            IAsyncResult asyncResult = hidDevice.BeginRead(inputBuff, 0, inputReportLength, new AsyncCallback(ReadCompleted), inputBuff);
            if (asyncResult.IsCompleted == false)//这里处理数据发送成功,但是没有到达设备HID,具体怎么处理根据自己情况定
            {

                WriteHID(sendValue);

                ...;//这里一定不能调用BeginAsyncRead(sendValue),否则会死循环
           
}
        }

private void ReadCompleted(IAsyncResult iResult)//异步读取成功的回调函数
       
{
            byte[] readBuff = (byte[])(iResult.AsyncState);
            try
            {
                hidDevice.EndRead(iResult);//读取结束,如果读取错误就会产生一个异常(EndRead和BeginRead要一起使用)
                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); //发出数据到达消息,把e传回到窗体界面显示
            }
            catch (IOException)//读写错误,设备已经被移除
           
{
                EventArgs ex = new EventArgs();
                OnDeviceRemoved(ex);//发出设备移除消息
                CloseDevice();
            }
        }

事件的定义如下:

public event EventHandler DataReceived;
        protected virtual void OnDataReceived(EventArgs e)
        {
            if (DataReceived != null) DataReceived(this, e);
        }

       

public event EventHandler DeviceRemoved;
        protected virtual void OnDeviceRemoved(EventArgs e)
        {
            if (DeviceRemoved != null) DeviceRemoved(this, e);
        }


说明,一般发送数据用同步,接收数据一定要异步。因为发送数据要么成功要么失败,都是即时给出结果信息。但是接收就不一样,直接用Read会出现死机。因为调试发现会有这种情况,数据发送成功,但是没有到达设备HID;或者发送成功,HID有返回数据,但是Read没有读到。这种情况Read不会抛出异常,什么反应都没有,程序直接死掉;或者界面根本没出来直接在后台死机。


6、OK,试一下能不能通讯成功

窗体的后台程序要绑定上面的事件

usbHID.DataReceived += usbHID_DataReceived;
            usbHID.DeviceRemoved += usbHID_DeviceRemoved;

void usbHID_DeviceRemoved(object sender, EventArgs e)
        {
            if (InvokeRequired)
            {
                Invoke(new EventHandler(usbHID_DeviceRemoved), new object[] { sender, e });
            }
            else
            {
                tb_information.Text = "设备连接";
            }
        }

        void usbHID_DataReceived(object sender, EventArgs e)
        {
            report myRP = (report)e;
            if (InvokeRequired)
            {
                Invoke(new EventHandler(usbHID_DataReceived), new object[] { sender, e });
            }
            else
            {。。。。;//自己的处理}

      }


7、异常说明

如果通信不成功,特别是发送不成功,看看传的参数即字符串对不对

发送的参数及发送给HID的report有一定的格式说明,前几位有特别的意义,注意查看一下。第一位貌似是ID,但是我不清楚这个ID是标识什么的。我开始发送不成功,然后在发送的字符串前面即第一位加了一个byte转成的char,byte值为0。然后就可以发送成功了。


下来我会研究一下HID的report的说明。

此外网上有关C#通讯USB HID的资料也不是很多,大体都是这样的。所以如果有作者发现雷同部分请多多谅解-_-



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