第七十二章 USB鼠标键盘(Host)实验
本章我们介绍如何使用STM32H7R7的USB HOST来驱动USB鼠标/键盘。
本章分为如下几个小节:
72.1 USB鼠标键盘简介
72.2 硬件设计
72.3 程序设计
72.4 下载验证
72.1 USB鼠标键盘简介
传统的鼠标和键盘是采用PS/2接口和电脑通信的,但是现在PS/2接口在电脑上逐渐消失,所以现在越来越多的鼠标键盘采用的是USB接口,而不是PS/2接口的了。
USB鼠标键盘属于USB HID设备。USB HID即:Human Interface Device(人机交互设备)的缩写,键盘、鼠标与游戏杆等都属于此类设备。不过HID设备并不一定要有人机接口,只要符合HID类别规范的设备都是HID设备。关于USB HID的知识,我们这里就不详细介绍了,请大家自行百度学习。
我们直接移植官方的USB HID例程,官方例程路径:8,STM32参考资料→1,STM32CubeH7RS固件包 →STM32Cube_FW_H7RS_V1.0.0 →Projects →STM32H7S78-DK →Applications →USB_Host →HID_RTOS,该例程支持USB鼠标和键盘等USB HID设备,本章我们将移植这个例程到我们的开发板上。
72.2 硬件设计
1. 例程功能
本实验代码,开机的时候先显示一些提示信息,然后初始化USB HOST,并不断轮询。当检测到USB鼠标/键盘的插入后,显示设备类型,并显示设备输入数据:
如果是USB鼠标:将显示鼠标移动的坐标(X,Y坐标)以及按键(左中右)。
如果是USB键盘:将显示键盘输入的数字/字母等内容(不是所有按键都支持,部分按键没有做解码支持,比如F1~F12)。
2. 硬件资源
1)LED灯
LED0: LED0 – PD14
LED1 :LED1 – PC0
2)串口1(PB14/PB15连接在板载USB转串口芯片CH340上面)
3)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(包括MCU屏和RGB屏,都支持)
4)HyperRAM芯片
5)USB_HOST接口(D-/D+连接在PM5/PM6上)
72.3 程序设计
72.3.1 程序解析
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。
本实验,我们在上一个实验的基础上修改,对照官方HID例子,然后,我们在工程里面添加USB HID相关代码,由于官方提供的USB_APP文件不太好用,我们正点原子团队直接编写了一套USB_APP代码,读者可以直接拷贝我们例程中的USB_APP文件夹,即:实验61 USB鼠标键盘(Host)实验→Middlewares→USB→USB_APP。如图72.3.2.1所示:

图72.3.2.1 USB鼠标键盘工程分组
1. USB驱动代码
usbh_hid_mouse.h,首先介绍usbh_hid_mouse.h里面定义的一个结构体,具体如下:
/* 鼠标信息结构体*/
typedef struct _HID_MOUSE_Info
{
uint8_t x; /* x轴增量(强制转换成signed char后使用)*/
uint8_t y; /* y轴增量(强制转换成signed char后使用)*/
uint8_t buttons[3]; /* 将buttons修改为button,存储按键状态 */
}
HID_MOUSE_Info_TypeDef;
HID_MOUSE_Info_TypeDef结构体中,成员x和y表示鼠标的横向/纵向移动的增量值,在使用的时候,需要强制转换成unsigned char类型后使用(有±)。button的bit0、bit1、bit2分别表示鼠标的:左键、右键和滚轮键的按下状态。我们通过这个结构体,就可以获得当前鼠标的输入状态。
usbh_hid_mouse.c,总共有三个函数,首先介绍的是USBH鼠标初始化函数,其定义如下:
/* 鼠标信息(坐标+按钮状态)*/
HID_MOUSE_Info_TypeDef mouse_info;
/* 鼠标上报数据长度,最多USBH_HID_MOUSE_REPORT_SIZE个字节*/
uint8_t mouse_report_data[USBH_HID_MOUSE_REPORT_SIZE];
uint8_t mouse_rx_report_buf[USBH_HID_MOUSE_REPORT_SIZE];
/**
* @brief USBH 鼠标初始化
* @param phost : USBH句柄指针
* @retval USB状态
* @arg USBH_OK(0) , 正常;
* @arg USBH_BUSY(1) , 忙;
* @arg 其他 , 失败;
*/
USBH_StatusTypeDef USBH_HID_MouseInit(USBH_HandleTypeDef *phost)
{
uint32_t i;
HID_HandleTypeDef *HID_Handle = (HID_HandleTypeDef *)
phost->pActiveClass->pData;
mouse_info.x = 0U;
mouse_info.y = 0U;
mouse_info.buttons[0] = 0U;
mouse_info.buttons[1] = 0U;
mouse_info.buttons[2] = 0U;
for (i = 0U; i < sizeof(mouse_report_data); i++)
{
mouse_report_data[i] = 0U;
mouse_rx_report_buf[i] = 0U;
}
if (HID_Handle->length > sizeof(mouse_report_data))
{
HID_Handle->length = (uint16_t)sizeof(mouse_report_data);
}
HID_Handle->pData = mouse_rx_report_buf;
if ((HID_QUEUE_SIZE * sizeof(mouse_report_data)) > sizeof(phost->
device.Data))
{
return USBH_FAIL;
}
else
{
USBH_HID_FifoInit(&HID_Handle->fifo, phost->device.Data, (uint16_t)(HID_QUEUE_SIZE * sizeof(mouse_report_data)));
}
return USBH_OK;
}
USBH_HID_MouseInit函数,用于初始化鼠标,指定USB鼠标输出数据的缓存,初始化FIFO等,该函数在USBH_HID_InterfaceInit函数里面被调用。
接下来介绍的是USBH获取鼠标信息函数,其定义如下:
/**
* @brief USBH 获取鼠标信息
* @param phost : USBH句柄指针
* @retval 鼠标信息(HID_MOUSE_Info_TypeDef)
*/
HID_MOUSE_Info_TypeDef *USBH_HID_GetMouseInfo(USBH_HandleTypeDef *phost)
{
if (USBH_HID_MouseDecode(phost) == USBH_OK)
{
return &mouse_info;
}
else
{
return NULL;
}
}
USBH_HID_GetMouseInfo函数,用于获取鼠标信息,通过USBH_HID_MouseDecode函数获取当前鼠标传回来的数据。该函数在USB_Demo函数里面被调用。
最后介绍的是USBH 鼠标数据解析函数,其定义如下:
/**
* @brief USBH 鼠标数据解析函数
* @param phost : USBH句柄指针
* @retval USB状态
* @arg USBH_OK(0) , 正常;
* @arg USBH_BUSY(1) , 忙;
* @arg 其他 , 失败;
*/
static USBH_StatusTypeDef USBH_HID_MouseDecode(USBH_HandleTypeDef *phost)
{
HID_HandleTypeDef *HID_Handle = (HID_HandleTypeDef *)
phost->pActiveClass->pData;
if ((HID_Handle->length == 0U) || (HID_Handle->fifo.buf == NULL))
{
return USBH_FAIL;
}
/*Fill report */
if (USBH_HID_FifoRead(&HID_Handle->fifo, &mouse_report_data, HID_Handle
->length) == HID_Handle->length)
{
/*Decode report */
mouse_info.x = (uint8_t)HID_ReadItem((HID_Report_ItemTypedef *) &prop_x,
0U);
mouse_info.y = (uint8_t)HID_ReadItem((HID_Report_ItemTypedef *) &prop_y,
0U);
mouse_info.buttons[0] = (uint8_t)HID_ReadItem((HID_Report_ItemTypedef *)
&prop_b1, 0U);
mouse_info.buttons[1] = (uint8_t)HID_ReadItem((HID_Report_ItemTypedef *)
&prop_b2, 0U);
mouse_info.buttons[2] = (uint8_t)HID_ReadItem((HID_Report_ItemTypedef *)
&prop_b3, 0U);
return USBH_OK;
}
return USBH_FAIL;
}
USBH_HID_MouseDecode函数,用于解析USB HOST获取到的USB鼠标数据,并存放到mouse_info结构体里面。该函数被USBH_HID_GetMouseInfo函数调用。
2. main.c代码
下面是main.c的程序,具体如下:
int main(void)
{
uint32_t t = 0;
HID_TypeTypeDef hid_type = HID_UNKNOWN;
HID_MOUSE_Info_TypeDef *hid_mouse_info_struct;
HID_KEYBD_Info_TypeDef *hid_keybd_info_struct;
int16_t mouse_x_abs;
int16_t mouse_y_abs;
uint8_t keyboard_ascii_code;
sys_mpu_config(); /* 配置MPU */
sys_cache_enable(); /* 使能Cache */
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(300, 6, 2); /* 配置时钟,600MHz */
delay_init(600); /* 初始化延时 */
usart_init(115200); /* 初始化串口 */
led_init(); /* 初始化LED */
hyperram_init(); /* 初始化HyperRAM */
lcd_init(); /* 初始化LCD */
my_mem_init(SRAMIN); /* 初始化AXI-SRAM1~4内存池 */
my_mem_init(SRAMEX); /* 初始化XSPI2 HyperRAM内存池 */
my_mem_init(SRAM12); /* 初始化AHB-SRAM1~2内存池 */
my_mem_init(SRAMDTCM); /* 初始化DTCM内存池 */
my_mem_init(SRAMITCM); /* 初始化ITCM内存池 */
lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
lcd_show_string(30, 70, 200, 16, 16, "USB Mouse/Keyboard TEST", RED);
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
USBH_Init(&g_usbh_handle, usbh_user_process, HOST_HS);
USBH_RegisterClass(&g_usbh_handle, USBH_HID_CLASS);
USBH_Start(&g_usbh_handle);
while (1)
{
USBH_Process(&g_usbh_handle);
switch (g_status)
{
case STA_IDLE:
{
break;
}
case STA_READY:
{
if (hid_type == HID_UNKNOWN)
{
hid_type = USBH_HID_GetDeviceType(&g_usbh_handle);
if (hid_type == HID_MOUSE)
{
hid_mouse_info_struct = NULL;
mouse_x_abs = 0;
mouse_y_abs = 0;
lcd_show_string(30, 110, 200, 16, 16, "Mouse", BLUE);
lcd_show_string(30, 130, 200, 16, 16, "Button: Left
Middle Right", BLUE);
lcd_show_string(30, 150, 200, 16, 16, "X: 0", BLUE);
lcd_show_string(30, 170, 200, 16, 16, "Y: 0", BLUE);
}
else if (hid_type == HID_KEYBOARD)
{
hid_keybd_info_struct = NULL;
keyboard_ascii_code = 0;
lcd_show_string(30, 110, 200, 16, 16, "Keyboard",BLUE);
lcd_show_string(30, 130, 200, 16, 16, "ASCII: ",BLUE);
lcd_show_string(30, 150, 200, 16, 16, "ASCII Code: ",
BLUE);
}
}
break;
}
case STA_DISCONNECT:
{
g_status = STA_IDLE;
hid_type = HID_UNKNOWN;
lcd_fill(30, 110, 240, 186, WHITE);
break;
}
}
if (hid_type == HID_MOUSE)
{
hid_mouse_info_struct = USBH_HID_GetMouseInfo(&g_usbh_handle);
if (hid_mouse_info_struct != NULL)
{
/* 处理鼠标左键数据 */
if (hid_mouse_info_struct->buttons[0] != 0)
{
lcd_show_string(94, 130, 200, 16, 16, "Left", RED);
}
else
{
lcd_show_string(94, 130, 200, 16, 16, "Left", BLUE);
}
/* 处理鼠标右键数据 */
if (hid_mouse_info_struct->buttons[1] != 0)
{
lcd_show_string(190, 130, 200, 16, 16, "Right", RED);
}
else
{
lcd_show_string(190, 130, 200, 16, 16, "Right", BLUE);
}
/* 处理鼠标中键数据 */
if (hid_mouse_info_struct->buttons[2] != 0)
{
lcd_show_string(134, 130, 200, 16, 16, "Middle", RED);
}
else
{
lcd_show_string(134, 130, 200, 16, 16, "Middle", BLUE);
}
/* 处理鼠标X轴移动数据 */
mouse_x_abs += (int8_t)hid_mouse_info_struct->x;
if (mouse_x_abs > 9999)
{
mouse_x_abs = 9999;
}
else if (mouse_x_abs < -9999)
{
mouse_x_abs = -9999;
}
if (mouse_x_abs < 0)
{
lcd_show_string(54, 150, 200, 16, 16, "-", BLUE);
lcd_show_num(62, 150, -mouse_x_abs, 4, 16, BLUE);
}
else
{
lcd_show_string(54, 150, 200, 16, 16, " ", BLUE);
lcd_show_num(62, 150, mouse_x_abs, 4, 16, BLUE);
}
/* 处理鼠标Y轴移动数据 */
mouse_y_abs += (int8_t)hid_mouse_info_struct->y;
if (mouse_y_abs > 9999)
{
mouse_y_abs = 9999;
}
else if (mouse_y_abs < -9999)
{
mouse_y_abs = -9999;
}
if (mouse_y_abs < 0)
{
lcd_show_string(54, 170, 200, 16, 16, "-", BLUE);
lcd_show_num(62, 170, -mouse_y_abs, 4, 16, BLUE);
}
else
{
lcd_show_string(54, 170, 200, 16, 16, " ", BLUE);
lcd_show_num(62, 170, mouse_y_abs, 4, 16, BLUE);
}
}
}
else if (hid_type == HID_KEYBOARD)
{
hid_keybd_info_struct = USBH_HID_GetKeybdInfo(&g_usbh_handle);
if (hid_keybd_info_struct != NULL)
{
keyboard_ascii_code = USBH_HID_GetASCIICode
(hid_keybd_info_struct);
if (keyboard_ascii_code != 0)
{
/* 显示键盘输入字符 */
lcd_show_char(86, 130, keyboard_ascii_code, 16, 0, BLUE);
/* 显示键盘输入字符的ASCII码 */
lcd_show_num(126, 150, keyboard_ascii_code, 2, 16, BLUE);
}
else
{
lcd_show_string(86, 130, 200, 16, 16, " ", BLUE);
lcd_show_string(126, 150, 200, 16, 16, " ", BLUE);
}
}
}
if (++t == 200000)
{
t = 0;
LED0_TOGGLE();
}
delay_us(1);
}
}
main函数,初始化各个外设后,通过USBH_Init等3个函数初始化并启动USB主机通信,然后进入死循环,不停的调用USBH_Process,判断USB HOST枚举是否死机、处理连接状态、判断HID设备类型、以及执行各种USB处理,确定HID 设备后,通过调用USBH_HID_GetMouseInfo或者USBH_HID_GetKeybdInfo函数读取相应的设备数据,然后进行相应的处理,最后输出到屏幕中。
72.4 下载验证
将程序下载到开发板后,然后在USB_HOST端子插入USB鼠标/键盘,注意:此时USB SLAVE口不要插USB线到电脑,否则会干扰!
等USB鼠标/键盘成功识别后,便可以看到LCD显示USB Connected,并显示设备类型:USB Mouse或者USB KeyBoard,同时也会显示输入的数据,如图72.4.1和图72.4.2所示:

图72.4.1 USB鼠标测试

图72.4.2 USB键盘测试
其中,图72.4.1是USB鼠标测试界面,图72.4.2是USB键盘测试界面。
最后,特别提醒大家,由于例程的HID内核,只处理了第一个接口描述符,所以对于USB符合设备,只能识别第一个描述符所代表的设备。体现到实际使用中,就是:USB无线鼠标,一般是无法使用(被识别为键盘),而USB无线键盘,可以使用,因为键盘在第一个描述符,鼠标在第二个描述符 。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70040860/viewspace-3098948/ ,如需转载,请注明出处,否则将追究法律责任。