STM32开发(19)----CubeMX配置PWR电源管理

文章详细介绍了如何使用STM32CubeMX配置PWR电源管理,包括电源系统、电源监控器和电源管理模式。通过实验展示了如何进入SLEEP_MODE、STOP_MODE和STANDBY_MODE,强调了低功耗模式在实际项目中的重要性。


前言

本章介绍使用STM32CubeMX对PWR进行配置的方法,PWR的原理、概念和特点,配置各个步骤的功能,并通过实验方式实现展示结果。

一、什么是PWR?

电源对电子设备的重要性不言而喻,它是保证系统稳定运行的基础,而保证系统能稳定运行后,又有低功耗的要求。在很多应用场合中都对电子设备的功耗要求非常苛刻,如某些传感器信息采集设备,仅靠小型的电池提供电源,要求工作长达数年之久,且期间不需要任何维护;由于智慧穿戴设备的小型化要求,电池体积不能太大导致容量也比较小,所以也很有必要从控制功耗入手,提高设备的续行时间。因此, STM32 有专门的电源管理外设监控电源并管理设备的运行模式,确保系统正常运行,并尽量降低器件的功耗。

PWR分为下列几个部分内容:

1.电源系统

为了方便进行电源管理, STM32 把它的外设、内核等模块跟据功能划分了供电区域
在这里插入图片描述
从上图可知,STM32 的电源系统主要分为备份域供电、内核供电以及 AD/传感器供电三部分:
① AD/传感器电路供电和参考电压
STM32 的 ADC/温度传感器 模块配备独立的供电方式

② 电压调节器
在 STM32 的电源系统中调压器供电的电路是最主要的部分,调压器为备份域及待机电路以外的所有数字电路供电,其中包括内核、数字外设以及 RAM,调压器的输出电压约为 1.8V,因而使用调压器供电的这些电路区域被称为 1.8V 域。调压器可以运行在“运行模式”、“停止模式”以及“待机模式”。在运行模式下, 1.8V域全功率运行;在停止模式下 1.8V 域运行在低功耗状态, 1.8V 区域的所有时钟都被关闭,相应的外设都停止了工作,但它会保留内核寄存器以及 SRAM 的内容;在待机模式下,整个 1.8V 域都断电,该区域的内核寄存器及 SRAM 内容都会丢失 (备份区域的寄存器不受影响)

③ 电池备份区域
电池备份区域也就是后备供电区域,使用电池或者其他电源连接到 VBAT 脚上,当 VDD断电时,可以保存备份寄存器的内容和维持 RTC 的功能。同时 VBAT 引脚也为 RTC 和 LSE 振荡器供电,这保证了当主要电源被切断时, RTC 能够继续工作。切换到 VBAT 供电由复位模块中的掉电复位功能控制。

2.电源监控器

STM32 芯片主要通过引脚 VDD 从外部获取电源,在它的内部具有编程电压检测器(PVD)用于检测 VDD 的电压,以实现复位功能及掉电紧急处理功能,保证系统可靠地运行。
在这里插入图片描述
从上图中可知: POR、 PDR 功能是使用其电压阈值与外部供电电压 VDD 比较,当低于工作阈值时,会直接进入复位状态,这可防止电压不足导致的误操作。除此之外, STM32 还提供了可编程电压检测器 PVD,它也是实时检测 VDD 的电压,当检测到电压低于编程的 VPVD 阈值时,会向内核产生一个 PVD 中断 (EXTI16 线中断) 以使内核在复位前进行紧急处理。

3.电源管理

在 STM32 的除了正常工作外具有三种低功耗模式:睡眠、停止以及待机。在上电复位后, STM32 处于运行状态时,当内核不需要继续运行,就可以选择进入后面的三种模式降低功耗。这三种低功耗模式电源消耗不同、唤醒时间不同和唤醒源不同。
睡眠模式:
在这里插入图片描述
停止模式:
在这里插入图片描述
待机模式:
在这里插入图片描述

二、实验过程

1.STM32CubeMX配置

选择芯片stm32f103c6t6,新建工程
在这里插入图片描述

设置时钟源,最小系统外部晶振8Mhz,作为外部高速HSE时钟源。由于没有外接外部低速晶振,这里低速时钟源选择旁路时钟源。

在这里插入图片描述

配置时钟树,这里使用官方推荐的配置

在这里插入图片描述
配置GPIO中断用于唤醒
在这里插入图片描述
使能中断
在这里插入图片描述
配置串口用于打印测试
在这里插入图片描述
USART1的参数配置如下,波特率115200,传输数据长度为8 Bit,奇偶检验无,停止位1.其他参数默认
在这里插入图片描述
RTC配置
在这里插入图片描述
Code Generator中设置只拷贝使用到的库,分离.c和.h文件
在这里插入图片描述

设置好项目名称和路径,点击GENERATE CODE即可,生成后使用keil5 IDE打开。
在这里插入图片描述

2.代码实现

在usart.c文件后面添加如下代码,代码中添加了#ifdef宏定义进行条件编译,如果使用GUNC编译,则PUTCHAR_PROTOTYPE 定义为int __io_putchar(int ch)函数,否则定义为int fputc(int ch, FILE *f)函数。

/* USER CODE BEGIN 0 */
#include "stdio.h"
#ifdef __GNUC__
  /* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf
     set to 'Yes') calls __io_putchar() */
  #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
  #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
/**
  * @brief  Retargets the C library printf function to the USART.
  * @param  None
  * @retval None
  */
PUTCHAR_PROTOTYPE
{
  /* Place your implementation of fputc here */
  /* e.g. write a character to the EVAL_COM1 and Loop until the end of transmission */
  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
 
  return ch;
}
/* USER CODE END 0 */

SLEEP_MODE模式

睡眠模式: 内核停止,程序以WFI指令进入睡眠模式,所以只要产生任意中断都会退出睡眠模式。所以进入睡眠模式前先调用HAL_SuspendTick()函数挂起系统滴答定时器,否则将会被系统滴答定时器(SysTick)中断在1ms内唤醒。程序运行到HAL_PWR_EnterSLEEPMode()函数时,系统进入睡眠模式,程序停止运行。当按下WAKEUP按键时,触发外部中断0,此时系统被唤醒。继续执行HAL_ResumeTick()语句回复系统滴答定时器。

main函数如下:

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_RTC_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  printf("\r\n********    STM32F13c6t6 LowPower Test  *******\r\n");
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    printf("Press WAKEUP button to enter LP modes \r\n\r\n");
  
    printf("SleepMode!\r\nPress WAKE_UP button to wake up ...\r\n");
    HAL_SuspendTick();
  
     /* Request to enter SLEEP mode */
    HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
  
    /* Resume Tick interrupt if disabled prior to sleep mode entry */
    HAL_ResumeTick();
    printf("\r\nSLEEP_MODE wake up,system running continue ... \r\n");
         
}
  /* USER CODE END 3 */
}

实验结果

程序开始,进入低功耗模式,触发中断系统继续执行,运行结果如下
在这里插入图片描述

STOP_MODE模式

停止模式: 内核停止,外设也停止工作,所有外设时钟也关闭。此处设置了RTC唤醒。RTC时钟配置为外部时钟,频率为32.768KHz。经过16分频后的时基为16 /(~32.768KHz) = ~0,488 ms。若要20s后唤醒,则唤醒计数器的值为~20s/0,488ms = 40983 = 0xA017。由于停止模式PLL、HSI、HSE RC振荡器均被禁止,所以系统被唤醒(RTC唤醒或WAKUP按键外部中断唤醒)后需要重新配置系统时钟,同时关闭WAKEUP定时器。
由于外设时钟停止,所以程序返回main()函数中也需要重新初始化串口才能打印输出信息。

/* USER CODE BEGIN 0 */
static void SYSCLKConfig_STOP(void)
{
  RCC_ClkInitTypeDef RCC_ClkInitStruct;
  RCC_OscInitTypeDef RCC_OscInitStruct;
  uint32_t pFLatency = 0;
  
  /* Get the Oscillators configuration according to the internal RCC registers */
  HAL_RCC_GetOscConfig(&RCC_OscInitStruct);
  
  /* After wake-up from STOP reconfigure the system clock: Enable HSE and PLL */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  if(HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /* Get the Clocks configuration according to the internal RCC registers */
  HAL_RCC_GetClockConfig(&RCC_ClkInitStruct, &pFLatency);
  
  /* Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2 
     clocks dividers */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  if(HAL_RCC_ClockConfig(&RCC_ClkInitStruct, pFLatency) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_RTC_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  printf("\r\n********    STM32F13c6t6 LowPower Test  *******\r\n");
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    printf("Press WAKEUP button to enter STOP modes \r\n\r\n");

    printf("STOPpMode!\r\nPress WAKE_UP button to wake up ...\r\n");
    
 
    HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
		SYSCLKConfig_STOP();
    MX_USART1_UART_Init();
    printf("\r\nSTOP_MODE wake up,system running continue ... \r\n");
}
  /* USER CODE END 3 */
}

实验结果

程序开始,进入STOP_MODE低功耗模式,运行结果如下
在这里插入图片描述

STANDBY_MODE模式

待机模式:此时内核停止,外设也停止工作,1.2V域断电,SRAM和寄存器内容将丢失。待机模式唤醒后是重新运行程序,相当于重启,而不是在程序原来的位置继续运行。待机模式只能从WAKEUP引脚上升沿或下降沿,RTC,复位引脚,IWDG(独立看门狗) 复位唤醒。不能通过其他中断唤醒。前面睡眠模式和停止模式可以通过WAKEUP引脚的外部中断唤醒,但是待机模式不能通过外部中断唤醒。

实验过程以后再添加

总结

本章介绍了STM32的几种低功耗模式,这个在实际项目中用的非常多,特别是STOP_MODE和SLEEP_MODE

<think>我们正在将STM32CubeMX生成的HID设备配置(包括报告描述符)迁移到标准库(如STM32 USB Device Library)。标准库通常指ST提供的旧版USB库(如STM32_USB-FS-Device_Lib)。迁移的关键步骤包括:描述符数据移植、类驱动回调函数实现、端点配置和USB中断处理。 步骤1:提取CubeMX生成的描述符数据 在CubeMX生成的工程中,找到报告描述符和设备描述符。通常位于`Core/Src/usbd_hid.c`或类似文件中。复制报告描述符数组。 步骤2:在标准库工程中创建HID类驱动文件 标准库通常有类似`usbd_custom_hid`的模板。我们可以基于此创建HID类驱动文件。需要实现以下回调函数: - `USBD_CUSTOM_HID_Init` - `USBD_CUSTOM_HID_DeInit` - `USBD_CUSTOM_HID_Setup` (处理控制请求) - `USBD_CUSTOM_HID_EP0_TxReady` - `USBD_CUSTOM_HID_EP0_RxReady` - `USBD_CUSTOM_HID_DataIn` (发送完成回调) - `USBD_CUSTOM_HID_DataOut` (接收完成回调) 步骤3:设备描述符和配置描述符的修改 将CubeMX生成的设备描述符、配置描述符、字符串描述符等复制到标准库的描述符文件(如`usbd_desc.c`)中。注意配置描述符中关于HID接口的部分要与CubeMX生成的一致。 步骤4:报告描述符的注册 在标准库中,报告描述符需要在HID类初始化时注册。通常在`USBD_CUSTOM_HID_Init`函数中通过调用`USBD_LL_Init`等函数设置。 步骤5:端点配置 根据CubeMX配置,在标准库中初始化相同的端点。例如,CubeMX配置了中断IN端点1,则标准库中也需要在`USBD_LL_Init`中配置端点1为中断IN。 步骤6:实现报告发送函数 标准库中发送报告的机制与HAL不同。标准库使用`USBD_LL_Transmit`函数发送数据。 步骤7:修改USB初始化流程 在main函数中,初始化USB设备库,并注册HID类。 下面提供关键代码示例: 1. 报告描述符(取自CubeMX,假设为键盘): ```c /* 报告描述符 */ const uint8_t hidReportDescriptor[41] = { 0x05, 0x01, // USAGE_PAGE (Generic Desktop) 0x09, 0x06, // USAGE (Keyboard) 0xa1, 0x01, // COLLECTION (Application) 0x05, 0x07, // USAGE_PAGE (Keyboard) 0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl) 0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0x01, // LOGICAL_MAXIMUM (1) 0x75, 0x01, // REPORT_SIZE (1) 0x95, 0x08, // REPORT_COUNT (8) 0x81, 0x02, // INPUT (Data,Var,Abs) 0x95, 0x01, // REPORT_COUNT (1) 0x75, 0x08, // REPORT_SIZE (8) 0x81, 0x03, // INPUT (Cnst,Var,Abs) 0x95, 0x05, // REPORT_COUNT (5) 0x75, 0x01, // REPORT_SIZE (1) 0x05, 0x08, // USAGE_PAGE (LEDs) 0x19, 0x01, // USAGE_MINIMUM (Num Lock) 0x29, 0x05, // USAGE_MAXIMUM (Kana) 0x91, 0x02, // OUTPUT (Data,Var,Abs) 0x95, 0x01, // REPORT_COUNT (1) 0x75, 0x03, // REPORT_SIZE (3) 0x91, 0x03, // OUTPUT (Cnst,Var,Abs) 0x95, 0x06, // REPORT_COUNT (6) 0x75, 0x08, // REPORT_SIZE (8) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0xFF, // LOGICAL_MAXIMUM (255) 0x05, 0x07, // USAGE_PAGE (Keyboard) 0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated)) 0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application) 0x81, 0x00, // INPUT (Data,Ary,Abs) 0xc0 // END_COLLECTION }; ``` 2. 在标准库中,我们创建一个自定义HID类驱动文件(如`usbd_customhid.c`): ```c #include "usbd_customhid.h" /* 定义端点地址和大小(根据CubeMX配置) */ #define CUSTOMHID_EPIN_ADDR 0x81 #define CUSTOMHID_EPIN_SIZE 0x40 /* 64字节 */ /* 报告描述符 */ static const uint8_t hidReportDescriptor[41] = { ... }; /* 如上 */ /* 设备描述符、配置描述符等在其他文件(usbd_desc.c)中定义 */ /* 类驱动回调结构体 */ USBD_Class_cb_TypeDef USBD_CUSTOM_HID_cb = { USBD_CUSTOM_HID_Init, USBD_CUSTOM_HID_DeInit, USBD_CUSTOM_HID_Setup, NULL, /* EP0_TxSent */ USBD_CUSTOM_HID_EP0_RxReady, USBD_CUSTOM_HID_DataIn, USBD_CUSTOM_HID_DataOut, NULL, /* SOF */ NULL, /* IsoINIncomplete */ NULL /* IsoOUTIncomplete */ }; /* 初始化函数 */ uint8_t USBD_CUSTOM_HID_Init(USBD_HandleTypeDef *pdev, uint8_t cfgidx) { /* 打开接收端点 */ USBD_LL_OpenEP(pdev, CUSTOMHID_EPIN_ADDR, USBD_EP_TYPE_INTR, CUSTOMHID_EPIN_SIZE); /* 准备接收(如果需要OUT端点) */ // USBD_LL_PrepareReceive(pdev, CUSTOMHID_EPOUT_ADDR, ...); return USBD_OK; } /* 其他回调函数实现(根据需求) */ uint8_t USBD_CUSTOM_HID_Setup(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req) { /* 处理HID类特定请求 */ switch (req->bRequest) { case USB_REQ_GET_DESCRIPTOR: /* 处理报告描述符请求 */ if ((req->wValue >> 8) == HID_REPORT_DESC) { USBD_CtlSendData(pdev, (uint8_t*)hidReportDescriptor, sizeof(hidReportDescriptor)); } break; case HID_SET_IDLE: /* 处理SET_IDLE请求 */ USBD_CtlSendStatus(pdev); break; default: USBD_CtlError(pdev, req); return USBD_FAIL; } return USBD_OK; } /* 数据发送函数(供应用程序调用) */ uint8_t USBD_CUSTOM_HID_SendReport(USBD_HandleTypeDef *pdev, uint8_t *report, uint16_t len) { /* 检查端点是否忙 */ if(pdev->ep_in[CUSTOMHID_EPIN_ADDR & 0x7F].busy) { return USBD_BUSY; } USBD_LL_Transmit(pdev, CUSTOMHID_EPIN_ADDR, report, len); return USBD_OK; } ``` 3. 在`usbd_desc.c`中,配置描述符需要包含HID接口: ```c /* 配置描述符 */ const uint8_t USBD_ConfigDescriptor[USB_CONFIG_DESC_SIZ] = { /* 配置描述符 */ 0x09, /* bLength: 9字节 */ USB_CONFIGURATION_DESCRIPTOR_TYPE, /* bDescriptorType: 配置 */ USB_CONFIG_DESC_SIZ, /* wTotalLength: 包括接口和端点 */ 0x01, /* bNumInterfaces: 1个接口 */ 0x01, /* bConfigurationValue: 配置1 */ 0x00, /* iConfiguration: 无字符串 */ 0x80, /* bmAttributes: 总线供电 */ 0x32, /* MaxPower 100mA */ /* 接口描述符 */ 0x09, /* bLength: 9 */ USB_INTERFACE_DESCRIPTOR_TYPE, /* bDescriptorType: 接口 */ 0x00, /* bInterfaceNumber: 0 */ 0x00, /* bAlternateSetting: 0 */ 0x01, /* bNumEndpoints: 1个端点 */ 0x03, /* bInterfaceClass: HID */ 0x01, /* bInterfaceSubClass: Boot Interface */ 0x01, /* bInterfaceProtocol: 键盘 */ 0x00, /* iInterface: 无 */ /* HID描述符 */ 0x09, /* bLength: 9 */ HID_DESCRIPTOR_TYPE, /* bDescriptorType: HID */ 0x11, /* bcdHID: 1.11 */ 0x01, 0x00, /* bCountryCode: 无 */ 0x01, /* bNumDescriptors: 1个报告描述符 */ HID_REPORT_DESC_TYPE, /* bDescriptorType: 报告描述符 */ sizeof(hidReportDescriptor), /* wItemLength: 报告描述符长度 */ 0x00, /* 端点描述符 */ 0x07, /* bLength: 7 */ USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType: 端点 */ CUSTOMHID_EPIN_ADDR, /* bEndpointAddress: EP1 IN */ 0x03, /* bmAttributes: 中断传输 */ 0x40, /* wMaxPacketSize: 64 */ 0x00, 0x0A /* bInterval: 10ms */ }; ``` 4. 在main函数中初始化USB: ```c int main(void) { /* 系统初始化 */ /* ... */ /* 初始化USB设备库 */ USBD_Init(&USB_Device, &Device_Descriptor, &USBD_CUSTOM_HID_cb); /* 启动设备 */ USBD_Start(&USB_Device); while(1) { /* 应用程序 */ /* 发送键盘报告 */ uint8_t report[8] = {0,0,0x04,0,0,0,0,0}; // 发送字母a USBD_CUSTOM_HID_SendReport(&USB_Device, report, 8); Delay(1000); } } ``` 注意:以上代码仅为示例,实际使用时需要根据具体硬件和需求调整。 步骤8:调试 使用USB分析仪或软件(如Bus Hound)检查枚举过程和报告传输。 相关问题: 1. 如何在标准库中实现HID设备的SET_REPORT和GET_REPORT请求? 2. 如何修改报告描述符以适应不同的HID设备(如鼠标、游戏手柄)? 3. 标准库中如何处理USB挂起和恢复事件? 4. 如何在标准库中实现复合设备(例如HID和CDC)? 5. 标准库中如何优化HID报告的传输速度? 如果遇到问题,建议参考ST官方提供的标准库示例(如STM32F105/107的示例)[^1]。 </think>### 详细迁移步骤及完整代码实现 #### 一、迁移流程图解 ```mermaid graph TD A[提取CubeMX描述符] --> B[配置标准库端点] B --> C[实现回调函数] C --> D[注册HID类] D --> E[实现报告传输] E --> F[中断处理优化] ``` #### 二、完整迁移步骤 ##### 1. 提取CubeMX生成的描述符数据 在CubeMX生成的工程中查找以下关键数据: ```c /* CubeMX生成的报告描述符 (usbd_hid.c) */ const uint8_t hidReportDescriptor[50] = { 0x05, 0x01, // USAGE_PAGE (Generic Desktop) 0x09, 0x06, // USAGE (Keyboard) 0xA1, 0x01, // COLLECTION (Application) 0x05, 0x07, // USAGE_PAGE (Keyboard) ... // 其他描述符字节 }; /* 设备描述符 (usbd_desc.c) */ const uint8_t deviceDescriptor[18] = { 0x12, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x40, 0x83, 0x04, 0x57, 0x00, 0x02, 0x01, 0x02, 0x03, 0x01 }; ``` ##### 2. 标准库工程配置 ```c /* usb_conf.h - 端点配置 */ #define CUSTOMHID_EPIN_ADDR 0x81 // 与CubeMX一致 #define CUSTOMHID_EPIN_SIZE 64 // 报告长度 #define BTABLE_ADDRESS (0x00) /* 端点缓冲区对齐 (关键!) */ __attribute__((aligned(4))) uint8_t EP1_TxBuffer[CUSTOMHID_EPIN_SIZE]; ``` ##### 3. 实现HID类回调函数 ```c /* custom_hid_core.c */ USBD_Class_cb_TypeDef USBD_CUSTOM_HID_cb = { USBD_CUSTOM_HID_Init, USBD_CUSTOM_HID_DeInit, USBD_CUSTOM_HID_Setup, NULL, /* EP0_TxSent */ USBD_CUSTOM_HID_EP0_RxReady, USBD_CUSTOM_HID_DataIn, // 报告发送完成 USBD_CUSTOM_HID_DataOut, // 报告接收回调 NULL, /* SOF */ NULL, NULL }; uint8_t USBD_CUSTOM_HID_Setup(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req) { switch (req->bRequest) { case USB_REQ_GET_DESCRIPTOR: if(req->wValue >> 8 == HID_REPORT_DESC) { USBD_CtlSendData(pdev, (uint8_t*)hidReportDescriptor, sizeof(hidReportDescriptor)); } break; case HID_SET_IDLE: USBD_CtlSendStatus(pdev); break; default: USBD_CtlError(pdev, req); return USBD_FAIL; } return USBD_OK; } ``` ##### 4. 报告传输函数实现 ```c /* 报告发送函数 */ uint8_t USBD_CUSTOM_HID_SendReport(USBD_HandleTypeDef *pdev, uint8_t *report, uint16_t len) { if(pdev->dev_state != USBD_STATE_CONFIGURED) return USBD_FAIL; /* 寄存器操作代替HAL */ _SetEPTxCount(USBx, CUSTOMHID_EPIN_ADDR & 0x7F, len); USB_WritePMA(USBx, report, GetEPTxAddr(USBx, CUSTOMHID_EPIN_ADDR & 0x7F), len); _SetEPTxStatus(USBx, CUSTOMHID_EPIN_ADDR & 0x7F, USB_EP_TX_VALID); return USBD_OK; } ``` ##### 5. 中断处理实现 ```c /* stm32f10x_it.c */ void USB_LP_IRQHandler(void) { USBD_HandleTypeDef *pdev = &hUsbDeviceFS; /* 状态机处理 */ if(USB_ReadInterrupts(USB) & ISTR_CTR) { uint8_t epnum = USB_Get_EP_Index(USB); /* IN端点处理 */ if(USB_ReadEP(USB, epnum) & EP_CTR_TX) { USBD_LL_DataInStage(pdev, epnum); } /* OUT端点处理 */ if(USB_ReadEP(USB, epnum) & EP_CTR_RX) { USBD_LL_DataOutStage(pdev, epnum); } } /* 复位中断处理 */ if(USB_ReadInterrupts(USB) & ISTR_RESET) { USBD_LL_Reset(pdev); } } ``` ##### 6. USB初始化流程 ```c /* main.c */ int main(void) { /* 时钟配置 - 必须与CubeMX一致 */ RCC_USBCLKConfig(RCC_USBCLKSource_PLLCLK_Div3); // $$ \text{USB时钟} = \frac{\text{PLL时钟}}{3} $$ /* 初始化标准库USB */ USBD_Init(&USB_Device, &deviceDescriptor, &USBD_CUSTOM_HID_cb); /* 注册描述符 */ USBD_RegisterDescriptor(&USB_Device, USBD_GetCfgDesc, deviceDescriptor[7]); /* 启动USB */ USBD_Start(&USB_Device); while(1) { /* HID报告发送示例 */ uint8_t key_report[8] = {0,0,0x04,0,0,0,0,0}; // 发送字母'a' USBD_CUSTOM_HID_SendReport(&USB_Device, key_report, 8); Delay(1000); } } ``` #### 三、关键差异处理表 | **功能模块** | **CubeMX/HAL实现** | **标准库实现** | |------------------|-----------------------------|-----------------------------------| | 端点配置 | HAL自动处理 | 手动配置`usb_conf.h`和寄存器 | | 报告传输 | `USBD_HID_SendReport()` | 直接操作`_SetEPTxCount()`等寄存器 | | 中断处理 | HAL库回调 | 手动实现`USB_LP_IRQHandler`状态机 | | 描述符注册 | 自动关联 | 显式调用`USBD_RegisterDescriptor`| | 控制请求处理 | 集中处理 | 拆分到`Setup`和`EP0_RxReady` | #### 四、枚举故障排查技巧 1. **描述符验证错误** ```bash # 使用lsusb验证设备枚举 lsusb -v -d 0483:5720 ``` 检查输出中的`HID Descriptor`是否匹配 2. **端点超时问题** - 确认缓冲区4字节对齐:`__attribute__((aligned(4)))` - 检查端点配置是否冲突 - 验证时钟配置:$$ \text{APB1时钟} \div 3 = \text{48MHz} $$ 3. **报告传输失败** - 在`USBD_CUSTOM_HID_SendReport()`添加调试断点 - 检查`pdev->dev_state`是否为`USBD_STATE_CONFIGURED` - 验证端点状态寄存器值 #### 五、性能优化技巧 1. **DMA传输加速** ```c /* 在端点初始化中启用DMA */ _SetEPDmaEnable(USBx, ENDP1); USB_CNTR(USBx) |= USB_CNTR_DMAEN; ``` 2. **中断延迟优化** ```c NVIC_SetPriority(USB_LP_IRQn, 0); // 最高优先级 NVIC_EnableIRQ(USB_LP_IRQn); ``` 3. **功耗控制** ```c void USB_SuspendHandler(void) { if(USB_ReadInterrupts(USB) & ISTR_SUSP) { /* 进入低功耗模式 */ PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI); } } ``` #### 相关问题 1. 如何验证报告描述符是否符合HID 1.11规范? 2. 标准库中如何实现SET_REPORT/GET_REPORT控制请求? 3. 迁移后出现端点超时错误如何调试? 4. 复合设备(HID+CDC)描述符的结构如何设计? 5. 如何优化标准库的HID报告传输延迟? > 实际迁移时建议使用STM32F1xx标准外设库(STM32 USB-FS-Device_Lib V4.0.0)作为基础[^1],CubeMX生成的描述符可直接复用但其HAL层实现需要转换为寄存器操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叶与花语

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值