2026-03-31 11:04:43 +08:00
|
|
|
|
#include <stdio.h>
|
|
|
|
|
|
#include "freertos/FreeRTOS.h"
|
|
|
|
|
|
#include "freertos/task.h"
|
|
|
|
|
|
#include "esp_log.h"
|
|
|
|
|
|
#include "tinyusb.h"
|
|
|
|
|
|
#include "tusb.h"
|
|
|
|
|
|
#include "rust_app_wrapper.h"
|
|
|
|
|
|
|
|
|
|
|
|
static const char *TAG = "USB";
|
|
|
|
|
|
|
2026-04-03 15:59:19 +08:00
|
|
|
|
#define REPORT_ID_KEYBOARD 1
|
|
|
|
|
|
#define REPORT_ID_MOUSE 2
|
|
|
|
|
|
|
2026-04-03 17:01:33 +08:00
|
|
|
|
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + 2 * TUD_HID_DESC_LEN)
|
2026-03-31 11:04:43 +08:00
|
|
|
|
|
|
|
|
|
|
#define USB_VID 0x30D8
|
|
|
|
|
|
#define USB_PID 0x50A6
|
2026-04-03 15:59:19 +08:00
|
|
|
|
#define USB_BCD 0x0200
|
2026-03-31 11:04:43 +08:00
|
|
|
|
|
2026-04-09 13:10:24 +08:00
|
|
|
|
enum {
|
|
|
|
|
|
ITF_NUM_KEYBOARD, // 0
|
|
|
|
|
|
ITF_NUM_MOUSE, // 1
|
|
|
|
|
|
ITF_NUM_TOTAL
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-31 11:04:43 +08:00
|
|
|
|
static tusb_desc_device_t const desc_device =
|
|
|
|
|
|
{
|
|
|
|
|
|
.bLength = sizeof(tusb_desc_device_t),
|
|
|
|
|
|
.bDescriptorType = TUSB_DESC_DEVICE,
|
|
|
|
|
|
.bcdUSB = USB_BCD,
|
|
|
|
|
|
.bDeviceClass = 0x00,
|
|
|
|
|
|
.bDeviceSubClass = 0x00,
|
|
|
|
|
|
.bDeviceProtocol = 0x00,
|
|
|
|
|
|
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
|
|
|
|
|
|
.idVendor = USB_VID,
|
|
|
|
|
|
.idProduct = USB_PID,
|
2026-04-03 15:59:19 +08:00
|
|
|
|
.bcdDevice = 0x0100,
|
2026-03-31 11:04:43 +08:00
|
|
|
|
.iManufacturer = 0x01,
|
|
|
|
|
|
.iProduct = 0x02,
|
|
|
|
|
|
.iSerialNumber = 0x03,
|
|
|
|
|
|
.bNumConfigurations = 0x01
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-04-09 14:21:34 +08:00
|
|
|
|
// 独立键盘描述符(去掉 Report ID,使用单 Report 模式)
|
2026-04-09 13:10:24 +08:00
|
|
|
|
uint8_t const desc_hid_report_kb[] = {
|
2026-04-09 14:21:34 +08:00
|
|
|
|
TUD_HID_REPORT_DESC_KEYBOARD()
|
2026-04-09 13:10:24 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-04-09 14:21:34 +08:00
|
|
|
|
// 独立鼠标描述符(去掉 Report ID,使用单 Report 模式)
|
2026-04-09 13:10:24 +08:00
|
|
|
|
uint8_t const desc_hid_report_mouse[] = {
|
2026-04-09 14:21:34 +08:00
|
|
|
|
TUD_HID_REPORT_DESC_MOUSE()
|
2026-03-31 11:04:43 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-04-09 13:10:24 +08:00
|
|
|
|
// 按接口返回正确描述符
|
2026-03-31 11:04:43 +08:00
|
|
|
|
uint8_t const * tud_hid_descriptor_report_cb(uint8_t instance)
|
|
|
|
|
|
{
|
2026-04-09 13:10:24 +08:00
|
|
|
|
if (instance == ITF_NUM_KEYBOARD) {
|
|
|
|
|
|
return desc_hid_report_kb;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (instance == ITF_NUM_MOUSE) {
|
|
|
|
|
|
return desc_hid_report_mouse;
|
|
|
|
|
|
}
|
|
|
|
|
|
return NULL;
|
2026-03-31 11:04:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-03 15:59:19 +08:00
|
|
|
|
static char const *hid_string_descriptor[] = {
|
2026-04-09 13:10:24 +08:00
|
|
|
|
(char[]){0x09, 0x04}, // 0: 语言ID
|
|
|
|
|
|
"久鼎智控", // 1: 厂商
|
|
|
|
|
|
"智控键鼠套装", // 2: 产品
|
|
|
|
|
|
"1", // 3: 序列号
|
|
|
|
|
|
"HID 智控键盘", // 4: 键盘接口
|
|
|
|
|
|
"HID 智控鼠标", // 5: 鼠标接口
|
2026-03-31 11:04:43 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-04-03 17:01:33 +08:00
|
|
|
|
#define EPNUM_KEYBOARD 0x81
|
|
|
|
|
|
#define EPNUM_MOUSE 0x82
|
2026-04-03 15:59:19 +08:00
|
|
|
|
|
2026-04-09 13:10:24 +08:00
|
|
|
|
// ==============================
|
|
|
|
|
|
// 🔥 这里是修复重点!!!
|
|
|
|
|
|
// ==============================
|
2026-04-03 15:59:19 +08:00
|
|
|
|
static uint8_t const hid_configuration_descriptor[] = {
|
|
|
|
|
|
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
|
2026-04-09 13:10:24 +08:00
|
|
|
|
|
|
|
|
|
|
// 键盘 HID 接口
|
|
|
|
|
|
TUD_HID_DESCRIPTOR(
|
|
|
|
|
|
ITF_NUM_KEYBOARD, // 接口号
|
|
|
|
|
|
4, // 字符串索引
|
|
|
|
|
|
0, // 协议 = 0(必须!)
|
|
|
|
|
|
sizeof(desc_hid_report_kb),
|
|
|
|
|
|
EPNUM_KEYBOARD,
|
|
|
|
|
|
CFG_TUD_HID_EP_BUFSIZE,
|
|
|
|
|
|
10
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
|
|
|
|
// 鼠标 HID 接口
|
|
|
|
|
|
TUD_HID_DESCRIPTOR(
|
|
|
|
|
|
ITF_NUM_MOUSE, // 接口号
|
|
|
|
|
|
5, // 字符串索引
|
|
|
|
|
|
0, // 协议 = 0(必须!不能用 MOUSE 协议)
|
|
|
|
|
|
sizeof(desc_hid_report_mouse),
|
|
|
|
|
|
EPNUM_MOUSE,
|
|
|
|
|
|
CFG_TUD_HID_EP_BUFSIZE,
|
|
|
|
|
|
10
|
|
|
|
|
|
)
|
2026-04-03 15:59:19 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
{
|
|
|
|
|
|
(void) instance;
|
|
|
|
|
|
(void) report_id;
|
|
|
|
|
|
(void) report_type;
|
|
|
|
|
|
(void) buffer;
|
|
|
|
|
|
(void) bufsize;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-07 17:09:58 +08:00
|
|
|
|
// 暴露给 Rust 的函数
|
|
|
|
|
|
bool rust_is_usb_mounted() {
|
2026-04-09 08:55:43 +08:00
|
|
|
|
bool mounted = tud_mounted();
|
|
|
|
|
|
ESP_LOGD(TAG, "USB mounted state: %d", mounted);
|
|
|
|
|
|
return mounted;
|
2026-04-07 17:09:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-09 13:10:24 +08:00
|
|
|
|
//====================================================================
|
2026-04-09 14:21:34 +08:00
|
|
|
|
// 键盘函数(修复:去掉 Report ID)
|
2026-04-09 13:10:24 +08:00
|
|
|
|
//====================================================================
|
2026-04-07 17:09:58 +08:00
|
|
|
|
void rust_send_keyboard_report(uint8_t report_id, uint8_t modifier, uint8_t const* keycode) {
|
2026-04-09 14:21:34 +08:00
|
|
|
|
// 描述符现在没有 Report ID,所以传入 0
|
|
|
|
|
|
tud_hid_keyboard_report(0, modifier, keycode);
|
2026-04-07 17:09:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-09 13:10:24 +08:00
|
|
|
|
//====================================================================
|
2026-04-09 14:21:34 +08:00
|
|
|
|
// 鼠标函数(修复:使用 tud_hid_n_mouse_report 指定 instance)
|
2026-04-09 13:10:24 +08:00
|
|
|
|
//====================================================================
|
2026-04-08 15:15:20 +08:00
|
|
|
|
void rust_send_mouse_report(uint8_t report_id, uint8_t buttons, int8_t x, int8_t y, int8_t wheel, int8_t pan) {
|
2026-04-09 14:21:34 +08:00
|
|
|
|
ESP_LOGI(TAG, "Mouse report: btn=%d, x=%d, y=%d", buttons, x, y);
|
|
|
|
|
|
|
|
|
|
|
|
// 关键修复:使用 tud_hid_n_mouse_report 并指定正确的 instance
|
|
|
|
|
|
// ITF_NUM_KEYBOARD = 0 (键盘), ITF_NUM_MOUSE = 1 (鼠标)
|
|
|
|
|
|
// 描述符没有 Report ID,所以 report_id 传 0
|
|
|
|
|
|
bool result = tud_hid_n_mouse_report(ITF_NUM_MOUSE, 0, buttons, x, y, wheel, pan);
|
|
|
|
|
|
ESP_LOGI(TAG, "tud_hid_n_mouse_report result: %s", result ? "success" : "failed");
|
2026-04-08 15:15:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-07 17:09:58 +08:00
|
|
|
|
// 暴露 FreeRTOS 函数给 Rust
|
|
|
|
|
|
void rust_vTaskDelay(uint32_t xTicksToDelay) {
|
|
|
|
|
|
vTaskDelay(xTicksToDelay);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-08 14:07:37 +08:00
|
|
|
|
// 暴露日志函数给 Rust
|
|
|
|
|
|
void rust_esp_log_i(const char* tag, const char* message) {
|
|
|
|
|
|
ESP_LOGI(tag, "%s", message);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-31 11:04:43 +08:00
|
|
|
|
void app_main(void)
|
|
|
|
|
|
{
|
|
|
|
|
|
ESP_LOGI(TAG, "USB 初始化");
|
|
|
|
|
|
|
|
|
|
|
|
const tinyusb_config_t tusb_cfg = {
|
|
|
|
|
|
.port = TINYUSB_PORT_FULL_SPEED_0,
|
|
|
|
|
|
.phy = {
|
|
|
|
|
|
.skip_setup = false,
|
|
|
|
|
|
.self_powered = false,
|
|
|
|
|
|
.vbus_monitor_io = 0,
|
|
|
|
|
|
},
|
|
|
|
|
|
.task = {
|
|
|
|
|
|
.size = 3500,
|
|
|
|
|
|
.priority = 14,
|
|
|
|
|
|
.xCoreID = 0,
|
|
|
|
|
|
},
|
|
|
|
|
|
.descriptor = {
|
2026-04-03 09:43:49 +08:00
|
|
|
|
.device = &desc_device,
|
2026-03-31 11:04:43 +08:00
|
|
|
|
.qualifier = NULL,
|
|
|
|
|
|
.string = hid_string_descriptor,
|
2026-04-09 13:10:24 +08:00
|
|
|
|
.string_count = sizeof(hid_string_descriptor)/sizeof(hid_string_descriptor[0]),
|
2026-03-31 11:04:43 +08:00
|
|
|
|
.full_speed_config = hid_configuration_descriptor,
|
|
|
|
|
|
.high_speed_config = NULL,
|
|
|
|
|
|
},
|
|
|
|
|
|
.event_cb = NULL,
|
|
|
|
|
|
.event_arg = NULL,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
|
|
|
|
|
|
|
|
|
|
|
|
ESP_LOGI(TAG, "✅ USB HID 设备启动成功");
|
2026-04-09 08:55:43 +08:00
|
|
|
|
ESP_LOGI(TAG, "请连接 USB OTG 线到电脑");
|
2026-03-31 11:04:43 +08:00
|
|
|
|
|
2026-04-09 13:10:24 +08:00
|
|
|
|
rust_app_wrapper_init();
|
2026-03-31 11:04:43 +08:00
|
|
|
|
|
2026-04-09 08:55:43 +08:00
|
|
|
|
int count = 0;
|
|
|
|
|
|
bool last_mounted = false;
|
2026-03-31 11:04:43 +08:00
|
|
|
|
while (1) {
|
|
|
|
|
|
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
2026-04-09 08:55:43 +08:00
|
|
|
|
|
|
|
|
|
|
bool mounted = tud_mounted();
|
|
|
|
|
|
if (mounted != last_mounted) {
|
|
|
|
|
|
if (mounted) {
|
|
|
|
|
|
ESP_LOGI(TAG, "🔌 USB 已连接到主机");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
ESP_LOGI(TAG, "🔌 USB 已断开");
|
|
|
|
|
|
}
|
|
|
|
|
|
last_mounted = mounted;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (count % 10 == 0) {
|
|
|
|
|
|
ESP_LOGI(TAG, "USB 设备运行中... 挂载状态: %s", mounted ? "已连接" : "未连接");
|
|
|
|
|
|
}
|
|
|
|
|
|
count++;
|
2026-03-31 11:04:43 +08:00
|
|
|
|
}
|
2026-04-09 13:10:24 +08:00
|
|
|
|
}
|