实现步骤
- 初始化蓝牙控制器:配置并启用蓝牙硬件。
- 初始化 Bluedroid 栈:初始化 Bluetooth 协议栈。
- 配置 HID 设备:设置 HID 报告描述符,注册 HID 回调函数。
- 处理 HID 事件:处理配对、连接、断开等事件。
- 发送键盘报告:根据按键事件发送 HID 报告到主机。
ESP-IDF 蓝牙 HID 函数详解
蓝牙控制器初始化与启用
esp_bt_controller_init
原型:
c
esp_err_t esp_bt_controller_init(const esp_bt_controller_config_t *cfg);
功能: 初始化蓝牙控制器。需要在调用任何其他蓝牙函数之前先初始化控制器。
参数:
cfg
: 指向蓝牙控制器配置结构体的指针。
返回值:
ESP_OK
:成功。- 其他错误码表示失败。
esp_bt_controller_enable
原型:
c
esp_err_t esp_bt_controller_enable(esp_bt_mode_t mode);
功能: 启用蓝牙控制器,并指定工作模式(例如 Bluetooth Classic、BLE 或同时支持两者)。
参数:
mode
: 蓝牙工作模式,常用值为ESP_BT_MODE_CLASSIC_BT
或ESP_BT_MODE_BTDM
(双模式)。
返回值:
ESP_OK
:成功。- 其他错误码表示失败。
Bluedroid 栈初始化与启用
esp_bluedroid_init
原型:
c
esp_err_t esp_bluedroid_init(void);
功能: 初始化 Bluedroid 栈。需要在控制器启用后调用。
返回值:
ESP_OK
:成功。- 其他错误码表示失败。
esp_bluedroid_enable
原型:
c
esp_err_t esp_bluedroid_enable(void);
功能: 启用 Bluedroid 栈。
返回值:
ESP_OK
:成功。- 其他错误码表示失败。
HID 设备配置与回调
esp_hidd_device_register_app
原型:
c
esp_err_t esp_hidd_device_register_app(esp_hidd_app_param_t *param, esp_hidd_callbacks_t *callbacks);
功能: 注册 HID 设备应用,包括设置设备信息和回调函数。
参数:
param
: 指向 HID 应用参数结构体的指针,包含设备描述符、报告描述符等信息。callbacks
: 指向 HID 回调函数结构体的指针,用于处理 HID 事件。
返回值:
ESP_OK
:成功。- 其他错误码表示失败。
esp_hidd_device_init
原型:
c
esp_err_t esp_hidd_device_init(void);
功能: 初始化 HID 设备模块。
返回值:
ESP_OK
:成功。- 其他错误码表示失败。
HID 报告发送
esp_hidd_device_send_report
原型:
c
esp_err_t esp_hidd_device_send_report(esp_hidd_conn_state_t conn_state, uint8_t report_type, uint8_t report_id, uint8_t *report, size_t report_len);
功能: 发送 HID 报告到主机。
参数:
conn_state
: 连接状态,通常为已连接状态。report_type
: 报告类型(如输入报告、输出报告等)。report_id
: 报告 ID,用于区分不同的报告。report
: 指向要发送的报告数据的指针。report_len
: 报告数据的长度。
返回值:
ESP_OK
:成功。- 其他错误码表示失败。
辅助功能函数
esp_bt_gap_set_device_name
原型:
c
esp_err_t esp_bt_gap_set_device_name(const char *name);
功能: 设置设备的蓝牙名称。
参数:
name
: 指向设备名称字符串的指针。
返回值:
ESP_OK
:成功。- 其他错误码表示失败。
esp_bt_gap_set_scan_mode
原型:
c
esp_err_t esp_bt_gap_set_scan_mode(esp_bt_scan_mode_t mode);
功能: 设置设备的扫描模式(可见性和可连接性)。
参数:
mode
: 扫描模式,常用值为ESP_BT_CONNECTABLE
、ESP_BT_NON_CONNECTABLE
等。
返回值:
ESP_OK
:成功。- 其他错误码表示失败。
详细流程解析
初始化蓝牙控制器
首先,需要初始化并启用蓝牙控制器。这是整个蓝牙功能的基础。
c
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
esp_err_t ret = esp_bt_controller_init(&bt_cfg);
if (ret != ESP_OK) {
// 处理错误
}
ret = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT);
if (ret != ESP_OK) {
// 处理错误
}
初始化 Bluedroid 栈
在控制器启用后,初始化并启用 Bluedroid 栈。
c
ret = esp_bluedroid_init();
if (ret != ESP_OK) {
// 处理错误
}
ret = esp_bluedroid_enable();
if (ret != ESP_OK) {
// 处理错误
}
配置 HID 设备
设置设备名称和扫描模式
c
ret = esp_bt_gap_set_device_name("ESP32 Keyboard");
if (ret != ESP_OK) {
// 处理错误
}
ret = esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE | ESP_BT_GENERAL_DISCOVERABLE);
if (ret != ESP_OK) {
// 处理错误
}
配置 HID 报告描述符
HID 报告描述符定义了设备的功能和支持的报告类型。一个典型的键盘报告描述符如下:
c
static const uint8_t hid_report_descriptor[] = {
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x06, // Usage (Keyboard)
0xA1, 0x01, // Collection (Application)
0x05, 0x07, // Usage Page (Key Codes)
0x19, 0xE0, // Usage Minimum (224)
0x29, 0xE7, // Usage Maximum (231)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x08, // Report Count (8)
0x81, 0x02, // Input (Data, Variable, Absolute)
0x95, 0x01, // Report Count (1)
0x75, 0x08, // Report Size (8)
0x81, 0x03, // Input (Constant, Variable, Absolute)
0x95, 0x06, // Report Count (6)
0x75, 0x08, // Report Size (8)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x65, // Logical Maximum (101)
0x05, 0x07, // Usage Page (Key Codes)
0x19, 0x00, // Usage Minimum (0)
0x29, 0x65, // Usage Maximum (101)
0x81, 0x00, // Input (Data, Array)
0xC0 // End Collection
};
设置 HID 应用参数和回调
c
esp_hidd_app_param_t app_param = {
.hid_desc_len = sizeof(hid_report_descriptor),
.hid_desc = hid_report_descriptor,
.cdesc_len = 0,
.cdesc = NULL
};
esp_hidd_callbacks_t callbacks = {
.connect_cb = &hid_event_callback,
.disconnect_cb = &hid_event_callback,
.get_report_cb = &hid_event_callback,
.set_report_cb = &hid_event_callback,
.intr_data_cb = &hid_event_callback
};
ret = esp_hidd_device_register_app(&app_param, &callbacks);
if (ret != ESP_OK) {
// 处理错误
}
处理 HID 事件
通过回调函数处理 HID 设备的事件,如连接、断开、报告请求等。
c
static void hid_event_callback(esp_hidd_event_t event, esp_hidd_event_param_t *param) {
switch (event) {
case ESP_HIDD_EVENT_BLE_CONNECT:
// 处理连接事件
break;
case ESP_HIDD_EVENT_BLE_DISCONNECT:
// 处理断开事件
break;
// 处理其他事件
default:
break;
}
}
发送键盘报告
根据按键事件构建 HID 报告并发送给主机。
c
typedef struct {
uint8_t modifier;
uint8_t reserved;
uint8_t keycode[6];
} __attribute__((packed)) hid_keyboard_report_t;
bool send_key_report(uint8_t modifier, uint8_t keycode[6]) {
hid_keyboard_report_t report;
report.modifier = modifier;
report.reserved = 0;
if (keycode) {
memcpy(report.keycode, keycode, sizeof(report.keycode));
} else {
memset(report.keycode, 0, sizeof(report.keycode));
}
return esp_hidd_device_send_report(/*连接状态*/, /*报告类型*/, /*报告ID*/, (uint8_t *)&report, sizeof(report)) == ESP_OK;
}
代码示例
以下是一个简化的完整示例,展示如何使用 ESP-IDF 实现一个蓝牙键盘。
c
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_hidd_device.h"
#include "esp_gap_bt_api.h"
// HID 报告描述符
static const uint8_t hid_report_descriptor[] = {
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x06, // Usage (Keyboard)
0xA1, 0x01, // Collection (Application)
0x05, 0x07, // Usage Page (Key Codes)
0x19, 0xE0, // Usage Minimum (224)
0x29, 0xE7, // Usage Maximum (231)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x08, // Report Count (8)
0x81, 0x02, // Input (Data, Variable, Absolute)
0x95, 0x01, // Report Count (1)
0x75, 0x08, // Report Size (8)
0x81, 0x03, // Input (Constant, Variable, Absolute)
0x95, 0x06, // Report Count (6)
0x75, 0x08, // Report Size (8)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x65, // Logical Maximum (101)
0x05, 0x07, // Usage Page (Key Codes)
0x19, 0x00, // Usage Minimum (0)
0x29, 0x65, // Usage Maximum (101)
0x81, 0x00, // Input (Data, Array)
0xC0 // End Collection
};
// HID 事件回调函数
static void hid_event_callback(esp_hidd_event_t event, esp_hidd_event_param_t *param) {
switch (event) {
case ESP_HIDD_EVENT_BLE_CONNECT:
printf("HID BLE Connected\n");
break;
case ESP_HIDD_EVENT_BLE_DISCONNECT:
printf("HID BLE Disconnected\n");
break;
case ESP_HIDD_EVENT_GET_REPORT:
// 处理 GET_REPORT 请求
break;
case ESP_HIDD_EVENT_SET_REPORT:
// 处理 SET_REPORT 请求
break;
default:
break;
}
}
void app_main(void) {
esp_err_t ret;
// 初始化 NVS
ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// 初始化蓝牙控制器
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ret = esp_bt_controller_init(&bt_cfg);
if (ret != ESP_OK) {
printf("Bluetooth controller initialize failed: %s\n", esp_err_to_name(ret));
return;
}
// 启用蓝牙控制器
ret = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT);
if (ret != ESP_OK) {
printf("Bluetooth controller enable failed: %s\n", esp_err_to_name(ret));
return;
}
// 初始化 Bluedroid
ret = esp_bluedroid_init();
if (ret != ESP_OK) {
printf("Bluedroid initialize failed: %s\n", esp_err_to_name(ret));
return;
}
// 启用 Bluedroid
ret = esp_bluedroid_enable();
if (ret != ESP_OK) {
printf("Bluedroid enable failed: %s\n", esp_err_to_name(ret));
return;
}
// 设置设备名称
ret = esp_bt_gap_set_device_name("ESP32 Keyboard");
if (ret != ESP_OK) {
printf("Set device name failed: %s\n", esp_err_to_name(ret));
return;
}
// 设置可见和可连接模式
ret = esp_bt_gap_set_scan_mode(ESP_BT_SCAN_MODE_CONNECTABLE_DISCOVERABLE);
if (ret != ESP_OK) {
printf("Set scan mode failed: %s\n", esp_err_to_name(ret));
return;
}
// 配置 HID 应用参数
esp_hidd_app_param_t app_param = {
.hid_desc_len = sizeof(hid_report_descriptor),
.hid_desc = hid_report_descriptor,
.cdesc_len = 0,
.cdesc = NULL
};
// 配置 HID 回调函数
esp_hidd_callbacks_t callbacks = {
.connect_cb = hid_event_callback,
.disconnect_cb = hid_event_callback,
.get_report_cb = hid_event_callback,
.set_report_cb = hid_event_callback,
.intr_data_cb = hid_event_callback
};
// 注册 HID 应用
ret = esp_hidd_device_register_app(&app_param, &callbacks);
if (ret != ESP_OK) {
printf("HID device register app failed: %s\n", esp_err_to_name(ret));
return;
}
// 初始化 HID 设备
ret = esp_hidd_device_init();
if (ret != ESP_OK) {
printf("HID device init failed: %s\n", esp_err_to_name(ret));
return;
}
printf("Bluetooth HID Keyboard initialized.\n");
// 示例:发送 'A' 键的按下和释放
uint8_t modifier = 0x00; // 无修饰键
uint8_t keycode[6] = {0x04, 0, 0, 0, 0, 0}; // HID_KEY_A = 0x04
// 按下 'A'
if (send_key_report(modifier, keycode)) {
printf("Sent key press 'A'\n");
}
vTaskDelay(100 / portTICK_PERIOD_MS);
// 释放所有键
if (send_key_report(0, NULL)) {
printf("Sent key release\n");
}
}
// 发送键盘报告的函数
bool send_key_report(uint8_t modifier, uint8_t keycode[6]) {
typedef struct {
uint8_t modifier;
uint8_t reserved;
uint8_t keycode[6];
} __attribute__((packed)) hid_keyboard_report_t;
hid_keyboard_report_t report;
report.modifier = modifier;
report.reserved = 0;
if (keycode) {
memcpy(report.keycode, keycode, sizeof(report.keycode));
} else {
memset(report.keycode, 0, sizeof(report.keycode));
}
// 假设连接状态为已连接,报告类型为输入报告,报告ID为 0
return esp_hidd_device_send_report(ESP_HIDD_CONN_STATE_CONNECTED, ESP_HIDD_REPORT_TYPE_INPUT, 0, (uint8_t *)&report, sizeof(report)) == ESP_OK;
}
添加 HID 服务之前需要首先添加电池管理服务
创建服务函数
c
void hidd_le_create_service(esp_gatt_if_t gatts_if)
{
/* Here should added the battery service first, because the hid service should include the battery service.
After finish to added the battery service then can added the hid service. */
esp_ble_gatts_create_attr_tab(bas_att_db, gatts_if, BAS_IDX_NB, 0);
}