在嵌入式硬件开发中,USB设备的通信是一个非常常见的需求,尤其是对于需要与上位机(PC)进行数据交换的设备。USB(Universal Serial Bus)提供了一种标准的硬件接口,可以实现各种类型的数据传输。通过上位机的接口与USB设备通信,开发者可以进行实时数据传输、设备控制等操作。本文将详细介绍如何使用C#实现自定义USB设备与上位机之间的通信。
开发思路概述
开发过程可以简要分为以下几个步骤:
- 识别设备:通过Windows提供的API来查找并识别目标USB设备。
- 连接设备:获取设备的路径信息,并通过设备路径与USB设备建立连接。
- 数据传输:通过已建立的连接,使用读写操作进行数据交互。
- 关闭连接:完成数据传输后,关闭USB设备,释放资源。
通过直接调用Windows的DLL(如hid.dll
、setupapi.dll
、kernel32.dll
),我们可以实现对USB设备的底层控制。下面将详细介绍每个步骤的实现方法。
1. 配置项目环境
首先,我们需要引入必要的DLL文件,这些文件提供了与USB设备进行交互的功能。常用的DLL包括:
hid.dll
:用于与HID(Human Interface Device)设备进行通信。setupapi.dll
:用于列出和访问设备信息。kernel32.dll
:用于设备句柄管理和文件操作。
将这些DLL文件复制到项目目录下,确保C#程序可以调用这些API。
2. 识别设备
通过调用HidD_GetHidGuid
函数,我们可以获取所有HID设备的GUID(全球唯一标识符)。接下来,使用SetupDiGetClassDevs
和SetupDiEnumDeviceInterfaces
函数列出系统中所有的HID设备接口。这些接口包含设备的路径信息,用于后续的设备连接。
[DllImport("hid.dll")]public static extern void HidD_GetHidGuid(ref Guid HidGuid);
[DllImport("setupapi.dll", SetLastError = true)]public static extern IntPtr SetupDiGetClassDevs(ref Guid ClassGuid, uint Enumerator, IntPtr HwndParent, DIGCF Flags);
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]public static extern Boolean SetupDiEnumDeviceInterfaces(IntPtr hDevInfo, IntPtr devInfo, ref Guid interfaceClassGuid, UInt32 memberIndex, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData);
通过这些API,可以获取设备接口的路径,并通过路径来标识目标设备。
3. 连接设备
一旦获得了设备的路径信息,就可以通过CreateFile
函数打开设备文件,从而获得设备的操作句柄。如果连接成功,就可以通过该句柄与设备进行后续操作。
[DllImport("kernel32.dll", SetLastError = true)]private static extern int CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode, uint lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, uint hTemplateFile);
在连接成功后,我们还需要获取设备的VID(厂商ID)和PID(产品ID)信息,用于确认是否是自定义的USB设备。
[DllImport("hid.dll")]private static extern Boolean HidD_GetAttributes(IntPtr hidDeviceObject, out HIDD_ATTRIBUTES HIDD_ATTRIBUTES);
4. 数据传输
设备连接成功后,可以通过WriteFile
和ReadFile
函数进行数据传输。WriteFile
用于向设备发送数据,而ReadFile
则用于从设备接收数据。
[DllImport("kernel32.dll", SetLastError = true)] //发送数据DLLpublic static extern Boolean WriteFile(IntPtr hFile, byte[] lpBuffer, uint nNumberOfBytesToWrite, ref uint nNumberOfBytesWrite, IntPtr lpOverlapped);
[DllImport("kernel32.dll", SetLastError = true)] //接收函数DLLprivate static extern bool ReadFile(IntPtr hFile, byte[] lpBuffer, uint nNumberOfBytesToRead, ref uint lpNumberOfBytesRead, IntPtr lpOverlapped);
为了实现异步接收,可以使用OVERLAPPED
结构来配置异步IO操作,并通过事件机制处理数据接收。
[StructLayout(LayoutKind.Sequential)]public struct OVERLAPPED{ public IntPtr Internal; public IntPtr InternalHigh; public int Offset; public int OffsetHigh; public IntPtr hEvent;}
使用WriteFile
进行同步写入数据,而ReadFile
则可以通过设置OVERLAPPED
结构体来进行异步读取操作。
5. 关闭设备
数据传输完成后,为了避免资源浪费和系统资源的泄漏,必须关闭USB设备连接并释放相关资源。
[DllImport("kernel32.dll")]static public extern int CloseHandle(int hObject);
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]private static extern Boolean SetupDiDestroyDeviceInfoList(IntPtr deviceInfoSet);
6. 完整代码示例
以下是一个完整的C#代码示例,展示了如何实现与USB设备的连接和数据传输。
public class USBCommunication{ private IntPtr hDevInfo; private IntPtr HidHandle; private string devicePathName;
public void ConnectToDevice() { // 获取设备GUID Guid guidHID = Guid.Empty; HidD_GetHidGuid(ref guidHID);
// 获取所有HID设备 hDevInfo = SetupDiGetClassDevs(ref guidHID, 0, IntPtr.Zero, DIGCF.DIGCF_PRESENT | DIGCF.DIGCF_DEVICEINTERFACE);
// 设备连接步骤 int bufferSize = 0; ArrayList HIDUSBAddress = new ArrayList();
// 设备路径获取及连接 SP_DEVICE_INTERFACE_DATA deviceInterfaceData = new SP_DEVICE_INTERFACE_DATA(); deviceInterfaceData.cbSize = Marshal.SizeOf(deviceInterfaceData); bool result = SetupDiEnumDeviceInterfaces(hDevInfo, IntPtr.Zero, ref guidHID, 0, ref deviceInterfaceData);
if (result) { SP_DEVINFO_DATA strtInterfaceData = new SP_DEVINFO_DATA(); result = SetupDiGetDeviceInterfaceDetail(hDevInfo, ref deviceInterfaceData, IntPtr.Zero, 0, ref bufferSize, strtInterfaceData);
if (result) { IntPtr detailDataBuffer = Marshal.AllocHGlobal(bufferSize); SP_DEVICE_INTERFACE_DETAIL_DATA detailData = new SP_DEVICE_INTERFACE_DETAIL_DATA(); detailData.cbSize = Marshal.SizeOf(typeof(SP_DEVICE_INTERFACE_DETAIL_DATA));
Marshal.StructureToPtr(detailData, detailDataBuffer, false); result = SetupDiGetDeviceInterfaceDetail(hDevInfo, ref deviceInterfaceData, detailDataBuffer, bufferSize, ref bufferSize, strtInterfaceData);
if (result) { IntPtr pdevicePathName = (IntPtr)((int)detailDataBuffer + 4); devicePathName = Marshal.PtrToStringAuto(pdevicePathName); int aa = CreateFile(devicePathName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0); if (aa != -1) { HidHandle = (IntPtr)aa; } } } } }
public void SendData(byte[] data) { uint bytesWritten = 0; bool result = WriteFile(HidHandle, data, (uint)data.Length, ref bytesWritten, IntPtr.Zero); if (!result) { Console.WriteLine("Data transmission failed."); } }
public void ReceiveData() { byte[] buffer = new byte[64]; uint bytesRead = 0; OVERLAPPED overlap = new OVERLAPPED(); overlap.hEvent = CreateEvent(IntPtr.Zero, false, false, null);
bool result = ReadFile(HidHandle, buffer, 64, ref bytesRead, ref overlap); if (result) { Console.WriteLine("Received Data: "); for (int i = 0; i < bytesRead; i++) { Console.WriteLine("Byte {0}: {1}", i, buffer[i]); } } else { Console.WriteLine("Failed to receive data."); } }
public void CloseConnection() { CloseHandle(HidHandle); SetupDiDestroyDeviceInfoList(hDevInfo); }}
7. 总结
通过C#调用Windows的底层API,我们可以实现与自定义USB设备的高效通信。通过合理的步骤来识别设备、连接设备并实现数据的读写操作,开发者可以轻松实现与各种USB设备的交互。
本教程提供了与USB设备通信的基础框架,开发者可以根据实际需求进一步优化和扩展功能,例如支持异步数据传输、错误处理和设备的动态识别等。