本帖最后由 正点原子运营 于 2026-2-12 15:31 编辑
第四十八章 USB鼠标(Host)实验
1)实验平台:正点原子DNESP32P4开发板
2)章节摘自【正点原子】ESP32-P4开发指南— V1.0
3)购买链接:https://detail.tmall.com/item.htm?id=873309579825
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/esp32/ATK-DNESP32P4.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子DNESP32S3开发板技术交流群:132780729
本章我们介绍如何使用ESP32-P4的USB HOST来驱动USB鼠标。
本章分为如下几个部分:
48.1 USB鼠标键盘简介
48.2 硬件设计
48.3 程序设计
48.4 下载验证
48.1 USB鼠标键盘简介
传统的鼠标是采用PS/2接口和电脑通信的,但是现在PS/2接口在电脑上逐渐消失,所以现在越来越多的鼠标采用的是USB接口,而不是PS/2接口的了。
USB鼠标属于USB HID设备。USB HID即:Human Interface Device(人机交互设备)的缩写,键盘、鼠标与游戏杆等都属于此类设备。不过HID设备并不一定要有人机接口,只要符合HID类别规范的设备都是HID设备。关于USB HID的知识,我们这里就不详细介绍了,请大家自行百度学习。
ESP32-P4的USB OTG HS接口是支持鼠标功能的。Espressif官方在ESP-IDF中提供了针对USB HOST HID人机交互的示例代码,路径为:esp-idf\examples\peripherals\usb\host\hid。本章实验将参考官方示例代码,通过ESP32-P4的USB HOST接口实现对鼠标设备操作。
48.2 硬件设计
48.2.1 程序功能
本实验代码,开机的时候先显示一些提示信息,然后初始化USB HOST,并不断轮询。当检测到USB鼠标的插入后,PC机通过USB接口将显示鼠标移动的坐标(X,Y坐标)和左右按键的状态发送至DNSP32P4开发板,然后把这些数据显示在LCD上。
48.2.2 硬件资源
1)RGBLCD/MIPILCD(引脚太多,不罗列出来)
2)USB_HOST HS接口
3)本实验接入的是小米蓝牙鼠标无线双模静音版鼠标
48.2.3 原理图
USB HOST原理图已在47.2.3小节中详细阐述,为避免重复,此处不再赘述。
48.3 程序设计
48.3.1 USB HOST HID的IDF驱动
usb_host_hid组件驱动位于ESP-IDF在线组件注册表中。如果需要将该组件添加到项目工程中,可按照以下步骤操作:
1)打开ESP-IDF注册表。
2)搜索 “usb_host_hid”组件。
3)将组件安装到项目中。
组件安装完成后,系统会自动更新main文件夹中的特殊组件清单文件idf_component.yml在项目编译时,系统会根据清单文件从注册表中下载并集成该组件到工程中。关于上述操作流程,可参考本书籍第八章的内容。
为了使用 usb_host_hid组件提供的功能,首先需要在代码中导入以下头文件:
- #include "usb/usb_host.h"
- #include "usb/hid_host.h"
- #include "usb/hid_usage_mouse.h"
复制代码 接下来,作者将本章节实验用到的usb_host_hid函数,这些函数的描述及其作用如下:
1,安装USB HID主机的USB Host HID类驱动hid_host_install
该函数用于安装 USB HID 主机的 USB Host HID 类驱动,其函数原型如下:
- esp_err_t hid_host_install(const hid_host_driver_config_t *config);
复制代码 函数形参:
表48.3.1.1 hid_host_install函数形参描述
返回值:
ESP_OK表示配置成功。
其他错误码表示配置失败。
config为指向USB Host HID 类驱动的配置结构体。接下来,笔者将详细介绍hid_host_driver_config_t结构体中的各个成员变量,如下代码所示:
- /**
- * @brief HID 配置结构体
- */
- typedef struct {
- /* 当设置为 true 时,将创建一个处理 USB 事件的后台任务。*/
- bool create_background_task;
- size_t task_priority; /* 创建的后台任务的任务优先级 */
- size_t stack_size; /* 创建的后台任务的堆栈大小 */
- BaseType_t core_id; /* 选择后台任务运行的核心或 tskNO_AFFINITY */
- hid_host_driver_event_cb_t callback;/* HID驱动事件发生时调用的回调函数,不能为空 */
- void *callback_arg; /* 用户提供的参数,将传递给回调函数 */
- } hid_host_driver_config_t;
复制代码 上述结构体用于传递USB Host HID 类配置参数,以下对各个成员做简单介绍。
1)create_background_task:
若此字段设置为true,则创建USB事件后台任务。
2)task_priority:
设置USB事件后台任务优先级。
3)stack_size:
设置USB事件后台任务堆栈大小。
4)core_id:
将USB事件任务运行在哪个内核。
5)callback
USB事件任务调用此函数。
6)callback_arg
回调函数的传入形参。
2,获取HID设备参数hid_host_device_get_params
该函数用于获取HID设备参数,其函数原型如下:
- esp_err_t hid_host_device_get_params(hid_host_device_handle_t hid_dev_handle,
- hid_host_dev_params_t *dev_params);
复制代码 函数形参:
表48.3.1.2 hid_host_device_get_params函数形参描述
返回值:
ESP_OK表示获取信息成功。
其他错误码表示获取信息失败。
3,打开USB HID主机设备hid_host_device_open
该函数用于打开USB HID主机设备,其函数原型如下:
- esp_err_t hid_host_device_open(hid_host_device_handle_t hid_dev_handle,
- const hid_host_device_config_t *config);
复制代码 函数形参:
表48.3.1.3 hid_host_device_open函数形参描述
返回值:
ESP_OK表示打开设备成功。
其他错误码表示打开设备失败。
4,设置HID请求协议hid_class_request_set_protocol
该函数用于设置HID请求协议,其函数原型如下:
- esp_err_t hid_class_request_set_protocol(
- hid_host_device_handle_t hid_dev_handle,
- hid_report_protocol_t protocol);
复制代码 函数形参:
表48.3.1.4 hid_class_request_set_protocol函数形参描述
返回值:
ESP_OK表示设置协议成功。
其他错误码表示设置协议失败。
5,设置HID请求协议的空闲时间hid_class_request_set_idle
该函数用于设置HID请求协议的空闲时间,其函数原型如下:
- esp_err_t hid_class_request_set_idle(hid_host_device_handle_t hid_dev_handle,
- uint8_t duration,
- uint8_t report_id);
复制代码 函数形参:
表48.3.1.5 hid_class_request_set_idle函数形参描述
返回值:
ESP_OK表示设置协议空闲时间成功。
其他错误码表示设置协议空闲时间失败。
6,启动HID设备hid_host_device_start
该函数用于启动HID设备,其函数原型如下:
- esp_err_t hid_host_device_start(hid_host_device_handle_t hid_dev_handle);
复制代码 函数形参:
表48.3.1.6 hid_host_device_start函数形参描述
返回值:
ESP_OK表示启动HID设备成功。
其他错误码表示启动HID设备失败。
7,获取设备的原始输入报告数据hid_host_device_get_raw_input_report_data
该函数用于获取设备的原始输入报告数据,其函数原型如下:
- esp_err_t hid_host_device_get_raw_input_report_data(
- hid_host_device_handle_t hid_dev_handle,
- uint8_t *data,
- size_t data_length_max,
- size_t *data_length);
复制代码 函数形参:
表48.3.1.7 hid_host_device_get_raw_input_report_data函数形参描述
返回值:
ESP_OK表示获取输入报告数据成功。
其他错误码表示获取输入报告数据失败。
8,关闭USB HID主机设备hid_host_device_close
该函数用于关闭USB HID主机设备,其函数原型如下:
- esp_err_t hid_host_device_close(hid_host_device_handle_t hid_dev_handle);
复制代码 函数形参:
表48.3.1.8 hid_host_device_close函数形参描述
返回值:
ESP_OK表示关闭HID设备成功。
其他错误码表示关闭HID设备失败。
48.3.2 程序流程图
图48.3.2.1 USB鼠标实验程序流程图
48.3.3 程序解析
本章节的例程是基于13_lcd实验编写的,所以笔者重点讲解有区别的文件。
1,USB HID 驱动
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。USB HID MSC驱动源码包括两个文件:usb_hid_mouse.c和usb_hid_mouse.h。
usb_hid_mouse.h主要用于声明usb_hid_mouse函数和USB消息等结构体,以便在其他文件中调用,具体内容不再赘述。
下面我们再解析usb_hid_mouse.c的程序,这里笔者分了几个部分来讲解,如下代码所示:
1,usb_hid_mouse
该函数初始化USB HID鼠标功能。它创建USB主机任务,安装HID主机驱动程序,并创建消息队列来接收和处理来自鼠标的事件。
- /**
- * @brief 鼠标初始化
- * [url=home.php?mod=space&uid=271674]@param[/url] 无
- * @retval ESP_OK:初始化成功
- */
- esp_err_t usb_hid_mouse(void)
- {
- /* 创建USB线程 */
- BaseType_t task_created = xTaskCreatePinnedToCore(usb_lib_task,
- "usb_events",
- 4096,
- xTaskGetCurrentTaskHandle(),
- 2, NULL, 0);
- assert(task_created == pdTRUE);
- /* 等待usb_lib_task的通知继续 */
- ulTaskNotifyTake(false, 1000);
- /* HID 主机配置 */
- const hid_host_driver_config_t hid_host_driver_config = {
- .create_background_task = true, /* 创建回调函数任务 */
- .task_priority = 5, /* 优先级5 */
- .stack_size = 4096, /* 任务堆栈 */
- .core_id = 0, /* 内核ID */
- .callback = hid_host_device_callback, /* 回调函数,即任务函数 */
- .callback_arg = NULL /* 回调函数传入参数 */
- };
- /* 初始化hid host */
- ESP_ERROR_CHECK(hid_host_install(&hid_host_driver_config));
- /* 创建消息队列 */
- app_event_queue = xQueueCreate(10, sizeof(app_event_queue_t));
- return ESP_OK;
- }
复制代码 2,hid_host_device_callbac
该函数是HID设备事件的回调函数。当设备发生连接、断开等事件时,它会将事件信息发送到消息队列中,以便其他任务或者回调实时处理。
- /**
- * @brief HID设备回调函数
- * @param hid_device_handle:HID设备句柄
- * @param event:HID设备事件
- * @param arg:未使用
- * @retval 无
- */
- void hid_host_device_callback(hid_host_device_handle_t hid_device_handle,
- const hid_host_driver_event_t event,
- void *arg)
- {
- const app_event_queue_t evt_queue = {
- .event_group = APP_EVENT_HID_HOST, /* HID事件 */
- .hid_host_device.handle = hid_device_handle, /* hid句柄 */
- .hid_host_device.event = event, /* 事件 */
- .hid_host_device.arg = arg, /* 传入参数 */
- .mouse_flag = CONNECT, /* 鼠标处于在线状态 */
- .coordinate_buf = {0}, /* 无坐标数据 */
- };
- if (app_event_queue)
- {
- xQueueSend(app_event_queue, &evt_queue, 0); /* 发送消息 */
- }
- }
复制代码 3,usb_lib_task
该函数是一个USB主机库任务,负责初始化USB主机并处理USB事件。它会在事件发生时调用相应的回调函数,并在所有事件处理完毕后注销USB主机库并删除任务。
- /**
- * @brief USB主机库的任务函数
- * @param arg:未使用
- * @retval 无
- */
- static void usb_lib_task(void *arg)
- {
- /* 主机配置 */
- const usb_host_config_t host_config = {
- .skip_phy_setup = false,
- .intr_flags = ESP_INTR_FLAG_LEVEL1,
- };
- /* usb host安装 */
- ESP_ERROR_CHECK(usb_host_install(&host_config));
- xTaskNotifyGive(arg);
- while (true)
- {
- uint32_t event_flags;
- usb_host_lib_handle_events(portMAX_DELAY, &event_flags);
- /* 只能注册一个客户端 */
- if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS)
- {
- ESP_ERROR_CHECK(usb_host_device_free_all());
- break;
- }
- }
- vTaskDelay(10); /* 必须延时 */
- /* 注销USB HOST */
- ESP_ERROR_CHECK(usb_host_uninstall());
- /* 删除当前任务 */
- vTaskDelete(NULL);
- }
复制代码 4,hid_print_new_device_report_heade
该函数根据当前协议类型输出报告的头部信息。当协议类型变化时(例如从鼠标到键盘或其他设备),它会打印设备类型(如“Mouse”或“Keyboard”)到日志中。
- /**
- * @brief 根据报告输出协议类型生成新行
- * @param proto:当前要输出的协议
- * @retval 无
- */
- static void hid_print_new_device_report_header(hid_protocol_t proto)
- {
- static hid_protocol_t prev_proto_output = -1;
- if (prev_proto_output != proto)
- {
- prev_proto_output = proto;
- if (proto == HID_PROTOCOL_MOUSE)
- {
- ESP_LOGI(usb_hid_mouse_tag,"Mouse\r\n");
- }
- else if (proto == HID_PROTOCOL_KEYBOARD)
- {
- ESP_LOGI(usb_hid_mouse_tag,"Keyboard\r\n");
- }
- else
- {
- ESP_LOGI(usb_hid_mouse_tag,"Generic\r\n");
- }
- }
- }
复制代码 5,hid_host_mouse_report_callbac
该函数用于处理来自USB HID鼠标设备的输入报告。它解析报告中的位移信息,更新鼠标的当前位置,并将坐标数据发送到消息队列中。
- /**
- * @brief USB HID主机鼠标接口报告回调处理程序
- * @param data:输入报表数据缓冲区的指针
- * @param length:输入报表数据缓冲区的长度
- * @retval 无
- */
- static void hid_host_mouse_report_callback(const uint8_t *const data,
- const int length)
- {
- hid_mouse_input_report_boot_t *mouse_report =
- (hid_mouse_input_report_boot_t *)data;
- if (length < sizeof(hid_mouse_input_report_boot_t))
- {
- return;
- }
- static int x_pos = 0;
- static int y_pos = 0;
- /* 从位移计算绝对位置 */
- x_pos += mouse_report->x_displacement;
- y_pos += mouse_report->y_displacement;
- hid_print_new_device_report_header(HID_PROTOCOL_MOUSE);
- app_event_queue_t evt_queue = {0};
- evt_queue.event_group = APP_EVENT_HID_HOST; /* HID事件 */
- evt_queue.mouse_flag = COORDINATE_DATA; /* 坐标数据 */
- /* 存储坐标数据 */
- sprintf(evt_queue.coordinate_buf,"X:%06d,Y:%06d,|%c|%c|",x_pos,
- y_pos,(mouse_report->buttons.button1 ? 'o' : ' '),
- (mouse_report->buttons.button2 ? 'o' : ' '));
- if (app_event_queue)
- {
- xQueueSend(app_event_queue, &evt_queue, 0); /* 发送消息 */
- }
- }
复制代码 6,hid_host_generic_report_callbac
该函数用于处理来自USB HID设备(如键盘、触摸板等)的通用输入报告。它将接收到的数据逐字节打印到日志中。
- /**
- * @brief USB HID主机通用接口报告回调处理程序
- * @param data:输入报表数据缓冲区的指针
- * @param length:输入报表数据缓冲区的长度
- * @retval 无
- */
- static void hid_host_generic_report_callback(const uint8_t *const data,
- const int length)
- {
- hid_print_new_device_report_header(HID_PROTOCOL_NONE);
- for (int i = 0; i < length; i++)
- {
- ESP_LOGI(usb_hid_mouse_tag,"%02X", data[i]);
- }
- }
复制代码 7,hid_host_interface_callbac
该函数是处理USB HID接口事件的回调函数。根据不同的事件(如输入报告、设备断开连接、传输错误),它会调用不同的处理函数,并在必要时将事件发送到消息队列。
- /**
- * @brief USB HID Host 接口事件
- * @param hid_device_handle:设备句柄
- * @param event:设备事件
- * @param arg:未使用
- * @retval 无
- */
- void hid_host_interface_callback(hid_host_device_handle_t hid_device_handle,
- const hid_host_interface_event_t event,
- void *arg)
- {
- uint8_t data[64] = { 0 };
- size_t data_length = 0;
- hid_host_dev_params_t dev_params;
- ESP_ERROR_CHECK(hid_host_device_get_params(hid_device_handle, &dev_params));
- switch (event)
- {
- case HID_HOST_INTERFACE_EVENT_INPUT_REPORT:
- ESP_ERROR_CHECK(hid_host_device_get_raw_input_report_data(
- hid_device_handle,
- data,
- 64,
- &data_length));
- if (HID_SUBCLASS_BOOT_INTERFACE == dev_params.sub_class)
- {
- if (HID_PROTOCOL_MOUSE == dev_params.proto)
- {
- hid_host_mouse_report_callback(data, data_length);
- }
- }
- else
- {
- hid_host_generic_report_callback(data, data_length);
- }
- break;
- case HID_HOST_INTERFACE_EVENT_DISCONNECTED:
- const app_event_queue_t evt_queue = {
- .event_group = APP_EVENT, /* 一般事件 */
- .hid_host_device = {0}, /* 设备信息清零 */
- .mouse_flag = DISCONNECT, /* 鼠标处于离线状态 */
- .coordinate_buf = {0}, /* 无坐标数据 */
- };
- if (app_event_queue)
- {
- xQueueSend(app_event_queue, &evt_queue, 0); /* 发送消息 */
- }
- ESP_ERROR_CHECK(hid_host_device_close(hid_device_handle));
- break;
- case HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR:
- break;
- default:
- break;
- }
- }
复制代码 8,hid_host_device_even
该函数处理HID设备连接或事件。它根据设备的协议类型(如键盘或鼠标)打开并配置相应的HID设备,并启动设备的通信。
- /**
- * @brief USB HID Host 设备事件
- * @param hid_device_handle:设备句柄
- * @param event:设备事件
- * @param arg:未使用
- * @retval 无
- */
- void hid_host_device_event(hid_host_device_handle_t hid_device_handle,
- const hid_host_driver_event_t event,
- void *arg)
- {
- hid_host_dev_params_t dev_params;
- ESP_ERROR_CHECK(hid_host_device_get_params(hid_device_handle, &dev_params));
- switch (event)
- {
- case HID_HOST_DRIVER_EVENT_CONNECTED:
- ESP_LOGI(usb_hid_mouse_tag, "HID Device, protocol '%s' CONNECTED",
- hid_proto_name_str[dev_params.proto]);
- const hid_host_device_config_t dev_config = {
- .callback = hid_host_interface_callback, /* hid接口回调函数 */
- .callback_arg = NULL /* 回调函数传入参数 */
- };
- /* 打开hid host设备 */
- ESP_ERROR_CHECK(hid_host_device_open(hid_device_handle,
- &dev_config));
- if (HID_SUBCLASS_BOOT_INTERFACE == dev_params.sub_class)
- {
- ESP_ERROR_CHECK(hid_class_request_set_protocol(
- hid_device_handle,
- HID_REPORT_PROTOCOL_BOOT));
- if (HID_PROTOCOL_KEYBOARD == dev_params.proto)
- {
- ESP_ERROR_CHECK(hid_class_request_set_idle(
- hid_device_handle, 0, 0));
- }
- }
- /* 开始hid host 设备 */
- ESP_ERROR_CHECK(hid_host_device_start(hid_device_handle));
- break;
- default:
- break;
- }
- }
复制代码
2,main.c驱动代码
在main.c里面编写如下代码。
- void app_main(void)
- {
- esp_err_t ret;
- app_event_queue_t evt_queue;
- ret = nvs_flash_init(); /* 初始化NVS */
- if(ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
- {
- ESP_ERROR_CHECK(nvs_flash_erase());
- ESP_ERROR_CHECK(nvs_flash_init());
- }
- lcd_init(); /* LCD屏初始化 */
- usb_hid_mouse(); /* HID mouse初始化 */
-
- lcd_show_string(30, 50, 200, 16, 16, "ESP32-P4", RED);
- lcd_show_string(30, 70, 200, 16, 16, "USB mouse TEST", RED);
- lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
- lcd_show_string(30, 110, 200, 16, 16, "status:disconnect", RED);
- while (1)
- {
- /* 等待消息队列 */
- if (xQueueReceive(app_event_queue, &evt_queue, portMAX_DELAY))
- {
- if (APP_EVENT_HID_HOST == evt_queue.event_group)
- {
- if (CONNECT == evt_queue.mouse_flag)
- {
- lcd_show_string(84, 110, 200, 16, 16, "connected.", BLUE);
- hid_host_device_event(evt_queue.hid_host_device.handle,
- evt_queue.hid_host_device.event,
- evt_queue.hid_host_device.arg);
- }
- else if (COORDINATE_DATA == evt_queue.mouse_flag)
- {
- lcd_show_string(30, 130, lcddev.width, 16,
- 16, evt_queue.coordinate_buf, BLUE);
- }
- }
- else if (APP_EVENT == evt_queue.event_group)
- {
- if (DISCONNECT == evt_queue.mouse_flag)
- {
- lcd_show_string(84, 110, 200, 16, 16, "disconnect", BLUE);
- }
- }
- }
- }
- }
复制代码 首先,程序初始化了NVS存储和LCD屏幕,并通过 usb_hid_mouse 函数初始化了USB HID鼠标设备。在LCD上显示设备的状态信息。进入主循环后,程序通过消息队列(xQueueReceive)监听和处理来自其他任务或事件的消息。当接收到鼠标设备的连接事件时,LCD显示“connected”;当接收到鼠标坐标数据时,LCD显示坐标信息;若设备断开连接,则LCD显示“disconnect”。通过这种方式,程序不仅管理了鼠标设备的连接状态,还将实时的设备状态和数据在LCD上显示。
48.4 下载验证
将程序下载到开发板后,将USB鼠标插入开发板的USB_HOST端口。如果鼠标被成功识别,LCD屏幕会显示连接成功的提示信息。此时,通过移动鼠标或按下左右按键,LCD将实时显示鼠标的当前坐标值和按键状态,如下图所示。
图48.4.1 鼠标提示信息 |