Code: Select all
/*
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
// #include "tusb_config.h"
#include <stdlib.h>
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "tinyusb.h"
#include "tinyusb_default_config.h"
#include "class/hid/hid_device.h"
#include "driver/gpio.h"
#define APP_BUTTON (GPIO_NUM_0) // 默认使用BOOT信号
static const char *TAG = "example";
/************* TinyUSB 描述符 ****************/
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + CFG_TUD_HID *TUD_HID_DESC_LEN)
/**
* @brief HID报告描述符
*
* 在本示例中,我们实现了键盘+鼠标HID设备,
* 因此必须定义两个报告描述符
*/
const uint8_t hid_report_descriptor[] = {
//
TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(HID_ITF_PROTOCOL_MOUSE))
};
/**
* @brief 字符串描述符
*/
const char* hid_string_descriptor[5] = {
// 字符串描述符指针数组
(char[]){0x09, 0x04}, // 0: 支持的语言是英语 (0x0409)
"TinyUSB", // 1: 制造商
"TinyUSB Device", // 2: 产品
"123456", // 3: 序列号,应该使用芯片ID
"Example HID interface", // 4: HID
};
/**
* @brief 配置描述符
*
* 这是一个简单的配置描述符,定义了1个配置和1个HID接口
*/
static const uint8_t hid_configuration_descriptor[] = {
// 配置编号、接口数量、字符串索引、总长度、属性、功耗(mA)
TUD_CONFIG_DESCRIPTOR(1, 1, 0, TUSB_DESC_TOTAL_LEN,
TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
//接口编号、字符串索引、启动协议、报告描述符长度、端点输入地址、大小和轮询间隔
TUD_HID_DESCRIPTOR(0, 4, false, sizeof(hid_report_descriptor), 0x81, 16,
1),
};
/********* TinyUSB HID 回调函数 ***************/
/**
* @brief 获取HID报告描述符回调函数
*
* 当主机请求获取HID报告描述符时,TinyUSB会调用此函数
*
* @param instance HID接口实例编号(本示例中未使用)
* @return 指向HID报告描述符的指针,其内容必须存在足够长的时间以完成传输
*/
// 当收到GET HID REPORT DESCRIPTOR请求时调用
// 应用程序返回描述符指针,其内容必须存在足够长的时间以完成传输
uint8_t const *tud_hid_descriptor_report_cb(uint8_t instance)
{
// 我们只使用一个接口和一个HID报告描述符,因此可以忽略参数'instance'
return hid_report_descriptor;
}
/**
* @brief 获取HID报告回调函数
*
* 当主机请求获取HID报告时,TinyUSB会调用此函数
*
* @param instance HID接口实例编号
* @param report_id 报告ID
* @param report_type 报告类型
* @param buffer 用于存储报告内容的缓冲区
* @param reqlen 请求的报告长度
* @return 实际填充的报告长度,返回0将导致堆栈暂停请求
*/
// 当收到GET_REPORT控制请求时调用
// 应用程序必须填充缓冲区报告的内容并返回其长度。
// 返回零将导致堆栈暂停请求
uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id,
hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen)
{
(void) instance;
(void) report_id;
(void) report_type;
(void) buffer;
(void) reqlen;
return 0;
}
/**
* @brief 设置HID报告回调函数
*
* 当主机发送SET_REPORT控制请求或在OUT端点上发送数据时,TinyUSB会调用此函数
*
* @param instance HID接口实例编号
* @param report_id 报告ID(当在OUT端点上接收数据时为0)
* @param report_type 报告类型(当在OUT端点上接收数据时为0)
* @param buffer 包含报告内容的缓冲区
* @param bufsize 缓冲区大小
*/
// 当收到SET_REPORT控制请求或
// 在OUT端点上收到数据时调用(Report ID = 0, Type = 0)
void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id,
hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize)
{
}
/********* 应用程序 ***************/
typedef enum {
MOUSE_DIR_RIGHT,
MOUSE_DIR_DOWN,
MOUSE_DIR_LEFT,
MOUSE_DIR_UP,
MOUSE_DIR_MAX,
} mouse_dir_t;
#define DISTANCE_MAX 125
#define DELTA_SCALAR 5
static void mouse_draw_square_next_delta(int8_t *delta_x_ret, int8_t
*delta_y_ret)
{
static mouse_dir_t cur_dir = MOUSE_DIR_RIGHT;
static uint32_t distance = 0;
// 计算下一个增量
if (cur_dir == MOUSE_DIR_RIGHT) {
*delta_x_ret = DELTA_SCALAR;
*delta_y_ret = 0;
} else if (cur_dir == MOUSE_DIR_DOWN) {
*delta_x_ret = 0;
*delta_y_ret = DELTA_SCALAR;
} else if (cur_dir == MOUSE_DIR_LEFT) {
*delta_x_ret = -DELTA_SCALAR;
*delta_y_ret = 0;
} else if (cur_dir == MOUSE_DIR_UP) {
*delta_x_ret = 0;
*delta_y_ret = -DELTA_SCALAR;
}
// 更新当前方向的累积距离
distance += DELTA_SCALAR;
// 检查是否需要改变方向
if (distance >= DISTANCE_MAX) {
distance = 0;
cur_dir++;
if (cur_dir == MOUSE_DIR_MAX) {
cur_dir = 0;
}
}
}
static void app_send_hid_demo(void)
{
// // 键盘输出:发送按键'a/A'的按下和释放
// ESP_LOGI(TAG, "发送键盘报告");
// uint8_t keycode[6] = {HID_KEY_A};
// /**
// * @brief 发送键盘报告
// *
// * 向主机发送键盘状态报告,包括按键和修饰键状态
// *
// * @param report_id 报告ID,使用HID_ITF_PROTOCOL_KEYBOARD
// * @param modifier 修饰键状态(如Ctrl、Shift等)
// * @param keycode 按键代码数组,最多6个按键
// */
// tud_hid_keyboard_report(HID_ITF_PROTOCOL_KEYBOARD, 0, keycode);
// vTaskDelay(pdMS_TO_TICKS(50));
// tud_hid_keyboard_report(HID_ITF_PROTOCOL_KEYBOARD, 0, NULL);
// // 鼠标输出:以正方形轨迹移动鼠标光标
// ESP_LOGI(TAG, "发送鼠标报告");
int8_t delta_x;
int8_t delta_y;
for (int i = 0; i < (DISTANCE_MAX / DELTA_SCALAR) * 4; i++) {
// 获取正方形绘制模式中的下一个x和y增量
mouse_draw_square_next_delta(&delta_x, &delta_y);
/**
* @brief 发送鼠标报告
*
* 向主机发送鼠标状态报告,包括按键状态和移动增量
*
* @param report_id 报告ID,使用HID_ITF_PROTOCOL_MOUSE
* @param buttons 鼠标按键状态
* @param delta_x X轴移动增量
* @param delta_y Y轴移动增量
* @param delta_wheel 滚轮移动增量
* @param delta_pan 平移移动增量(通常未使用)
*/
while( !tud_hid_ready())
{
// vTaskDelay(pdMS_TO_TICKS(1));
}
tud_hid_mouse_report(HID_ITF_PROTOCOL_MOUSE, 0x00, delta_x, delta_y,
0, 0);
}
}
/**
* @brief 设备描述符
* bcdUSB = 0x0200 表示 USB 2.0
*/
static const tusb_desc_device_t my_device_descriptor = {
.bLength = sizeof(tusb_desc_device_t),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200, // 修改这里:0x0200 = USB 2.0, 0x0110 = USB 1.1 .bDeviceClass = 0x00, // 设备类由接口定义
.bDeviceSubClass = 0x00,
.bDeviceProtocol = 0x00,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = 0x303A, // Espressif VID
.idProduct = 0x4000,
.bcdDevice = 0x0100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01
};
void app_main(void)
{
// 初始化将触发HID报告的按钮
const gpio_config_t boot_button_config = {
.pin_bit_mask = BIT64(APP_BUTTON),
.mode = GPIO_MODE_INPUT,
.intr_type = GPIO_INTR_DISABLE,
.pull_up_en = true,
.pull_down_en = false,
};
ESP_ERROR_CHECK(gpio_config(&boot_button_config));
ESP_LOGI(TAG, "USB初始化");
tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG();
tusb_cfg.descriptor.device = &my_device_descriptor;
tusb_cfg.descriptor.full_speed_config = hid_configuration_descriptor;
tusb_cfg.descriptor.string = hid_string_descriptor;
tusb_cfg.descriptor.string_count = sizeof(hid_string_descriptor) /
sizeof(hid_string_descriptor[0]);
#if (TUD_OPT_HIGH_SPEED)
tusb_cfg.descriptor.high_speed_config = hid_configuration_descriptor;
#endif // TUD_OPT_HIGH_SPEED
/**
* @brief 安装TinyUSB驱动
*
* 初始化并安装TinyUSB驱动,配置USB设备参数
*
* @param cfg TinyUSB配置结构体指针
* @return 成功返回ESP_OK,失败返回错误代码
*/
ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
ESP_LOGI(TAG, "USB初始化完成");
while (1) {
/**
* @brief 检查USB设备是否已挂载
*
* 检查USB设备是否已被主机识别并挂载
*
* @return 如果设备已挂载返回true,否则返回false
*/
if (tud_mounted()) {
static bool send_hid_data = true;
if (send_hid_data) {
for(int i = 0; i < 50; i++){
app_send_hid_demo();
}
}
send_hid_data = !gpio_get_level(APP_BUTTON);
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}