基础例程
代表非freertos代码,仅测试用。
功能就是亮灯、串口、按键。
按一下亮不同灯,串口打印不同数据。
接开发板的USB口会提示插入了USB设备。出现该U盘磁盘。
main函数代码
cpp
int main(void)
{
/*用户代码开始*/
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/*MCU配置*/
/* MCU Configuration--------------------------------------------------------*/
/*重置所有外设,初始化flash接口和滴答定时器*/
/* 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_USART1_UART_Init();
MX_USB_DEVICE_Init();
/*用户代码开始*/
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/*无限循环*/
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(blink_mode == 0)
{
HAL_GPIO_TogglePin(LED_T_GPIO_Port, LED_T_Pin);
printf("mode0\r\n");
HAL_Delay(500);
}
else if(blink_mode == 1)
{
HAL_GPIO_TogglePin(LED_T_GPIO_Port, LED_T_Pin);
printf("mode1\r\n");
HAL_Delay(250);
}
else if(blink_mode == 2)
{
HAL_GPIO_TogglePin(LED_T_GPIO_Port, LED_T_Pin);
printf("mode2\r\n");
HAL_Delay(100);
}
else if(blink_mode == 3)
{
HAL_GPIO_WritePin(LED_T_GPIO_Port, LED_T_Pin, GPIO_PIN_RESET);
printf("close\r\n");
HAL_Delay(500);
}
}
/* USER CODE END 3 */
}
代码分析
HAL_Init
/*配置flash的 预取值,指令缓存,数据缓存*/
/*设置中断优先级分组,分组4代表 0位子优先级、4位抢占优先级*/
/*使用SysTick作为时间基准源,并配置1毫秒的滴答时间
/*初始化低速硬件*/
cpp
HAL_StatusTypeDef HAL_Init(void)
{
/*配置flash的预取值,指令缓存,数据缓存*/
/* Configure Flash prefetch, Instruction cache, Data cache */
/*指令缓存*/
#if (INSTRUCTION_CACHE_ENABLE != 0U)
__HAL_FLASH_INSTRUCTION_CACHE_ENABLE();
#endif /* INSTRUCTION_CACHE_ENABLE */
/*数据缓存*/
#if (DATA_CACHE_ENABLE != 0U)
__HAL_FLASH_DATA_CACHE_ENABLE();
#endif /* DATA_CACHE_ENABLE */
/*预取指*/
#if (PREFETCH_ENABLE != 0U)
__HAL_FLASH_PREFETCH_BUFFER_ENABLE();
#endif /* PREFETCH_ENABLE */
/*设置中断优先级分组,分组4代表 0位子优先级、4位抢占优先级*/
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
/*使用SysTick作为时间基准源,并配置1毫秒的滴答时间(复位后的默认时钟是HSI)。内部告诉时钟为16Mhz*/
HAL_InitTick(TICK_INT_PRIORITY);
/*初始化底层硬件*/
HAL_MspInit();
/* Return function status */
return HAL_OK;
}
Flash配置
预取指、指令缓存、数据缓存。
三个宏定义的标志位都是1U,无符号整数
这里几个配置就是给FLASH的ACR寄存器置位。

cpp
/*指令缓存*/
#if (INSTRUCTION_CACHE_ENABLE != 0U)
__HAL_FLASH_INSTRUCTION_CACHE_ENABLE();
#endif /* INSTRUCTION_CACHE_ENABLE */
/*数据缓存*/
#if (DATA_CACHE_ENABLE != 0U)
__HAL_FLASH_DATA_CACHE_ENABLE();
#endif /* DATA_CACHE_ENABLE */
/*预取指*/
#if (PREFETCH_ENABLE != 0U)
__HAL_FLASH_PREFETCH_BUFFER_ENABLE();
#endif /* PREFETCH_ENABLE */
预取指是一种机制,用于在实际需要数据之前提前从内存中加载数据到缓存中。
指令缓存是一种高速缓存,用于存储最近或频繁使用的指令。
数据缓存用于存储最近或频繁使用的数据。
设置中断优先级分组
断言失败,程序会被中断。
cpp
void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup)
{
/* 断言宏,检查传入的参数是否有效 */
assert_param(IS_NVIC_PRIORITY_GROUP(PriorityGroup));
/* 设置优先级分组*/
NVIC_SetPriorityGrouping(PriorityGroup);
}

嵌套向量中断控制器(NVIC)集成在Cortex-M0处理器里,它与处理器内核紧密相连,并且提供了中断控制功能以及对系统异常的支持。
除了NVIC,系统控制块(SCB)位于系统控制空间(SCS)地址区域。
这里的240代表240个可屏蔽中断
设置Systick时间基准源
这里传入的 tick中断优先级为15U

最后就是将Systick中断优先级号配置为-1,
优先级配置为15(最低优先级),并设置重载值、初始值,
重载值(溢出值)设为16000-1,此时系统时钟为16Mhz即每1ms发生一次中断。
cpp
__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
/* 配置Systick为1ms的时间基准*/
16000000 / (1000/1)
if (HAL_SYSTICK_Config(SystemCoreClock / (1000U / uwTickFreq)) > 0U)
{
return HAL_ERROR; //配置寄存器成功返回的是0,就不会到这里
}
/* 配置Systick中断优先级 */
if (TickPriority < (1UL << __NVIC_PRIO_BITS)) //1UL<<4
{
/*配置systick中断号-1,tick优先级15,子优先级 0*/
HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0U);
uwTickPrio = TickPriority;
}
else
{
return HAL_ERROR;
}
/* Return function status */
return HAL_OK;
}
cpp
/*初始化Systick定时器,使其以特定的频率产生中断*/
/*给到这里的参数是16000*/
__STATIC_INLINE uint32_t SysTick_Config(uint32_t ticks)
{
/*检查重载值是否超出最大范围*/
if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk)
{
return (1UL);
}
/*设置重载寄存器*/
SysTick->LOAD = (uint32_t)(ticks - 1UL);
/*设置中断优先级*/
NVIC_SetPriority (SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL);
/*加载计数器值*/
SysTick->VAL = 0UL;
/*中断使能使能*/
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | //选择时钟源
SysTick_CTRL_TICKINT_Msk | //使能中断
SysTick_CTRL_ENABLE_Msk; //使能定时器
return (0UL);
}
使能SYSCFG和PWR时钟
初始化SYSCFG的时钟和APB2高速外设的时钟。
通讯线一般为高速如ADC、USART、SPI等,其最大频率通常为72MHz。
初始化PWR的时钟和APB1低速外设的时钟。如I2C、USART等,其最大频率通常为36MHz。
由于系统有多个USART,有些被连接到APB1,有些被连接到APB2,此处不矛盾。
cpp
void HAL_MspInit(void)
{
/*设置APB2使能寄存器上,用于控制SYSCFG的时钟*/
__HAL_RCC_SYSCFG_CLK_ENABLE();
/*设置APB1使能寄存器上,用于控制PWR的时钟*/
__HAL_RCC_PWR_CLK_ENABLE();
}
__IO
关键字是一个类型限定符,用于指定一个变量是可读写的(即双向的)。
它通常用于定义与外设寄存器相关的变量,这些变量需要通过内存映射的方式访问。
cpp
#define __HAL_RCC_SYSCFG_CLK_ENABLE()
do { \
__IO uint32_t tmpreg = 0x00U; \
SET_BIT(RCC->APB2ENR, RCC_APB2ENR_SYSCFGEN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_SYSCFGEN);\
UNUSED(tmpreg); \
} while(0U)
它在 RCC->APB2ENR 寄存器中设置 RCC_APB2ENR_SYSCFGEN 位,从而启用SYSCFG的时钟。APB2ENR 是高级外设总线2的使能寄存器。
cpp
#define __HAL_RCC_PWR_CLK_ENABLE()
do { \
__IO uint32_t tmpreg = 0x00U; \
SET_BIT(RCC->APB1ENR, RCC_APB1ENR_PWREN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->APB1ENR, RCC_APB1ENR_PWREN);\
UNUSED(tmpreg); \
} while(0U)
SystemClock_Config
系统时钟配置包括PWR使能、配置电压调节器等级、初始化SYSCLK、AHB、APB
AHB总线是一种高速总线,用于连接处理器核心、内存和高速外设。如内存和DMA控制器。
APB1/2总线相对AHB总线来讲,是一种低速总线,挂载常用外设。
系统时钟(SYSCLK)
AHB 总线时钟(HCLK)
APB1 总线时钟(PCLK1)
APB2 总线时钟(PCLK2)
系统时钟(SYSCLK)的来源可以是以下几种
高速内部振荡器(HSI)
高速外部晶振(HSE)
PLL(相位锁环)输出
系统时钟来源于高速外部/高速内部/锁相环,分频后得到AHB总线时钟,再分频得到APB1/2时钟。
SYSCLK (系统时钟)
↓
HCLK (AHB 总线时钟) = SYSCLK / AHB 分频值
↓
PCLK1 (APB1 总线时钟) = HCLK / APB1 分频值
↓
PCLK2 (APB2 总线时钟) = HCLK / APB2 分频值
cpp
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};//用于配置时钟源的结构体
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};//用于配置CPU和时钟总线的结构体
/*PWR使能*/
__HAL_RCC_PWR_CLK_ENABLE();
//启用电源模块(PWR模块的时钟)
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
//配置微控制器的电压调节器输出电压。
PWR_REGULATOR_VOLTAGE_SCALE1 表示选择电压调节器的最高电压等级,以支持更高的时钟频率。
/*配置RCC时钟和PLL锁相环*/
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 = 25;
RCC_OscInitStruct.PLL.PLLN = 144;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 3;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** 初始化SYSCLK、AHB和APB总线时钟 */
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
/*!< 系统时钟(SYSCLK)来源:PLL 输出时钟。 */
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
/*!< AHB 总线时钟(HCLK)分频:不分频,与 SYSCLK 同频。 */
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
/*!< APB1 总线时钟(PCLK1)分频:HCLK 的一半。 */
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
/*!< APB2 总线时钟(PCLK2)分频:与 HCLK 同频。 */
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
//初始化时钟 并设置FLASH有2个延迟周期
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
cpp
/*振荡器初始化结构体*/
typedef struct
{
uint32_t OscillatorType; /*!< 振荡器类型:
- 可选值:HSE、LSE、HSI、LSI。
- 可组合配置。 */
uint32_t HSEState; /*!< HSE 状态:
- 可选值:OFF、ON、BYPASS。 */
//BYPASS旁路模式(使用外部时钟信号)
uint32_t LSEState; /*!< LSE 状态:
- 可选值:OFF、ON、BYPASS。 */
uint32_t HSIState; /*!< HSI 状态:
- 可选值:OFF、ON。 */
uint32_t HSICalibrationValue; /*!< HSI 校准值:
- 范围:0x00~0x1F。
- 默认值:16。 */
uint32_t LSIState; /*!< LSI 状态:
- 可选值:OFF、ON。 */
RCC_PLLInitTypeDef PLL; /*!< PLL 配置:
- 包含 PLL 分频参数。 */
} RCC_OscInitTypeDef; /*!< 振荡器和 PLL 初始化结构体。 */
cpp
/*时钟初始化结构体*/
typedef struct
{
uint32_t ClockType; /*!< 要配置的时钟类型:
- 可选值:系统时钟(SYSCLK)、AHB 时钟(HCLK)、APB1 时钟(PCLK1)、APB2 时钟(PCLK2)。 */
uint32_t SYSCLKSource; /*!< 系统时钟(SYSCLK)的来源:
- 可选值:HSI、HSE、PLL 等。 */
uint32_t AHBCLKDivider; /*!< AHB 时钟(HCLK)的分频因子:
- 由系统时钟(SYSCLK)分频得到。 */
uint32_t APB1CLKDivider; /*!< APB1 时钟(PCLK1)的分频因子:
- 由 AHB 时钟(HCLK)分频得到。 */
uint32_t APB2CLKDivider; /*!< APB2 时钟(PCLK2)的分频因子:
- 由 AHB 时钟(HCLK)分频得到。 */
} RCC_ClkInitTypeDef; /*!< 时钟初始化结构体,用于配置系统时钟及相关分频器。 */
初始化配置外设
cpp
/*初始化用到的外设*/
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_USB_DEVICE_Init();
MX_GPIO_Init
使能GPIO时钟,
配置引脚的 模式、上下拉、速度。
设置外部中断EXTI0的优先级、使能外部中断。
cpp
void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0}; /*!< 初始化 GPIO 配置结构体 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOC_CLK_ENABLE(); /*!< 使能 GPIOC 时钟 */
__HAL_RCC_GPIOH_CLK_ENABLE(); /*!< 使能 GPIOH 时钟 */
__HAL_RCC_GPIOA_CLK_ENABLE(); /*!< 使能 GPIOA 时钟 */
/* Configure GPIO pin Output Level */
HAL_GPIO_WritePin(LED_T_GPIO_Port, LED_T_Pin, GPIO_PIN_RESET); /*!< 设置 LED_T 引脚为低电平(熄灭) */
/* Configure GPIO pin : PtPin */
GPIO_InitStruct.Pin = LED_T_Pin; /*!< 配置 LED_T 引脚 */
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; /*!< 设置为推挽输出模式 */
GPIO_InitStruct.Pull = GPIO_NOPULL; /*!< 禁用内部上下拉 */
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; /*!< 设置为低速 */
HAL_GPIO_Init(LED_T_GPIO_Port, &GPIO_InitStruct); /*!< 初始化 LED_T 引脚 */
/* Configure GPIO pin : PtPin */
GPIO_InitStruct.Pin = KEY1_Pin; /*!< 配置 KEY1 引脚 */
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; /*!< 设置为下降沿触发中断 */
GPIO_InitStruct.Pull = GPIO_PULLUP; /*!< 启用内部上拉 */
HAL_GPIO_Init(KEY1_GPIO_Port, &GPIO_InitStruct); /*!< 初始化 KEY1 引脚 */
/* EXTI interrupt init */
HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0); /*!< 设置 EXTI0 中断优先级 */
HAL_NVIC_EnableIRQ(EXTI0_IRQn); /*!< 使能 EXTI0 中断 */
}
MX_USART1_UART_Init
cpp
void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1; // 选择 USART1 实例
huart1.Init.BaudRate = 115200; // 波特率设置为 115200
huart1.Init.WordLength = UART_WORDLENGTH_8B; // 数据位长度为 8 位
huart1.Init.StopBits = UART_STOPBITS_1; // 1 个停止位
huart1.Init.Parity = UART_PARITY_NONE; // 无校验位
huart1.Init.Mode = UART_MODE_TX_RX; // 模式:发送和接收
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; // 无硬件流控制
huart1.Init.OverSampling = UART_OVERSAMPLING_16; // 过采样为 16
if (HAL_UART_Init(&huart1) != HAL_OK) // 初始化 UART
{
Error_Handler(); // 初始化失败时调用错误处理函数
}
}
MX_USB_DEVICE_Init
cpp
void MX_USB_DEVICE_Init(void)
{
// 初始化 USB 设备库
if (USBD_Init(&hUsbDeviceFS, /*USBD_HandleTypeDef hUsbDeviceFS */
&FS_Desc,
DEVICE_FS)
!= USBD_OK)
{
Error_Handler(); // 初始化失败,调用错误处理函数
}
// 注册 USB 设备类(Mass Storage Class)
if (USBD_RegisterClass(&hUsbDeviceFS, &USBD_MSC) != USBD_OK)
{
Error_Handler(); // 注册类失败,调用错误处理函数
}
// 注册存储接口函数(用于 Mass Storage Class)
if (USBD_MSC_RegisterStorage(&hUsbDeviceFS, &USBD_Storage_Interface_fops_FS) != USBD_OK)
{
Error_Handler(); // 注册存储接口失败,调用错误处理函数
}
// 启动 USB 设备库
if (USBD_Start(&hUsbDeviceFS) != USBD_OK)
{
Error_Handler(); // 启动失败,调用错误处理函数
}
}
初始化句柄
cpp
/*USB初始化句柄*/
typedef struct _USBD_HandleTypeDef
{
uint8_t id; // 设备实例 ID
uint32_t dev_config; // 当前设备配置
uint32_t dev_default_config; // 默认设备配置
uint32_t dev_config_status; // 配置状态
USBD_SpeedTypeDef dev_speed; // 设备速度(低速、全速、高速)
USBD_EndpointTypeDef ep_in[16]; // IN 端点数组(最多 16 个)
USBD_EndpointTypeDef ep_out[16]; // OUT 端点数组(最多 16 个)
__IO uint32_t ep0_state; // 端点 0 的状态
uint32_t ep0_data_len; // 端点 0 数据长度
__IO uint8_t dev_state; // 设备状态
__IO uint8_t dev_old_state; // 设备上一状态
uint8_t dev_address; // 设备地址
uint8_t dev_connection_status; // 连接状态
uint8_t dev_test_mode; // 测试模式
uint32_t dev_remote_wakeup; // 远程唤醒功能
uint8_t ConfIdx; // 配置索引
USBD_SetupReqTypedef request; // 设置请求结构体
USBD_DescriptorsTypeDef *pDesc; // 指向描述符的指针
USBD_ClassTypeDef *pClass[USBD_MAX_SUPPORTED_CLASS]; // 支持的类指针数组
void *pClassData; // 类数据指针
void *pClassDataCmsit[USBD_MAX_SUPPORTED_CLASS]; // CMSIT 类数据指针数组
void *pUserData[USBD_MAX_SUPPORTED_CLASS]; // 用户数据指针数组
void *pData; // 通用数据指针
void *pBosDesc; // BOS 描述符指针
void *pConfDesc; // 配置描述符指针
uint32_t classId; // 类 ID
uint32_t NumClasses; // 支持的类数量
#ifdef USE_USBD_COMPOSITE
USBD_CompositeElementTypeDef tclasslist[USBD_MAX_SUPPORTED_CLASS]; // 复合设备类列表
#endif /* USE_USBD_COMPOSITE */
} USBD_HandleTypeDef;
设备描述符
cpp
/*设备描述符*/
USBD_DescriptorsTypeDef FS_Desc =
{
USBD_FS_DeviceDescriptor, // 设备描述符
USBD_FS_LangIDStrDescriptor, // 语言 ID 字符串描述符
USBD_FS_ManufacturerStrDescriptor,// 制造商字符串描述符
USBD_FS_ProductStrDescriptor, // 产品字符串描述符
USBD_FS_SerialStrDescriptor, // 序列号字符串描述符
USBD_FS_ConfigStrDescriptor, // 配置字符串描述符
USBD_FS_InterfaceStrDescriptor // 接口字符串描述符
#if (USBD_LPM_ENABLED == 1) // 如果启用了 LPM(低功耗模式)
, USBD_FS_USR_BOSDescriptor // BOS 描述符
#endif /* (USBD_LPM_ENABLED == 1) */
};
cpp
/* #define for FS and HS identification */
#define DEVICE_FS 0 // 定义全速设备标识
#define DEVICE_HS 1 // 定义高速设备标识
初始化USB设备
cpp
void MX_USB_DEVICE_Init(void)
{
// 初始化 USB 设备库
if (USBD_Init(&hUsbDeviceFS, &FS_Desc, DEVICE_FS) != USBD_OK)
{
Error_Handler(); // 初始化失败,调用错误处理函数
}
// 注册 USB 设备类(Mass Storage Class)
if (USBD_RegisterClass(&hUsbDeviceFS, &USBD_MSC) != USBD_OK)
{
Error_Handler(); // 注册类失败,调用错误处理函数
}
// 注册存储接口函数(用于 Mass Storage Class)
if (USBD_MSC_RegisterStorage(&hUsbDeviceFS, &USBD_Storage_Interface_fops_FS) != USBD_OK)
{
Error_Handler(); // 注册存储接口失败,调用错误处理函数
}
// 启动 USB 设备库
if (USBD_Start(&hUsbDeviceFS) != USBD_OK)
{
Error_Handler(); // 启动失败,调用错误处理函数
}
}
USB_Init
主要作用是将设备描述符赋给初始化句柄。
然后调用USB设备的底层(Low Level)初始化 USBD_LL_Init。
cpp
USBD_StatusTypeDef USBD_Init(USBD_HandleTypeDef *pdev,
USBD_DescriptorsTypeDef *pdesc, uint8_t id)
{
USBD_StatusTypeDef ret;
// 检查设备句柄是否有效
if (pdev == NULL)
{
#if (USBD_DEBUG_LEVEL > 1U)
USBD_ErrLog("Invalid Device handle"); // 调试日志:无效设备句柄
#endif /* (USBD_DEBUG_LEVEL > 1U) */
return USBD_FAIL; // 返回失败
}
#ifdef USE_USBD_COMPOSITE
// 如果启用复合设备支持,初始化类列表
for (uint32_t i = 0; i < USBD_MAX_SUPPORTED_CLASS; i++)
{
pdev->pClass[i] = NULL; // 清空类指针
pdev->pUserData[i] = NULL; // 清空用户数据指针
pdev->tclasslist[i].Active = 0; // 设置类为非活动状态
}
pdev->NumClasses = 0; // 初始化类数量为 0
pdev->classId = 0; // 初始化类 ID 为 0
#else
// 如果未启用复合设备支持,仅初始化默认类
pdev->pClass[0] = NULL; // 清空类指针
pdev->pUserData[0] = NULL; // 清空用户数据指针
#endif /* USE_USBD_COMPOSITE */
pdev->pConfDesc = NULL; // 清空配置描述符指针
// 分配设备描述符
if (pdesc != NULL)
{
pdev->pDesc = pdesc; // 设置设备描述符
}
// 初始化设备状态
pdev->dev_state = USBD_STATE_DEFAULT; // 设置为默认状态
pdev->id = id; // 设置设备 ID
// 初始化底层驱动
ret = USBD_LL_Init(pdev); // 调用底层初始化函数
return ret; // 返回初始化结果
}
USBD_LL_Init
-
检查设备 ID :确保设备为全速设备(
DEVICE_FS
)。 -
链接驱动与堆栈:将 USB 设备句柄与底层驱动句柄相互关联。
-
配置 USB OTG FS 实例 :设置 USB OTG FS 的初始化参数,包括端点数量、速度、PHY 接口等。
-
初始化 USB OTG FS :调用 HAL 库的
HAL_PCD_Init
函数初始化 USB OTG FS。 -
注册回调函数(如果启用):注册 USB PCD 的各种回调函数,用于处理 USB 事件。
-
配置 FIFO :设置接收和发送 FIFO(缓冲区,单位字节) 的大小,用于数据传输。
这里将USB端点配置为 4,作用为
-
端点 0:控制端点(用于设备配置和控制请求)。
-
端点 1:IN 端点(用于从设备到主机的数据传输)。
-
端点 2:OUT 端点(用于从主机到设备的数据传输)。
-
端点 3:中断端点(用于设备状态的频繁更新,如键盘、鼠标)。
速度:决定了设备的数据传输速率,常见的有全速(12 Mbps)和高速(480 Mbps)。
PHY 接口:决定了设备的物理层接口类型,可以选择嵌入式 PHY 或外部 PHY。
嵌入式 PHY 是集成在微控制器芯片内部的物理层接口。
外部 PHY 是独立于微控制器芯片的物理层接口,通常以独立芯片的形式存在。
cpp
USBD_StatusTypeDef USBD_LL_Init(USBD_HandleTypeDef *pdev)
{
/* 初始化 USB IP */
if (pdev->id == DEVICE_FS) {
/* 将驱动与堆栈链接 */
hpcd_USB_OTG_FS.pData = pdev;
pdev->pData = &hpcd_USB_OTG_FS;
/* 配置 USB OTG FS 实例 */
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; // 禁用 DMA
hpcd_USB_OTG_FS.Init.phy_itface = PCD_PHY_EMBEDDED; // 使用嵌入式 PHY
hpcd_USB_OTG_FS.Init.Sof_enable = DISABLE; // 禁用 SOF 事件
hpcd_USB_OTG_FS.Init.low_power_enable = DISABLE; // 禁用低功耗模式
hpcd_USB_OTG_FS.Init.lpm_enable = DISABLE; // 禁用 LPM
hpcd_USB_OTG_FS.Init.vbus_sensing_enable = DISABLE; // 禁用 VBUS 检测
hpcd_USB_OTG_FS.Init.use_dedicated_ep1 = DISABLE; // 禁用专用端点 1
/* 初始化 USB OTG FS */
if (HAL_PCD_Init(&hpcd_USB_OTG_FS) != HAL_OK)
{
Error_Handler(); // 初始化失败,调用错误处理函数
}
#if (USE_HAL_PCD_REGISTER_CALLBACKS == 1U)
/* 注册 USB PCD 回调函数 */
HAL_PCD_RegisterCallback(&hpcd_USB_OTG_FS, HAL_PCD_SOF_CB_ID, PCD_SOFCallback);
HAL_PCD_RegisterCallback(&hpcd_USB_OTG_FS, HAL_PCD_SETUPSTAGE_CB_ID, PCD_SetupStageCallback);
HAL_PCD_RegisterCallback(&hpcd_USB_OTG_FS, HAL_PCD_RESET_CB_ID, PCD_ResetCallback);
HAL_PCD_RegisterCallback(&hpcd_USB_OTG_FS, HAL_PCD_SUSPEND_CB_ID, PCD_SuspendCallback);
HAL_PCD_RegisterCallback(&hpcd_USB_OTG_FS, HAL_PCD_RESUME_CB_ID, PCD_ResumeCallback);
HAL_PCD_RegisterCallback(&hpcd_USB_OTG_FS, HAL_PCD_CONNECT_CB_ID, PCD_ConnectCallback);
HAL_PCD_RegisterCallback(&hpcd_USB_OTG_FS, HAL_PCD_DISCONNECT_CB_ID, PCD_DisconnectCallback);
HAL_PCD_RegisterDataOutStageCallback(&hpcd_USB_OTG_FS, PCD_DataOutStageCallback);
HAL_PCD_RegisterDataInStageCallback(&hpcd_USB_OTG_FS, PCD_DataInStageCallback);
HAL_PCD_RegisterIsoOutIncpltCallback(&hpcd_USB_OTG_FS, PCD_ISOOUTIncompleteCallback);
HAL_PCD_RegisterIsoInIncpltCallback(&hpcd_USB_OTG_FS, PCD_ISOINIncompleteCallback);
#endif /* USE_HAL_PCD_REGISTER_CALLBACKS */
/* 配置 USB FIFO */
HAL_PCDEx_SetRxFiFo(&hpcd_USB_OTG_FS, 0x80); // 设置接收 FIFO 大小
HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 0, 0x40); // 设置发送 FIFO 0 大小
HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 1, 0x80); // 设置发送 FIFO 1 大小
/*
设备可以接收最多 128 字节的数据,存储在接收 FIFO 中。
设备可以发送最多 64 字节的数据,存储在发送 FIFO 0 中。
设备可以发送最多 128 字节的数据,存储在发送 FIFO 1 中。
*/
}
return USBD_OK; // 返回初始化成功
}
USBD_RegisterClass
就是将设备类绑定到USB句柄
cpp
USBD_StatusTypeDef USBD_RegisterClass(USBD_HandleTypeDef *pdev, //指向USB初始化句柄的指针
USBD_ClassTypeDef *pclass)//指向USB设备类的指针
{
uint16_t len = 0U;
// 检查类指针是否为空
if (pclass == NULL)
{
#if (USBD_DEBUG_LEVEL > 1U)
USBD_ErrLog("Invalid Class handle");
#endif /* (USBD_DEBUG_LEVEL > 1U) */
return USBD_FAIL;
}
// 将类绑定到USB设备句柄
pdev->pClass[0] = pclass;
// 获取设备配置描述符
#ifdef USE_USB_HS
if (pdev->pClass[pdev->classId]->GetHSConfigDescriptor != NULL)
{
pdev->pConfDesc = (void *)pdev->pClass[pdev->classId]->GetHSConfigDescriptor(&len);
}
#else /* Default USE_USB_FS */
if (pdev->pClass[pdev->classId]->GetFSConfigDescriptor != NULL)
{
pdev->pConfDesc = (void *)pdev->pClass[pdev->classId]->GetFSConfigDescriptor(&len);
}
#endif /* USE_USB_FS */
// 类数量加1
pdev->NumClasses ++;
return USBD_OK;
}
cpp
/*设备类指针*/
typedef struct _Device_cb
{
uint8_t (*Init)(struct _USBD_HandleTypeDef *pdev, uint8_t cfgidx); // 初始化设备类
uint8_t (*DeInit)(struct _USBD_HandleTypeDef *pdev, uint8_t cfgidx); // 反初始化设备类
uint8_t (*Setup)(struct _USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req); // 处理控制请求
uint8_t (*EP0_TxSent)(struct _USBD_HandleTypeDef *pdev); // EP0 发送完成回调
uint8_t (*EP0_RxReady)(struct _USBD_HandleTypeDef *pdev); // EP0 接收完成回调
uint8_t (*DataIn)(struct _USBD_HandleTypeDef *pdev, uint8_t epnum); // 数据 IN 端点回调
uint8_t (*DataOut)(struct _USBD_HandleTypeDef *pdev, uint8_t epnum); // 数据 OUT 端点回调
uint8_t (*SOF)(struct _USBD_HandleTypeDef *pdev); // SOF 周期性回调
uint8_t (*IsoINIncomplete)(struct _USBD_HandleTypeDef *pdev, uint8_t epnum); // Isochronous IN 未完成回调
uint8_t (*IsoOUTIncomplete)(struct _USBD_HandleTypeDef *pdev, uint8_t epnum); // Isochronous OUT 未完成回调
uint8_t *(*GetHSConfigDescriptor)(uint16_t *length); // 获取高速配置描述符
uint8_t *(*GetFSConfigDescriptor)(uint16_t *length); // 获取全速配置描述符
uint8_t *(*GetOtherSpeedConfigDescriptor)(uint16_t *length); // 获取其他速度配置描述符
uint8_t *(*GetDeviceQualifierDescriptor)(uint16_t *length); // 获取设备限定符描述符
#if (USBD_SUPPORT_USER_STRING_DESC == 1U)
uint8_t *(*GetUsrStrDescriptor)(struct _USBD_HandleTypeDef *pdev, uint8_t index, uint16_t *length); // 获取用户自定义字符串描述符
#endif /* USBD_SUPPORT_USER_STRING_DESC */
} USBD_ClassTypeDef;
USBD_MSC_RegisterStorage
将存储设备的操作接口绑定到USB句柄。
cpp
uint8_t USBD_MSC_RegisterStorage(USBD_HandleTypeDef *pdev, //USB句柄
USBD_StorageTypeDef *fops) //存储接口
{
// 检查存储操作接口指针是否为空
if (fops == NULL)
{
return (uint8_t)USBD_FAIL;
}
// 将存储操作接口绑定到设备句柄
pdev->pUserData[pdev->classId] = fops;
// 返回成功状态
return (uint8_t)USBD_OK;
}
cpp
/*USB存储设备的操作接口*/
typedef struct _USBD_STORAGE
{
int8_t (*Init)(uint8_t lun); // 初始化存储设备
int8_t (*GetCapacity)(uint8_t lun, uint32_t *block_num, uint16_t *block_size); // 获取存储容量
int8_t (*IsReady)(uint8_t lun); // 检查存储设备是否就绪
int8_t (*IsWriteProtected)(uint8_t lun); // 检查存储设备是否写保护
int8_t (*Read)(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len); // 读取数据
int8_t (*Write)(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len); // 写入数据
int8_t (*GetMaxLun)(void); // 获取最大逻辑单元号
int8_t *pInquiry; // 存储设备的 Inquiry 数据
} USBD_StorageTypeDef;
USBD_Start
启动USB设备,底层大概就是根据 USB基地址进行数据读写和获取状态。
用户手册关于USB会有两块地址,
一块是USB寄存器的基地址,另一块是USB的SRAM地址(缓冲区)。
cpp
/*启动USB设备*/
USBD_StatusTypeDef USBD_Start(USBD_HandleTypeDef *pdev)
{
#ifdef USE_USBD_COMPOSITE
pdev->classId = 0U; // 如果是复合设备,初始化类ID为0
#endif /* USE_USBD_COMPOSITE */
// 启动底层驱动
return USBD_LL_Start(pdev);
}
cpp
/*USB的底层启动程序*/
USBD_StatusTypeDef USBD_LL_Start(USBD_HandleTypeDef *pdev)
{
HAL_StatusTypeDef hal_status = HAL_OK; // HAL状态变量初始化为成功
USBD_StatusTypeDef usb_status = USBD_OK; // USB状态变量初始化为成功
// 启动HAL层的PCD(Peripheral Controller Driver)驱动
hal_status = HAL_PCD_Start(pdev->pData);
// 将HAL状态转换为USB状态
usb_status = USBD_Get_USB_Status(hal_status);
return usb_status; // 返回USB状态
}
cpp
/*Hal层的外设控制驱动*/
HAL_StatusTypeDef HAL_PCD_Start(PCD_HandleTypeDef *hpcd)
{
USB_OTG_GlobalTypeDef *USBx = hpcd->Instance; // 获取USB实例
__HAL_LOCK(hpcd); // 加锁,防止多线程访问冲突
// 如果启用了电池充电功能且不是ULPI PHY接口
if ((hpcd->Init.battery_charging_enable == 1U) &&
(hpcd->Init.phy_itface != USB_OTG_ULPI_PHY))
{
// 启用USB收发器
USBx->GCCFG |= USB_OTG_GCCFG_PWRDWN;
}
__HAL_PCD_ENABLE(hpcd); // 启用PCD(Peripheral Controller Driver)
(void)USB_DevConnect(hpcd->Instance); // 连接USB设备
__HAL_UNLOCK(hpcd); // 解锁
return HAL_OK; // 返回成功状态
}
循环部分
就是根据标志位翻转引脚和发串口。
中断部分
在xxx_it.c中,一个外部中断函数,一个

关于按键的中断,这里有一个中断嵌套的概念要提示。
高优先级中断打断低优先级中断,执行完再返回低优先级中断继续执行。
同等级优先级中断,比如中断A和中断B同优先级,则是先来的执行完了才处理后来的。
当外部中断被触发时(例如,按键按下导致 GPIO 状态变化),硬件会设置对应的中断标志位。对于外部中断,这些标志位通常存储在 EXTI(外部中断/事件控制器)的
PR
寄存器(中断挂起寄存器)中。
也就是一个按键多按几下,进不进外部中断要看PR寄存器清零的时机。
OTG_FS_IRQHandler
中断处理:处理 USB 设备模式下的各种中断事件,包括接收中断、发送中断、设备状态变化等。
状态更新:更新设备状态,如帧编号、中断标志等。
回调触发:根据中断类型触发相应的回调函数,通知上层应用处理数据传输或状态变化。
通过寄存器状态判断产生了哪种类型的中断,调用相应的处理函数。
cpp
void HAL_PCD_IRQHandler(PCD_HandleTypeDef *hpcd)
{
// 确保设备处于设备模式
if (USB_GetMode(hpcd->Instance) != USB_OTG_MODE_DEVICE) return;
// 避免无效中断
if (__HAL_PCD_IS_INVALID_INTERRUPT(hpcd)) return;
// 更新帧编号
hpcd->FrameNumber = (USBx_DEVICE->DSTS & USB_OTG_DSTS_FNSOF_Msk) >> USB_OTG_DSTS_FNSOF_Pos;
// 处理接收队列中断
if (__HAL_PCD_GET_FLAG(hpcd, USB_OTG_GINTSTS_RXFLVL))
{
// 读取接收数据并更新缓冲区
}
// 处理输出端点中断
if (__HAL_PCD_GET_FLAG(hpcd, USB_OTG_GINTSTS_OEPINT))
{
// 遍历输出端点,处理数据传输完成、设置阶段完成等事件
}
// 处理输入端点中断
if (__HAL_PCD_GET_FLAG(hpcd, USB_OTG_GINTSTS_IEPINT))
{
// 遍历输入端点,处理数据传输完成、FIFO 空等事件
}
// 处理唤醒中断
if (__HAL_PCD_GET_FLAG(hpcd, USB_OTG_GINTSTS_WKUINT))
{
// 触发唤醒回调
}
// 处理挂起中断
if (__HAL_PCD_GET_FLAG(hpcd, USB_OTG_GINTSTS_USBSUSP))
{
// 触发挂起回调
}
// 处理 USB 复位
if (__HAL_PCD_GET_FLAG(hpcd, USB_OTG_GINTSTS_USBRST))
{
// 初始化设备状态,触发复位回调
}
// 处理枚举完成
if (__HAL_PCD_GET_FLAG(hpcd, USB_OTG_GINTSTS_ENUMDNE))
{
// 触发枚举完成回调
}
// 处理其他中断(如 SOF、LPM、连接/断开事件)
}
带参数的宏
基本用法
cpp
#define MACRO_NAME(parameter_list) macro_body
加法宏
cpp
#define ADD(x, y) ((x) + (y))
字符串化运算符 和 标记化运算符
cpp
#define STRINGIFY(x) #x
#define TOUPPER(x) STR##x
int main() {
char *s = STRINGIFY(Hello World); // "Hello World"
char *t = TOUPPER(ing); // "STRING"
return 0;
}