黑神话悟空连招程序

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,不会阻塞 loop(),也保证线程 b 可以实时“打断”线程 a 的发送序列。
库依赖
• tvk/ESP32-BLE-Keyboard@^0.3.2(在 platformio.ini 或 Arduino IDE 的库管理器里安装即可)
硬件连线(可自己改)
• 按键 A → GPIO 25(下拉,按下 = HIGH)
• 按键 B → GPIO 26(下拉,按下 = HIGH)
• 按键 C → GPIO 27(下拉,按下 = HIGH)
功能说明
  1. 线程 a(固定在 Core 0
    监听按键 A;一旦按下,通过 BLE 依次发送字符 ‘j’ 共 5 次,间隔分别为 1 s、1 s、2 s、2 s。
    该线程可被线程 b 实时中断:一旦收到取消信号,立即终止本次“5连发”。
  2. 线程 b(固定在 Core 1
    监听按键 B;一旦按下,立即取消线程 a 的正在进行的“5连发”,随后立刻发送一次字符 ‘k’。
  3. 线程 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));
}
 
使用说明
  1. 用 Arduino IDE:安装 ESP32-BLE-Keyboard 库,选择 ESP32 板型,直接编译上传。
  2. 用 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 的优雅 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 键在这段时间里本来就被忽略),而是出于下面两个更实际的理由:
  1. 让 Bounce2 的内部计时器继续运行
    btnA.update() 每次被调用都会把当前引脚状态送进 Bounce2 的滤波算法,更新它的内部状态机。
    如果连续 6 秒一次都不调 update(),Bounce2 就会把 6 秒前的“抖动残留”当成当前状态,导致下一次 btnA.fell() 误判。
    因此每隔 50 ms 调一次,相当于“喂”一次状态,保证下次再按 A 时消抖计时正确。
  2. 顺带刷新 B 键和 C 键的状态
    在循环里我们只示范了 btnA.update(),但 Bounce2 的更新开销极小,顺手把它做了不会带来额外负担,也避免遗漏 B/C 键的检测。
所以这里的“保持消抖”更准确的说法是 “维持 Bounce2 状态机正常运行”,而不是“防止 A 键抖动”。
 
 
vTaskDelay(pdMS_TO_TICKS(10)); 出现在每个 FreeRTOS 任务的主循环末尾,是为了
  1. 主动让出 CPU
    FreeRTOS 是抢占式调度器,但任务内部如果一直忙等(while (1) { btn.update(); }),高优先级任务仍会抢占,只是低优先级任务可能长时间得不到时间片;
    加上 vTaskDelay(10) 后,任务明确告诉调度器“我休息 10 ms”,让其它同优先级或低优先级任务也能得到执行机会。
  2. 降低 CPU 占用 / 省电
    没有 vTaskDelay 时,三个任务都会以接近 100 % 的 CPU 利用率空转,浪费电能并增加热量。
    10 ms 的延时对按键响应(50 ms 消抖窗口)没有任何可见影响,却能把 CPU 占用降到 <1 %。
  3. 防止看门狗复位
    ESP32 的 idle hooktask watchdog 默认会检测有没有任务长时间占用 CPU 而不让出;
    如果某个任务一直不延时,可能触发 Task watchdog got triggered 的警告甚至重启。
因此,即使 btn.update() 本身很快,也必须在循环里加一点延时,这是 FreeRTOS 任务的“礼貌”做法。
 

我的按键自带10k上拉电阻,应该如何修改程序

你的按键已经在 硬件上接了 10 kΩ 上拉到 3.3 V,因此 GPIO 引脚常态是高电平,按下时被拉到低电平

  1. 取消内部上拉(避免与外部 10 kΩ 并联导致总阻值变小)。
  2. 把引脚设为普通输入即可pinMode(pin, INPUT))。

全部评论

·