USB的理解

USB基础知识详解

一、USB 2.0 是什么?

USB的"版本"与"速度"

复制代码
USB标准演进:
┌──────────────────────────────────────────┐
│ USB 1.0 (1996)  → 1.5 Mbps (低速)        │
│ USB 1.1 (1998)  → 12 Mbps (全速)         │
│ USB 2.0 (2000)  → 480 Mbps (高速) ⭐      │
│ USB 3.0 (2008)  → 5 Gbps (超高速)        │
│ USB 3.1 (2013)  → 10 Gbps               │
│ USB 3.2 (2017)  → 20 Gbps               │
│ USB4 (2019)     → 40 Gbps               │
└──────────────────────────────────────────┘

USB 2.0 = 一种物理层标准

  • 定义了电气特性(电压、电流)
  • 定义了物理接口(Type-A、Type-B、Micro、Type-C)
  • 定义了传输速度(最高480Mbps)

STM32H7的USB 2.0支持:

复制代码
STM32H743/H750:
├─ USB OTG FS (Full Speed): 12 Mbps
└─ USB OTG HS (High Speed): 480 Mbps ✅

这个"480 Mbps"理论带宽:
= 60 MB/s
= 每秒可传输3000万个16位采样点
→ 远超USB-4716的200kS/s需求!

二、USB的三个层次概念

关键理解:USB有三层含义,互不冲突!

复制代码
第1层:物理层版本(硬件速度)
       ↓
    USB 2.0 / USB 3.0
       ↓
第2层:设备类(Device Class - 功能类型)
       ↓
    HID / CDC / MSC / Audio / Custom...
       ↓
第3层:传输类型(Transfer Type - 数据传输方式)
       ↓
    Control / Bulk / Interrupt / Isochronous

形象比喻:

复制代码
USB 2.0      = 高速公路(480Mbps的道路)
设备类(Class) = 车辆类型(轿车/卡车/公交车)
传输类型      = 行驶方式(快递/客运/货运)

三、USB设备类(Device Class)详解

什么是USB设备类?

USB设备类 = 定义设备功能和通信协议的标准

USB-IF(USB标准组织)定义了多种设备类,每种类有标准的驱动程序

常见USB设备类对比

设备类 全称 功能 驱动 带宽 适用场景
HID Human Interface Device 键盘/鼠标/游戏手柄 ✅ 免驱 64KB/s 低速控制
CDC Communications Device Class 虚拟串口 ✅ 免驱 12Mbps 中速数据
MSC Mass Storage Class U盘 ✅ 免驱 480Mbps 文件存储
Audio USB Audio 声卡/麦克风 ✅ 免驱 480Mbps 音频流
UVC USB Video Class 摄像头 ✅ 免驱 480Mbps 视频流
Custom 自定义类 数据采集卡 ❌ 需自定义 480Mbps 高性能DAQ

STM32H7支持所有这些设备类!

c 复制代码
// STM32 HAL库支持的USB设备类:
USBD_HID      // 人机接口设备类
USBD_CDC      // 虚拟串口类
USBD_MSC      // 大容量存储类
USBD_AUDIO    // 音频类
USBD_DFU      // 固件升级类
USBD_CUSTOM   // 自定义类 ⭐

四、USB传输类型(Transfer Type)

四种传输类型对比

传输类型 特点 带宽 可靠性 适用场景
Control 控制传输 配置/命令
Bulk 批量传输 数据采集⭐
Interrupt 中断传输 HID设备
Isochronous 同步传输 音视频

数据采集卡应该用 Bulk 传输!

复制代码
Bulk传输特点:
✅ 高带宽(可占用全部可用带宽)
✅ 错误重传(保证数据完整性)
✅ 大包传输(512字节/包 @ USB 2.0 HS)
⚠️ 不保证实时性(但对DAQ影响不大)

五、研华USB-4716使用的方案

研华的USB架构(推测)

复制代码
硬件层:
USB 2.0 High Speed (480Mbps) ← STM32H7也支持

设备类:
Custom Device Class (自定义类) ← STM32H7也支持!
可能使用:Vendor Specific Device

传输类型:
Bulk Transfer (批量传输) ← STM32H7也支持!

驱动:
Windows: 自定义驱动 (.sys) 或 WinUSB
Linux: libusb

SDK:
封装DLL/SO库

关键发现:STM32H7可以完全实现!


六、STM32H7的USB能力详解

STM32H7的USB硬件

复制代码
STM32H743/H750有两个USB控制器:

1. USB_OTG_FS (Full Speed)
   ├─ 速度: 12 Mbps
   ├─ 接口: 内置PHY
   └─ 用途: 低速设备

2. USB_OTG_HS (High Speed) ⭐ 推荐
   ├─ 速度: 480 Mbps
   ├─ 接口: 内置PHY(需外部ULPI PHY达到480Mbps)
   └─ 用途: 高速数据传输

STM32H7支持的所有功能

c 复制代码
✅ USB 2.0 High Speed (480Mbps)
✅ 所有标准设备类 (HID/CDC/MSC/Audio...)
✅ 自定义设备类 (Custom Class)
✅ 所有传输类型 (Control/Bulk/Interrupt/Iso)
✅ OTG功能 (可做Host或Device)
✅ DMA传输 (减轻CPU负担)

结论:STM32H7的USB能力完全够用!


七、如何用STM32H7实现类似研华的方案

方案A:使用WinUSB(推荐)

复制代码
┌─────────────────────────────────────────┐
│ 优点:                                   │
│ ✅ Windows 8+ 免驱动                     │
│ ✅ 支持Bulk传输(高性能)                │
│ ✅ 微软官方支持                          │
│ ✅ 与研华方案最接近                      │
└─────────────────────────────────────────┘

实现步骤:

1. STM32固件配置为Custom USB Device
2. 使用Bulk端点传输数据
3. 提供WinUSB设备描述符
4. Windows自动加载WinUSB驱动
5. 上位机使用WinUSB API通信
固件配置示例
c 复制代码
// usbd_desc.c - USB设备描述符
#define USBD_VID                    0x0483  // STM厂商ID
#define USBD_PID                    0x5740  // 自定义产品ID
#define USBD_CLASS                  0xFF    // Vendor Specific
#define USBD_SUBCLASS               0x00
#define USBD_PROTOCOL               0x00

// Bulk端点配置
#define BULK_IN_EP                  0x81    // 数据输入端点
#define BULK_OUT_EP                 0x01    // 数据输出端点
#define BULK_MAX_PACKET_SIZE        512     // USB 2.0 HS最大包
Windows上位机示例
cpp 复制代码
#include <winusb.h>
#include <setupapi.h>

class MyDAQ {
private:
    HANDLE deviceHandle;
    WINUSB_INTERFACE_HANDLE winusbHandle;
    
public:
    bool Open() {
        // 1. 查找设备
        HDEVINFO deviceInfo = SetupDiGetClassDevs(
            &GUID_DEVINTERFACE_WINUSB,
            NULL, NULL,
            DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
        
        // 2. 打开设备
        deviceHandle = CreateFile(devicePath,
            GENERIC_WRITE | GENERIC_READ,
            FILE_SHARE_WRITE | FILE_SHARE_READ,
            NULL, OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
            NULL);
        
        // 3. 初始化WinUSB
        WinUsb_Initialize(deviceHandle, &winusbHandle);
        return true;
    }
    
    int ReadData(uint8_t* buffer, int length) {
        ULONG bytesRead;
        WinUsb_ReadPipe(winusbHandle, 0x81, // Bulk IN端点
                        buffer, length, &bytesRead, NULL);
        return bytesRead;
    }
    
    int WriteData(uint8_t* buffer, int length) {
        ULONG bytesWritten;
        WinUsb_WritePipe(winusbHandle, 0x01, // Bulk OUT端点
                         buffer, length, &bytesWritten, NULL);
        return bytesWritten;
    }
};

方案B:使用libusb(跨平台)

python 复制代码
# Python上位机(使用PyUSB)
import usb.core
import usb.util
import numpy as np

class MyDAQ:
    def __init__(self):
        # 查找设备(VID:PID = 0483:5740)
        self.dev = usb.core.find(idVendor=0x0483, idProduct=0x5740)
        if self.dev is None:
            raise ValueError('Device not found')
        
        # 设置配置
        self.dev.set_configuration()
        
    def start_acquisition(self, channels, samplerate):
        """发送开始采集命令"""
        cmd = bytes([0x01, channels, samplerate >> 8, samplerate & 0xFF])
        self.dev.write(0x01, cmd)  # 写到Bulk OUT端点
        
    def read_samples(self, num_samples):
        """读取采样数据"""
        data = self.dev.read(0x81, num_samples * 2, timeout=1000)
        # 转换为16位整数数组
        samples = np.frombuffer(data, dtype=np.uint16)
        # 转换为电压
        voltages = (samples / 65535.0) * 10.0 - 5.0  # ±5V
        return voltages

# 使用示例
daq = MyDAQ()
daq.start_acquisition(channels=4, samplerate=10000)
data = daq.read_samples(1000)  # 读取1000个采样点
print(f"Average: {np.mean(data):.3f}V")

八、完整实现对比

研华USB-4716 vs STM32H7方案

特性 研华USB-4716 STM32H7 + WinUSB
USB版本 USB 2.0 HS USB 2.0 HS ✅
理论带宽 480 Mbps 480 Mbps ✅
设备类 Custom Custom ✅
传输类型 Bulk Bulk ✅
Windows驱动 厂商提供 WinUSB(系统自带)✅
SDK 厂商提供 需自己开发 ⚠️
即插即用 ✅(Win8+)
跨平台 ✅(libusb)
开发难度 购买即用 需要开发 ⚠️

九、实战开发路线

第1步:验证USB通信(1周)

c 复制代码
// 使用STM32CubeMX生成USB CDC项目
// 实现简单的回环测试

void CDC_Receive_Callback(uint8_t* Buf, uint32_t Len) {
    // 收到什么就发送什么
    CDC_Transmit_FS(Buf, Len);
}
python 复制代码
# Python测试
import serial
ser = serial.Serial('COM3', 115200)
ser.write(b'Hello')
print(ser.read(5))  # 应该收到 'Hello'

第2步:切换到Bulk传输(1-2周)

c 复制代码
// 修改USB描述符为Custom Class
// 配置Bulk端点
// 实现高速数据传输

第3步:添加ADC功能(1周)

c 复制代码
// 配置ADC + DMA
// 采集数据存到缓冲区
// 通过USB Bulk发送

第4步:开发上位机SDK(2-3周)

cpp 复制代码
// 封装C++ DLL
// 提供类似研华的API
// 添加错误处理、缓冲管理

十、总结回答你的问题

Q1: USB 2.0是什么?

A: USB 2.0是物理层标准,定义了480Mbps的传输速度。STM32H7完全支持。

Q2: USB 2.0怎么用?

A:

  1. 硬件上:STM32H7有USB控制器,接好USB口即可
  2. 固件上:使用HAL库配置USB设备
  3. 上位机:使用WinUSB或libusb通信

Q3: 能和USB HID/CDC/Bulk一致吗?

A: 能!

  • USB 2.0 = 道路(物理层)
  • HID/CDC/Bulk = 车辆类型(设备类/传输方式)
  • 它们不矛盾,是不同层次的概念

Q4: USB类型是指什么?

A: 通常指设备类(Device Class)

  • HID = 人机接口设备
  • CDC = 虚拟串口
  • MSC = 大容量存储
  • Custom = 自定义设备(数据采集卡用这个)

Q5: STM32H7不支持吗?

A: 完全支持! STM32H7支持:

  • ✅ USB 2.0 High Speed (480Mbps)
  • ✅ 所有设备类(HID/CDC/MSC/Custom)
  • ✅ 所有传输类型(Bulk/Interrupt/等)

核心结论:

STM32H7的USB能力与研华USB-4716在硬件层面完全相同

区别只在于软件层面

  • 研华提供了现成的驱动和SDK
  • STM32H7需要你自己开发(但完全可行)

需要我提供完整的STM32H7 USB Bulk传输代码示例吗?


STM32H7 USB Bulk传输完整代码示例

我会提供一个完整的、可运行的USB Bulk数据采集卡项目。


项目结构

复制代码
STM32H7_USB_DAQ/
├── Core/
│   ├── Inc/
│   │   ├── main.h
│   │   ├── adc_handler.h
│   │   └── daq_protocol.h
│   └── Src/
│       ├── main.c
│       ├── adc_handler.c
│       └── daq_protocol.c
├── USB_DEVICE/
│   ├── App/
│   │   ├── usbd_custom.h
│   │   ├── usbd_custom.c
│   │   └── usbd_desc.c
│   └── Target/
│       └── usbd_conf.c
└── PC_Software/
    ├── Python/
    │   └── daq_test.py
    └── CPP/
        ├── MyDAQ_SDK.h
        └── MyDAQ_SDK.cpp

第一部分:STM32固件代码

1. USB设备描述符配置

c 复制代码
// USB_DEVICE/App/usbd_desc.c
#include "usbd_core.h"
#include "usbd_desc.h"

#define USBD_VID                      0x0483  // STM厂商ID
#define USBD_PID_FS                   0x5740  // 自定义产品ID
#define USBD_LANGID_STRING            0x0409  // 英语(美国)
#define USBD_MANUFACTURER_STRING      "MyCompany"
#define USBD_PRODUCT_STRING_FS        "STM32 USB DAQ"
#define USBD_CONFIGURATION_STRING_FS  "DAQ Config"
#define USBD_INTERFACE_STRING_FS      "DAQ Interface"

// USB设备描述符
__ALIGN_BEGIN uint8_t USBD_FS_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END = {
    0x12,                       // bLength
    USB_DESC_TYPE_DEVICE,       // bDescriptorType
    0x00, 0x02,                 // bcdUSB: USB 2.0
    0xFF,                       // bDeviceClass: Vendor Specific
    0x00,                       // bDeviceSubClass
    0x00,                       // bDeviceProtocol
    USB_MAX_EP0_SIZE,           // bMaxPacketSize
    LOBYTE(USBD_VID), HIBYTE(USBD_VID),     // idVendor
    LOBYTE(USBD_PID_FS), HIBYTE(USBD_PID_FS), // idProduct
    0x00, 0x02,                 // bcdDevice: 2.00
    USBD_IDX_MFC_STR,           // iManufacturer
    USBD_IDX_PRODUCT_STR,       // iProduct
    USBD_IDX_SERIAL_STR,        // iSerialNumber
    USBD_MAX_NUM_CONFIGURATION  // bNumConfigurations
};

// USB配置描述符
__ALIGN_BEGIN uint8_t USBD_FS_CfgDesc[USB_CUSTOM_CONFIG_DESC_SIZ] __ALIGN_END = {
    // Configuration Descriptor
    0x09,                              // bLength
    USB_DESC_TYPE_CONFIGURATION,       // bDescriptorType
    USB_CUSTOM_CONFIG_DESC_SIZ, 0x00,  // wTotalLength
    0x01,                              // bNumInterfaces
    0x01,                              // bConfigurationValue
    0x00,                              // iConfiguration
    0xC0,                              // bmAttributes: Self Powered
    0x32,                              // MaxPower: 100mA

    // Interface Descriptor
    0x09,                              // bLength
    USB_DESC_TYPE_INTERFACE,           // bDescriptorType
    0x00,                              // bInterfaceNumber
    0x00,                              // bAlternateSetting
    0x02,                              // bNumEndpoints (Bulk IN + Bulk OUT)
    0xFF,                              // bInterfaceClass: Vendor Specific
    0x00,                              // bInterfaceSubClass
    0x00,                              // bInterfaceProtocol
    0x00,                              // iInterface

    // Bulk OUT Endpoint Descriptor
    0x07,                              // bLength
    USB_DESC_TYPE_ENDPOINT,            // bDescriptorType
    CUSTOM_OUT_EP,                     // bEndpointAddress: 0x01
    0x02,                              // bmAttributes: Bulk
    LOBYTE(CUSTOM_DATA_FS_MAX_PACKET_SIZE),
    HIBYTE(CUSTOM_DATA_FS_MAX_PACKET_SIZE), // wMaxPacketSize: 64 bytes
    0x00,                              // bInterval

    // Bulk IN Endpoint Descriptor
    0x07,                              // bLength
    USB_DESC_TYPE_ENDPOINT,            // bDescriptorType
    CUSTOM_IN_EP,                      // bEndpointAddress: 0x81
    0x02,                              // bmAttributes: Bulk
    LOBYTE(CUSTOM_DATA_FS_MAX_PACKET_SIZE),
    HIBYTE(CUSTOM_DATA_FS_MAX_PACKET_SIZE), // wMaxPacketSize: 64 bytes
    0x00                               // bInterval
};

// BOS描述符(用于WinUSB自动识别 - Windows 8+)
__ALIGN_BEGIN uint8_t USBD_FS_BOSDesc[USB_SIZ_BOS_DESC] __ALIGN_END = {
    0x05,                              // bLength
    USB_DESC_TYPE_BOS,                 // bDescriptorType
    0x0C, 0x00,                        // wTotalLength
    0x01,                              // bNumDeviceCaps

    // Microsoft OS 2.0 descriptor
    0x07,                              // bLength
    0x10,                              // bDescriptorType
    0x02,                              // bDevCapabilityType
    0x02, 0x00, 0x00, 0x00            // dwWindowsVersion: Windows 8+
};

2. USB自定义类实现

c 复制代码
// USB_DEVICE/App/usbd_custom.h
#ifndef __USBD_CUSTOM_H
#define __USBD_CUSTOM_H

#include "usbd_ioreq.h"

// 端点地址
#define CUSTOM_IN_EP                    0x81  // Bulk IN
#define CUSTOM_OUT_EP                   0x01  // Bulk OUT

// 包大小
#define CUSTOM_DATA_HS_MAX_PACKET_SIZE  512   // High Speed: 512 bytes
#define CUSTOM_DATA_FS_MAX_PACKET_SIZE  64    // Full Speed: 64 bytes

#define USB_CUSTOM_CONFIG_DESC_SIZ      32

// 数据缓冲区
#define CUSTOM_DATA_OUT_MAX_SIZE        1024
#define CUSTOM_DATA_IN_MAX_SIZE         4096

// Custom类回调函数
typedef struct {
    int8_t (*Init)(void);
    int8_t (*DeInit)(void);
    int8_t (*Receive)(uint8_t *pbuf, uint32_t len);
    int8_t (*TransmitCplt)(uint8_t *pbuf, uint32_t len);
} USBD_CUSTOM_ItfTypeDef;

// Custom类句柄
typedef struct {
    uint32_t             data[CUSTOM_DATA_OUT_MAX_SIZE / 4];
    uint8_t              CmdOpCode;
    uint8_t              CmdLength;
    uint8_t              *RxBuffer;
    uint8_t              *TxBuffer;
    uint32_t             RxLength;
    uint32_t             TxLength;
    __IO uint32_t        TxState;
    __IO uint32_t        RxState;
} USBD_CUSTOM_HandleTypeDef;

// 导出类结构
extern USBD_ClassTypeDef USBD_CUSTOM;
#define USBD_CUSTOM_CLASS &USBD_CUSTOM

// API函数
uint8_t USBD_CUSTOM_RegisterInterface(USBD_HandleTypeDef *pdev,
                                       USBD_CUSTOM_ItfTypeDef *fops);
uint8_t USBD_CUSTOM_SetTxBuffer(USBD_HandleTypeDef *pdev,
                                 uint8_t *pbuff, uint32_t length);
uint8_t USBD_CUSTOM_TransmitPacket(USBD_HandleTypeDef *pdev);

#endif // __USBD_CUSTOM_H
c 复制代码
// USB_DEVICE/App/usbd_custom.c
#include "usbd_custom.h"
#include "usbd_ctlreq.h"

static uint8_t USBD_CUSTOM_Init(USBD_HandleTypeDef *pdev, uint8_t cfgidx);
static uint8_t USBD_CUSTOM_DeInit(USBD_HandleTypeDef *pdev, uint8_t cfgidx);
static uint8_t USBD_CUSTOM_Setup(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req);
static uint8_t USBD_CUSTOM_DataIn(USBD_HandleTypeDef *pdev, uint8_t epnum);
static uint8_t USBD_CUSTOM_DataOut(USBD_HandleTypeDef *pdev, uint8_t epnum);
static uint8_t *USBD_CUSTOM_GetFSCfgDesc(uint16_t *length);
static uint8_t *USBD_CUSTOM_GetHSCfgDesc(uint16_t *length);

USBD_ClassTypeDef USBD_CUSTOM = {
    USBD_CUSTOM_Init,
    USBD_CUSTOM_DeInit,
    USBD_CUSTOM_Setup,
    NULL,                  // EP0_TxSent
    NULL,                  // EP0_RxReady
    USBD_CUSTOM_DataIn,
    USBD_CUSTOM_DataOut,
    NULL,                  // SOF
    NULL,                  // IsoINIncomplete
    NULL,                  // IsoOutIncomplete
    USBD_CUSTOM_GetHSCfgDesc,
    USBD_CUSTOM_GetFSCfgDesc,
    USBD_CUSTOM_GetFSCfgDesc,
    NULL,                  // GetDeviceQualifierDescriptor
};

// 接收缓冲区
static uint8_t UserRxBufferFS[CUSTOM_DATA_OUT_MAX_SIZE];
static uint8_t UserTxBufferFS[CUSTOM_DATA_IN_MAX_SIZE];

static uint8_t USBD_CUSTOM_Init(USBD_HandleTypeDef *pdev, uint8_t cfgidx) {
    USBD_CUSTOM_HandleTypeDef *hcustom;
    
    // 分配类数据结构
    pdev->pClassData = USBD_malloc(sizeof(USBD_CUSTOM_HandleTypeDef));
    if (pdev->pClassData == NULL) {
        return USBD_FAIL;
    }
    
    hcustom = (USBD_CUSTOM_HandleTypeDef *)pdev->pClassData;
    
    // 初始化缓冲区
    hcustom->RxBuffer = UserRxBufferFS;
    hcustom->TxBuffer = UserTxBufferFS;
    hcustom->RxLength = 0;
    hcustom->TxLength = 0;
    hcustom->TxState = 0;
    hcustom->RxState = 0;
    
    // 打开端点
    if (pdev->dev_speed == USBD_SPEED_HIGH) {
        USBD_LL_OpenEP(pdev, CUSTOM_IN_EP, USBD_EP_TYPE_BULK,
                       CUSTOM_DATA_HS_MAX_PACKET_SIZE);
        USBD_LL_OpenEP(pdev, CUSTOM_OUT_EP, USBD_EP_TYPE_BULK,
                       CUSTOM_DATA_HS_MAX_PACKET_SIZE);
    } else {
        USBD_LL_OpenEP(pdev, CUSTOM_IN_EP, USBD_EP_TYPE_BULK,
                       CUSTOM_DATA_FS_MAX_PACKET_SIZE);
        USBD_LL_OpenEP(pdev, CUSTOM_OUT_EP, USBD_EP_TYPE_BULK,
                       CUSTOM_DATA_FS_MAX_PACKET_SIZE);
    }
    
    pdev->ep_in[CUSTOM_IN_EP & 0xFU].is_used = 1U;
    pdev->ep_out[CUSTOM_OUT_EP & 0xFU].is_used = 1U;
    
    // 准备接收数据
    USBD_LL_PrepareReceive(pdev, CUSTOM_OUT_EP,
                           hcustom->RxBuffer,
                           CUSTOM_DATA_OUT_MAX_SIZE);
    
    return USBD_OK;
}

static uint8_t USBD_CUSTOM_DeInit(USBD_HandleTypeDef *pdev, uint8_t cfgidx) {
    // 关闭端点
    USBD_LL_CloseEP(pdev, CUSTOM_IN_EP);
    USBD_LL_CloseEP(pdev, CUSTOM_OUT_EP);
    
    pdev->ep_in[CUSTOM_IN_EP & 0xFU].is_used = 0U;
    pdev->ep_out[CUSTOM_OUT_EP & 0xFU].is_used = 0U;
    
    // 释放类数据
    if (pdev->pClassData != NULL) {
        USBD_free(pdev->pClassData);
        pdev->pClassData = NULL;
    }
    
    return USBD_OK;
}

static uint8_t USBD_CUSTOM_Setup(USBD_HandleTypeDef *pdev,
                                  USBD_SetupReqTypedef *req) {
    USBD_CUSTOM_HandleTypeDef *hcustom = (USBD_CUSTOM_HandleTypeDef *)pdev->pClassData;
    uint16_t len = 0;
    uint8_t *pbuf = NULL;
    uint16_t status_info = 0;
    USBD_StatusTypeDef ret = USBD_OK;
    
    switch (req->bmRequest & USB_REQ_TYPE_MASK) {
    case USB_REQ_TYPE_CLASS:
        // 处理类特定请求(如果需要)
        break;
        
    case USB_REQ_TYPE_STANDARD:
        switch (req->bRequest) {
        case USB_REQ_GET_STATUS:
            if (pdev->dev_state == USBD_STATE_CONFIGURED) {
                USBD_CtlSendData(pdev, (uint8_t *)&status_info, 2U);
            } else {
                USBD_CtlError(pdev, req);
                ret = USBD_FAIL;
            }
            break;
            
        case USB_REQ_GET_INTERFACE:
            if (pdev->dev_state == USBD_STATE_CONFIGURED) {
                USBD_CtlSendData(pdev, (uint8_t *)&hcustom->CmdOpCode, 1U);
            } else {
                USBD_CtlError(pdev, req);
                ret = USBD_FAIL;
            }
            break;
            
        default:
            USBD_CtlError(pdev, req);
            ret = USBD_FAIL;
            break;
        }
        break;
        
    default:
        USBD_CtlError(pdev, req);
        ret = USBD_FAIL;
        break;
    }
    
    return ret;
}

static uint8_t USBD_CUSTOM_DataIn(USBD_HandleTypeDef *pdev, uint8_t epnum) {
    USBD_CUSTOM_HandleTypeDef *hcustom = (USBD_CUSTOM_HandleTypeDef *)pdev->pClassData;
    
    if (hcustom != NULL) {
        hcustom->TxState = 0;
        return USBD_OK;
    }
    
    return USBD_FAIL;
}

static uint8_t USBD_CUSTOM_DataOut(USBD_HandleTypeDef *pdev, uint8_t epnum) {
    USBD_CUSTOM_HandleTypeDef *hcustom = (USBD_CUSTOM_HandleTypeDef *)pdev->pClassData;
    
    if (hcustom != NULL) {
        // 获取接收长度
        hcustom->RxLength = USBD_LL_GetRxDataSize(pdev, epnum);
        
        // 调用接收回调
        ((USBD_CUSTOM_ItfTypeDef *)pdev->pUserData)->Receive(hcustom->RxBuffer,
                                                               hcustom->RxLength);
        
        return USBD_OK;
    }
    
    return USBD_FAIL;
}

uint8_t USBD_CUSTOM_SetTxBuffer(USBD_HandleTypeDef *pdev,
                                 uint8_t *pbuff, uint32_t length) {
    USBD_CUSTOM_HandleTypeDef *hcustom = (USBD_CUSTOM_HandleTypeDef *)pdev->pClassData;
    
    if (hcustom != NULL) {
        hcustom->TxBuffer = pbuff;
        hcustom->TxLength = length;
        return USBD_OK;
    }
    
    return USBD_FAIL;
}

uint8_t USBD_CUSTOM_TransmitPacket(USBD_HandleTypeDef *pdev) {
    USBD_CUSTOM_HandleTypeDef *hcustom = (USBD_CUSTOM_HandleTypeDef *)pdev->pClassData;
    USBD_StatusTypeDef ret = USBD_BUSY;
    
    if (hcustom != NULL) {
        if (hcustom->TxState == 0U) {
            hcustom->TxState = 1U;
            
            USBD_LL_Transmit(pdev, CUSTOM_IN_EP,
                            hcustom->TxBuffer,
                            hcustom->TxLength);
            
            ret = USBD_OK;
        }
    }
    
    return ret;
}

static uint8_t *USBD_CUSTOM_GetFSCfgDesc(uint16_t *length) {
    *length = sizeof(USBD_FS_CfgDesc);
    return USBD_FS_CfgDesc;
}

static uint8_t *USBD_CUSTOM_GetHSCfgDesc(uint16_t *length) {
    *length = sizeof(USBD_FS_CfgDesc);
    return USBD_FS_CfgDesc;
}

3. 数据采集协议定义

c 复制代码
// Core/Inc/daq_protocol.h
#ifndef __DAQ_PROTOCOL_H
#define __DAQ_PROTOCOL_H

#include <stdint.h>

// 命令码定义
#define CMD_GET_INFO            0x01
#define CMD_AI_CONFIG           0x10
#define CMD_AI_START            0x11
#define CMD_AI_STOP             0x12
#define CMD_AI_READ             0x13
#define CMD_AO_WRITE            0x20
#define CMD_DIO_CONFIG          0x30
#define CMD_DIO_READ            0x31
#define CMD_DIO_WRITE           0x32

// 响应码
#define RESP_OK                 0x00
#define RESP_ERROR              0xFF

// 命令包结构
typedef struct __attribute__((packed)) {
    uint8_t  cmd;           // 命令码
    uint8_t  length;        // 数据长度
    uint8_t  data[62];      // 数据(最大62字节,总包64字节)
} CMD_Packet_t;

// AI配置结构
typedef struct __attribute__((packed)) {
    uint8_t  channels;      // 通道数 (1-16)
    uint8_t  range;         // 量程 (0=±10V, 1=±5V, 2=±2.5V...)
    uint32_t sampleRate;    // 采样率 (Hz)
    uint16_t samplesPerChannel; // 每通道采样数
} AI_Config_t;

// 数据包结构
typedef struct __attribute__((packed)) {
    uint8_t  status;        // 状态
    uint8_t  channel;       // 通道号
    uint16_t samples;       // 采样点数
    uint16_t data[];        // ADC数据
} DATA_Packet_t;

// 设备信息
typedef struct __attribute__((packed)) {
    char     name[32];      // 设备名称
    uint8_t  version[4];    // 版本号
    uint16_t aiChannels;    // AI通道数
    uint16_t aoChannels;    // AO通道数
    uint16_t diChannels;    // DI通道数
    uint16_t doChannels;    // DO通道数
    uint32_t maxSampleRate; // 最大采样率
} Device_Info_t;

#endif // __DAQ_PROTOCOL_H
c 复制代码
// Core/Src/daq_protocol.c
#include "daq_protocol.h"
#include "adc_handler.h"
#include "usbd_custom.h"
#include <string.h>

extern USBD_HandleTypeDef hUsbDeviceFS;
extern ADC_HandleTypeDef hadc1;

static AI_Config_t aiConfig;
static uint8_t isAcquiring = 0;

// 设备信息
static const Device_Info_t deviceInfo = {
    .name = "STM32H7 USB DAQ",
    .version = {1, 0, 0, 0},
    .aiChannels = 16,
    .aoChannels = 2,
    .diChannels = 8,
    .doChannels = 8,
    .maxSampleRate = 200000
};

void DAQ_ProcessCommand(uint8_t *buffer, uint32_t length) {
    CMD_Packet_t *cmd = (CMD_Packet_t *)buffer;
    uint8_t response[64] = {0};
    uint32_t respLen = 0;
    
    switch (cmd->cmd) {
    case CMD_GET_INFO:
        // 返回设备信息
        response[0] = RESP_OK;
        memcpy(&response[1], &deviceInfo, sizeof(Device_Info_t));
        respLen = 1 + sizeof(Device_Info_t);
        break;
        
    case CMD_AI_CONFIG:
        // 配置模拟输入
        memcpy(&aiConfig, cmd->data, sizeof(AI_Config_t));
        ADC_Configure(&aiConfig);
        response[0] = RESP_OK;
        respLen = 1;
        break;
        
    case CMD_AI_START:
        // 开始采集
        isAcquiring = 1;
        ADC_Start();
        response[0] = RESP_OK;
        respLen = 1;
        break;
        
    case CMD_AI_STOP:
        // 停止采集
        isAcquiring = 0;
        ADC_Stop();
        response[0] = RESP_OK;
        respLen = 1;
        break;
        
    case CMD_AO_WRITE:
        // 模拟输出(如果有DAC)
        // TODO: 实现DAC输出
        response[0] = RESP_OK;
        respLen = 1;
        break;
        
    default:
        response[0] = RESP_ERROR;
        respLen = 1;
        break;
    }
    
    // 发送响应
    if (respLen > 0) {
        USBD_CUSTOM_SetTxBuffer(&hUsbDeviceFS, response, respLen);
        USBD_CUSTOM_TransmitPacket(&hUsbDeviceFS);
    }
}

void DAQ_SendData(uint8_t channel, uint16_t *data, uint16_t samples) {
    static uint8_t txBuffer[512];
    DATA_Packet_t *packet = (DATA_Packet_t *)txBuffer;
    
    packet->status = RESP_OK;
    packet->channel = channel;
    packet->samples = samples;
    memcpy(packet->data, data, samples * sizeof(uint16_t));
    
    uint32_t totalLen = sizeof(DATA_Packet_t) + samples * sizeof(uint16_t);
    
    USBD_CUSTOM_SetTxBuffer(&hUsbDeviceFS, txBuffer, totalLen);
    USBD_CUSTOM_TransmitPacket(&hUsbDeviceFS);
}

4. ADC处理代码

c 复制代码
// Core/Inc/adc_handler.h
#ifndef __ADC_HANDLER_H
#define __ADC_HANDLER_H

#include "main.h"
#include "daq_protocol.h"

void ADC_Configure(AI_Config_t *config);
void ADC_Start(void);
void ADC_Stop(void);
void ADC_DMA_ConvCpltCallback(void);

#endif
c 复制代码
// Core/Src/adc_handler.c
#include "adc_handler.h"
#include "daq_protocol.h"

#define ADC_BUFFER_SIZE 1024
static uint16_t adcBuffer[ADC_BUFFER_SIZE];
static AI_Config_t currentConfig;

extern ADC_HandleTypeDef hadc1;

void ADC_Configure(AI_Config_t *config) {
    memcpy(&currentConfig, config, sizeof(AI_Config_t));
    
    // 配置ADC采样率(通过定时器触发)
    // 配置ADC通道数
    // 配置ADC量程(通过PGA或多路复用器)
    
    // 这里简化处理,实际需要根据硬件配置
}

void ADC_Start(void) {
    // 启动ADC DMA传输
    HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adcBuffer, ADC_BUFFER_SIZE);
}

void ADC_Stop(void) {
    HAL_ADC_Stop_DMA(&hadc1);
}

// DMA传输完成回调
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) {
    // 后半部分缓冲区已满
    DAQ_SendData(0, &adcBuffer[ADC_BUFFER_SIZE / 2], ADC_BUFFER_SIZE / 2);
}

void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc) {
    // 前半部分缓冲区已满
    DAQ_SendData(0, adcBuffer, ADC_BUFFER_SIZE / 2);
}

5. 主程序

c 复制代码
// Core/Src/main.c
#include "main.h"
#include "usb_device.h"
#include "daq_protocol.h"

ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;
TIM_HandleTypeDef htim2;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_ADC1_Init(void);
static void MX_TIM2_Init(void);

int main(void) {
    HAL_Init();
    SystemClock_Config();
    
    MX_GPIO_Init();
    MX_DMA_Init();
    MX_ADC1_Init();
    MX_TIM2_Init();
    MX_USB_DEVICE_Init();
    
    // 启动定时器(用于ADC触发)
    HAL_TIM_Base_Start(&htim2);
    
    while (1) {
        // 主循环
        HAL_Delay(100);
    }
}

static void MX_ADC1_Init(void) {
    ADC_MultiModeTypeDef multimode = {0};
    ADC_ChannelConfTypeDef sConfig = {0};
    
    hadc1.Instance = ADC1;
    hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV2;
    hadc1.Init.Resolution = ADC_RESOLUTION_16B;
    hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
    hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
    hadc1.Init.LowPowerAutoWait = DISABLE;
    hadc1.Init.ContinuousConvMode = DISABLE;
    hadc1.Init.NbrOfConversion = 1;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIG_T2_TRGO;
    hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
    hadc1.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DMA_CIRCULAR;
    hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
    
    if (HAL_ADC_Init(&hadc1) != HAL_OK) {
        Error_Handler();
    }
    
    // 配置ADC通道
    sConfig.Channel = ADC_CHANNEL_0;
    sConfig.Rank = ADC_REGULAR_RANK_1;
    sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
    sConfig.SingleDiff = ADC_SINGLE_ENDED;
    sConfig.OffsetNumber = ADC_OFFSET_NONE;
    sConfig.Offset = 0;
    
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
        Error_Handler();
    }
}

static void MX_TIM2_Init(void) {
    TIM_ClockConfigTypeDef sClockSourceConfig = {0};
    TIM_MasterConfigTypeDef sMasterConfig = {0};
    
    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 0;
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 2399;  // 100kHz @ 240MHz
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
    
    if (HAL_TIM_Base_Init(&htim2) != HAL_OK) {
        Error_Handler();
    }
    
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    
    if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK) {
        Error_Handler();
    }
}

void Error_Handler(void) {
    __disable_irq();
    while (1) {}
}

第二部分:Windows上位机代码

1. Python测试程序

python 复制代码
# PC_Software/Python/daq_test.py
import usb.core
import usb.util
import struct
import numpy as np
import matplotlib.pyplot as plt

class STM32_DAQ:
    """STM32 USB数据采集卡Python SDK"""
    
    # 命令码
    CMD_GET_INFO = 0x01
    CMD_AI_CONFIG = 0x10
    CMD_AI_START = 0x11
    CMD_AI_STOP = 0x12
    CMD_AI_READ = 0x13
    CMD_AO_WRITE = 0x20
    
    # VID:PID
    VID = 0x0483
    PID = 0x5740
    
    # 端点地址
    EP_OUT = 0x01
    EP_IN = 0x81
    
    def __init__(self):
        self.dev = None
        self.device_info = None
        
    def open(self):
        """打开设备"""
        # 查找设备
        self.dev = usb.core.find(idVendor=self.VID, idProduct=self.PID)
        
        if self.dev is None:
            raise ValueError('Device not found')
        
        # 设置配置
        try:
            self.dev.set_configuration()
        except usb.core.USBError as e:
            print(f"Could not set configuration: {e}")
            # Windows可能需要先detach kernel driver
            pass
        
        print(f"Device opened: {self.dev}")
        
        # 获取设备信息
        self.get_device_info()
        
    def close(self):
        """关闭设备"""
        if self.dev:
            usb.util.dispose_resources(self.dev)
            self.dev = None
    
    def send_command(self, cmd, data=b''):
        """发送命令"""
        packet = struct.pack('BB', cmd, len(data)) + data
        packet += b'\x00' * (64 - len(packet))  # 填充到64字节
        
        try:
            self.dev.write(self.EP_OUT, packet, timeout=1000)
        except usb.core.USBError as e:
            print(f"Write error: {e}")
            raise
    
    def receive_response(self, timeout=1000):
        """接收响应"""
        try:
            data = self.dev.read(self.EP_IN, 512, timeout=timeout)
            return bytes(data)
        except usb.core.USBError as e:
            print(f"Read error: {e}")
            return None
    
    def get_device_info(self):
        """获取设备信息"""
        self.send_command(self.CMD_GET_INFO)
        response = self.receive_response()
        
        if response and response[0] == 0x00:  # RESP_OK
            # 解析设备信息 (简化版)
            name = response[1:33].decode('utf-8', errors='ignore').strip('\x00')
            print(f"Device Name: {name}")
            self.device_info = {'name': name}
            return self.device_info
        
        return None
    
    def ai_config(self, channels=1, range_code=0, sample_rate=10000, samples=1000):
        """配置模拟输入
        
        Args:
            channels: 通道数 (1-16)
            range_code: 量程 (0=±10V, 1=±5V, etc.)
            sample_rate: 采样率 (Hz)
            samples: 每通道采样数
        """
        data = struct.pack('<BBIHH', 
                          channels, 
                          range_code, 
                          sample_rate, 
                          samples, 
                          0)  # padding
        
        self.send_command(self.CMD_AI_CONFIG, data)
        response = self.receive_response()
        
        if response and response[0] == 0x00:
            print(f"AI configured: {channels}ch, {sample_rate}Hz")
            return True
        
        print("AI config failed")
        return False
    
    def ai_start(self):
        """开始采集"""
        self.send_command(self.CMD_AI_START)
        response = self.receive_response()
        
        if response and response[0] == 0x00:
            print("Acquisition started")
            return True
        
        print("Start failed")
        return False
    
    def ai_stop(self):
        """停止采集"""
        self.send_command(self.CMD_AI_STOP)
        response = self.receive_response()
        
        if response and response[0] == 0x00:
            print("Acquisition stopped")
            return True
        
        return False
    
    def ai_read(self, num_samples=1000, timeout=5000):
        """读取采样数据"""
        all_data = []
        
        start_time = usb.core.get_time()
        
        while len(all_data) < num_samples:
            try:
                data = self.dev.read(self.EP_IN, 512, timeout=100)
                
                # 解析数据包
                if len(data) >= 4:
                    status = data[0]
                    channel = data[1]
                    samples = struct.unpack('<H', bytes(data[2:4]))[0]
                    
                    # 提取ADC数据
                    adc_data = []
                    for i in range(samples):
                        if 4 + i*2 + 1 < len(data):
                            value = struct.unpack('<H', bytes(data[4+i*2:4+i*2+2]))[0]
                            adc_data.append(value)
                    
                    all_data.extend(adc_data)
                    
            except usb.core.USBTimeoutError:
                if usb.core.get_time() - start_time > timeout:
                    break
                continue
        
        return np.array(all_data[:num_samples])
    
    def adc_to_voltage(self, adc_values, range_code=0):
        """ADC值转电压
        
        Args:
            adc_values: ADC值数组 (16位)
            range_code: 量程代码 (0=±10V, 1=±5V, etc.)
        """
        # 16位ADC: 0-65535
        voltage_ranges = {
            0: 20.0,   # ±10V
            1: 10.0,   # ±5V
            2: 5.0,    # ±2.5V
            3: 2.5,    # ±1.25V
        }
        
        v_range = voltage_ranges.get(range_code, 20.0)
        voltages = (adc_values / 65535.0) * v_range - (v_range / 2)
        
        return voltages


def test_basic():
    """基本功能测试"""
    print("=== STM32 DAQ Basic Test ===\n")
    
    # 打开设备
    daq = STM32_DAQ()
    daq.open()
    
    # 配置采集
    daq.ai_config(channels=1, range_code=0, sample_rate=10000, samples=1000)
    
    # 开始采集
    daq.ai_start()
    
    # 读取数据
    print("Reading data...")
    adc_data = daq.ai_read(num_samples=1000)
    
    # 停止采集
    daq.ai_stop()
    
    # 转换为电压
    voltages = daq.adc_to_voltage(adc_data, range_code=0)
    
    # 显示统计
    print(f"\nData statistics:")
    print(f"  Samples: {len(voltages)}")
    print(f"  Mean: {np.mean(voltages):.3f}V")
    print(f"  Std: {np.std(voltages):.3f}V")
    print(f"  Min: {np.min(voltages):.3f}V")
    print(f"  Max: {np.max(voltages):.3f}V")
    
    # 绘图
    plt.figure(figsize=(12, 4))
    plt.plot(voltages)
    plt.xlabel('Sample')
    plt.ylabel('Voltage (V)')
    plt.title('ADC Data')
    plt.grid(True)
    plt.show()
    
    # 关闭设备
    daq.close()


def test_continuous():
    """连续采集测试"""
    print("=== Continuous Acquisition Test ===\n")
    
    daq = STM32_DAQ()
    daq.open()
    
    daq.ai_config(channels=1, range_code=0, sample_rate=100000, samples=10000)
    daq.ai_start()
    
    try:
        all_data = []
        for i in range(10):
            data = daq.ai_read(num_samples=1000, timeout=1000)
            all_data.extend(data)
            print(f"Block {i+1}: {len(data)} samples")
        
        voltages = daq.adc_to_voltage(np.array(all_data), range_code=0)
        
        print(f"\nTotal samples: {len(voltages)}")
        print(f"Mean: {np.mean(voltages):.3f}V")
        
    finally:
        daq.ai_stop()
        daq.close()


if __name__ == '__main__':
    # 安装依赖: pip install pyusb numpy matplotlib
    
    # Windows用户需要安装libusb驱动:
    # 1. 下载Zadig: https://zadig.akeo.ie/
    # 2. 运行Zadig,选择STM32设备
    # 3. 选择WinUSB驱动并安装
    
    test_basic()
    # test_continuous()

2. C++ SDK实现

cpp 复制代码
// PC_Software/CPP/MyDAQ_SDK.h
#ifndef MYDAQ_SDK_H
#define MYDAQ_SDK_H

#include <windows.h>
#include <winusb.h>
#include <setupapi.h>
#include <vector>
#include <string>

#pragma comment(lib, "setupapi.lib")
#pragma comment(lib, "winusb.lib")

// 设备GUID (需要在.inf文件中定义相同的GUID)
// {88BAE032-5A81-49f0-BC3D-A4FF138216D6}
DEFINE_GUID(GUID_DEVINTERFACE_MYDAQ,
    0x88bae032, 0x5a81, 0x49f0, 0xbc, 0x3d, 0xa4, 0xff, 0x13, 0x82, 0x16, 0xd6);

class MyDAQ {
public:
    MyDAQ();
    ~MyDAQ();
    
    // 设备管理
    bool Open();
    void Close();
    bool IsOpen() const { return m_winusbHandle != INVALID_HANDLE_VALUE; }
    
    // 设备信息
    struct DeviceInfo {
        char name[32];
        uint8_t version[4];
        uint16_t aiChannels;
        uint16_t aoChannels;
        uint16_t diChannels;
        uint16_t doChannels;
        uint32_t maxSampleRate;
    };
    bool GetDeviceInfo(DeviceInfo& info);
    
    // 模拟输入
    bool AI_Config(uint8_t channels, uint8_t range, uint32_t sampleRate, uint16_t samplesPerCh);
    bool AI_Start();
    bool AI_Stop();
    int AI_Read(std::vector<uint16_t>& data, int maxSamples, int timeoutMs = 1000);
    
    // 数据转换
    static double ADC_ToVoltage(uint16_t adc, uint8_t range);
    static std::vector<double> ADC_ToVoltage(const std::vector<uint16_t>& adc, uint8_t range);
    
private:
    HANDLE m_deviceHandle;
    WINUSB_INTERFACE_HANDLE m_winusbHandle;
    
    // 命令码
    enum Command {
        CMD_GET_INFO = 0x01,
        CMD_AI_CONFIG = 0x10,
        CMD_AI_START = 0x11,
        CMD_AI_STOP = 0x12,
        CMD_AI_READ = 0x13,
        CMD_AO_WRITE = 0x20
    };
    
    // 内部方法
    bool FindDevice(std::wstring& devicePath);
    bool SendCommand(uint8_t cmd, const uint8_t* data, int length);
    bool ReceiveResponse(uint8_t* buffer, int maxLength, int& received, int timeoutMs = 1000);
};

#endif // MYDAQ_SDK_H
cpp 复制代码
// PC_Software/CPP/MyDAQ_SDK.cpp
#include "MyDAQ_SDK.h"
#include <iostream>

MyDAQ::MyDAQ() 
    : m_deviceHandle(INVALID_HANDLE_VALUE)
    , m_winusbHandle(INVALID_HANDLE_VALUE) {
}

MyDAQ::~MyDAQ() {
    Close();
}

bool MyDAQ::FindDevice(std::wstring& devicePath) {
    HDEVINFO deviceInfo = SetupDiGetClassDevs(
        &GUID_DEVINTERFACE_MYDAQ,
        NULL,
        NULL,
        DIGCF_PRESENT | DIGCF_DEVICEINTERFACE
    );
    
    if (deviceInfo == INVALID_HANDLE_VALUE) {
        std::cerr << "SetupDiGetClassDevs failed" << std::endl;
        return false;
    }
    
    SP_DEVICE_INTERFACE_DATA interfaceData;
    interfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
    
    bool found = false;
    
    for (DWORD i = 0; SetupDiEnumDeviceInterfaces(deviceInfo, NULL,
        &GUID_DEVINTERFACE_MYDAQ, i, &interfaceData); i++) {
        
        DWORD requiredSize = 0;
        SetupDiGetDeviceInterfaceDetail(deviceInfo, &interfaceData,
            NULL, 0, &requiredSize, NULL);
        
        PSP_DEVICE_INTERFACE_DETAIL_DATA detailData =
            (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(requiredSize);
        
        if (detailData) {
            detailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
            
            if (SetupDiGetDeviceInterfaceDetail(deviceInfo, &interfaceData,
                detailData, requiredSize, NULL, NULL)) {
                
                devicePath = detailData->DevicePath;
                found = true;
                free(detailData);
                break;
            }
            
            free(detailData);
        }
    }
    
    SetupDiDestroyDeviceInfoList(deviceInfo);
    return found;
}

bool MyDAQ::Open() {
    std::wstring devicePath;
    
    if (!FindDevice(devicePath)) {
        std::cerr << "Device not found" << std::endl;
        return false;
    }
    
    // 打开设备
    m_deviceHandle = CreateFile(
        devicePath.c_str(),
        GENERIC_WRITE | GENERIC_READ,
        FILE_SHARE_WRITE | FILE_SHARE_READ,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
        NULL
    );
    
    if (m_deviceHandle == INVALID_HANDLE_VALUE) {
        std::cerr << "CreateFile failed: " << GetLastError() << std::endl;
        return false;
    }
    
    // 初始化WinUSB
    if (!WinUsb_Initialize(m_deviceHandle, &m_winusbHandle)) {
        std::cerr << "WinUsb_Initialize failed: " << GetLastError() << std::endl;
        CloseHandle(m_deviceHandle);
        m_deviceHandle = INVALID_HANDLE_VALUE;
        return false;
    }
    
    std::cout << "Device opened successfully" << std::endl;
    return true;
}

void MyDAQ::Close() {
    if (m_winusbHandle != INVALID_HANDLE_VALUE) {
        WinUsb_Free(m_winusbHandle);
        m_winusbHandle = INVALID_HANDLE_VALUE;
    }
    
    if (m_deviceHandle != INVALID_HANDLE_VALUE) {
        CloseHandle(m_deviceHandle);
        m_deviceHandle = INVALID_HANDLE_VALUE;
    }
}

bool MyDAQ::SendCommand(uint8_t cmd, const uint8_t* data, int length) {
    uint8_t packet[64] = {0};
    packet[0] = cmd;
    packet[1] = (uint8_t)length;
    
    if (data && length > 0) {
        memcpy(&packet[2], data, length);
    }
    
    ULONG bytesWritten;
    if (!WinUsb_WritePipe(m_winusbHandle, 0x01, packet, sizeof(packet),
        &bytesWritten, NULL)) {
        std::cerr << "WinUsb_WritePipe failed: " << GetLastError() << std::endl;
        return false;
    }
    
    return true;
}

bool MyDAQ::ReceiveResponse(uint8_t* buffer, int maxLength,
                             int& received, int timeoutMs) {
    ULONG bytesRead;
    ULONG timeout = timeoutMs;
    
    // 设置超时
    if (!WinUsb_SetPipePolicy(m_winusbHandle, 0x81,
        PIPE_TRANSFER_TIMEOUT, sizeof(timeout), &timeout)) {
        std::cerr << "WinUsb_SetPipePolicy failed: " << GetLastError() << std::endl;
    }
    
    if (!WinUsb_ReadPipe(m_winusbHandle, 0x81, buffer, maxLength,
        &bytesRead, NULL)) {
        DWORD error = GetLastError();
        if (error != ERROR_SEM_TIMEOUT) {
            std::cerr << "WinUsb_ReadPipe failed: " << error << std::endl;
        }
        return false;
    }
    
    received = (int)bytesRead;
    return true;
}

bool MyDAQ::GetDeviceInfo(DeviceInfo& info) {
    if (!SendCommand(CMD_GET_INFO, NULL, 0)) {
        return false;
    }
    
    uint8_t buffer[512];
    int received;
    
    if (!ReceiveResponse(buffer, sizeof(buffer), received)) {
        return false;
    }
    
    if (received < sizeof(DeviceInfo) + 1 || buffer[0] != 0x00) {
        return false;
    }
    
    memcpy(&info, &buffer[1], sizeof(DeviceInfo));
    return true;
}

bool MyDAQ::AI_Config(uint8_t channels, uint8_t range,
                       uint32_t sampleRate, uint16_t samplesPerCh) {
    uint8_t data[10];
    data[0] = channels;
    data[1] = range;
    *(uint32_t*)&data[2] = sampleRate;
    *(uint16_t*)&data[6] = samplesPerCh;
    
    if (!SendCommand(CMD_AI_CONFIG, data, 8)) {
        return false;
    }
    
    uint8_t response[64];
    int received;
    
    if (!ReceiveResponse(response, sizeof(response), received)) {
        return false;
    }
    
    return response[0] == 0x00;
}

bool MyDAQ::AI_Start() {
    if (!SendCommand(CMD_AI_START, NULL, 0)) {
        return false;
    }
    
    uint8_t response[64];
    int received;
    
    if (!ReceiveResponse(response, sizeof(response), received)) {
        return false;
    }
    
    return response[0] == 0x00;
}

bool MyDAQ::AI_Stop() {
    if (!SendCommand(CMD_AI_STOP, NULL, 0)) {
        return false;
    }
    
    uint8_t response[64];
    int received;
    
    if (!ReceiveResponse(response, sizeof(response), received)) {
        return false;
    }
    
    return response[0] == 0x00;
}

int MyDAQ::AI_Read(std::vector<uint16_t>& data, int maxSamples, int timeoutMs) {
    data.clear();
    data.reserve(maxSamples);
    
    uint8_t buffer[512];
    DWORD startTime = GetTickCount();
    
    while ((int)data.size() < maxSamples) {
        int received;
        if (!ReceiveResponse(buffer, sizeof(buffer), received, 100)) {
            if (GetTickCount() - startTime > (DWORD)timeoutMs) {
                break;
            }
            continue;
        }
        
        if (received < 4) continue;
        
        uint8_t status = buffer[0];
        uint8_t channel = buffer[1];
        uint16_t samples = *(uint16_t*)&buffer[2];
        
        for (int i = 0; i < samples && (int)data.size() < maxSamples; i++) {
            if (4 + i*2 + 1 < received) {
                uint16_t value = *(uint16_t*)&buffer[4 + i*2];
                data.push_back(value);
            }
        }
    }
    
    return (int)data.size();
}

double MyDAQ::ADC_ToVoltage(uint16_t adc, uint8_t range) {
    const double ranges[] = {20.0, 10.0, 5.0, 2.5};  // ±10V, ±5V, ±2.5V, ±1.25V
    double vRange = (range < 4) ? ranges[range] : 20.0;
    
    return (adc / 65535.0) * vRange - (vRange / 2.0);
}

std::vector<double> MyDAQ::ADC_ToVoltage(const std::vector<uint16_t>& adc, uint8_t range) {
    std::vector<double> voltages;
    voltages.reserve(adc.size());
    
    for (uint16_t value : adc) {
        voltages.push_back(ADC_ToVoltage(value, range));
    }
    
    return voltages;
}

3. C++测试程序

cpp 复制代码
// PC_Software/CPP/test_main.cpp
#include "MyDAQ_SDK.h"
#include <iostream>
#include <fstream>
#include <numeric>
#include <cmath>

void PrintStatistics(const std::vector<double>& data) {
    if (data.empty()) return;
    
    double sum = std::accumulate(data.begin(), data.end(), 0.0);
    double mean = sum / data.size();
    
    double sq_sum = 0.0;
    for (double v : data) {
        sq_sum += (v - mean) * (v - mean);
    }
    double std_dev = std::sqrt(sq_sum / data.size());
    
    double min_val = *std::min_element(data.begin(), data.end());
    double max_val = *std::max_element(data.begin(), data.end());
    
    std::cout << "Statistics:" << std::endl;
    std::cout << "  Samples: " << data.size() << std::endl;
    std::cout << "  Mean: " << mean << " V" << std::endl;
    std::cout << "  Std Dev: " << std_dev << " V" << std::endl;
    std::cout << "  Min: " << min_val << " V" << std::endl;
    std::cout << "  Max: " << max_val << " V" << std::endl;
}

void SaveToCSV(const std::vector<double>& data, const char* filename) {
    std::ofstream file(filename);
    file << "Sample,Voltage\n";
    
    for (size_t i = 0; i < data.size(); i++) {
        file << i << "," << data[i] << "\n";
    }
    
    file.close();
    std::cout << "Data saved to " << filename << std::endl;
}

int main() {
    std::cout << "=== STM32 DAQ C++ SDK Test ===" << std::endl << std::endl;
    
    // 创建DAQ对象
    MyDAQ daq;
    
    // 打开设备
    if (!daq.Open()) {
        std::cerr << "Failed to open device" << std::endl;
        return 1;
    }
    
    // 获取设备信息
    MyDAQ::DeviceInfo info;
    if (daq.GetDeviceInfo(info)) {
        std::cout << "Device: " << info.name << std::endl;
        std::cout << "Version: " << (int)info.version[0] << "."
                  << (int)info.version[1] << "."
                  << (int)info.version[2] << std::endl;
        std::cout << "AI Channels: " << info.aiChannels << std::endl;
        std::cout << "Max Sample Rate: " << info.maxSampleRate << " Hz" << std::endl;
        std::cout << std::endl;
    }
    
    // 配置采集
    std::cout << "Configuring acquisition..." << std::endl;
    if (!daq.AI_Config(1, 0, 10000, 1000)) {
        std::cerr << "AI_Config failed" << std::endl;
        return 1;
    }
    
    // 开始采集
    std::cout << "Starting acquisition..." << std::endl;
    if (!daq.AI_Start()) {
        std::cerr << "AI_Start failed" << std::endl;
        return 1;
    }
    
    // 读取数据
    std::cout << "Reading data..." << std::endl;
    std::vector<uint16_t> adc_data;
    int samples_read = daq.AI_Read(adc_data, 1000, 5000);
    
    std::cout << "Read " << samples_read << " samples" << std::endl;
    
    // 停止采集
    daq.AI_Stop();
    
    // 转换为电压
    std::vector<double> voltages = MyDAQ::ADC_ToVoltage(adc_data, 0);
    
    // 显示统计
    PrintStatistics(voltages);
    
    // 保存到文件
    SaveToCSV(voltages, "daq_data.csv");
    
    // 关闭设备
    daq.Close();
    
    std::cout << "\nTest completed successfully!" << std::endl;
    
    return 0;
}

第三部分:使用说明

1. STM32固件编译

bash 复制代码
# 使用STM32CubeIDE或Keil编译
# 1. 用STM32CubeMX配置项目
# 2. 添加上述代码文件
# 3. 编译并下载到STM32H7

2. Windows驱动安装

方法A:使用Zadig安装WinUSB驱动(最简单)
复制代码
1. 下载Zadig: https://zadig.akeo.ie/
2. 插入STM32设备
3. 运行Zadig
4. 选择 "STM32 USB DAQ"
5. 选择 "WinUSB" 驱动
6. 点击 "Install Driver"
方法B:创建INF文件(更专业)
ini 复制代码
; mydaq.inf
[Version]
Signature="$Windows NT$"
Class=USBDevice
ClassGuid={88BAE032-5A81-49f0-BC3D-A4FF138216D6}
Provider=%ProviderName%
DriverVer=01/01/2025,1.0.0.0

[Manufacturer]
%ManufacturerName%=Standard,NTamd64

[Standard.NTamd64]
%DeviceName%=USB_Install, USB\VID_0483&PID_5740

[USB_Install]
Include=winusb.inf
Needs=WINUSB.NT

[USB_Install.Services]
Include=winusb.inf
Needs=WINUSB.NT.Services

[USB_Install.HW]
AddReg=Dev_AddReg

[Dev_AddReg]
HKR,,DeviceInterfaceGUIDs,0x10000,"{88BAE032-5A81-49f0-BC3D-A4FF138216D6}"

[Strings]
ProviderName="MyCompany"
ManufacturerName="MyCompany"
DeviceName="STM32 USB DAQ"

3. Python测试

bash 复制代码
# 安装依赖
pip install pyusb numpy matplotlib

# 运行测试
python daq_test.py

4.C++编译

bash 复制代码
# Visual Studio
# 1. 创建Win32 Console项目
# 2. 添加MyDAQ_SDK.h/cpp和test_main.cpp
# 3. 编译运行

关键要点总结

  1. USB硬件层:STM32H7完全支持USB 2.0 HS (480Mbps)

  2. USB设备类:使用Vendor Specific (0xFF)自定义类

  3. 传输方式:Bulk传输,最适合数据采集

  4. 驱动方案:WinUSB(Windows 8+免驱)

  5. 数据流程

    复制代码
    ADC → DMA → STM32缓冲 → USB Bulk → PC缓冲 → 应用程序
  6. 性能优化

    • 使用DMA传输ADC数据
    • 双缓冲机制(Half/Full回调)
    • USB端点使用最大包(512字节)

这套代码提供了完整的实现框架,可以根据实际硬件(ADC芯片、通道数等)进行调整!

相关推荐
Han.miracle1 小时前
JavaEE--网络编程 传输层 (一) UDP TCP特点
运维·服务器·网络·java-ee·三次握手·四次挥手·超时重传
xixixi777771 小时前
移动通信的基石——公共陆地移动网络
大数据·网络·安全·通信·plmn
深圳市恒讯科技1 小时前
高防服务器支持哪些协议和端口防护?
运维·服务器·网络
虹科网络安全1 小时前
艾体宝案例 | 从“被动合规”到“主动防御”:Capitec如何用KnowBe4安全意识平台重塑金融安全防线
网络·安全·金融
GEMjay1 小时前
学习:《The QUIC Transport Protocol: Design and Internet-Scale Deployment》
网络
玩转以太网1 小时前
W55MH32 单芯片以太网方案:实现TLS加密功能保障工业数据安全传输
网络·物联网
jenchoi4132 小时前
【2025-11-30】软件供应链安全日报:最新漏洞预警与投毒预警情报汇总
网络·安全·web安全·网络安全·npm
Bruce_Liuxiaowei2 小时前
[特殊字符] Mac 高效排查:使用 lsof 查找和管理端口占用进程
网络·macos
Evan芙2 小时前
Rocky Linux 9 双网卡 bond0 绑定
linux·服务器·网络