黑神话悟空连招程序
19 2025-07-30 09:48
需求:
写arduino程序,完成功能:1.分三个线程到esp32的两个内核,线程a接收按钮a,通过t-vk/ESP32 BLE Keyboard@^0.3.2库蓝牙发送按键j 5次,间隔时间分别为1s、1s、2s、2s.2.线程b接收按钮b,中断线程a并蓝牙发送按键k,3.线程c接收按钮c蓝牙发送按键k
下面给出一份可直接使用的 Arduino 代码(PlatformIO / Arduino-IDE 均可),实现题目要求的 3 个“线程”任务,并把它们分别固定到 ESP32 的两个核上。
代码基于 FreeRTOS 原生 API,不会阻塞
代码基于 FreeRTOS 原生 API,不会阻塞
loop()
,也保证线程 b 可以实时“打断”线程 a 的发送序列。库依赖
• tvk/ESP32-BLE-Keyboard@^0.3.2(在
• tvk/ESP32-BLE-Keyboard@^0.3.2(在
platformio.ini
或 Arduino IDE 的库管理器里安装即可)硬件连线(可自己改)
• 按键 A → GPIO 25(下拉,按下 = HIGH)
• 按键 B → GPIO 26(下拉,按下 = HIGH)
• 按键 C → GPIO 27(下拉,按下 = HIGH)
• 按键 A → GPIO 25(下拉,按下 = HIGH)
• 按键 B → GPIO 26(下拉,按下 = HIGH)
• 按键 C → GPIO 27(下拉,按下 = HIGH)
功能说明
-
线程 a(固定在 Core 0)
监听按键 A;一旦按下,通过 BLE 依次发送字符 ‘j’ 共 5 次,间隔分别为 1 s、1 s、2 s、2 s。
该线程可被线程 b 实时中断:一旦收到取消信号,立即终止本次“5连发”。 -
线程 b(固定在 Core 1)
监听按键 B;一旦按下,立即取消线程 a 的正在进行的“5连发”,随后立刻发送一次字符 ‘k’。 -
线程 c(固定在 Core 1)
监听按键 C;一旦按下,立即发送一次字符 ‘k’。
代码
cpp
复制
/*
* ESP32 三线程 BLE 键盘示例
* Board: ESP32 Dev Module
* Lib: tvk/ESP32-BLE-Keyboard@^0.3.2
*/
#include <Arduino.h>
#include <BleKeyboard.h>
//------------------ 按键宏定义 ------------------
#define BTN_A 25
#define BTN_B 26
#define BTN_C 27
//------------------ BLE 键盘对象 ------------------
BleKeyboard bleKeyboard("ESP32-TripleThread", "Espressif", 100);
//------------------ FreeRTOS 句柄/信号量 ------------------
static TaskHandle_t taskA_handle = NULL;
static TaskHandle_t taskB_handle = NULL;
static TaskHandle_t taskC_handle = NULL;
// 用于线程 a 的取消信号
static volatile bool a_cancel_flag = false;
//------------------ 线程 a:5连发 'j' ------------------
void task_a(void *pvParameters)
{
(void)pvParameters;
pinMode(BTN_A, INPUT_PULLDOWN);
for (;;)
{
// 等待按键 A 按下
while (digitalRead(BTN_A) == LOW) vTaskDelay(pdMS_TO_TICKS(20));
// 防抖
vTaskDelay(pdMS_TO_TICKS(50));
if (digitalRead(BTN_A) == LOW) continue;
// 开始 5 连发
const uint16_t delays_ms[] = {1000, 1000, 2000, 2000};
a_cancel_flag = false;
for (int i = 0; i < 5; ++i)
{
if (a_cancel_flag) break; // 线程 b 要求终止
if (bleKeyboard.isConnected())
bleKeyboard.print("j");
if (i < 4) // 最后一次不用 delay
{
uint32_t wait = delays_ms[i];
while (wait > 0 && !a_cancel_flag) // 每 50 ms 检查一次取消
{
vTaskDelay(pdMS_TO_TICKS(50));
wait -= 50;
}
if (a_cancel_flag) break;
}
}
// 等待按键松开
while (digitalRead(BTN_A) == HIGH) vTaskDelay(pdMS_TO_TICKS(20));
}
}
//------------------ 线程 b:中断 a 并发送 'k' ------------------
void task_b(void *pvParameters)
{
(void)pvParameters;
pinMode(BTN_B, INPUT_PULLDOWN);
for (;;)
{
while (digitalRead(BTN_B) == LOW) vTaskDelay(pdMS_TO_TICKS(20));
vTaskDelay(pdMS_TO_TICKS(50));
if (digitalRead(BTN_B) == LOW) continue;
// 1. 打断线程 a
a_cancel_flag = true;
// 2. 发送 'k'
if (bleKeyboard.isConnected())
bleKeyboard.print("k");
while (digitalRead(BTN_B) == HIGH) vTaskDelay(pdMS_TO_TICKS(20));
}
}
//------------------ 线程 c:发送 'k' ------------------
void task_c(void *pvParameters)
{
(void)pvParameters;
pinMode(BTN_C, INPUT_PULLDOWN);
for (;;)
{
while (digitalRead(BTN_C) == LOW) vTaskDelay(pdMS_TO_TICKS(20));
vTaskDelay(pdMS_TO_TICKS(50));
if (digitalRead(BTN_C) == LOW) continue;
if (bleKeyboard.isConnected())
bleKeyboard.print("k");
while (digitalRead(BTN_C) == HIGH) vTaskDelay(pdMS_TO_TICKS(20));
}
}
//------------------ setup() ------------------
void setup()
{
Serial.begin(115200);
// 初始化 BLE
bleKeyboard.begin();
// 创建任务并绑定到指定核心
xTaskCreatePinnedToCore(task_a, "TaskA", 4096, nullptr, 2, &taskA_handle, 0);
xTaskCreatePinnedToCore(task_b, "TaskB", 4096, nullptr, 3, &taskB_handle, 1);
xTaskCreatePinnedToCore(task_c, "TaskC", 4096, nullptr, 3, &taskC_handle, 1);
}
//------------------ loop() 保持空 ------------------
void loop()
{
// 所有工作都在任务里完成
vTaskDelay(pdMS_TO_TICKS(1000));
}
使用说明
-
用 Arduino IDE:安装
ESP32-BLE-Keyboard
库,选择 ESP32 板型,直接编译上传。 -
用 PlatformIO:在项目
platformio.ini
中追加ini
-
复制
lib_deps = tvk/ESP32-BLE-Keyboard@^0.3.2
然后pio run -t upload
。 -
烧录后,用手机/电脑蓝牙配对名为 “ESP32-TripleThread” 的键盘,即可测试按键 A/B/C 的对应行为。
是否需要加入thomasfredericks/Bounce2@^2.72库?
不需要再额外添加 Bounce2 库。
Bounce2 只是用来做“消抖”的,而上面代码里已经用 vTaskDelay + 状态循环 在 FreeRTOS 任务里完成了简单软件消抖,效果足够,也不会阻塞其它线程。
Bounce2 只是用来做“消抖”的,而上面代码里已经用 vTaskDelay + 状态循环 在 FreeRTOS 任务里完成了简单软件消抖,效果足够,也不会阻塞其它线程。
如果你想把 Bounce2 的优雅 API 也用起来当然没问题(代码会更短、可读性更好),但不是“必须”。
下面简单示范两种写法,你可以按需选择。
下面简单示范两种写法,你可以按需选择。
/*
* ESP32 三线程 BLE 键盘 + Bounce2 消抖
* Board: ESP32 Dev Module
*/
#include <Arduino.h>
#include <BleKeyboard.h>
#include <Bounce2.h>
//------------------ 按键宏定义 ------------------
#define BTN_A 25
#define BTN_B 26
#define BTN_C 27
//------------------ BLE 键盘对象 ------------------
BleKeyboard bleKeyboard("ESP32-TripleThread", "Espressif", 100);
//------------------ Bounce2 消抖对象 ------------------
Bounce btnA = Bounce();
Bounce btnB = Bounce();
Bounce btnC = Bounce();
//------------------ FreeRTOS 句柄/信号量 ------------------
static TaskHandle_t taskA_handle = NULL;
static TaskHandle_t taskB_handle = NULL;
static TaskHandle_t taskC_handle = NULL;
static volatile bool a_cancel_flag = false;
//------------------ 线程 a:5连发 'j' ------------------
void task_a(void *pvParameters)
{
(void)pvParameters;
for (;;)
{
btnA.update();
if (btnA.fell()) // 按键 A 按下
{
const uint16_t delays_ms[] = {1000, 1000, 2000, 2000};
a_cancel_flag = false;
for (int i = 0; i < 5; ++i)
{
if (a_cancel_flag) break; // 线程 b 要求终止
if (bleKeyboard.isConnected())
bleKeyboard.print("j");
if (i < 4) // 最后一次不用 delay
{
uint32_t remain = delays_ms[i];
while (remain > 0 && !a_cancel_flag)
{
vTaskDelay(pdMS_TO_TICKS(50));
btnA.update(); // 保持消抖刷新
remain -= 50;
}
if (a_cancel_flag) break;
}
}
}
vTaskDelay(pdMS_TO_TICKS(10)); // 让出 CPU
}
}
//------------------ 线程 b:打断 a 并发送 'k' ------------------
void task_b(void *pvParameters)
{
(void)pvParameters;
for (;;)
{
btnB.update();
if (btnB.fell()) // 按键 B 按下
{
a_cancel_flag = true; // 打断线程 a
if (bleKeyboard.isConnected())
bleKeyboard.print("k");
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
//------------------ 线程 c:发送 'k' ------------------
void task_c(void *pvParameters)
{
(void)pvParameters;
for (;;)
{
btnC.update();
if (btnC.fell()) // 按键 C 按下
{
if (bleKeyboard.isConnected())
bleKeyboard.print("k");
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
//------------------ setup() ------------------
void setup()
{
Serial.begin(115200);
// 初始化 Bounce2
btnA.attach(BTN_A, INPUT_PULLDOWN);
btnB.attach(BTN_B, INPUT_PULLDOWN);
btnC.attach(BTN_C, INPUT_PULLDOWN);
btnA.interval(20);
btnB.interval(20);
btnC.interval(20);
// 初始化 BLE
bleKeyboard.begin();
// 创建任务并绑定到指定核心
xTaskCreatePinnedToCore(task_a, "TaskA", 4096, nullptr, 2, &taskA_handle, 0);
xTaskCreatePinnedToCore(task_b, "TaskB", 4096, nullptr, 3, &taskB_handle, 1);
xTaskCreatePinnedToCore(task_c, "TaskC", 4096, nullptr, 3, &taskC_handle, 1);
}
//------------------ loop() 保持空 ------------------
void loop()
{
vTaskDelay(pdMS_TO_TICKS(1000));
}
我的按键引脚是5/4/2,并且使用上拉电阻。请修改程序
/*
* ESP32 三线程 BLE 键盘 + Bounce2(上拉)
* 按键:A→5,B→4,C→2(另一端全部接地)
*/
#include <Arduino.h>
#include <BleKeyboard.h>
#include <Bounce2.h>
//------------------ 按键宏定义(上拉) ------------------
#define BTN_A 5
#define BTN_B 4
#define BTN_C 2
//------------------ BLE 键盘对象 ------------------
BleKeyboard bleKeyboard("ESP32-TripleThread", "Espressif", 100);
//------------------ Bounce2 消抖对象 ------------------
Bounce btnA = Bounce();
Bounce btnB = Bounce();
Bounce btnC = Bounce();
//------------------ FreeRTOS 句柄/信号量 ------------------
static TaskHandle_t taskA_handle = NULL;
static TaskHandle_t taskB_handle = NULL;
static TaskHandle_t taskC_handle = NULL;
static volatile bool a_cancel_flag = false;
//------------------ 线程 a:5连发 'j' ------------------
void task_a(void *pvParameters)
{
(void)pvParameters;
for (;;)
{
btnA.update();
if (btnA.fell()) // 按键 A 被按下(低电平)
{
const uint16_t delays_ms[] = {1000, 1000, 2000, 2000};
a_cancel_flag = false;
for (int i = 0; i < 5; ++i)
{
if (a_cancel_flag) break;
if (bleKeyboard.isConnected())
bleKeyboard.print("j");
if (i < 4)
{
uint32_t remain = delays_ms[i];
while (remain > 0 && !a_cancel_flag)
{
vTaskDelay(pdMS_TO_TICKS(50));
btnA.update(); // 保持消抖
remain -= 50;
}
if (a_cancel_flag) break;
}
}
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
//------------------ 线程 b:打断 a 并发送 'k' ------------------
void task_b(void *pvParameters)
{
(void)pvParameters;
for (;;)
{
btnB.update();
if (btnB.fell()) // 按键 B 被按下
{
a_cancel_flag = true; // 打断线程 a
if (bleKeyboard.isConnected())
bleKeyboard.print("k");
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
//------------------ 线程 c:发送 'k' ------------------
void task_c(void *pvParameters)
{
(void)pvParameters;
for (;;)
{
btnC.update();
if (btnC.fell()) // 按键 C 被按下
{
if (bleKeyboard.isConnected())
bleKeyboard.print("k");
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
//------------------ setup() ------------------
void setup()
{
Serial.begin(115200);
// 初始化 Bounce2(上拉)
btnA.attach(BTN_A, INPUT_PULLUP);
btnB.attach(BTN_B, INPUT_PULLUP);
btnC.attach(BTN_C, INPUT_PULLUP);
btnA.interval(20);
btnB.interval(20);
btnC.interval(20);
// 初始化 BLE
bleKeyboard.begin();
// 创建任务并绑定到指定核心
xTaskCreatePinnedToCore(task_a, "TaskA", 4096, nullptr, 2, &taskA_handle, 0);
xTaskCreatePinnedToCore(task_b, "TaskB", 4096, nullptr, 3, &taskB_handle, 1);
xTaskCreatePinnedToCore(task_c, "TaskC", 4096, nullptr, 3, &taskC_handle, 1);
}
//------------------ loop() ------------------
void loop()
{
vTaskDelay(pdMS_TO_TICKS(1000));
}
这里的“保持消抖”并不是为了检测按键 A(因为本轮 5 连发已经开始,A 键在这段时间里本来就被忽略),而是出于下面两个更实际的理由:
-
让 Bounce2 的内部计时器继续运行
btnA.update()
每次被调用都会把当前引脚状态送进 Bounce2 的滤波算法,更新它的内部状态机。
如果连续 6 秒一次都不调update()
,Bounce2 就会把 6 秒前的“抖动残留”当成当前状态,导致下一次btnA.fell()
误判。
因此每隔 50 ms 调一次,相当于“喂”一次状态,保证下次再按 A 时消抖计时正确。 -
顺带刷新 B 键和 C 键的状态
在循环里我们只示范了btnA.update()
,但 Bounce2 的更新开销极小,顺手把它做了不会带来额外负担,也避免遗漏 B/C 键的检测。
所以这里的“保持消抖”更准确的说法是 “维持 Bounce2 状态机正常运行”,而不是“防止 A 键抖动”。
vTaskDelay(pdMS_TO_TICKS(10));
出现在每个 FreeRTOS 任务的主循环末尾,是为了-
主动让出 CPU
FreeRTOS 是抢占式调度器,但任务内部如果一直忙等(while (1) { btn.update(); }
),高优先级任务仍会抢占,只是低优先级任务可能长时间得不到时间片;
加上vTaskDelay(10)
后,任务明确告诉调度器“我休息 10 ms”,让其它同优先级或低优先级任务也能得到执行机会。 -
降低 CPU 占用 / 省电
没有vTaskDelay
时,三个任务都会以接近 100 % 的 CPU 利用率空转,浪费电能并增加热量。
10 ms 的延时对按键响应(50 ms 消抖窗口)没有任何可见影响,却能把 CPU 占用降到 <1 %。 -
防止看门狗复位
ESP32 的 idle hook 和 task watchdog 默认会检测有没有任务长时间占用 CPU 而不让出;
如果某个任务一直不延时,可能触发Task watchdog got triggered
的警告甚至重启。
因此,即使
btn.update()
本身很快,也必须在循环里加一点延时,这是 FreeRTOS 任务的“礼貌”做法。我的按键自带10k上拉电阻,应该如何修改程序
你的按键已经在 硬件上接了 10 kΩ 上拉到 3.3 V,因此 GPIO 引脚常态是高电平,按下时被拉到低电平。
- 取消内部上拉(避免与外部 10 kΩ 并联导致总阻值变小)。
- 把引脚设为普通输入即可(
pinMode(pin, INPUT)
)。
全部评论