炸鸡派-基础测试例程

基础例程

代表非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
  1. 检查设备 ID :确保设备为全速设备(DEVICE_FS)。

  2. 链接驱动与堆栈:将 USB 设备句柄与底层驱动句柄相互关联。

  3. 配置 USB OTG FS 实例 :设置 USB OTG FS 的初始化参数,包括端点数量、速度、PHY 接口等。

  4. 初始化 USB OTG FS :调用 HAL 库的 HAL_PCD_Init 函数初始化 USB OTG FS。

  5. 注册回调函数(如果启用):注册 USB PCD 的各种回调函数,用于处理 USB 事件。

  6. 配置 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;
}
相关推荐
Natsume171017 分钟前
嵌入式开发:GPIO、UART、SPI、I2C 驱动开发详解与实战案例
c语言·驱动开发·stm32·嵌入式硬件·mcu·架构·github
MeshddY1 小时前
(超详细)数据库项目初体验:使用C语言连接数据库完成短地址服务(本地运行版)
c语言·数据库·单片机
m0_555762901 小时前
STM32常见外设
stm32·单片机·嵌入式硬件
森焱森1 小时前
无人机三轴稳定化控制(1)____飞机的稳定控制逻辑
c语言·单片机·算法·无人机
循环过三天2 小时前
3-1 PID算法改进(积分部分)
笔记·stm32·单片机·学习·算法·pid
天天爱吃肉82183 小时前
ZigBee通信技术全解析:从协议栈到底层实现,全方位解读物联网核心无线技术
python·嵌入式硬件·物联网·servlet
东风点点吹3 小时前
STM32F103的boot跳转APP不成功问题排除
stm32·单片机·嵌入式硬件
猫猫的小茶馆5 小时前
【STM32】预分频因子(Prescaler)和重装载值(Reload Value)
c语言·stm32·单片机·嵌入式硬件·mcu·51单片机
riveting6 小时前
明远智睿H618:开启多场景智慧生活新时代
人工智能·嵌入式硬件·智能硬件·lga封装·3506
三万棵雪松7 小时前
【STM32HAL-第1讲 基础篇-单片机简介】
stm32·单片机·嵌入式硬件