项目介绍:
本项目使用Arduino框架进行开发,基于USBHID扩展库,将ESP32-S2实现为一个USB键盘&鼠标复合设备,通过扩展板上的摇杆设备,来控制鼠标的移动,然后使用一个按键作为鼠标左键,另外一个按键则自动输入一串字符"eetree.cn"
设计思路:
硬禾提供的ESP32核心板是ESP32-S2-MINI-1,集成了USB TYPE -C接口,当使用该USB接口连接到电脑设备后,可以通过USBHID将自身模拟被电脑识别为键盘和鼠标,从而通过扩展板上的输入设备,来模拟键盘和鼠标的控制。
而在Arduino 的ESP32实例中,就有KeyboardAndMouseControol的例子。基于该实例,结合本次项目的要求,进行了具体的功能实现:
- 将自身模拟为USBHID设备,实际需要为键盘和鼠标复合设备
- 通过读取扩展板上的摇杆输入设备的信息,转换为上下左右四个方向的动作,从而控制鼠标的运动
- 通过读取扩展板上的ADC按键的信息,分别来控制左键的点击,以及模拟键盘输入字符串。
硬件介绍:
本次使用的硬件由硬禾提供,分别为:
ESP32-S2核心板:
ESP32-S2扩展板:
在扩展板上,提供了PWM输入的摇杆设备,以及ADC输入的按键设备。
通过扩展板原理图,可以得到具体的GPIO信息:
从上可以得知:
- 按键连接到了A_OUT,对应GPIO1
- 摇杆连接到了PWM_OUT,对应GPIO2
因为扩展板提供了上述外设完整的连接和控制线路,所以无需其他辅助,仅使用ESP32-S2核心板+扩展板就可以完成全部工作。
实现功能:
ESP32-S2开发方式非常多,可以使用ESP-IDF开发,也可以使用Arduino开发,还可以使用micropython和circuitpython进行开发。本项目使用Arduino框架进行开发。
通过Arduino,最终实现的功能如下:
- 将设备连接到电脑后,在电脑上会识别为HID设备,包括了鼠标和键盘设备
- 当拨动扩展板上的摇杆设备时,电脑上的鼠标指针将会对应的进行运动
- 但按下旋转编码器的按键时,将会出发鼠标左键的操作
- 当按下K2按键时,将会出发键盘操作,自动输出一串字符"eetree.cn"
代码说明:
/*
ESP32-S2 键盘鼠标模拟
*/
#include "USB.h"
#include "USBHIDMouse.h"
#include "USBHIDKeyboard.h"
const byte adcPin = 1; // ADC引脚
const byte pwmPin = 2; // PWM引脚
// 定时器和中断设置
hw_timer_t * timer1 = NULL;
volatile SemaphoreHandle_t timerSemaphore1;
portMUX_TYPE timerMux1 = portMUX_INITIALIZER_UNLOCKED;
hw_timer_t * timer2 = NULL;
volatile SemaphoreHandle_t timerSemaphore2;
portMUX_TYPE timerMux2 = portMUX_INITIALIZER_UNLOCKED;
// 定时器内变量
volatile uint32_t isrAnalogValue = 0;
volatile uint32_t isrAnalogVolts = 0;
volatile uint32_t isrPwmValue = 0;
// 普通变量
uint32_t analogValue = 0;
uint32_t analogVolts = 0;
uint32_t pwmValue = 0;
// 键盘定义
const uint32_t keyNum = 5;
const uint32_t keyMap[10] = {1, 0, 2, 1110, 3, 3050, 4, 3370, 5, 3530};
const uint32_t keyPrecision = 100;
// 方向定义
const uint32_t directionNum = 4;
const uint32_t directionMap[8] = {1, 2472, 2, 1170, 3, 1289, 4, 2640};
const uint32_t directioPrecision = 50;
// 键鼠定义
USBHIDMouse Mouse;
USBHIDKeyboard Keyboard;
void ARDUINO_ISR_ATTR onTimer1(){
portENTER_CRITICAL_ISR(&timerMux1);
// ADC读取
isrAnalogValue = analogRead(adcPin);
isrAnalogVolts = analogReadMilliVolts(adcPin);
portEXIT_CRITICAL_ISR(&timerMux1);
// 释放信号量
xSemaphoreGiveFromISR(timerSemaphore1, NULL);
}
void ARDUINO_ISR_ATTR onTimer2(){
portENTER_CRITICAL_ISR(&timerMux2);
// PWM读取
isrPwmValue = pulseIn(pwmPin, HIGH);
portEXIT_CRITICAL_ISR(&timerMux2);
// 释放信号量
xSemaphoreGiveFromISR(timerSemaphore2, NULL);
}
uint32_t getKey(uint32_t val) {
if (val >= 3700) {
//
} else {
for (int i = 0; i < keyNum; i++) {
if (keyMap[i*2+1]>0 && val > keyMap[i*2+1]-keyPrecision && val < keyMap[i*2+1] + keyPrecision) return keyMap[i*2];
}
}
return 0;
}
uint32_t getDirection(uint32_t val) {
if (val >= 3700) {
//
} else {
for (int i = 0; i < directionNum; i++) {
if (directionMap[i*2+1]>0 && val > directionMap[i*2+1]-directioPrecision && val < directionMap[i*2+1] + directioPrecision) return directionMap[i*2];
}
}
return 0;
}
void setup ()
{
Serial.begin (115200);
Serial.println ();
Mouse.begin();
Keyboard.begin();
USB.begin();
// 创建信号量
timerSemaphore1 = xSemaphoreCreateBinary();
timerSemaphore2 = xSemaphoreCreateBinary();
// 定时器1设置
timer1 = timerBegin(0, 80, true); // 启动定时器
timerAttachInterrupt(timer1, &onTimer1, true); // 设置中断回调
timerAlarmWrite(timer1, 100000, true); // 中短周期设置为1000000-1s
// 定时器2设置
timer2 = timerBegin(1, 80, true); // 启动定时器
timerAttachInterrupt(timer2, &onTimer2, true); // 设置中断回调
timerAlarmWrite(timer2, 100000, true); // 中短周期设置为1000000-1s
// 定时器启用
timerAlarmEnable(timer1); // 使能定时器
delay(50);
timerAlarmEnable(timer2); // 使能定时器
// 设置ADC分辨率,2^12,4096
analogReadResolution(12);
// 设置PWM输入
pinMode(pwmPin, INPUT);
}
void loop () {
// 检查ADC信号量
if (xSemaphoreTake(timerSemaphore1, 0) == pdTRUE){
// 获取中断内的数据
portENTER_CRITICAL(&timerMux1);
analogValue = isrAnalogValue;
analogVolts = isrAnalogVolts;
portEXIT_CRITICAL(&timerMux1);
// 输出
// Serial.printf("analogValue=%d analogVolts=%d\n", analogValue, analogVolts);
uint32_t key = getKey(analogValue);
if(key) {
Serial.print(analogValue);
Serial.print(" ");
Serial.println(key);
switch(key) {
case 2:
// K2按键
//Keyboard.write('2');
Keyboard.print("eetree.cn");
delay(300);
break;
case 3:
// 鼠标左键
Mouse.click(MOUSE_LEFT);
delay(100);
break;
}
}
}
// 检查PWM信号量
if (xSemaphoreTake(timerSemaphore2, 0) == pdTRUE){
// 获取中断内的数据
portENTER_CRITICAL(&timerMux2);
pwmValue = isrPwmValue;
portEXIT_CRITICAL(&timerMux2);
// 输出
//Serial.printf("pwmVal=%d\n", pwmValue);
uint32_t direction = getDirection(pwmValue);
if(direction) {
Serial.print(pwmValue);
Serial.print(" ");
Serial.println(direction);
switch(direction) {
case 1:
// 鼠标左移
Mouse.move(-40, 0);
break;
case 2:
// 鼠标上移
Mouse.move(0, -40);
break;
case 3:
// 鼠标右移
Mouse.move(40, 0);
break;
case 4:
// 鼠标下移
Mouse.move(0, 40);
break;
}
}
}
}
在上述代码中,调用了三个支持库:
- USB.h:USB支持
- USBHIDMouse.h:USB鼠标支持
- USBHIDKeyboard:USB键盘支持
因为摇杆为PWM输入,故代码中定义了摇杆各个方向对应的PWM输入信息:
// 方向定义
const uint32_t directionNum = 4;
const uint32_t directionMap[8] = {1, 2472, 2, 1170, 3, 1289, 4, 2640};
const uint32_t directioPrecision = 50;
其中设置了4个方向,容错范围为上下50
而am建使用ADC输入,故代码中定义了各按键对应的ADC输入信息:
// 键盘定义
const uint32_t keyNum = 5;
const uint32_t keyMap[10] = {1, 0, 2, 1110, 3, 3050, 4, 3370, 5, 3530};
const uint32_t keyPrecision = 100;
为了兼容ESP32-S2和MSP430的扩展板,这里定义了5个按键。
实际使用时,K2对应2,旋转编码器按键为3。
同时,对读取到的模拟值设置了上下100的容错范围。
为了更有效的获取摇杆和按键的数据,代码中使用了定时器来进行读取,使得其信息的获取,不受主流程代码的影响,有效提升了获取输入信息的稳定。
在获取到按键信息和摇杆信息后,再根据实际获取的情况,控制键盘和鼠标的动作。
操作步骤:
- 使用Arduino IDE编写代码
- 设置连接参数
- 编译并下载代码
- 实际控制测试:
- 可拨动摇杆方向,观察鼠标运动
- 按一下K2,观察键盘输出
- 按一下旋转编码器按键,观察鼠标点击情况