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:
- 硬件上:STM32H7有USB控制器,接好USB口即可
- 固件上:使用HAL库配置USB设备
- 上位机:使用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(¤tConfig, 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. 编译运行
关键要点总结
-
USB硬件层:STM32H7完全支持USB 2.0 HS (480Mbps)
-
USB设备类:使用Vendor Specific (0xFF)自定义类
-
传输方式:Bulk传输,最适合数据采集
-
驱动方案:WinUSB(Windows 8+免驱)
-
数据流程:
ADC → DMA → STM32缓冲 → USB Bulk → PC缓冲 → 应用程序 -
性能优化:
- 使用DMA传输ADC数据
- 双缓冲机制(Half/Full回调)
- USB端点使用最大包(512字节)
这套代码提供了完整的实现框架,可以根据实际硬件(ADC芯片、通道数等)进行调整!