USB-HID-BLER
将有线鼠标键盘游戏手柄等USB HID转换为蓝牙设备,附带鼠键宏和指纹解锁功能。
硬件开源地址 前作
主要功能
- 使用esp32-c3的GPIO模拟USB HOST,识别低速USB HID设备并读取其报告描述符和报告
- TEANSLATE工作模式: 检测设备类型,尝试对鼠标和键盘的报告描述符进行解析,将其报告翻译成预定义的标准报告并转发
- PASSTHOUGH工作模式: 对于其他类型设备和解析失败的鼠标键盘,将其报告描述符和报告原样转发
- 集成电池管理,可使用电池供电、使用USB供电、使用USB为电池供电
- 鼠键宏: 对于工作在TEANSLATE模式的设备可以定义任意鼠标/键盘宏(施工中)
- 指纹解锁:集成指纹模块,通过模拟键盘发送密码实现指纹解锁Windows(施工中)
- 图形化管理软件: 通过蓝牙HID协议无线控制设备状态(施工中)
ESP32-C3固件
开源地址: https://github.com/dnstzzx/usb-hid-bler
固件代码主要由以下几个部分组成:
- 基于esp32_usb_soft_host的软低速USB HOST
仅支持低速HID设备,以后可能会考虑用esp32-s3的USB PHY支持全速设备
识别方法:对设备供电后,D-被拉高的为低速设备,D+被拉高的为全速/高速设备
- 基于乐鑫官方例程的BLE HID Device,用于实现将报告转发到蓝牙主机
- 解析HID报告描述符并尝试将鼠标键盘设备报告翻译为预定义的标准报告,为便于移植该部分代码已分离到HID-REPORT-TRANSLATER
- 鼠键宏, 施工中
- 下位机通讯,施工中
USB软总线
本作品带有两路USB A接口用于接入HID设备,均为通过GPIO进行模拟。实现源自esp32_usb_soft_host,根据原作者的描述存在以下注意事项:
- 需要将Menuconfig->compiler options->optimization level设置为O2
- 需要将 Component config-> ESP System Setting -> Memory protection关闭
idf.py set-target命令会重置优化级别到Og,需要重新设置
USB Host实现主要位于usb_host.c,大部分代码通过一个周期为1ms的定时器中断执行。定时器中断ISR代码如下:
void IRAM_ATTR usb_process()
{
#if CONFIG_IDF_TARGET_ESP32C3
cpu_ll_enable_cycle_count();
#endif
for(int k=0;k<NUM_USB;k++)
{
current = ¤t_usb[k];
if(current->isValid)
{
setPins(current->DP,current->DM);
timerCallBack();
fsm_Mashine();
}
}
}
可见每个定时器周期会对每个USB端口分别执行一次timerCallBack和一次fsm_Mashine。timerCallBack根据上一周期的状态机进行NRZI读写,而fsm_Mashine负责更新状态机。你可能会好奇在ISR中如何进行延时操作(USB低速模式时钟周期为1.5MHz即每传输一位需要等待0.667微秒)。实际上这是通过填充空指令NOP实现的(NOP在Risc-v中被拓展为addi x0, x0, 0 而x0寄存器被硬编码为0,即为一次无意义的加法运算),填充过程如下:
void (*delay_pntA)() =NULL;
#define cpuDelay(x) {
(*delay_pntA)();}
void setDelay(uint8_t ticks)
{
// opcodes of void test_delay() {__asm__ (" nop"); __asm__ (" nop"); __asm__ (" nop"); ...}
//36 41 00 3d f0 1d f0 00 // one nop
//36 41 00 3d f0 3d f0 3d f0 3d f0 3d f0 1d f0 00 // five nops
//36 41 00 3d f0 3d f0 3d f0 3d f0 3d f0 3d f0 1d f0 00 00 00 //
int MAX_DELAY_CODE_SIZE = 0x210;
uint8_t* pntS;
// it can't execute but can read & write
if(!delay_pntA)
{
pntS = heap_caps_aligned_alloc(32,MAX_DELAY_CODE_SIZE, MALLOC_CAP_8BIT);
}
else
{
pntS = heap_caps_realloc(delay_pntA, MAX_DELAY_CODE_SIZE, MALLOC_CAP_8BIT);
//~ printf("pntS = %p\n",pntS);
}
uint8_t* pnt = (uint8_t*)pntS;
//put head of delay procedure
for(int k