新版本高效跳闪-1
75 2025-08-22 08:55
单次按动版本
#include <Arduino.h>
#include <BleKeyboard.h>
#define PIN_BTN 4 // 按键 → GND(下降沿触发)
#define PIN_LED 16
BleKeyboard bleKeyboard("21's Knob", "luowei", 88);
// 按键消息队列
static QueueHandle_t btnQueue;
// 中断回调(Arduino 标准写法,不需要 attachInterruptArg)
void IRAM_ATTR onBtn() {
static uint32_t last = 0;
uint32_t now = xTaskGetTickCountFromISR();
if (now - last < 2) return; // 2 tick ≈ 2 ms 防抖
last = now;
uint8_t dummy = 1;
xQueueSendFromISR(btnQueue, &dummy, NULL);
}
void setup() {
pinMode(PIN_BTN, INPUT_PULLUP);
pinMode(PIN_LED, OUTPUT);
attachInterrupt(digitalPinToInterrupt(PIN_BTN), onBtn, FALLING);
btnQueue = xQueueCreate(8, sizeof(uint8_t));
// NimBLE 已在库内启用,无需手动设置连接间隔;
bleKeyboard.begin();
}
void loop() {
uint8_t dummy;
if (xQueueReceive(btnQueue, &dummy, portMAX_DELAY) == pdTRUE) {
if (bleKeyboard.isConnected()) {
bleKeyboard.write(KEY_LEFT_CTRL);
delay(30); // 36跳得高
bleKeyboard.print(" ");
}
digitalWrite(PIN_LED, HIGH);
vTaskDelay(pdMS_TO_TICKS(50));
digitalWrite(PIN_LED, LOW);
}
}
}这段程序引入队列有什么好处?是否必要?
在这段程序里引入 FreeRTOS 队列(btnQueue)的主要目的是把中断里的“事件”转发给 loop() 上下文,从而避免在中断里直接调用任何可能阻塞、耗时或引发竞态的代码。好处可以概括为:
-
解耦中断与业务逻辑
中断只负责“通知”发生了按键事件,不直接执行BleKeyboard.write()
等耗时操作;真正的按键处理放在loop()
,既简洁又安全。 -
避免在中断里使用非 ISR 安全函数
BleKeyboard.write()
最终会调用 NimBLE 的 API,这些 API 不是为中断环境设计的;放在中断里运行可能会触发断言或产生不可预期的行为。 -
自带消抖与缓存
如果按键机械抖动导致中断多次触发,队列可以把这些事件暂存起来,随后由loop()
按顺序逐一处理(虽然示例里中断里已经有 2 tick 去抖,但仍可防极端情况)。 -
便于后续扩展
以后如果需要在按键事件中附带更多信息(单击/双击/长按类型、时间戳等),直接改成传结构体即可;而不用把更多变量塞进全局、再加 volatile 保护。
是否“必要”?
-
从“代码能跑”角度:不是绝对必要。把
BleKeyboard.write()
直接放进中断,大多数场景也能“看起来”正常工作,因为一次按键中断里只发两个 HID 报文,耗时 <1 ms,ISR 结束前 BLE 协议栈大概率还没机会触发更高优先级中断。 -
从“稳健、可维护”角度:非常推荐。一旦以后增加更多逻辑(比如连发、组合键、长按检测、低功耗休眠等),直接在中断里写代码会变得脆弱,队列/事件机制能让代码结构清晰且易于扩展。
一句话总结
队列不是“不写就烧不进去”的必要,而是“想写得稳健、优雅、可扩展”的必要。
队列不是“不写就烧不进去”的必要,而是“想写得稳健、优雅、可扩展”的必要。
全部评论