在嵌入式硬件开发中,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)] //发送数据DLL
public static extern Boolean WriteFile(IntPtr hFile, byte[] lpBuffer, uint nNumberOfBytesToWrite, ref uint nNumberOfBytesWrite, IntPtr lpOverlapped);
[DllImport("kernel32.dll", SetLastError = true)] //接收函数DLL
private 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设备通信的基础框架,开发者可以根据实际需求进一步优化和扩展功能,例如支持异步数据传输、错误处理和设备的动态识别等。