【STM32】STM32F103 USB驱动HAL库HAL_PCDEx_PMAConfig()的接口的理解

简单说说这篇文章的背景

最近想要研究USB驱动的开发,具体是用STM32F103R8来跑STM32CubeMX生成的例程。单独跑了CDC和HID,都能正常跑通。到想要跑CDC+HID混合USB设备的时候犯了难。把HID的数据端口设置为0x83后,无论如何都没办法正常通信。于是开始研究HAL_PCDEx_PMAConfig()。遗憾的是,在搜索引擎上并没有找到相关说明,因此写下这篇文章。

STM32F103R8的一些说明

STM32F103R8的USB外设为USB Device,相应的,高端的STM32 MCU会有USB外设为USB OTA。USB OTA在分配端点内存时,是通过HAL_PCDEx_SetTxFiFo()与HAL_PCDEx_SetRxFiFo()接口,开发者无需关注内存的地址映射。而USB Device在分配内存时,需要通过入参pmaadress,传入该端点对应的pma地址。

从手册上我们可以看到,STM32F103R8的PMA的大小为512Bytes,由USB外设与CAN外设公用,可以通过USB外设寄存器BTABLE设置PMA的起始地址。
在这里插入图片描述

从手册上我们能看到,端点的缓冲区描述表保存在PMA的起始地址部分,一个端点需要8个字节保存缓冲区配置信息:
在这里插入图片描述

另外,从手册上我们还能看到,分组缓冲区都是从底部开始使用的。也就是说完成配置端点0的ADDR0_TX与COUNT0_TX后,需要确保端点0OUT的使用不会覆盖缓冲区配置信息,同时确保端点0的底部地址,大于有效的缓冲区配置区域。
在这里插入图片描述

例如STM32CubeMX的CDC例程使用了单缓冲RX端点0,单缓冲TX端点0,单缓冲RX端点1,单缓冲TX端点1,单缓冲RX端点2。将单缓冲TX端点0的地址设置为0x18,就能保证端点0、1、2的缓冲区配置信息能够正常保存。
同时,需要确保预留足够的空间给单缓冲TX端点0,若单缓冲TX端点0的数据超过了4个字节,导致单缓冲RX端点2的缓冲区配置信息被覆盖,将导致端点缓冲RX端点2工作异常。

再看看HAL_PCDEx_PMAConfig()的入参:

  • PCD_HandleTypeDef *hpcd为我们的PCD句柄
  • uint16_t ep_addr为我们的端点,通过第八位区分断电为IN或OUT
  • uint16_t ep_kind为我们的端点类型,端点类型有单缓冲区端点,与双缓冲区端点。当设置端点类型为双缓冲区时,端点只能设置为单向。
  • uint32_t pmaadress为我们的端点缓冲区对应PMA的底部地址

代入疑问

起初,我是基于CDC的例程,引入了HID的端点配置。

  /* USER CODE BEGIN EndPoint_Configuration */
  HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x00 , PCD_SNG_BUF, 0x18);
  HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x80 , PCD_SNG_BUF, 0x58);
  /* USER CODE END EndPoint_Configuration */
  /* USER CODE BEGIN EndPoint_Configuration_CDC */
  HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_IN_EP , PCD_SNG_BUF, 0x98);
  HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_OUT_EP , PCD_SNG_BUF, 0xD8);
  HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_CMD_EP , PCD_SNG_BUF, 0x118);
  /* USER CODE END EndPoint_Configuration_CDC */
  /* USER CODE BEGIN EndPoint_Configuration_HID */
  HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x83 , PCD_SNG_BUF, 0x158);
  /* USER CODE END EndPoint_Configuration_HID */

可以看到在配置单缓冲TX端点0时,底部地址覆盖了端点3的缓冲区配置信息,这就导致了在实际使用端点3时,MCU无法正确索引到端点3对应的PMA地址。所以当我们把单缓冲TX端点0的底部地址配置为0x20时,就能确保端点3的缓冲区配置信息不被覆盖。


  /* USER CODE BEGIN EndPoint_Configuration */
  HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x00 , PCD_SNG_BUF, 0x20);
  HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x80 , PCD_SNG_BUF, 0x60);
  /* USER CODE END EndPoint_Configuration */
  /* USER CODE BEGIN EndPoint_Configuration_CDC */
  HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_IN_EP , PCD_SNG_BUF, 0xA0);
  HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_OUT_EP , PCD_SNG_BUF, 0xE0);
  HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , CDC_CMD_EP , PCD_SNG_BUF, 0x120);
  /* USER CODE END EndPoint_Configuration_CDC */
  /* USER CODE BEGIN EndPoint_Configuration_HID */
  HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData , 0x83 , PCD_SNG_BUF, 0x160);
  /* USER CODE END EndPoint_Configuration_HID */

另外,为什么底部地址的间隔为0x40,即64,那是因为USB Full Speed设备,最大一包数据为64个字节,将缓冲区间隔设置为64个字节时,能确保接收完整的一包数据。

结论

在配置PMA时,需要留意在设置单缓冲TX端点0时,不要覆盖使用到的端点的缓冲区配置信息。可以简单的用以下公式设置单缓冲TX端点0的底部地址:(使用到的最大端点n * 8)+ COUNT0_TX

STM32F103C8T6是一款基于ARM Cortex-M3内核的微控制器,它广泛应用于嵌入式系统开发中。HAL库(Hardware Abstraction Layer)是ST公司提供的一套软件,用于简化STM32微控制器的驱动和应用程序开发。 systemclock_configHAL库中的一个函数,用于配置系统时钟。系统时钟是微控制器中的一个重要参数,它决定了微控制器的运行速度和外设的工作频率。在使用STM32F103C8T6进行开发时,我们需要根据具体需求来配置系统时钟。 以下是一个示例代码,展示了如何使用HAL库中的systemclock_config函数来配置系统时钟: ```c #include "stm32f1xx_hal.h" void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct; RCC_ClkInitTypeDef RCC_ClkInitStruct; // 初始化RCC_OscInitStruct结构体 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.PLLMUL = RCC_PLL_MUL9; RCC_OscInitStruct.PLL.PREDIV = RCC_PREDIV_DIV1; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } // 初始化RCC_ClkInitStruct结构体 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_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } } ``` 在上述代码中,我们首先定义了RCC_OscInitTypeDef和RCC_ClkInitTypeDef结构体,用于配置时钟源和时钟分频等参数。然后,我们调用HAL_RCC_OscConfigHAL_RCC_ClockConfig函数来配置系统时钟。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值