1394 字
7 分钟
基于C#的自定义USB设备与上位机通信开发指南

在嵌入式硬件开发中,USB设备的通信是一个非常常见的需求,尤其是对于需要与上位机(PC)进行数据交换的设备。USB(Universal Serial Bus)提供了一种标准的硬件接口,可以实现各种类型的数据传输。通过上位机的接口与USB设备通信,开发者可以进行实时数据传输、设备控制等操作。本文将详细介绍如何使用C#实现自定义USB设备与上位机之间的通信。

开发思路概述#

开发过程可以简要分为以下几个步骤:

  1. 识别设备:通过Windows提供的API来查找并识别目标USB设备。
  2. 连接设备:获取设备的路径信息,并通过设备路径与USB设备建立连接。
  3. 数据传输:通过已建立的连接,使用读写操作进行数据交互。
  4. 关闭连接:完成数据传输后,关闭USB设备,释放资源。

通过直接调用Windows的DLL(如hid.dllsetupapi.dllkernel32.dll),我们可以实现对USB设备的底层控制。下面将详细介绍每个步骤的实现方法。

1. 配置项目环境#

首先,我们需要引入必要的DLL文件,这些文件提供了与USB设备进行交互的功能。常用的DLL包括:

  • hid.dll:用于与HID(Human Interface Device)设备进行通信。
  • setupapi.dll:用于列出和访问设备信息。
  • kernel32.dll:用于设备句柄管理和文件操作。

将这些DLL文件复制到项目目录下,确保C#程序可以调用这些API。

2. 识别设备#

通过调用HidD_GetHidGuid函数,我们可以获取所有HID设备的GUID(全球唯一标识符)。接下来,使用SetupDiGetClassDevsSetupDiEnumDeviceInterfaces函数列出系统中所有的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. 数据传输#

设备连接成功后,可以通过WriteFileReadFile函数进行数据传输。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设备通信的基础框架,开发者可以根据实际需求进一步优化和扩展功能,例如支持异步数据传输、错误处理和设备的动态识别等。

基于C#的自定义USB设备与上位机通信开发指南
https://hw.rscclub.website/posts/csharpusb/
作者
杨月昌
发布于
2017-11-18
许可协议
CC BY-NC-SA 4.0