【官方原创】STM32 USBx Host HID standardalone移植示例 LAT1449

关键字:USBx, Host, standalone

1.设计目的

目前USBx host standalone的官方示例较少,仅有一个。不过使用CubeMX可以快速地生成

USBx Host相关类的示例工程,会很方便大家的开发。这里以NUCLEO-H563为例,实现

USBx Host HID类,大家可以以此为参考移植到其他的USBx Host类。

2.示例移植

官方示例参考代码:

\STM32Cube\Repository\STM32Cube_FW_H5_V1.2.0\Projects\STM32H573I

DK\Applications\USBX\Ux_Host_HID_Standalone

2.1.生成CubeMX工程

新建CubeMX工程 :STM32H563ZIT6U,选择without TrustZone activated. 另外,

CubeMX中未作说明的配置保持默认.

2.1.1 System Core相关配置

在System Core框架下,Cortex_M33标签页面下使用的是HCLK. 如下图:

RCC的标签页下面:由于作为USB Host,所以这里使用外部时钟作为USB的时钟源, 采用

BYPASS Clock Source MCO 引脚输出作为MCU的系统时钟源(来源于板载ST-LINK的输出时

钟),如下图:

在ICACHE的标签页的配置如下图:

另外,SYS标签页下面的Timebase Source为Systick

2.1.2 Connectivity 的相关配置

根据NUCLEO-H563的硬件原理图定义,这里选择USART3打印输出相关的USB操作信息。

不用开中断或者DMA,波特率默认115200.

注意修改USART3使用的端口引脚,这儿是PD8与PD9,与默认CubeMX配置引脚不一样。

在USB下面的配置如下:中断优先等级设置为6;

2.1.3 Middleware 的相关配置

在USBx下面, 由于是standalone的示例,所以这儿不用选择操作系统的中间件。

在Host Class FS 下面选择, 也可以只选择Keyboard或者Mouse,或者两者都选。

USBx 的具体配置如下图,可以看出主要检查或修改了默认的如下5处地方,

UXHost memory pool size 和 USBX Host System Stack Size 均由默认的 1024 设置为

22K(22*1024 = 22528),可以根据项目情况适当调整这个大小。

2.1.4 System Clock 相关配置

由于选择了Bypass模式的8Mhz,这里注意要修改为一致。USB Host IP的时钟需要48Mhz,

这里选择PLL1Q=48Mhz ;

2.1.5 生成项目工程

为项目命名,生成工程,配置堆栈的大小:

这时候可能会提示警告,如下图所示,不用担心,直接选择yes生成代码。

产生问题的原因是这儿的Driver VBUS_FS没有相关的GPIO或者硬件去配置,如下图:

后续为了能驱动这儿的VBUS,需要注意的是连接NUCLEO-H563板上的JP2选择

'STLK'(JP2:1~2)和 'USB USER'(JP2:9~10)用于 VBUSC 供电。如下图所示:

生成工程代码,编译没有问题,这时候可以接着添加相关的应用代码。

2.2.添加运用代码

2.2.1 完善串口打印

不同的IAR版本,它的重定位函数可能不一样,参考\STM32H573I

DK\Applications\USBX\Ux_Host_HID_Standalone 的例程,添加相关函数,这里不再过多描

述,相关截图如下:

在main.c中添加:

复制代码
/* USER CODE BEGIN Includes */ // 包含标准C头文件 
#include "stdio.h" 
/* USER CODE END PFP */ 
 
/* USER CODE BEGIN PFP */ 
#if defined(__ICCARM__) 
/* New definition from EWARM V9, compatible with EWARM8 */ 
int iar_fputc(int ch); 
#define PUTCHAR_PROTOTYPE int iar_fputc(int ch) 
size_t __write(int file, unsigned char const *ptr, size_t len); 
#elif defined ( __CC_ARM ) || defined(__ARMCC_VERSION) 
/* ARM Compiler 5/6*/ 
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f) 
#elif defined(__GNUC__) 
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch) 
#endif /* __ICCARM__ */ 
 
/* USER CODE END PFP */ 

/* USER CODE BEGIN 4 */ 
#if defined(__ICCARM__) 
size_t __write(int file, unsigned char const *ptr, size_t len) 
{ 
  size_t idx; 
  unsigned char const *pdata = ptr; 
 
  for (idx = 0; idx < len; idx++) 
  { 
    iar_fputc((int)*pdata); 
    pdata++; 
  } 
  return len; 
} 
#endif /* __ICCARM__ */ 
 
/** 
  * @brief  Retargets the C library printf function to the USART. 
  */ 
PUTCHAR_PROTOTYPE 
{ 
  /* Place your implementation of putchar here */ 
  /* e.g. write a character to the USART3 and Loop until the end of transmission */ 
  HAL_UART_Transmit(&huart3, (uint8_t *)&ch, 1, 0xFFFF); 
 
  return ch; 
} 
/* USER CODE END 4 */

注意NUCLEO-H563是USART3,例程中默认的是USART1,注意修改。

在app_usbx_host.h文件中,添加如下代码:

复制代码
/* USER CODE BEGIN EM */ 
#define USBH_UsrLog(...)   printf(__VA_ARGS__);\ 
                           printf("\r\n"); 
 
#define USBH_ErrLog(...)   printf("ERROR: ") ;\ 
                           printf(__VA_ARGS__);\ 
                           printf("\r\n"); 
/* USER CODE END EM */

验证串口打印的功能,在main函数中随便输出一个字符串看看,确认没有问题(可选)。

2.2.2 添加必要的USBx的初始化函数

复制代码
/* USER CODE BEGIN MX_USBX_Host_Init1 */ 
/* Initialize USBX_Host */ 
USBX_APP_Host_Init(); 
/* USER CODE END MX_USBX_Host_Init1 */

在MX_USBX_Host_Init 中去添加应用的初始代码USBX_APP_Host_Init的初始化;

复制代码
/* USER CODE BEGIN PFP */ 
VOID USBX_APP_Host_Init(VOID); 
extern HCD_HandleTypeDef hhcd_USB_DRD_FS; 
/* USER CODE END PFP */

添加该函数的声明和指针 句柄hhcd_USB_DRD_FS;

添加头文件:#include "ux_hcd_stm32.h"

复制代码
/* USER CODE BEGIN Includes */ 
#include "ux_hcd_stm32.h" 
/* USER CODE END Includes */

添加应用的初始化的具体代码:

此时还没有开始枚举,打印的log为如下:

复制代码
/* USER CODE BEGIN 1 */ 
   
/** 
  * @brief  USBX_APP_Host_Init 
  *         Initialization of USB host. 
  * @param  none 
  * @retval none 
  */ 
VOID USBX_APP_Host_Init(VOID) 
{ 
  /* USER CODE BEGIN USB_Host_Init_PreTreatment_0 */ 
 
  /* USER CODE END USB_Host_Init_PreTreatment_0 */ 
 
  /* Initialize the LL driver */ 
  /* Register all the USB host controllers available in this system. */ 
  ux_host_stack_hcd_register(_ux_system_host_hcd_stm32_name, 
                             _ux_hcd_stm32_initialize, (ULONG)USB_DRD_FS, 
                             (ULONG)&hhcd_USB_DRD_FS); 
 
  /* Enable USB Global Interrupt */ 
  HAL_HCD_Start(&hhcd_USB_DRD_FS); 
 
  /* USER CODE BEGIN USB_Host_Init_PostTreatment1 */ 
 
  /* Start Application Message */ 
  USBH_UsrLog("**** USB DRD HID Host **** \r\n"); 
  USBH_UsrLog("USB Host library started.\r\n"); 
 
  /* Wait for Device to be attached */ 
  USBH_UsrLog("Starting HID Application"); 
  USBH_UsrLog("Connect your HID Device"); 
 
  /* USER CODE END USB_Host_Init_PostTreatment1 */ 
} 
 
/* USER CODE END 1 */ 

2.2.2 添加USBx的处理函数

复制代码
  /* USER CODE BEGIN 3 */ 
    USBX_Host_Process(NULL); 
     
  } 
  /* USER CODE END 3 */

在main函数的while循环中添加函数 USBX_Host_Process(NULL);

添加该函数的定义和申明在app_usbx_host.c文件中实现,请注意这里还需要添加键盘和鼠标的

应用处理函数。

复制代码
/* USER CODE BEGIN EFP */ 
VOID USBX_Host_Process(VOID *arg); 
/* USER CODE END EFP */ 

/* USER CODE BEGIN 1 */ 
VOID USBX_Host_Process(VOID *arg) 
{ 
ux_host_stack_tasks_run(); 
USBX_HOST_HID_MOUSE_Task(); 
USBX_HOST_HID_KEYBORAD_Task(); 
} 
/* USER CODE END 1 */

2.2.3 修改 USBx Host 的 timming 函数

2.2.4 修改USBx 的ux_host_event_callback 函数和 ux_host_error_callback 函数。

STM32CubeMX生成的event几乎为空,没有执行函数,不难理解为什么没有实现枚举了。复

制例程的函数内容,并定义HID的Mouse和Keyboard的相关句柄。

复制代码
/* USER CODE BEGIN PV */ 
UX_HOST_CLASS_HID          
*hid_instance; 
UX_HOST_CLASS_HID_MOUSE    *mouse; 
UX_HOST_CLASS_HID_KEYBOARD *keyboard; 
/* USER CODE END PV */

/* USER CODE BEGIN _ux_utility_time_get */ 
time_tick = HAL_GetTick(); 
/* USER CODE END _ux_utility_time_get */ 

在ux_host_event_callback 中添加必要的设备插入,HID检测等处理。

复制代码
UINT ux_host_event_callback(ULONG event, UX_HOST_CLASS *current_class, VOID *current_instance) 
{ 
  UINT status = UX_SUCCESS; 
 
  /* USER CODE BEGIN ux_host_event_callback0 */ 
 
  /* Get current Hid Client */ 
  UX_HOST_CLASS_HID_CLIENT *client = (UX_HOST_CLASS_HID_CLIENT *) current_instance; 
 
  /* USER CODE END ux_host_event_callback0 */ 
 
  switch (event) 
  { 
    case UX_DEVICE_INSERTION: 
 
      /* USER CODE BEGIN UX_DEVICE_INSERTION */ 
      USBH_UsrLog("\n usb Client Plugged"); 
      /* Get current Hid Class */ 
      if (current_class->ux_host_class_entry_function == ux_host_class_hid_entry) 
      { 
        if (hid_instance == UX_NULL) 
        { 
          /* Get current Hid Instance */ 
          hid_instance = (UX_HOST_CLASS_HID *)current_instance; 
        } 
      } 
      /* USER CODE END UX_DEVICE_INSERTION */ 
      break; 
    case UX_DEVICE_REMOVAL: 
      /* USER CODE BEGIN UX_DEVICE_REMOVAL */ 
 
      /* Free HID Instance */ 
      if ((VOID*)hid_instance == current_instance) 
      { 
        hid_instance = UX_NULL; 
      } 
      /* USER CODE END UX_DEVICE_REMOVAL */ 
      break; 
 
    case UX_HID_CLIENT_INSERTION: 
 
      /* USER CODE BEGIN UX_HID_CLIENT_INSERTION */ 
 
      USBH_UsrLog("\nHID Client Plugged"); 
 
      /* Check the HID_client if this is a HID keyboard device */ 
      if (client->ux_host_class_hid_client_handler == ux_host_class_hid_keyboard_entry) 
      { 
        /* Get current Hid Client */ 
        if (keyboard == UX_NULL) 
        { 
          keyboard = client -> ux_host_class_hid_client_local_instance; 
 
          USBH_UsrLog("HID_Keyboard_Device"); 
          USBH_UsrLog("PID: %#x ", 
(UINT)keyboard->ux_host_class_hid_keyboard_hid->ux_host_class_hid_device->ux_device_descriptor.idProduct); 
          USBH_UsrLog("VID: %#x ", 
(UINT)keyboard->ux_host_class_hid_keyboard_hid->ux_host_class_hid_device->ux_device_descriptor.idVendor); 
          USBH_UsrLog("USB HID Host Keyboard App..."); 
          USBH_UsrLog("keyboard is ready...\n"); 
        } 
      } 
      /* USER CODE END UX_HID_CLIENT_INSERTION */ 
 
      break;
case UX_HID_CLIENT_REMOVAL: 
 
      /* USER CODE BEGIN UX_HID_CLIENT_REMOVAL */ 
 
      /* Clear hid client local instance */ 
      if ((VOID*)keyboard == client->ux_host_class_hid_client_local_instance) 
      { 
        /* Clear hid keyboard instance */ 
        keyboard = UX_NULL; 
 
        USBH_UsrLog("\nHID Client Keyboard Unplugged"); 
      } 
 
      if ((VOID*)mouse == client->ux_host_class_hid_client_local_instance) 
      { 
        /* Clear hid mouse instance */ 
        mouse = UX_NULL; 
 
        USBH_UsrLog("\nHID Client Mouse Unplugged"); 
      } 
 
      /* USER CODE END UX_HID_CLIENT_REMOVAL */ 
 
      break; 
 
    case UX_DEVICE_CONNECTION: 
 
      /* USER CODE BEGIN UX_DEVICE_CONNECTION */ 
 
      /* USER CODE END UX_DEVICE_CONNECTION */ 
 
      break; 
 
    case UX_DEVICE_DISCONNECTION: 
 
      /* USER CODE BEGIN UX_DEVICE_DISCONNECTION */ 
 
      /* USER CODE END UX_DEVICE_DISCONNECTION */ 
 
      break; 
 
    default: 
 
      /* USER CODE BEGIN EVENT_DEFAULT */ 
 
      /* USER CODE END EVENT_DEFAULT */ 
      break; 
  } 
  /* USER CODE BEGIN ux_host_event_callback1 */ 
  /* USER CODE END ux_host_event_callback1 */ 
 
  return status; 
}

在ux_host_error_callback中添加必要的错误处理。在本LAT中只是添加了一些打印信息,并

没有做太多处理。

复制代码
case UX_DEVICE_ENUMERATION_FAILURE: 
/* USER CODE BEGIN UX_DEVICE_ENUMERATION_FAILURE */ 
USBH_UsrLog("USB Device Enumeration Failure"); 
/* USER CODE END UX_DEVICE_ENUMERATION_FAILURE */ 
break; 
case  UX_NO_DEVICE_CONNECTED: 
/* USER CODE BEGIN UX_NO_DEVICE_CONNECTED */ 
USBH_UsrLog("USB Device disconnected"); 
/* USER CODE END UX_NO_DEVICE_CONNECTED */ 
break; 

编译这时会发现,枚举成功了。(注意,这儿实验的是USB Host的功能,所以通过串口打印的

log 查看相关流程操作,PC电脑端查看不了)。

请注意在这里如果要运行,请注释掉USBX_HOST_HID_MOUSE_Task(); 和

USBX_HOST_HID_KEYBORAD_Task();

2.2.5 添加相关类的操作函数

打开函数USBX_HOST_HID_MOUSE_Task();和BX_HOST_HID_KEYBORAD_Task();添加必要的

HID实现函数。

复制代码
/* USER CODE BEGIN Includes */ 
#include "app_usbx_host.h" 
/* USER CODE END Includes */ 
 
 
/* USER CODE BEGIN PV */ 
extern UX_HOST_CLASS_HID_KEYBOARD *keyboard; 
 
/* USER CODE END PV */ 
 
/* USER CODE BEGIN 1 */ 
 
/** 
  * @brief  USBX_HOST_HID_KEYBORAD_Task 
  * @param  none 
  * @retval none 
  */ 
VOID USBX_HOST_HID_KEYBORAD_Task(VOID) 
{ 
  ULONG keyboard_key; 
  ULONG keyboard_state; 
 
  /* Start if the hid client is a keyboard and connected */ 
  if ((keyboard != NULL) && 
      (keyboard->ux_host_class_hid_keyboard_state == (ULONG) UX_HOST_CLASS_INSTANCE_LIVE)) 
  { 
    /* Get a key and state from the keyboard */ 
    if ((keyboard != NULL) && ux_host_class_hid_keyboard_key_get(keyboard, &keyboard_key, &keyboard_state) == UX_SUCCESS) 
    { 
      /* Print the key pressed */ 
      USBH_UsrLog("%c", (CHAR)keyboard_key); 
    } 
  } 
} 
 
/* USER CODE END 1 */ 
ux_host_keyboard.c 
/* USER CODE BEGIN EFP */ 
VOID USBX_HOST_HID_KEYBORAD_Task(VOID); 
/* USER CODE END EFP */
 
/* USER CODE BEGIN Includes */ 
#include "app_usbx_host.h" 
/* USER CODE END Includes */ 
/* USER CODE BEGIN PV */ 
extern UX_HOST_CLASS_HID_MOUSE *mouse; 
/* USER CODE END PV */ 
/* USER CODE BEGIN 1 */ 
VOID USBX_HOST_HID_MOUSE_Task(VOID) 
{ 
  static LONG old_Pos_x = 0; 
  static LONG old_Pos_y = 0; 
  LONG actual_Pos_x = 0; 
  LONG actual_Pos_y = 0; 
  ULONG actual_mouse_buttons = 0; 
  static ULONG old_mouse_buttons = 0; 
  SLONG actual_mouse_wheel = 0; 
  static SLONG old_mouse_wheel = 0; 
 
  /* Check if mouse instance is Null */ 
  if ((mouse != NULL) && 
      (mouse -> ux_host_class_hid_mouse_state == (ULONG) UX_HOST_CLASS_INSTANCE_LIVE)) 
  { 
    /* Get Mouse position */ 
    if ((mouse != NULL) && ux_host_class_hid_mouse_position_get(mouse, &actual_Pos_x, &actual_Pos_y) == UX_SUCCESS) 
    { 
      if (((actual_Pos_x != old_Pos_x) || (actual_Pos_y != old_Pos_y)) && 
        (actual_Pos_x != 0) && (actual_Pos_y != 0)) 
      { 
        USBH_UsrLog("Pos_x = %ld Pos_y= %ld", actual_Pos_x, actual_Pos_y); 
        /* Update (x,y) old position */ 
        old_Pos_x = actual_Pos_x; 
        old_Pos_y = actual_Pos_y; 
      } 
    } 
    /* Get Mouse buttons value */ 
    if ((mouse != NULL) && ux_host_class_hid_mouse_buttons_get(mouse, &actual_mouse_buttons) == UX_SUCCESS) 
    { 
      if (actual_mouse_buttons != old_mouse_buttons) 
      { 
        /* Check which button is pressed */ 
        if (actual_mouse_buttons & UX_HOST_CLASS_HID_MOUSE_BUTTON_1_PRESSED) 
        { 
          USBH_UsrLog("Left Button Pressed"); 
        } 
        if (actual_mouse_buttons & UX_HOST_CLASS_HID_MOUSE_BUTTON_2_PRESSED) 
        { 
          USBH_UsrLog("Right Button Pressed"); 
        } 
        if (actual_mouse_buttons & UX_HOST_CLASS_HID_MOUSE_BUTTON_3_PRESSED) 
        { 
          USBH_UsrLog("Middle Button Pressed"); 
        } 
        /* Update button old value */ 
        old_mouse_buttons = actual_mouse_buttons; 
      } 
    } 
    /* Get hid wheel mouse position */ 
    if ((mouse != NULL) && ux_host_class_hid_mouse_wheel_get(mouse, &actual_mouse_wheel) == UX_SUCCESS) 
    { 
      if ((actual_mouse_wheel != old_mouse_wheel) && (actual_mouse_wheel != 0)) 
      { 
        USBH_UsrLog("Pos_weel = %ld", actual_mouse_wheel); 
 
        /* Update wheel mouse movement value */ 
        old_mouse_wheel = actual_mouse_wheel; 
      } 
    } 
  } 
} 
/* USER CODE END 1 */
/* USER CODE BEGIN EFP */ 
VOID USBX_HOST_HID_MOUSE_Task(VOID); 
/* USER CODE END EFP */

编译运行,请不要注释掉USBX_HOST_HID_MOUSE_Task(); 和

USBX_HOST_HID_KEYBORAD_Task();这时候会发现USBx Host HID 的示例就成功了。


重要通知 - 请仔细阅读 意法半导体公司及其子公司 ("ST")保留随时对 ST 产品和 / 或本文档进行变更的权利,恕不另行通知。买方在订货之前应获取关于 ST 产 品的最新信息。 ST 产品的销售依照订单确认时的相关 ST 销售条款。 买方自行负责对 ST 产品的选择和使用, ST 概不承担与应用协助或买方产品设计相关的任何责任。 ST 不对任何知识产权进行任何明示或默示的授权或许可。 转售的 ST 产品如有不同于此处提供的信息的规定,将导致 ST 针对该产品授予的任何保证失效。 ST 和 ST 徽标是 ST 的商标。若需 ST 商标的更多信息,请参考 www.st.com/trademarks。所有其他产品或服务名称均为其 各自所有者的财 产。 本文档是ST中国本地团队的技术性文章,旨在交流与分享,并期望借此给予客户产品应用上足够的帮助或提醒。若文中内容存有局限或与ST 官网资料不一致,请以实际应用验证结果和ST官网最新发布的内容为准。您拥有完全自主权是否采纳本文档(包括代码,电路图等)信息, 我们也不承担因使用或采纳本文档内容而导致的任何风险。 本文档中的信息取代本文档所有早期版本中提供的信息。 © 2020 STMicroelectronics - 保留所有权利

相关推荐
竹林8182 小时前
用wagmi v2构建DeFi前端:从连接钱包到读取合约数据的完整实战与避坑指南
前端·javascript
over6972 小时前
面试官视角:TypeScript Pick 工具类型深度解析与手写实现
前端·面试
木斯佳2 小时前
前端八股文面经大全:字节AIDP前端一面(2026-04-13)·面经深度解析
前端·音视频·webrtc·断点续传
若阳安好2 小时前
【java】任务流批处理平台
java·开发语言
Kinghiee2 小时前
从零打造生产级前端错误监控 SDK:架构设计与 Vue3 实践
前端·javascript·vue.js·去重·错误捕获·上报·离线持久化
辰哥单片机设计2 小时前
STM32项目分享:空气质量检测系统(机智云)
stm32·单片机·嵌入式硬件
小凡同志2 小时前
OpenSpec 手把手实战:从零跑通一个完整功能
前端·ai编程·claude
吴声子夜歌2 小时前
Vue3——元素样式绑定
前端·javascript·vue.js·es6
San30.2 小时前
前端进阶:从浏览器渲染原理到网络请求全链路解析
前端·网络·网络请求·浏览器渲染机制