关键字: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 - 保留所有权利