⌨️ 蓝牙键盘

基于 HID over GATT Profile 的无线键盘,支持多设备切换和按键无冲。

📐 键盘系统架构

flowchart TB subgraph Keyboard["蓝牙键盘"] MATRIX[按键矩阵] --> SCAN[扫描电路] SCAN --> MCU[BLE MCU] MCU --> ANT[天线] MCU --> LED[状态 LED] end subgraph Host["主机设备"] ANT2[天线] --> HOST[Host OS] HOST --> HID[HID 驱动] HID --> OS[操作系统] end ANT -.->|BLE 连接 | ANT2 style Keyboard fill:#e3f2fd,stroke:#007bff style Host fill:#d4edda,stroke:#28a745

📋 HID 键盘报告格式

// HID 键盘输入报告 (标准 8 键无冲)
// Report ID: 0x01
// 总长度:8 bytes

typedef struct {
    uint8_t  report_id;      // Report ID (0x01)
    uint8_t  modifier;       // 修饰键 (Ctrl/Shift/Alt 等)
    uint8_t  reserved;       // 保留
    uint8_t  keycode[6];     // 按键码 (最多 6 键同时按下)
} keyboard_input_report_t;

// 修饰键位定义
#define KB_MOD_LCTRL    0x01
#define KB_MOD_LSHIFT   0x02
#define KB_MOD_LALT     0x04
#define KB_MOD_LGUI     0x08  // Windows/Command
#define KB_MOD_RCTRL    0x10
#define KB_MOD_RSHIFT   0x20
#define KB_MOD_RALT     0x40
#define KB_MOD_RGUI     0x80

// 常用按键码 (Usage ID)
#define KB_KEY_A        0x04
#define KB_KEY_B        0x05
#define KB_KEY_C        0x06
#define KB_KEY_ENTER    0x28
#define KB_KEY_ESCAPE   0x29
#define KB_KEY_BACKSPACE 0x2A
#define KB_KEY_TAB      0x2B
#define KB_KEY_SPACE    0x2C
#define KB_KEY_F1       0x3A
#define KB_KEY_F12      0x45

// 示例:按下 Ctrl+A (全选)
uint8_t keyboard_report[] = {
    0x01,                   // Report ID
    KB_MOD_LCTRL,           // Modifier: Left Ctrl
    0x00,                   // Reserved
    KB_KEY_A, 0x00, 0x00,   // Keycode: A
    0x00, 0x00
};

// 示例:按下 Shift+1 (感叹号)
uint8_t keyboard_report_shift1[] = {
    0x01,
    KB_MOD_LSHIFT,          // Modifier: Left Shift
    0x00,
    0x1E, 0x00, 0x00,       // Keycode: 1
    0x00, 0x00
};

📊 HID 报告描述符

// 键盘 HID 报告描述符
const uint8_t keyboard_report_descriptor[] = {
    0x05, 0x01,        // Usage Page (Desktop)
    0x09, 0x06,        // Usage (Keyboard)
    0xA1, 0x01,        // Collection (Application)
    
    0x85, 0x01,        //   Report ID (1)
    
    // 修饰键 (8 个)
    0x05, 0x07,        //   Usage Page (Keyboard)
    0x19, 0xE0,        //   Usage Minimum (224) - Left Ctrl
    0x29, 0xE7,        //   Usage Maximum (231) - Right GUI
    0x15, 0x00,        //   Logical Minimum (0)
    0x25, 0x01,        //   Logical Maximum (1)
    0x75, 0x01,        //   Report Size (1)
    0x95, 0x08,        //   Report Count (8)
    0x81, 0x02,        //   Input (Data, Var, Abs)
    
    // 保留字节
    0x95, 0x01,        //   Report Count (1)
    0x75, 0x08,        //   Report Size (8)
    0x81, 0x01,        //   Input (Cnst)
    
    // LED 输出 (5 个:NumLock/CapsLock/ScrollLock/Compose/Kana)
    0x05, 0x08,        //   Usage Page (LED)
    0x19, 0x01,        //   Usage Minimum (1)
    0x29, 0x05,        //   Usage Maximum (5)
    0x95, 0x05,        //   Report Count (5)
    0x75, 0x01,        //   Report Size (1)
    0x91, 0x02,        //   Output (Data, Var, Abs)
    
    // LED 填充
    0x95, 0x01,        //   Report Count (1)
    0x75, 0x03,        //   Report Size (3)
    0x91, 0x01,        //   Output (Cnst)
    
    // 按键 (6 键无冲)
    0x95, 0x06,        //   Report Count (6)
    0x75, 0x08,        //   Report Size (8)
    0x15, 0x00,        //   Logical Minimum (0)
    0x25, 0x65,        //   Logical Maximum (101)
    0x05, 0x07,        //   Usage Page (Keyboard)
    0x19, 0x00,        //   Usage Minimum (0)
    0x29, 0x65,        //   Usage Maximum (101)
    0x81, 0x00,        //   Input (Data, Arr, Abs)
    
    0xC0               // End Collection
};

// LED 定义
#define LED_NUM_LOCK    0x01
#define LED_CAPS_LOCK   0x02
#define LED_SCROLL_LOCK 0x04
#define LED_COMPOSE     0x08
#define LED_KANA        0x10

📈 完整工作流程

sequenceDiagram participant KB as 蓝牙键盘 participant Host as 主机 Note over KB,Host: 1. 配对模式 KB->>KB: 长按配对键 KB->>Host: ADV_IND (键盘) Note over KB,Host: 2. 连接配对 Host->>KB: CONNECT_IND Host->>KB: 配对请求 KB-->>Host: 确认配对 Note over KB,Host: 3. 服务发现 Host->>KB: 发现 HID 服务 Host->>KB: 读取报告描述符 Note over KB,Host: 4. 启用通知 Host->>KB: 写入 CCCD Note over KB,Host: 5. 按键输入 loop 按键事件 KB->>Host: 按下通知 KB->>Host: 释放通知 (全 0) end Note over KB,Host: 6. LED 控制 Host->>KB: 输出报告 (CapsLock) KB->>KB: 点亮 LED Note over KB,Host: 7. 低功耗 KB->>KB: 无操作 10s KB->>Host: 进入休眠

💻 固件代码示例

#include "ble_hids.h"

// HID 服务句柄
BLE_HIDS_DEF(m_hids, 1);

// 键盘报告缓冲区
static uint8_t m_kb_report[8];
static uint8_t m_last_keys[6] = {0};

// 初始化键盘 HID 服务
void keyboard_hids_init(void) {
    ble_hids_init_t hids_init = {0};
    
    // 配置报告映射
    hids_init.rep_map.p_rep_map = (uint8_t *)keyboard_report_descriptor;
    hids_init.rep_map.rep_map_len = sizeof(keyboard_report_descriptor);
    
    // 配置输入报告
    ble_hids_inp_rep_t *p_inp_rep = &hids_init.inp_rep[0];
    p_inp_rep->max_len = 8;
    p_inp_rep->rep_ref.report_id = 1;
    p_inp_rep->rep_ref.report_type = BLE_HIDS_INPUT_REP;
    p_inp_rep->evt_handler = kb_input_evt_handler;
    
    // 配置输出报告 (LED 控制)
    ble_hids_outp_rep_t *p_outp_rep = &hids_init.outp_rep[0];
    p_outp_rep->max_len = 1;
    p_outp_rep->rep_ref.report_id = 1;
    p_outp_rep->rep_ref.report_type = BLE_HIDS_OUTPUT_REP;
    p_outp_rep->evt_handler = kb_output_evt_handler;
    p_outp_rep->security_mode.ccm.cps = 1;
    
    hids_init.inp_rep_count = 1;
    hids_init.outp_rep_count = 1;
    
    // HID 信息
    hids_init.hid_information.bcd_hid = 0x0111;
    hids_init.hid_information.b_country_code = 0;
    hids_init.hid_information.flags = 0x01;  // 远程唤醒
    
    ble_hids_init(&m_hids, &hids_init);
}

// 发送按键报告
void keyboard_send_keys(uint8_t modifier, uint8_t *keys, uint8_t count) {
    m_kb_report[0] = 0x01;      // Report ID
    m_kb_report[1] = modifier;  // Modifier
    m_kb_report[2] = 0x00;      // Reserved
    
    // 填充按键 (最多 6 个)
    for (int i = 0; i < 6; i++) {
        m_kb_report[3 + i] = (i < count) ? keys[i] : 0x00;
    }
    
    // 发送输入报告
    ble_hids_inp_rep_send(&m_hids, 0, 8, m_kb_report, m_conn_handle);
}

// 按键按下
void key_press(uint8_t keycode) {
    uint8_t keys[6] = {0};
    uint8_t count = 0;
    
    // 查找空位
    for (int i = 0; i < 6; i++) {
        if (m_last_keys[i] == keycode) {
            return;  // 已按下,忽略
        }
        if (m_last_keys[i] == 0x00) {
            m_last_keys[i] = keycode;
            count = i + 1;
            break;
        }
    }
    
    // 构建报告
    memcpy(keys, m_last_keys, 6);
    keyboard_send_keys(0, keys, count);
}

// 按键释放
void key_release(uint8_t keycode) {
    // 从数组中移除
    for (int i = 0; i < 6; i++) {
        if (m_last_keys[i] == keycode) {
            m_last_keys[i] = 0x00;
        }
    }
    
    // 发送空报告或剩余按键
    keyboard_send_keys(0, m_last_keys, 6);
}

// LED 输出报告处理
void kb_output_evt_handler(ble_hids_t * p_hids, 
                           ble_hids_evt_t * p_evt) {
    if (p_evt->evt_type == BLE_HIDS_EVT_OUTP_REP_WRITE) {
        uint8_t led_state = p_evt->params.outp_rep.data[0];
        
        // 控制 LED
        if (led_state & LED_CAPS_LOCK) {
            led_on(CAPS_LOCK_LED);
        } else {
            led_off(CAPS_LOCK_LED);
        }
        
        if (led_state & LED_NUM_LOCK) {
            led_on(NUM_LOCK_LED);
        } else {
            led_off(NUM_LOCK_LED);
        }
    }
}

⌨️ 多设备切换

flowchart TB subgraph Profiles["配对配置文件"] P1[设备 1
PC] P2[设备 2
手机] P3[设备 3
平板] end SWITCH[切换按键] --> SELECT{选择设备} SELECT -->|Fn+1| P1 SELECT -->|Fn+2| P2 SELECT -->|Fn+3| P3 P1 --> CONNECT1[连接设备 1] P2 --> CONNECT2[连接设备 2] P3 --> CONNECT3[连接设备 3] style Profiles fill:#e3f2fd,stroke:#007bff style SWITCH fill:#ffc107,color:#000
// 多设备切换实现
typedef struct {
    ble_gap_addr_t addr;      // 设备地址
    ble_gap_irk_t  irk;       // 身份解析密钥
    bool           bonded;    // 已配对
} device_profile_t;

// 存储 3 个设备配置
static device_profile_t m_profiles[3];
static uint8_t m_current_device = 0;

// 切换设备
void switch_device(uint8_t index) {
    if (index >= 3) return;
    
    // 断开当前连接
    sd_ble_gap_disconnect(m_conn_handle, 
                          BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
    
    // 更新当前设备
    m_current_device = index;
    
    // 使用对应设备的密钥广播
    if (m_profiles[index].bonded) {
        // 已配对,直接连接
        sd_ble_gap_connect(&m_profiles[index].addr, 
                           &m_scan_params,
                           &m_conn_params,
                           NULL);
    } else {
        // 未配对,进入广播模式
        start_advertising();
    }
    
    // 更新 LED 指示
    update_led_indicator(index);
}

⚡ 功耗优化

参数 配置值 说明
连接间隔 20ms - 50ms 键盘不需要超低延迟
从机延迟 4-9 允许跳过事件
监督超时 2s 平衡功耗与响应
广播间隔 30-60ms 快速重连
休眠电流 <5μA 深度睡眠

🔍 调试要点

常见问题

  • 按键重复:去抖时间不足
  • 多键无冲:检查报告格式
  • LED 不亮:输出报告未启用
  • 切换失败:清除配对信息

推荐工具

  • Device Manager: Windows 查看
  • Bluetooth Explorer: macOS 调试
  • nRF Sniffer: 空口抓包
  • Keyboard Tester: 在线测试