1.项目介绍
本文展示的是一个基于ESP32-S2的鼠标以及键盘模拟器。
ESP32-S2通过测量摇杆发送出来的PWM信号的脉宽和频率依赖来获取摇杆的上下左右情况,ESP32-S2依据这个结果在屏幕上对鼠标进行相应的偏移以此来获取模拟鼠标移动光标的效果。
ESP32-S2通过读取2个独立按键发送出来的电压值,依据不同的采样电压来判断出不同的按键被按下,当S1被按下时,ESP32-S向PC发送鼠标左键被按下的消息;当S1抬起时,ESP32-S2向PC发送鼠标左键被释放消息。当S2被按下时,EPS32-S2向PC发送键盘按下消息,按下按键为:eetree.cn
2.硬件链接
由于本次所有的连接都是通过配套的转接板进行展示,不需要额外连线,因此此处展示的是硬件链接示意图。
ESP32-S2连接摇杆电路以及独立按键电路,以此读取摇杆以及按键状态。通过USB线缆链接PC机,枚举成HID设备,用于模拟鼠标以及键盘。
如下所示为独立按键以及旋钮对应的硬件电路,此处使用电阻分压网络构成,最终输出的是一个电压值,不同的按键被按下,输出的电压值也各不相同,EPS32-S2通过读取输出管脚的电压值以此来判断那个按键被按下,设计很精妙,只是看不太懂.....
如下为摇杆部分的电路图,正常情况下,摇杆应该需要四个AD管脚进行采样以此来获取到对应的摇杆状态,但是这里使用运放一通操作,然后输出只需要一个管脚来展示摇杆的状态,通过输出PWM的占空比、频率来进行区分。只能说溜溜溜,奈何看不懂咋实现的....
3.代码说明
本次代码使用的PlatformIO+Arduino框架进行编写,速度快、代码量少,舒适...
3.1.初始化
如下代码为steup函数中的初始化操作,主要是对USB键盘对象、USB鼠标对象、UBUS串口对象进行初始化,最终还需要对USB对象进行初始化。
Keyboard.begin();
Mouse.begin();
USBSerial.begin();
USB.begin();
3.2.按键相关
如下为按键相关代码,对于按键状态获取操作在key_get中,分为两个部分
- 通过读取PWM输入管脚的电平变化,并通过定时器的计数值来记录对应的时间戳,最后通过时间戳计算出PWM的频率以及占空比,并通过占空比和频率来区分摇杆在X、Y方向上的偏移量。
- 通过读取ADC管脚的电压,来判断当前那些按键被按下。
按键的存储使用的是一个8bit的无符号整形数,每一位都代表对应按键的状态,为1表示按键被按下,为0表示按键被抬起。
enum {
KEY_NONE = 0x0, /* 无按键按下 */
KEY_S1 = 0x1 << 0, /* 独立按键 S1被按下 */
KEY_S2 = 0x1 << 1, /* 独立按键 S2被按下 */
KEY_X1 = 0x1 << 2, /* 摇杆 左移动 */
KEY_X2 = 0x1 << 3, /* 摇杆 右移动 */
KEY_Y1 = 0x1 << 4, /* 摇杆 上移动 */
KEY_Y2 = 0x1 << 5, /* 摇杆 下移动 */
};
USBHIDKeyboard Keyboard;
USBHIDMouse Mouse;
USBCDC USBSerial;
static void pwd_measure(int pin, int *freq, int *duty)
{
unsigned long time_begin;
unsigned long time_turn;
unsigned long time_end;
float tmp_freq;
float tmp_duty;
/* 等待当前周期过去,直到进入到新的周期 */
if (HIGH == digitalRead(2))
{
/* 当前处于高电平状态 */
/* 等待高电平完成 */
while (digitalRead(2) == HIGH);
/* 等待低电平完成 */
while (digitalRead(2) == LOW);
}
else
{
/* 当前处于低电平状态 */
/* 等待低电平完成 */
while (digitalRead(2) == LOW);
}
/* 获取PWM起始时间 */
time_begin = micros();
/* 等待高电平完成 */
while (digitalRead(2) == HIGH);
/* 获取PWM电平翻转时间 */
time_turn = micros();
/* 等待低电平完成 */
while (digitalRead(2) == LOW);
/* 获取PWM结束时间 */
time_end = micros();
/* 计算频率 */
tmp_freq = 1000000.0 / (time_end - time_begin);
/* 计算占空比 */
tmp_duty = (float)(time_turn - time_begin) / (time_end - time_begin) * 100;
//USBSerial.printf("begin:%ld, turn:%ld, end:%ld\n", time_begin, time_turn, time_end);
//USBSerial.printf("freq:%f, duty:%f\n", tmp_freq, tmp_duty);
/* 设置返回值 */
*freq = tmp_freq;
*duty = tmp_duty;
return;
}
static uint8_t key_get(void)
{
uint8_t key_status;
int pwm_freq;
int pwm_duty;
uint16_t analog_value;
/* 读取摇杆值 */
/*
* 上下改变占空比,左右改变频率,默认:频率:300,占空比:57
* 最上(占空比):34
* 最下(占空比):80
* 最左(频率):227
* 最右(频率):435
*/
pwd_measure(2, &pwm_freq, &pwm_duty);
//USBSerial.printf("freq:%d, duty:%d\n", pwm_freq, pwm_duty);
if (pwm_freq <= 290)
{
/* 左摇杆 */
key_status |= KEY_X1;
}
else if (310 <= pwm_freq)
{
/* 右摇杆 */
key_status |= KEY_X2;
}
if (pwm_duty <= 54)
{
/* 上摇杆 */
key_status |= KEY_Y1;
}
else if (60 <= pwm_duty)
{
/* 右摇杆 */
key_status |= KEY_Y2;
}
/* 读取独立按键分压值 */
/*
* 默认: 8191
* K1: 7621
* K2: 5044
* K1与K2:2100
*/
analog_value = analogRead(1);
//USBSerial.printf("analog_value:%d\n", analog_value);
if (analog_value <= 3000)
{
/* S1、S2都被按下 */
key_status |= KEY_S1;
key_status |= KEY_S2;
}
else if (analog_value <= 6000)
{
/* S2被按下 */
key_status |= KEY_S2;
}
else if (analog_value <= 8000)
{
/* S1被按下 */
key_status |= KEY_S1;
}
else
{
}
return key_status;
}
3.4.逻辑实现
串联各个模块的主要逻辑如下所示,当读取到按键值中有任意按键被按下时,进入后续处理。
当S1被按下时,通过给Mouse对象设置鼠标左键被按下;当S2抬起时,设置Mouse对象释放鼠标左键。
当S2被按下时,传递PC键盘按下消息为:eetree.cn。并且该按键会判断上一次按下的值,避免持续按下导致功能被持续触发。
当摇杆X、Y方向的偏移达到阈值之后,表示X1、X2、Y1、Y2被按下,此时会将传递给鼠标消息的中X、Y变量进行赋值用于标识鼠标移动,以此来实现光标跟随摇杆的效果。
void loop()
{
static uint8_t key_save = KEY_NONE;
uint8_t key;
/* 获取按键信息 */
key = key_get();
if (key != 0)
{
/* 检测S1状态是否变化 */
if ((key & KEY_S1) != (key_save & KEY_S1))
{
if (key & KEY_S1)
{
USBSerial.printf("S1 down\n");
/* 按下左键 */
Mouse.press(MOUSE_LEFT);
}
else
{
USBSerial.printf("S1 up\n");
/* 释放左键 */
Mouse.release(MOUSE_LEFT);
}
}
/* 检测S2状态是否变化 */
if ((key & KEY_S2) != (key_save & KEY_S2))
{
if (key & KEY_S2)
{
USBSerial.printf("S2 down\n");
uint8_t input[] = {
"eetree.cn"
};
Keyboard.write(input, sizeof(input)-1);
}
else
{
USBSerial.printf("S2 up\n");
}
}
/* 检测X1是否按下 */
if (key & KEY_X1)
{
USBSerial.printf("X1 down\n");
/* 光标向左边移动 */
Mouse.move(-10, 0);
}
/* 检测X2是否按下 */
if (key & KEY_X2)
{
USBSerial.printf("X2 down\n");
/* 光标向右边移动 */
Mouse.move(10, 0);
}
/* 检测Y1是否按下 */
if (key & KEY_Y1)
{
USBSerial.printf("Y1 down\n");
/* 光标向上边移动 */
Mouse.move(0, -10);
}
/* 检测Y2是否按下 */
if (key & KEY_Y2)
{
USBSerial.printf("Y2 down\n");
/* 光标向下边移动 */
Mouse.move(0, 10);
}
}
/* 更新保存的按键信息 */
key_save = key;
delay(10);
}
4.功能展示
4.1.鼠标
如下图所示,为鼠标左键按下+鼠标移动共同作用,展示出来的效果就是记事本中的字符被选中高亮。由于是动态过程,图片不太好展示,清晰展示请看视频演示。
4.2.键盘输入
如下图所示,模拟的式键盘输入,当S2按键被按下时,ESP32会向PC机发送按键被按下消息,按下按键为:eetree.cn,S2每次被按下就会触发一次输入。
5.心得
本次使用的是老熟人了ESP32系列单片机,ESP32-S2是ESP32的基础上外设有一些调整,新增的USB接口支持。ESP32系列一向是支持很不错的产品,官方有ESP-IDF、ARDUINO开发框架。第三方有Micropython,开发十分简单快速。
板卡对应的扩展板,设计很巧妙,不论是多个按键通过模拟电压输出,还是摇杆通过PWM输出信号都很厉害,第一次见。只是可惜知识有限,不知道具体是咋实现的,比较可惜。
作为一个资深白嫖,这一次白嫖失败了,还是时间管理有问题,以前的难度都比较简单,能够快速冲一把。这回被FPGA给背刺了,血亏....
下回一定要提前搞、提前搞、提前搞,对着这个FPGA开发板立誓,哼。