基于STM32F407的USB CDC通信实现

一、系统概述

1.1 USB CDC(Communication Device Class)介绍

USB CDC类允许将USB设备模拟成串口设备,实现与PC的虚拟串口通信
优点:即插即用、高速传输、免驱动(Windows 10自动识别)

1.2 硬件连接

// STM32F407 USB OTG FS连接
// PA11: USB_DM (Data Minus)
// PA12: USB_DP (Data Plus)
// VBUS: 5V电源检测(可选)

二、完整工程代码

2.1 工程结构

STM32F407_USB_CDC/
├── Core/
│   ├── Inc/
│   │   ├── main.h
│   │   ├── usbd_cdc_if.h
│   │   ├── usb_device.h
│   │   └── ...
│   ├── Src/
│   │   ├── main.c
│   │   ├── usbd_cdc_if.c
│   │   ├── usb_device.c
│   │   └── ...
│   └── Startup/
├── Drivers/
├── Middlewares/
│   └── ST/
│       ├── STM32_USB_Device_Library/
│       └── STM32_USB_Host_Library/
└── MDK-ARM/

2.2 主程序文件

// main.c
#include "main.h"
#include "usb_device.h"
#include "usbd_cdc_if.h"
#include <stdio.h>
#include <string.h>

// 全局变量
PCD_HandleTypeDef hpcd_USB_OTG_FS;
UART_HandleTypeDef huart2;  // 调试串口
TIM_HandleTypeDef htim2;    // 定时器

// 环形缓冲区
#define USB_RX_BUFFER_SIZE 1024
uint8_t usb_rx_buffer[USB_RX_BUFFER_SIZE];
uint16_t usb_rx_write_index = 0;
uint16_t usb_rx_read_index = 0;
uint16_t usb_rx_count = 0;

// 调试信息缓冲区
char debug_buffer[128];

// 系统状态
typedef enum {
    USB_NOT_READY = 0,
    USB_READY,
    USB_BUSY
} USB_Status;

USB_Status usb_status = USB_NOT_READY;
volatile uint8_t usb_connected = 0;

// 函数声明
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART2_UART_Init(void);
static void MX_USB_OTG_FS_PCD_Init(void);
static void MX_TIM2_Init(void);
void Error_Handler(void);
void USB_Transmit(const char* data, uint16_t len);
void USB_Printf(const char* format, ...);
void Process_USB_Data(uint8_t* data, uint16_t len);
void LED_Indicator(void);

int main(void) {
    // HAL库初始化
    HAL_Init();
    
    // 系统时钟配置
    SystemClock_Config();
    
    // 外设初始化
    MX_GPIO_Init();
    MX_USART2_UART_Init();
    MX_USB_OTG_FS_PCD_Init();
    MX_TIM2_Init();
    
    // USB设备初始化
    MX_USB_DEVICE_Init();
    
    // 启动定时器
    HAL_TIM_Base_Start_IT(&htim2);
    
    // 开机信息
    printf("STM32F407 USB CDC Demo Started\r\n");
    printf("System Clock: %ld Hz\r\n", HAL_RCC_GetSysClockFreq());
    printf("USB CDC Virtual COM Port Ready\r\n");
    
    // 主循环
    while (1) {
        // 1. USB数据接收处理
        if (usb_rx_count > 0) {
            uint8_t data;
            data = usb_rx_buffer[usb_rx_read_index];
            usb_rx_read_index = (usb_rx_read_index + 1) % USB_RX_BUFFER_SIZE;
            usb_rx_count--;
            
            // 处理接收到的数据
            static uint8_t cmd_buffer[64];
            static uint8_t cmd_index = 0;
            
            if (data == '\r' || data == '\n') {
                if (cmd_index > 0) {
                    cmd_buffer[cmd_index] = '\0';
                    Process_USB_Data(cmd_buffer, cmd_index);
                    cmd_index = 0;
                }
            } else if (cmd_index < sizeof(cmd_buffer) - 1) {
                cmd_buffer[cmd_index++] = data;
            }
        }
        
        // 2. LED状态指示
        LED_Indicator();
        
        // 3. 低功耗处理
        if (!usb_connected) {
            HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
        }
    }
}

// USB数据接收回调函数
void CDC_RxCallback(uint8_t* Buf, uint32_t *Len) {
    for (uint32_t i = 0; i < *Len; i++) {
        uint16_t next_index = (usb_rx_write_index + 1) % USB_RX_BUFFER_SIZE;
        
        if (next_index != usb_rx_read_index) {  // 缓冲区未满
            usb_rx_buffer[usb_rx_write_index] = Buf[i];
            usb_rx_write_index = next_index;
            usb_rx_count++;
        } else {
            // 缓冲区满,丢弃数据
            break;
        }
    }
    
    // 准备接收下一包数据
    CDC_Receive_FS(Buf, USB_RX_BUFFER_SIZE);
}

// 处理USB接收数据
void Process_USB_Data(uint8_t* data, uint16_t len) {
    char response[128];
    
    printf("Received: %s\r\n", data);
    
    // 命令解析
    if (strcmp((char*)data, "LED ON") == 0) {
        HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_SET);
        sprintf(response, "LED ON OK\r\n");
    } else if (strcmp((char*)data, "LED OFF") == 0) {
        HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_RESET);
        sprintf(response, "LED OFF OK\r\n");
    } else if (strcmp((char*)data, "GET TEMP") == 0) {
        // 模拟温度读取
        float temp = 25.5f;
        sprintf(response, "Temperature: %.1f C\r\n", temp);
    } else if (strcmp((char*)data, "GET VOLT") == 0) {
        // 模拟电压读取
        float voltage = 3.3f;
        sprintf(response, "Voltage: %.2f V\r\n", voltage);
    } else if (strcmp((char*)data, "HELP") == 0) {
        sprintf(response, "Available Commands:\r\n"
                          "LED ON/OFF - Control LED\r\n"
                          "GET TEMP   - Get temperature\r\n"
                          "GET VOLT   - Get voltage\r\n"
                          "HELP       - Show this help\r\n");
    } else {
        sprintf(response, "Unknown command: %s\r\n", data);
    }
    
    // 发送响应
    USB_Transmit(response, strlen(response));
}

// USB数据发送函数
void USB_Transmit(const char* data, uint16_t len) {
    if (usb_status != USB_READY) {
        return;
    }
    
    uint8_t status = CDC_Transmit_FS((uint8_t*)data, len);
    
    if (status != USBD_OK) {
        usb_status = USB_BUSY;
    } else {
        usb_status = USB_READY;
    }
}

// USB格式化输出
void USB_Printf(const char* format, ...) {
    char buffer[256];
    va_list args;
    
    va_start(args, format);
    int len = vsnprintf(buffer, sizeof(buffer), format, args);
    va_end(args);
    
    if (len > 0) {
        USB_Transmit(buffer, len);
    }
}

// LED状态指示
void LED_Indicator(void) {
    static uint32_t last_toggle = 0;
    uint32_t current_time = HAL_GetTick();
    
    if (usb_connected) {
        // USB连接时,LED常亮
        HAL_GPIO_WritePin(GPIOD, GPIO_PIN_13, GPIO_PIN_SET);
    } else {
        // USB未连接时,LED闪烁
        if (current_time - last_toggle > 500) {  // 500ms闪烁
            HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_13);
            last_toggle = current_time;
        }
    }
}

// 定时器中断回调
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIM2) {
        static uint32_t counter = 0;
        counter++;
        
        // 每5秒发送一次状态信息
        if (usb_connected && (counter % 500 == 0)) {
            USB_Printf("System Status: Time=%lus\r\n", counter/100);
        }
    }
}

// USB连接状态回调
void USB_ConnectCallback(uint8_t state) {
    usb_connected = state;
    
    if (state) {
        printf("USB Connected\r\n");
        USB_Printf("STM32F407 USB CDC Ready\r\n");
    } else {
        printf("USB Disconnected\r\n");
    }
}

// 系统时钟配置
void SystemClock_Config(void) {
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
    
    // 配置HSE
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLM = 8;
    RCC_OscInitStruct.PLL.PLLN = 336;
    RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
    RCC_OscInitStruct.PLL.PLLQ = 7;
    
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
        Error_Handler();
    }
    
    // 配置系统时钟
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                                |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
    
    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK) {
        Error_Handler();
    }
    
    // 配置外设时钟
    PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_USART2|RCC_PERIPHCLK_CLK48;
    PeriphClkInitStruct.Usart2ClockSelection = RCC_USART2CLKSOURCE_PCLK1;
    PeriphClkInitStruct.Clk48ClockSelection = RCC_CLK48CLKSOURCE_PLLQ;
    
    if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK) {
        Error_Handler();
    }
}

// USB OTG FS初始化
static void MX_USB_OTG_FS_PCD_Init(void) {
    hpcd_USB_OTG_FS.Instance = USB_OTG_FS;
    hpcd_USB_OTG_FS.Init.dev_endpoints = 4;
    hpcd_USB_OTG_FS.Init.speed = PCD_SPEED_FULL;
    hpcd_USB_OTG_FS.Init.dma_enable = DISABLE;
    hpcd_USB_OTG_FS.Init.phy_itface = PCD_PHY_EMBEDDED;
    hpcd_USB_OTG_FS.Init.Sof_enable = DISABLE;
    hpcd_USB_OTG_FS.Init.low_power_enable = DISABLE;
    hpcd_USB_OTG_FS.Init.lpm_enable = DISABLE;
    hpcd_USB_OTG_FS.Init.vbus_sensing_enable = DISABLE;
    hpcd_USB_OTG_FS.Init.use_dedicated_ep1 = DISABLE;
    
    if (HAL_PCD_Init(&hpcd_USB_OTG_FS) != HAL_OK) {
        Error_Handler();
    }
}

// 串口初始化(调试用)
static void MX_USART2_UART_Init(void) {
    huart2.Instance = USART2;
    huart2.Init.BaudRate = 115200;
    huart2.Init.WordLength = UART_WORDLENGTH_8B;
    huart2.Init.StopBits = UART_STOPBITS_1;
    huart2.Init.Parity = UART_PARITY_NONE;
    huart2.Init.Mode = UART_MODE_TX_RX;
    huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    huart2.Init.OverSampling = UART_OVERSAMPLING_16;
    
    if (HAL_UART_Init(&huart2) != HAL_OK) {
        Error_Handler();
    }
}

// 定时器初始化
static void MX_TIM2_Init(void) {
    TIM_ClockConfigTypeDef sClockSourceConfig = {0};
    TIM_MasterConfigTypeDef sMasterConfig = {0};
    
    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 8400 - 1;  // 84MHz/8400 = 10kHz
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 10 - 1;       // 10kHz/10 = 1kHz (1ms)
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    
    if (HAL_TIM_Base_Init(&htim2) != HAL_OK) {
        Error_Handler();
    }
    
    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK) {
        Error_Handler();
    }
    
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK) {
        Error_Handler();
    }
}

// GPIO初始化
static void MX_GPIO_Init(void) {
    __HAL_RCC_GPIOD_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    // LED引脚配置
    GPIO_InitStruct.Pin = GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
    
    // USB引脚配置
    GPIO_InitStruct.Pin = GPIO_PIN_11 | GPIO_PIN_12;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF10_OTG_FS;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

// 错误处理
void Error_Handler(void) {
    while (1) {
        HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_14);
        HAL_Delay(100);
    }
}

// 重定向printf到串口
int __io_putchar(int ch) {
    HAL_UART_Transmit(&huart2, (uint8_t*)&ch, 1, HAL_MAX_DELAY);
    return ch;
}

2.3 USB CDC接口实现

// usbd_cdc_if.c
#include "usbd_cdc_if.h"

// 全局变量
USBD_HandleTypeDef *hUsbDeviceFS = NULL;
static int8_t CDC_Init_FS(void);
static int8_t CDC_DeInit_FS(void);
static int8_t CDC_Control_FS(uint8_t cmd, uint8_t* pbuf, uint16_t length);
static int8_t CDC_Receive_FS(uint8_t* pbuf, uint32_t *Len);

// USB CDC接口描述符
USBD_CDC_ItfTypeDef USBD_Interface_fops_FS = {
    CDC_Init_FS,
    CDC_DeInit_FS,
    CDC_Control_FS,
    CDC_Receive_FS
};

// 接收缓冲区
uint8_t UserRxBufferFS[APP_RX_DATA_SIZE];
uint8_t UserTxBufferFS[APP_TX_DATA_SIZE];

// CDC初始化
static int8_t CDC_Init_FS(void) {
    // 设置Rx缓冲区
    USBD_CDC_SetRxBuffer(hUsbDeviceFS, UserRxBufferFS);
    
    // 启动接收
    USBD_CDC_ReceivePacket(hUsbDeviceFS);
    
    return (USBD_OK);
}

// CDC反初始化
static int8_t CDC_DeInit_FS(void) {
    return (USBD_OK);
}

// CDC控制命令处理
static int8_t CDC_Control_FS(uint8_t cmd, uint8_t* pbuf, uint16_t length) {
    switch (cmd) {
        case CDC_SEND_ENCAPSULATED_COMMAND:
            break;
            
        case CDC_GET_ENCAPSULATED_RESPONSE:
            break;
            
        case CDC_SET_COMM_FEATURE:
            break;
            
        case CDC_GET_COMM_FEATURE:
            break;
            
        case CDC_CLEAR_COMM_FEATURE:
            break;
            
        case CDC_SET_LINE_CODING:
            // 设置串口参数(波特率、数据位、停止位、校验位)
            // 这些参数存储在pbuf中,但虚拟串口通常不需要处理
            break;
            
        case CDC_GET_LINE_CODING:
            // 返回当前串口参数
            // 设置默认参数:115200,8,N,1
            pbuf[0] = 0x00;   // 波特率 115200
            pbuf[1] = 0xC2;
            pbuf[2] = 0x01;
            pbuf[3] = 0x00;
            pbuf[4] = 0x00;   // 1停止位
            pbuf[5] = 0x00;   // 无校验
            pbuf[6] = 0x08;   // 8数据位
            break;
            
        case CDC_SET_CONTROL_LINE_STATE:
            // 处理DTR/RTS信号
            // 可以用来检测USB连接状态
            if (pbuf[0] & 0x01) {  // DTR置位
                extern void USB_ConnectCallback(uint8_t state);
                USB_ConnectCallback(1);
            } else {  // DTR复位
                extern void USB_ConnectCallback(uint8_t state);
                USB_ConnectCallback(0);
            }
            break;
            
        case CDC_SEND_BREAK:
            break;
            
        default:
            break;
    }
    
    return (USBD_OK);
}

// 数据接收回调
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len) {
    // 调用用户回调函数
    extern void CDC_RxCallback(uint8_t* Buf, uint32_t *Len);
    CDC_RxCallback(Buf, Len);
    
    // 准备接收下一包数据
    USBD_CDC_SetRxBuffer(hUsbDeviceFS, Buf);
    USBD_CDC_ReceivePacket(hUsbDeviceFS);
    
    return (USBD_OK);
}

// 数据发送函数
uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len) {
    uint8_t result = USBD_OK;
    
    // 设置Tx缓冲区
    USBD_CDC_SetTxBuffer(hUsbDeviceFS, Buf, Len);
    
    // 发送数据
    result = USBD_CDC_TransmitPacket(hUsbDeviceFS);
    
    return result;
}

2.4 USB设备配置

// usbd_conf.c
#include "usbd_conf.h"

PCD_HandleTypeDef hpcd_USB_OTG_FS;
void Error_Handler(void);

// USB设备初始化回调
void HAL_PCD_MspInit(PCD_HandleTypeDef* pcdHandle) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    if(pcdHandle->Instance == USB_OTG_FS) {
        // 使能USB OTG FS时钟
        __HAL_RCC_USB_OTG_FS_CLK_ENABLE();
        
        // 配置USB GPIO
        // PA11 - USB_DM
        // PA12 - USB_DP
        __HAL_RCC_GPIOA_CLK_ENABLE();
        
        GPIO_InitStruct.Pin = GPIO_PIN_11 | GPIO_PIN_12;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
        GPIO_InitStruct.Alternate = GPIO_AF10_OTG_FS;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
        
        // 使能USB中断
        HAL_NVIC_SetPriority(OTG_FS_IRQn, 5, 0);
        HAL_NVIC_EnableIRQ(OTG_FS_IRQn);
    }
}

// USB设备反初始化回调
void HAL_PCD_MspDeInit(PCD_HandleTypeDef* pcdHandle) {
    if(pcdHandle->Instance == USB_OTG_FS) {
        // 禁用USB时钟
        __HAL_RCC_USB_OTG_FS_CLK_DISABLE();
        
        // 禁用USB GPIO
        HAL_GPIO_DeInit(GPIOA, GPIO_PIN_11 | GPIO_PIN_12);
        
        // 禁用USB中断
        HAL_NVIC_DisableIRQ(OTG_FS_IRQn);
    }
}

// USB OTG FS中断处理
void OTG_FS_IRQHandler(void) {
    HAL_PCD_IRQHandler(&hpcd_USB_OTG_FS);
}

2.5 USB描述符

// usbd_desc.c
#include "usbd_desc.h"

// USB设备描述符
uint8_t *USBD_FS_DeviceDescriptor(USBD_SpeedTypeDef speed, uint16_t *length) {
    static uint8_t dev_desc[USB_LEN_DEV_DESC] = {
        0x12,                       // bLength
        0x01,                       // bDescriptorType (Device)
        0x00, 0x02,                 // bcdUSB 2.00
        0x02,                       // bDeviceClass (CDC)
        0x00,                       // bDeviceSubClass
        0x00,                       // bDeviceProtocol
        0x40,                       // bMaxPacketSize0
        0x83, 0x04,                 // idVendor (0x0483)
        0x40, 0x57,                 // idProduct (0x5740)
        0x00, 0x02,                 // bcdDevice 2.00
        0x01,                       // iManufacturer
        0x02,                       // iProduct
        0x03,                       // iSerialNumber
        0x01                        // bNumConfigurations
    };
    
    *length = sizeof(dev_desc);
    return dev_desc;
}

// USB配置描述符
uint8_t *USBD_FS_ConfigDescriptor(USBD_SpeedTypeDef speed, uint16_t *length) {
    static uint8_t config_desc[USB_LEN_CONFIG_DESC] = {
        // 配置描述符
        0x09,                       // bLength
        0x02,                       // bDescriptorType (Configuration)
        0x43, 0x00,                 // wTotalLength 67
        0x02,                       // bNumInterfaces
        0x01,                       // bConfigurationValue
        0x00,                       // iConfiguration
        0xC0,                       // bmAttributes (Self Powered)
        0x32,                       // MaxPower 100mA
        
        // 接口0描述符 (通信接口)
        0x09,                       // bLength
        0x04,                       // bDescriptorType (Interface)
        0x00,                       // bInterfaceNumber
        0x00,                       // bAlternateSetting
        0x01,                       // bNumEndpoints
        0x02,                       // bInterfaceClass (CDC)
        0x02,                       // bInterfaceSubClass (ACM)
        0x01,                       // bInterfaceProtocol (AT)
        0x00,                       // iInterface
        
        // 功能描述符
        0x05,                       // bLength
        0x24,                       // bDescriptorType (CS_INTERFACE)
        0x00,                       // bDescriptorSubtype (Header)
        0x10, 0x01,                 // bcdCDC 1.10
        
        0x04,                       // bLength
        0x24,                       // bDescriptorType
        0x02,                       // bDescriptorSubtype (ACM)
        0x02,                       // bmCapabilities
        
        0x05,                       // bLength
        0x24,                       // bDescriptorType
        0x06,                       // bDescriptorSubtype (Union)
        0x00,                       // bMasterInterface
        0x01,                       // bSlaveInterface
        
        0x05,                       // bLength
        0x24,                       // bDescriptorType
        0x01,                       // bDescriptorSubtype (Call Management)
        0x00,                       // bmCapabilities
        0x01,                       // bDataInterface
        
        // 端点描述符 (通知端点)
        0x07,                       // bLength
        0x05,                       // bDescriptorType (Endpoint)
        0x81,                       // bEndpointAddress (IN)
        0x03,                       // bmAttributes (Interrupt)
        0x08, 0x00,                 // wMaxPacketSize
        0xFF,                       // bInterval
        
        // 接口1描述符 (数据接口)
        0x09,                       // bLength
        0x04,                       // bDescriptorType (Interface)
        0x01,                       // bInterfaceNumber
        0x00,                       // bAlternateSetting
        0x02,                       // bNumEndpoints
        0x0A,                       // bInterfaceClass (CDC Data)
        0x00,                       // bInterfaceSubClass
        0x00,                       // bInterfaceProtocol
        0x00,                       // iInterface
        
        // 端点描述符 (数据输出端点)
        0x07,                       // bLength
        0x05,                       // bDescriptorType (Endpoint)
        0x02,                       // bEndpointAddress (OUT)
        0x02,                       // bmAttributes (Bulk)
        0x40, 0x00,                 // wMaxPacketSize
        0x00,                       // bInterval
        
        // 端点描述符 (数据输入端点)
        0x07,                       // bLength
        0x05,                       // bDescriptorType (Endpoint)
        0x82,                       // bEndpointAddress (IN)
        0x02,                       // bmAttributes (Bulk)
        0x40, 0x00,                 // wMaxPacketSize
        0x00                        // bInterval
    };
    
    *length = sizeof(config_desc);
    return config_desc;
}

// 字符串描述符
uint8_t *USBD_FS_StringStrDescriptor(USBD_SpeedTypeDef speed, uint8_t idx, uint16_t *length) {
    static uint8_t string_desc[256];
    
    if (idx == 0) {
        // 语言ID
        string_desc[0] = 0x04;
        string_desc[1] = 0x03;
        string_desc[2] = 0x09;
        string_desc[3] = 0x04;
        *length = 4;
    } else if (idx == 1) {
        // 厂商字符串
        const char manufacturer[] = "STMicroelectronics";
        string_desc[0] = 2 + strlen(manufacturer) * 2;
        string_desc[1] = 0x03;
        for (int i = 0; i < strlen(manufacturer); i++) {
            string_desc[2 + i * 2] = manufacturer[i];
            string_desc[3 + i * 2] = 0;
        }
        *length = string_desc[0];
    } else if (idx == 2) {
        // 产品字符串
        const char product[] = "STM32 Virtual COM Port";
        string_desc[0] = 2 + strlen(product) * 2;
        string_desc[1] = 0x03;
        for (int i = 0; i < strlen(product); i++) {
            string_desc[2 + i * 2] = product[i];
            string_desc[3 + i * 2] = 0;
        }
        *length = string_desc[0];
    } else if (idx == 3) {
        // 序列号
        const char serial[] = "00000000001A";
        string_desc[0] = 2 + strlen(serial) * 2;
        string_desc[1] = 0x03;
        for (int i = 0; i < strlen(serial); i++) {
            string_desc[2 + i * 2] = serial[i];
            string_desc[3 + i * 2] = 0;
        }
        *length = string_desc[0];
    } else {
        return NULL;
    }
    
    return string_desc;
}

三、PC端测试程序(Python)

# usb_cdc_test.py
import serial
import time
import threading
import sys

class USB_CDC_Test:
    def __init__(self, port='COM3', baudrate=115200):
        self.port = port
        self.baudrate = baudrate
        self.serial = None
        self.running = False
        self.receive_thread = None
        
    def connect(self):
        """连接串口"""
        try:
            self.serial = serial.Serial(
                port=self.port,
                baudrate=self.baudrate,
                bytesize=serial.EIGHTBITS,
                parity=serial.PARITY_NONE,
                stopbits=serial.STOPBITS_ONE,
                timeout=1
            )
            
            if self.serial.is_open:
                print(f"Connected to {self.port}")
                return True
            else:
                print(f"Failed to connect to {self.port}")
                return False
                
        except serial.SerialException as e:
            print(f"Serial error: {e}")
            return False
            
    def disconnect(self):
        """断开连接"""
        if self.serial and self.serial.is_open:
            self.serial.close()
            print("Disconnected")
            
    def start_receive_thread(self):
        """启动接收线程"""
        self.running = True
        self.receive_thread = threading.Thread(target=self.receive_data)
        self.receive_thread.daemon = True
        self.receive_thread.start()
        
    def stop_receive_thread(self):
        """停止接收线程"""
        self.running = False
        if self.receive_thread:
            self.receive_thread.join(timeout=1)
            
    def receive_data(self):
        """接收数据线程"""
        while self.running and self.serial and self.serial.is_open:
            try:
                if self.serial.in_waiting > 0:
                    data = self.serial.read(self.serial.in_waiting)
                    if data:
                        self.process_received_data(data)
                time.sleep(0.01)
            except Exception as e:
                print(f"Receive error: {e}")
                break
                
    def process_received_data(self, data):
        """处理接收到的数据"""
        try:
            text = data.decode('utf-8', errors='ignore')
            print(f"Received: {text}", end='')
        except Exception as e:
            print(f"Decode error: {e}")
            
    def send_command(self, command):
        """发送命令"""
        if self.serial and self.serial.is_open:
            try:
                cmd = command + '\r\n'
                self.serial.write(cmd.encode())
                print(f"Sent: {command}")
            except Exception as e:
                print(f"Send error: {e}")
        else:
            print("Serial port not open")
            
    def interactive_mode(self):
        """交互模式"""
        print("\n=== STM32 USB CDC Test ===")
        print("Commands:")
        print("  led on     - Turn LED ON")
        print("  led off    - Turn LED OFF")
        print("  get temp   - Get temperature")
        print("  get volt   - Get voltage")
        print("  help       - Show help")
        print("  exit       - Exit program")
        print("===========================\n")
        
        self.start_receive_thread()
        
        while True:
            try:
                cmd = input("Enter command: ").strip().lower()
                
                if cmd == 'exit':
                    break
                elif cmd == 'help':
                    self.send_command("HELP")
                elif cmd == 'led on':
                    self.send_command("LED ON")
                elif cmd == 'led off':
                    self.send_command("LED OFF")
                elif cmd == 'get temp':
                    self.send_command("GET TEMP")
                elif cmd == 'get volt':
                    self.send_command("GET VOLT")
                else:
                    print("Unknown command")
                    
            except KeyboardInterrupt:
                print("\nExiting...")
                break
            except Exception as e:
                print(f"Error: {e}")
                
        self.stop_receive_thread()
        self.disconnect()
        
    def auto_test(self):
        """自动测试模式"""
        if not self.connect():
            return
            
        self.start_receive_thread()
        time.sleep(2)  # 等待连接稳定
        
        test_commands = [
            "HELP",
            "LED ON",
            "GET TEMP",
            "GET VOLT",
            "LED OFF"
        ]
        
        for cmd in test_commands:
            print(f"\n>>> Testing command: {cmd}")
            self.send_command(cmd)
            time.sleep(1)  # 等待响应
            
        time.sleep(2)
        self.stop_receive_thread()
        self.disconnect()
        
def main():
    """主函数"""
    # 检测可用串口
    import serial.tools.list_ports
    
    ports = list(serial.tools.list_ports.comports())
    
    if not ports:
        print("No COM ports found!")
        return
        
    print("Available COM ports:")
    for i, port in enumerate(ports):
        print(f"  {i+1}. {port.device} - {port.description}")
        
    # 选择端口
    try:
        choice = int(input("\nSelect port number (0 for auto test): "))
        
        if choice == 0:
            # 自动测试所有端口
            for port in ports:
                print(f"\nTrying {port.device}...")
                test = USB_CDC_Test(port.device)
                test.auto_test()
        elif 1 <= choice <= len(ports):
            port_name = ports[choice-1].device
            test = USB_CDC_Test(port_name)
            
            mode = input("Select mode (1: Interactive, 2: Auto test): ")
            
            if mode == '1':
                if test.connect():
                    test.interactive_mode()
            elif mode == '2':
                test.auto_test()
            else:
                print("Invalid mode")
        else:
            print("Invalid choice")
            
    except ValueError:
        print("Invalid input")
    except Exception as e:
        print(f"Error: {e}")
        
if __name__ == "__main__":
    main()

四、Keil5工程配置

4.1 工程设置

Target Options:
- Device: STM32F407VGTx
- Target: 
  - Xtal: 8.0MHz
  - Use MicroLIB: ✓
  - Optimization: Level 2 (-O2)
  
C/C++:
- Define: USE_HAL_DRIVER, STM32F407xx, USE_USB_OTG_FS
- Include Paths: Add all required paths

4.2 链接器设置

// STM32F407VGTx_FLASH.ld
MEMORY
{
    RAM    (xrw)    : ORIGIN = 0x20000000,   LENGTH = 128K
    FLASH  (rx)     : ORIGIN = 0x8000000,    LENGTH = 1024K
}

五、USB描述符工具

# usb_descriptor_generator.py
def generate_cdc_descriptor(vid=0x0483, pid=0x5740):
    """生成CDC描述符"""
    desc = {
        'device': [
            0x12, 0x01, 0x00, 0x02, 0x02, 0x00, 0x00, 0x40,
            vid & 0xFF, vid >> 8,
            pid & 0xFF, pid >> 8,
            0x00, 0x02, 0x01, 0x02, 0x03, 0x01
        ],
        'config': [
            # 配置描述符
            0x09, 0x02, 0x43, 0x00, 0x02, 0x01, 0x00, 0xC0, 0x32,
            # 接口0
            0x09, 0x04, 0x00, 0x00, 0x01, 0x02, 0x02, 0x01, 0x00,
            # 功能描述符
            0x05, 0x24, 0x00, 0x10, 0x01,
            0x04, 0x24, 0x02, 0x02,
            0x05, 0x24, 0x06, 0x00, 0x01,
            0x05, 0x24, 0x01, 0x00, 0x01,
            # 端点(通知)
            0x07, 0x05, 0x81, 0x03, 0x08, 0x00, 0xFF,
            # 接口1
            0x09, 0x04, 0x01, 0x00, 0x02, 0x0A, 0x00, 0x00, 0x00,
            # 端点(OUT)
            0x07, 0x05, 0x02, 0x02, 0x40, 0x00, 0x00,
            # 端点(IN)
            0x07, 0x05, 0x82, 0x02, 0x40, 0x00, 0x00
        ]
    }
    
    return desc

def print_descriptor_c_code(desc):
    """输出C语言格式的描述符"""
    print("// USB Device Descriptor")
    print(f"static uint8_t dev_desc[{len(desc['device'])}] = {{")
    for i, byte in enumerate(desc['device']):
        if i % 8 == 0:
            print("    ", end="")
        print(f"0x{byte:02X},", end=" ")
        if i % 8 == 7:
            print()
    print("};\n")
    
    print("// USB Configuration Descriptor")
    print(f"static uint8_t config_desc[{len(desc['config'])}] = {{")
    for i, byte in enumerate(desc['config']):
        if i % 8 == 0:
            print("    ", end="")
        print(f"0x{byte:02X},", end=" ")
        if i % 8 == 7:
            print()
    print("};")

参考代码 基于STM32F407开发的USB的CDC模式与电脑进行通信 www.youwenfan.com/contentcnu/70387.html

六、高级功能扩展

6.1 大容量数据传输

// usb_cdc_bulk.c
#include "usb_cdc_bulk.h"

#define BULK_BUFFER_SIZE 4096
#define PACKET_SIZE 64

typedef struct {
    uint8_t data[BULK_BUFFER_SIZE];
    uint32_t length;
    uint32_t transferred;
    uint8_t in_progress;
} BulkTransfer;

BulkTransfer bulk_tx, bulk_rx;

// 大容量数据发送
uint8_t USB_Bulk_Transmit(uint8_t* data, uint32_t length) {
    if (bulk_tx.in_progress || length > BULK_BUFFER_SIZE) {
        return 0;
    }
    
    memcpy(bulk_tx.data, data, length);
    bulk_tx.length = length;
    bulk_tx.transferred = 0;
    bulk_tx.in_progress = 1;
    
    // 开始传输
    USB_Bulk_Transmit_Next();
    
    return 1;
}

// 传输下一包
void USB_Bulk_Transmit_Next(void) {
    if (!bulk_tx.in_progress) return;
    
    uint32_t remaining = bulk_tx.length - bulk_tx.transferred;
    uint32_t to_send = (remaining > PACKET_SIZE) ? PACKET_SIZE : remaining;
    
    if (to_send > 0) {
        CDC_Transmit_FS(&bulk_tx.data[bulk_tx.transferred], to_send);
        bulk_tx.transferred += to_send;
    } else {
        bulk_tx.in_progress = 0;
    }
}

// 传输完成回调
void CDC_TxCompleteCallback(void) {
    if (bulk_tx.in_progress) {
        USB_Bulk_Transmit_Next();
    }
}

6.2 数据流控制

// usb_cdc_flow.c
#include "usb_cdc_flow.h"

typedef struct {
    uint32_t baud_rate;
    uint8_t stop_bits;
    uint8_t parity;
    uint8_t data_bits;
} LineCoding;

LineCoding line_coding = {115200, 0, 0, 8};
volatile uint8_t dtr_state = 0;
volatile uint8_t rts_state = 0;

// 设置流控制
void USB_SetFlowControl(uint8_t enable) {
    if (enable) {
        // 硬件流控制
        // 等待CTS信号
    } else {
        // 软件流控制
    }
}

// 获取连接状态
uint8_t USB_IsConnected(void) {
    return dtr_state;  // DTR表示连接状态
}

// 设置波特率
void USB_SetBaudRate(uint32_t baud) {
    line_coding.baud_rate = baud;
    // 重新配置串口(如果需要)
}

七、故障排除

7.1 常见问题解决

// 1. USB不识别
// 检查项:
// - 电源是否正常(5V)
// - USB线是否完好
// - PA11/PA12连接是否正确
// - 上拉电阻是否连接

// 2. 数据传输不稳定
// 解决方案:
// - 增加USB缓冲区大小
// - 添加数据校验
// - 降低传输速率

7.2 调试信息输出

// debug_usb.c
#include "debug_usb.h"

void USB_Debug_PrintStatus(void) {
    char status[256];
    
    sprintf(status, 
           "USB Status:\r\n"
           "  Connected: %s\r\n"
           "  DTR: %s\r\n"
           "  RTS: %s\r\n"
           "  Baud: %lu\r\n"
           "  Buffer Free: %d/%d\r\n",
           usb_connected ? "Yes" : "No",
           dtr_state ? "High" : "Low",
           rts_state ? "High" : "Low",
           line_coding.baud_rate,
           USB_RX_BUFFER_SIZE - usb_rx_count,
           USB_RX_BUFFER_SIZE);
           
    USB_Transmit(status, strlen(status));
}

八、性能优化

8.1 零拷贝传输

// usb_cdc_zero_copy.c
typedef struct {
    uint8_t* buffer;
    uint32_t size;
    uint8_t ready;
} ZeroCopyBuffer;

ZeroCopyBuffer zcb_tx, zcb_rx;

// 零拷贝发送
uint8_t* USB_GetTxBuffer(uint32_t size) {
    if (zcb_tx.ready || size > USB_TX_BUFFER_SIZE) {
        return NULL;
    }
    
    zcb_tx.buffer = usb_tx_buffer;
    zcb_tx.size = size;
    zcb_tx.ready = 1;
    
    return zcb_tx.buffer;
}

// 提交发送
void USB_CommitTxBuffer(void) {
    if (zcb_tx.ready) {
        CDC_Transmit_FS(zcb_tx.buffer, zcb_tx.size);
        zcb_tx.ready = 0;
    }
}
posted @ 2026-05-18 09:06  晃悠人生  阅读(115)  评论(0)    收藏  举报