硬件介绍:2023寒假一起练平台(5)基于ESP32-S2模块的开发板。主控使用了ESP32-S2-MINI-1。集成PCB板载天线,模组配置了4MB SPI flash,32 位LX7 单核处理器,工作频率高达 240 MHz。43 个 GPIO 口,14 个电容式传感 IO,支持 SPI、I2C、I2S、UART、ADC/DAC 和 PWM 等各种标准外设,支持 LCD 接口(8-bit 并口 RGB、8080、6800 接口),支持 8-/16-bit DVP 图像传感器接口,最高时钟频率支持到 40 MHz ,支持全速 USB OTG。编程支持Arduino、C/C++、microPython。
任务选择:我实现的是项目5- 实现一个USB键盘鼠标设备.IO扩展板上有一个用X、Y二轴电位计制作的游戏手柄,并且此芯片支持USB通信。要求:实现一个USB鼠标&键盘复合设备,摇动游戏手柄实现鼠标的移动,一个按键实现左键点击,另一个按键按下实现键盘敲入一串字符"eetree.cn"。编程语言使用Arduino,编程工具使用Vscode+platformio。
任务实现:首先是要实现Esp32-S2模拟鼠标和键盘。第一时间想到的是找HID的库。搜了一下,在Arduino下没有找到合适Esp32-S2使用的类库。正一筹莫展时,群里的大佬提示了一下,乐鑫官方就提供了模拟鼠标键盘的库文件。只需要引入"USBHIDKeyboard.h","USBHIDMouse.h"即可。经过测试,官网提供的类库,非常好用!
接下来就是解决输入部分啦。扩展板上有一个编码器和一个摇杆。摇杆要用来模拟鼠标的移动,编码器自身有一个按键,加上扩展板上的按键,分别用来模拟鼠标左键和键盘输入。
参加了老师的直播课讲解,这个摇杆是使用PWM方式将信号提供给Esp32S2的。推动摇杆,会改变PWM的频率和高电平占空比。用硬禾的梅林雀去读了一下PWM的输出。可以很直观地看见方波的形状、频率。明白摇杆电位器变化,对应输入波形的变化。
// 摇杆的控制,使用外部中断法获得摇杆的动作
#ifndef _JOYSTICK_H_
#define _JOYSTICK_H_
#include <Arduino.h>
#define PWMPIN 2 // 摇杆PWM输入管脚
#define ANALOG_PIN 1 // 两个按键的 管脚输入
portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; // 声明一个portMUX_TYPE类型的变量,利用其对主代码和中断之间的同步进行处理
ulong raiseTime, fallTime;
float fre, duty;
// 高低跳变触发
void handleInterrupt()
{
ulong now = micros();
if (digitalRead(PWMPIN) == 1) // 高电平
{
portENTER_CRITICAL_ISR(&mux);
auto totaltime = now - raiseTime;
fre = 1000000.0 / (float)totaltime;
duty = (float)(fallTime - raiseTime) / (float)totaltime;
portEXIT_CRITICAL_ISR(&mux);
raiseTime = now;
}
else
{
fallTime = now;
}
}
// 初始化摇杆
void init_joystick()
{
pinMode(PWMPIN, INPUT); // 先把引脚设置为输入模式
pinMode(ANALOG_PIN, INPUT);
attachInterrupt(digitalPinToInterrupt(PWMPIN), handleInterrupt, CHANGE);
}
// 通过摇杆的值判断 当前的动作 动作8个方向 +原地 共9个动作值
// 0:为原地不动 2 :上 4:右 6:下 8:左
// 1 :左上 3:右上 5:右下 6:左下
uint8_t action()
{
uint16_t widthwise = (int)(fre - 210); // 横向
uint16_t longitudinal = (int)(duty * 100); // 纵向
if (widthwise < 64)
{ // 左移动
if (longitudinal > 64) // 纵向下移
{
return 7;
}
else if (longitudinal < 49) // 纵向上移
{
return 1;
}
else
{ // 纵向无移动
return 8;
}
}
else if (widthwise > 83) // 右移动
{
if (longitudinal > 64) // 纵向下移
{
return 5;
}
else if (longitudinal < 49) // 纵向上移
{
return 3;
}
else
{ // 纵向无移动
return 4;
}
}
else // 横向无移动
{
if (longitudinal > 64) // 纵向下移
{
return 6;
}
else if (longitudinal < 49) // 纵向上移
{
return 2;
}
else
{ // 纵向无移动
return 0;
}
}
return 0;
}
// 检查按键是否有被按下 0:无按下 1:编码器按键 2:右侧按键
uint8_t checkKey()
{
int analog_value = 0;
analog_value = (analogRead(ANALOG_PIN) + 50) / 100;
if (analog_value >= 21 && analog_value <= 23) // 右侧按键
return 1;
if (analog_value >= 60 && analog_value <= 62) // 编码器按键
return 2;
return 0;
}
#endif
这里使用了外部中断。上下沿变化都会触发中断。当电平有变化时,进入中断,记录电平跳变的时间点(微秒)。当当前电平为高电平时,就可以通过当前时间与上次记录的时间的时间差,计算出PWM的频率;通过当前时间分别与记录的上升沿的时间和下降沿的时间的时间差,计算出高电平占比值。经过实际验证,在上下左右八个方向上频率值或者占空比都有明显显得变化。这样就把采集摇杆的移动动作变成了八个数字,再加上不移动时,一共9个命令数字。就可以很好地控制鼠标的移动了。
// 控制鼠标移动
void mouseMove(uint8_t direct)
{
switch (direct)
{
case 1:
Mouse.move(-MOUSESTEP, -MOUSESTEP);
break;
case 2:
Mouse.move(0, -MOUSESTEP);
break;
case 3:
Mouse.move(MOUSESTEP, -MOUSESTEP);
break;
case 4:
Mouse.move(MOUSESTEP, 0);
break;
case 5:
Mouse.move(MOUSESTEP, MOUSESTEP);
break;
case 6:
Mouse.move(0, MOUSESTEP);
break;
case 7:
Mouse.move(-MOUSESTEP, MOUSESTEP);
break;
case 8:
Mouse.move(-MOUSESTEP, -0);
break;
default:
break;
}
}
按键部分分两块,一个是实体按键,一个是编码器的按键。这部分按键的触发是电阻网络通过修改电阻值来告诉单片机按键的动作的。利用Esp32S2的ADC来读取端口的电压值,然后转换成按键的动作。
收集到摇杆和编码器按键的动作,就可以利用乐鑫官方的程序来控制鼠标和键盘了。这里设定为独立按键作为鼠标左键,编码器按键作为键盘输入。
void loop()
{
uint8_t keyval = 0;
mouseMove(action());
keyval = checkKey();
if (keyval == 1)
{
Mouse.press(MOUSE_LEFT); //左键按下
}
else if (keyval == 2)
{
Mouse.release(MOUSE_LEFT);
Keyboard.print("eetree.cn");
delay(130);
}
else
{
Mouse.release(MOUSE_LEFT);
}
delay(8);
}
这里程序是在loop函数中不停地循环。每次循环会有个短暂的延时(8毫秒)。人手的动作会比较缓慢,移动鼠标设置每次移动5个像素点,这样既不会鼠标移动太快,也不会过慢。但是键盘这里会有点问题,每次按下编码器按键,会有延时,导致程序循环检查到按键的按下,就会通过键盘输出多个“eetree.cn”。所以这里增加了个较长的延时,每当编码器按键按下后,模拟键盘输出字符,然后进入延时,短时间内不再响应外界输入动作。突然想到一个用途,可以把这个ESP32模块做成一个输入密码的秘钥,电脑开机后,输入密码时,插上模块,迅速输入密码^_^
心得体会:非常感谢电子森林举办的寒假一起练活动。通过此次活动了解额ESP32的HID库的使用,在各位老师的帮助下能够在开发板上愉快的玩耍啦!