c#收发数据给USB HID

  • Post author:
  • Post category:其他



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<string> deviceList = new List<string>();

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


private void GetDeviceList(ref List<string> 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的资料也不是很多,大体都是这样的。所以如果有作者发现雷同部分请多多谅解

-_-





版权声明:本文为liuxufeiyang000原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。