feat(keyboard): 添加完整的HID键盘功能模块

- 新增keyboard_module.c实现完整的键盘HID功能,包括按键映射、
  协议处理和报告生成
- 添加hid_protocol_event和hid_report_event事件系统支持
- 实现键盘和consumer类型的HID报告处理
- 支持Boot协议和Report协议两种模式
- 添加hid_keymap_def.h定义键盘映射表

refactor(ble_hid): 重构HIDS模块为BLE HID模块

- 将hids_module.c重命名为ble_hid_module.c
- 集成新的hid_protocol_event和hid_report_event事件处理
- 改进协议切换逻辑,添加协议事件发布功能
- 优化BLE HID报告发送机制

refactor(CMakeLists): 更新构建配置

- 添加新的事件模块源文件到构建列表
- 添加keyboard_module.c替换原有的button_map_module.c
- 添加ble_hid_module.c替换原有的hids_module.c
- 配置HID密钥映射定义路径编译选项

refactor(events): 简化USB HID事件结构

- 移除USB HID事件中的冗余状态字段
- 更新事件日志和分析器字段定义

docs(hid): 添加HID报告描述符文档

- 定义REPORT_ID_KEYBOARD和REPORT_ID_CONSUMER枚举值
- 整理HID报告相关的常量定义
This commit is contained in:
2026-03-14 18:00:14 +08:00
parent a3196ef162
commit cd8101428d
13 changed files with 833 additions and 148 deletions

View File

@@ -10,18 +10,25 @@ project(new_kbd)
zephyr_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/inc)
zephyr_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src/events)
zephyr_include_directories(${CMAKE_CURRENT_SOURCE_DIR}/configuration/atguigu_mini_keyboard_nrf52840)
target_compile_definitions(app PRIVATE
APP_HID_KEYMAP_DEF_PATH=\"hid_keymap_def.h\"
)
target_sources(app PRIVATE
src/main.c
src/events/battery_status_event.c
src/events/config_event.c
src/events/hid_protocol_event.c
src/events/hid_report_event.c
src/events/mode_event.c
src/events/usb_hid_event.c
src/modules/battery_module.c
src/modules/ble_adv_ctrl_module.c
src/modules/ble_bond_module.c
src/modules/button_map_module.c
src/modules/keyboard_module.c
src/modules/mode_switch_module.c
src/modules/usb_hid_module.c
src/modules/hids_module.c
src/modules/ble_hid_module.c
)

View File

@@ -0,0 +1,43 @@
/*
* HID keymap for current new_kbd numeric keypad layout.
* 说明:
* - 本文件仿照 nrf_desktop 的 hid_keymap_def.h 组织方式;
* - 仅由 keyboard_module.c 包含一次(通过 APP_HID_KEYMAP_DEF_PATH
* - 条目必须按 key_id 升序排列(按 KEY_ID(col, row) 计算后的数值顺序)。
*/
#include <caf/key_id.h>
/*
* 防止该定义文件被多处 include 导致重复符号。
* 约定仅由 keyboard_module.c 包含一次。
*/
const struct {} hid_keymap_def_include_once;
static const struct hid_keymap hid_keymap[] = {
/* col 0 */
{ KEY_ID(0, 1), 0x0053, REPORT_ID_KEYBOARD }, /* Num Lock */
{ KEY_ID(0, 2), 0x005F, REPORT_ID_KEYBOARD }, /* Keypad 7 */
{ KEY_ID(0, 3), 0x005C, REPORT_ID_KEYBOARD }, /* Keypad 4 */
{ KEY_ID(0, 4), 0x0059, REPORT_ID_KEYBOARD }, /* Keypad 1 */
{ KEY_ID(0, 5), 0x0062, REPORT_ID_KEYBOARD }, /* Keypad 0 */
/* col 1 */
{ KEY_ID(1, 1), 0x0054, REPORT_ID_KEYBOARD }, /* Keypad / */
{ KEY_ID(1, 2), 0x0060, REPORT_ID_KEYBOARD }, /* Keypad 8 */
{ KEY_ID(1, 3), 0x005D, REPORT_ID_KEYBOARD }, /* Keypad 5 */
{ KEY_ID(1, 4), 0x005A, REPORT_ID_KEYBOARD }, /* Keypad 2 */
{ KEY_ID(1, 5), 0x0063, REPORT_ID_KEYBOARD }, /* Keypad . */
/* col 2 */
{ KEY_ID(2, 1), 0x0055, REPORT_ID_KEYBOARD }, /* Keypad * */
{ KEY_ID(2, 2), 0x0061, REPORT_ID_KEYBOARD }, /* Keypad 9 */
{ KEY_ID(2, 3), 0x005E, REPORT_ID_KEYBOARD }, /* Keypad 6 */
{ KEY_ID(2, 4), 0x005B, REPORT_ID_KEYBOARD }, /* Keypad 3 */
/* col 3 */
{ KEY_ID(3, 0), 0x00E2, REPORT_ID_CONSUMER }, /* Mute */
{ KEY_ID(3, 1), 0x0056, REPORT_ID_KEYBOARD }, /* Keypad - */
{ KEY_ID(3, 3), 0x0057, REPORT_ID_KEYBOARD }, /* Keypad + */
{ KEY_ID(3, 5), 0x0058, REPORT_ID_KEYBOARD }, /* Keypad Enter */
};

View File

@@ -1,9 +1,14 @@
#ifndef HID_REPORT_DESCRIPTOR_H_
#define HID_REPORT_DESCRIPTOR_H_
#include "hid_types.h"
#include <zephyr/usb/class/usbd_hid.h>
/* 与 HID Report Map 对齐的 Report ID。 */
enum {
REPORT_ID_KEYBOARD = 1,
REPORT_ID_CONSUMER = 3,
};
/*
* HID_USAGE_PAGE() 只支持 1 字节 Usage Page。
* Vendor Defined Page(0xFF00) 需要 2 字节编码,因此在本地补一个 16 位版本,

View File

@@ -0,0 +1,42 @@
#include "hid_protocol_event.h"
static const char *const hid_transport_name[] = {
[HID_TRANSPORT_BLE] = "BLE",
[HID_TRANSPORT_USB] = "USB",
};
static const char *const hid_protocol_name[] = {
[HID_PROTO_BOOT] = "BOOT",
[HID_PROTO_REPORT] = "REPORT",
};
static void log_hid_protocol_event(const struct app_event_header *aeh)
{
const struct hid_protocol_event *event = cast_hid_protocol_event(aeh);
__ASSERT_NO_MSG(event->transport < ARRAY_SIZE(hid_transport_name));
__ASSERT_NO_MSG(event->protocol < ARRAY_SIZE(hid_protocol_name));
APP_EVENT_MANAGER_LOG(aeh, "transport=%s protocol=%s",
hid_transport_name[event->transport],
hid_protocol_name[event->protocol]);
}
static void profile_hid_protocol_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct hid_protocol_event *event = cast_hid_protocol_event(aeh);
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->transport);
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->protocol);
}
APP_EVENT_INFO_DEFINE(hid_protocol_event,
ENCODE(NRF_PROFILER_ARG_U8, NRF_PROFILER_ARG_U8),
ENCODE("transport", "protocol"),
profile_hid_protocol_event);
APP_EVENT_TYPE_DEFINE(hid_protocol_event,
log_hid_protocol_event,
&hid_protocol_event_info,
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,30 @@
#ifndef HID_PROTOCOL_EVENT_H__
#define HID_PROTOCOL_EVENT_H__
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
enum hid_transport_type {
HID_TRANSPORT_BLE = 0,
HID_TRANSPORT_USB,
};
enum hid_protocol_type {
HID_PROTO_BOOT = 0,
HID_PROTO_REPORT,
};
/*
* HID 传输层在收到主机 set_protocol 请求后上报该事件,
* keyboard_module 会依据当前 active 传输的协议格式打包 hid_report_event。
*/
struct hid_protocol_event {
struct app_event_header header;
enum hid_transport_type transport;
enum hid_protocol_type protocol;
};
APP_EVENT_TYPE_DECLARE(hid_protocol_event);
#endif /* HID_PROTOCOL_EVENT_H__ */

View File

@@ -0,0 +1,53 @@
#include "hid_report_event.h"
static const char *const hid_protocol_name[] = {
[HID_PROTO_BOOT] = "BOOT",
[HID_PROTO_REPORT] = "REPORT",
};
static void log_hid_report_event(const struct app_event_header *aeh)
{
const struct hid_report_event *event = cast_hid_report_event(aeh);
uint8_t report_id = 0x00;
uint16_t payload_len = event->dyndata.size;
__ASSERT_NO_MSG(event->protocol < ARRAY_SIZE(hid_protocol_name));
if (event->protocol == HID_PROTO_REPORT) {
__ASSERT_NO_MSG(event->dyndata.size >= 1U);
report_id = event->dyndata.data[0];
payload_len = event->dyndata.size - 1U;
}
APP_EVENT_MANAGER_LOG(aeh, "protocol=%s report_id=0x%02x payload_len=%u",
hid_protocol_name[event->protocol],
report_id,
payload_len);
}
static void profile_hid_report_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct hid_report_event *event = cast_hid_report_event(aeh);
uint8_t report_id = 0x00;
if ((event->protocol == HID_PROTO_REPORT) && (event->dyndata.size >= 1U)) {
report_id = event->dyndata.data[0];
}
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->protocol);
nrf_profiler_log_encode_uint8(buf, report_id);
nrf_profiler_log_encode_uint16(buf, event->dyndata.size);
}
APP_EVENT_INFO_DEFINE(hid_report_event,
ENCODE(NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U16),
ENCODE("protocol", "report_id", "len"),
profile_hid_report_event);
APP_EVENT_TYPE_DEFINE(hid_report_event,
log_hid_report_event,
&hid_report_event_info,
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));

View File

@@ -0,0 +1,24 @@
#ifndef HID_REPORT_EVENT_H__
#define HID_REPORT_EVENT_H__
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
#include "hid_protocol_event.h"
/*
* HID 输入报告统一事件:
* - protocol 指示当前 dyndata 编码规则;
* - 当 protocol=HID_PROTO_REPORTdyndata[0]=report_iddyndata[1..]=payload
* - 当 protocol=HID_PROTO_BOOTdyndata 仅包含 boot payload不含 report_id
*/
struct hid_report_event {
struct app_event_header header;
enum hid_protocol_type protocol;
struct event_dyndata dyndata;
};
APP_EVENT_TYPE_DYNDATA_DECLARE(hid_report_event);
#endif /* HID_REPORT_EVENT_H__ */

View File

@@ -10,27 +10,17 @@ static const char *const usb_hid_usbd_state_name[] = {
[USB_HID_USBD_SUSPENDED] = "SUSPENDED",
};
static const char *const usb_hid_stack_state_name[] = {
[USB_HID_STACK_OFF] = "OFF",
[USB_HID_STACK_READY] = "READY",
[USB_HID_STACK_ACTIVE] = "ACTIVE",
[USB_HID_STACK_SUSPENDED] = "SUSPENDED",
[USB_HID_STACK_ERROR] = "ERROR",
};
static void log_usb_hid_event(const struct app_event_header *aeh)
{
const struct usb_hid_event *event = cast_usb_hid_event(aeh);
__ASSERT_NO_MSG(event->evt_type < ARRAY_SIZE(usb_hid_evt_type_name));
__ASSERT_NO_MSG(event->usbd_state < ARRAY_SIZE(usb_hid_usbd_state_name));
__ASSERT_NO_MSG(event->hid_state < ARRAY_SIZE(usb_hid_stack_state_name));
APP_EVENT_MANAGER_LOG(aeh, "type=%s en=%u usbd=%s hid=%s",
APP_EVENT_MANAGER_LOG(aeh, "type=%s en=%u usbd=%s",
usb_hid_evt_type_name[event->evt_type],
event->enable,
usb_hid_usbd_state_name[event->usbd_state],
usb_hid_stack_state_name[event->hid_state]);
usb_hid_usbd_state_name[event->usbd_state]);
}
static void profile_usb_hid_event(struct log_event_buf *buf,
@@ -41,15 +31,13 @@ static void profile_usb_hid_event(struct log_event_buf *buf,
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->evt_type);
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->enable);
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->usbd_state);
nrf_profiler_log_encode_uint8(buf, (uint8_t)event->hid_state);
}
APP_EVENT_INFO_DEFINE(usb_hid_event,
ENCODE(NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8,
NRF_PROFILER_ARG_U8),
ENCODE("evt_type", "enable", "usbd", "hid"),
ENCODE("evt_type", "enable", "usbd"),
profile_usb_hid_event);
APP_EVENT_TYPE_DEFINE(usb_hid_event,

View File

@@ -18,22 +18,12 @@ enum usb_hid_usbd_state {
USB_HID_USBD_SUSPENDED,
};
/* HID 协议栈状态(偏“服务是否运行”) */
enum usb_hid_stack_state {
USB_HID_STACK_OFF = 0,
USB_HID_STACK_READY,
USB_HID_STACK_ACTIVE,
USB_HID_STACK_SUSPENDED,
USB_HID_STACK_ERROR,
};
struct usb_hid_event {
struct app_event_header header;
enum usb_hid_event_type evt_type;
bool enable;
enum usb_hid_usbd_state usbd_state;
enum usb_hid_stack_state hid_state;
};
APP_EVENT_TYPE_DECLARE(usb_hid_event);

View File

@@ -1,13 +1,16 @@
#include <bluetooth/services/hids.h>
#include <stdint.h>
#include <app_event_manager.h>
#define MODULE hids
#define MODULE ble_hid
#include <caf/events/module_state_event.h>
#include <caf/events/ble_common_event.h>
#include "hid_types.h"
#include "hid_protocol_event.h"
#include "hid_report_event.h"
#include "hid_report_descriptor.h"
#include "mode_event.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
@@ -17,13 +20,27 @@ LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
#define KEYBOARD_REPORT_LEN 30
#define CONSUMER_REPORT_LEN 2
#define KEYBOARD_LED_REPORT_LEN 1
#define BOOT_KEYBOARD_REPORT_LEN 8
/* 注册 HIDS 实例。此版本聚焦最小可用链路Boot + Report。 */
BT_HIDS_DEF(hids_obj, INPUT_REPORT_COUNT, OUTPUT_REPORT_COUNT, 0);
static struct bt_conn *active_conn;
static enum bt_hids_pm current_pm = BT_HIDS_PM_REPORT;
static bool ble_mode_selected;
static enum hid_protocol_type pm_to_protocol(enum bt_hids_pm pm)
{
return (pm == BT_HIDS_PM_BOOT) ? HID_PROTO_BOOT : HID_PROTO_REPORT;
}
static void publish_hid_protocol_event(enum hid_protocol_type protocol)
{
struct hid_protocol_event *event = new_hid_protocol_event();
event->transport = HID_TRANSPORT_BLE;
event->protocol = protocol;
APP_EVENT_SUBMIT(event);
}
static void pm_evt_handler(enum bt_hids_pm_evt evt, struct bt_conn *conn)
{
@@ -33,10 +50,16 @@ static void pm_evt_handler(enum bt_hids_pm_evt evt, struct bt_conn *conn)
case BT_HIDS_PM_EVT_BOOT_MODE_ENTERED:
current_pm = BT_HIDS_PM_BOOT;
LOG_INF("HIDS protocol: boot");
if (active_conn) {
publish_hid_protocol_event(HID_PROTO_BOOT);
}
break;
case BT_HIDS_PM_EVT_REPORT_MODE_ENTERED:
current_pm = BT_HIDS_PM_REPORT;
LOG_INF("HIDS protocol: report");
if (active_conn) {
publish_hid_protocol_event(HID_PROTO_REPORT);
}
break;
default:
break;
@@ -59,7 +82,6 @@ static void boot_keyboard_output_report_handler(struct bt_hids_rep *rep,
{
ARG_UNUSED(conn);
/* Basic boot protocol support: accept host LED writes and keep state locally. */
if (!write || !rep || (rep->size == 0) || !rep->data) {
return;
}
@@ -73,22 +95,11 @@ static void keyboard_output_report_handler(struct bt_hids_rep *rep,
{
ARG_UNUSED(conn);
/*
* Report LED NumLock
*
*/
if (!write || !rep || !rep->data || (rep->size < KEYBOARD_LED_REPORT_LEN)) {
return;
}
uint8_t leds = rep->data[0];
LOG_DBG("Report KB out report 0x%02x", leds);
/*
* LED CAF NumLock
*
*/
ARG_UNUSED(leds);
LOG_DBG("Report KB out report 0x%02x", rep->data[0]);
}
static int hids_service_init(void)
@@ -113,10 +124,6 @@ static int hids_service_init(void)
input_report[1].size = CONSUMER_REPORT_LEN;
input_report[1].handler = report_notify_handler;
/*
* Report
* Report Map REPORT_ID_KEYBOARD 1 LED Output
*/
output_report[0].id = REPORT_ID_KEYBOARD;
output_report[0].size = KEYBOARD_LED_REPORT_LEN;
output_report[0].handler = keyboard_output_report_handler;
@@ -140,6 +147,8 @@ static void handle_ble_peer_event(const struct ble_peer_event *event)
if (bt_hids_connected(&hids_obj, active_conn)) {
LOG_WRN("bt_hids_connected failed");
}
/* 连接建立后按当前协议主动同步一次,避免 keyboard_module 等待下一次 set_protocol。 */
publish_hid_protocol_event(pm_to_protocol(current_pm));
break;
case PEER_STATE_DISCONNECTED:
@@ -156,6 +165,80 @@ static void handle_ble_peer_event(const struct ble_peer_event *event)
}
}
static bool handle_hid_report_event(const struct hid_report_event *event)
{
if (!ble_mode_selected || !active_conn) {
return false;
}
uint8_t report_id;
const uint8_t *payload;
size_t payload_len;
/*
* keyboard_module protocol payload
* BLE
*/
if ((current_pm == BT_HIDS_PM_BOOT) && (event->protocol != HID_PROTO_BOOT)) {
return false;
}
if ((current_pm == BT_HIDS_PM_REPORT) && (event->protocol != HID_PROTO_REPORT)) {
return false;
}
if (event->protocol == HID_PROTO_BOOT) {
report_id = REPORT_ID_KEYBOARD;
payload = event->dyndata.data;
payload_len = event->dyndata.size;
} else {
if (event->dyndata.size < 1U) {
return false;
}
report_id = event->dyndata.data[0];
payload = &event->dyndata.data[1];
payload_len = event->dyndata.size - 1U;
}
int err = 0;
if (event->protocol == HID_PROTO_BOOT) {
if (report_id != REPORT_ID_KEYBOARD) {
return false;
}
if (payload_len != BOOT_KEYBOARD_REPORT_LEN) {
LOG_WRN("Invalid boot keyboard payload len=%u", payload_len);
return false;
}
err = bt_hids_boot_kb_inp_rep_send(&hids_obj, active_conn,
payload, payload_len, NULL);
} else {
uint8_t rep_index;
if (report_id == REPORT_ID_KEYBOARD) {
rep_index = 0U;
} else if (report_id == REPORT_ID_CONSUMER) {
rep_index = 1U;
} else {
return false;
}
if (payload_len > UINT8_MAX) {
LOG_WRN("Payload too large=%u", payload_len);
return false;
}
err = bt_hids_inp_rep_send(&hids_obj, active_conn, rep_index,
payload, (uint8_t)payload_len, NULL);
}
if (err) {
LOG_WRN("BLE HID send failed report=0x%02x err=%d", report_id, err);
}
return false;
}
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_module_state_event(aeh)) {
@@ -183,11 +266,22 @@ static bool app_event_handler(const struct app_event_header *aeh)
return false;
}
if (is_mode_event(aeh)) {
const struct mode_event *event = cast_mode_event(aeh);
ble_mode_selected = (event->mode_type == MODE_TYPE_BLE);
return false;
}
if (is_hid_report_event(aeh)) {
return handle_hid_report_event(cast_hid_report_event(aeh));
}
__ASSERT_NO_MSG(false);
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
/* Ensure GATT HIDS is registered before BLE is enabled by ble_state. */
APP_EVENT_SUBSCRIBE_EARLY(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, ble_peer_event);
APP_EVENT_SUBSCRIBE(MODULE, mode_event);
APP_EVENT_SUBSCRIBE(MODULE, hid_report_event);

View File

@@ -1,69 +0,0 @@
#include <app_event_manager.h>
#define MODULE button_map
#include <caf/events/module_state_event.h>
#include <caf/events/button_event.h>
#include <caf/key_id.h>
#include <zephyr/dt-bindings/input/input-event-codes.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE);
/*
* keymap[row][col] 直接对齐板级 DTS 里的 MATRIX_KEY(row, col, key) 定义。
* 值为 Linux input key code-1 表示该矩阵位置未映射功能键。
*/
static const int16_t keymap[6][4] = {
/* row 0 */ { -1, -1, -1, INPUT_KEY_MUTE },
/* row 1 */ { INPUT_KEY_NUMLOCK, INPUT_KEY_KPSLASH, INPUT_KEY_KPASTERISK, INPUT_KEY_KPMINUS },
/* row 2 */ { INPUT_KEY_KP7, INPUT_KEY_KP8, INPUT_KEY_KP9, -1 },
/* row 3 */ { INPUT_KEY_KP4, INPUT_KEY_KP5, INPUT_KEY_KP6, INPUT_KEY_KPPLUS },
/* row 4 */ { INPUT_KEY_KP1, INPUT_KEY_KP2, INPUT_KEY_KP3, -1 },
/* row 5 */ { INPUT_KEY_KP0, INPUT_KEY_KPDOT, -1, INPUT_KEY_KPENTER },
};
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_button_event(aeh)) {
const struct button_event *event = cast_button_event(aeh);
uint8_t row = KEY_ROW(event->key_id);
uint8_t col = KEY_COL(event->key_id);
/*
* 防御性检查:若行列越界,说明 buttons_def 与实际扫描结果不一致,
* 记录错误以便尽快发现硬件矩阵定义或固件配置问题。
*/
if ((row >= ARRAY_SIZE(keymap)) || (col >= ARRAY_SIZE(keymap[0]))) {
LOG_ERR("Unknown key_id=0x%04x (row=%u, col=%u)", event->key_id, row, col);
return false;
}
int16_t code = keymap[row][col];
if (code < 0) {
LOG_INF("Button %s row=%u col=%u unmapped", event->pressed ? "down" : "up", row, col);
} else {
LOG_INF("Button %s row=%u col=%u keycode=%d", event->pressed ? "down" : "up", row, col, code);
}
return false;
}
if (is_module_state_event(aeh)) {
const struct module_state_event *event = cast_module_state_event(aeh);
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
module_set_state(MODULE_STATE_READY);
}
return false;
}
/* 未处理但已订阅的事件类型不应进入这里。 */
__ASSERT_NO_MSG(false);
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, button_event);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);

View File

@@ -0,0 +1,368 @@
#include <string.h>
#include <stdlib.h>
#include <app_event_manager.h>
#define MODULE keyboard
#include <caf/events/module_state_event.h>
#include <caf/events/button_event.h>
#include <caf/key_id.h>
#include "hid_report_descriptor.h"
#include "hid_protocol_event.h"
#include "hid_report_event.h"
#include "mode_event.h"
#include <zephyr/sys/util.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
/*
* 参考 nrf_desktop 的表驱动设计:
* - key_id -> (usage_id, report_id) 映射定义在外部 hid_keymap_def.h
* - keyboard_module 内部完成映射表校验与查询,不再依赖独立 hid_keymap 模块。
*/
struct hid_keymap {
uint16_t key_id;
uint16_t usage_id;
uint8_t report_id;
};
#include APP_HID_KEYMAP_DEF_PATH
static bool hid_keymap_initialized;
/* 比较函数:供 bsearch 按 key_id 升序查找映射项。 */
static int hid_keymap_compare(const void *a, const void *b)
{
const struct hid_keymap *pa = a;
const struct hid_keymap *pb = b;
return ((int)pa->key_id - (int)pb->key_id);
}
/*
* 初始化并校验 hid_keymap
* - 仅在 CONFIG_ASSERT 打开时执行校验,避免 release 构建引入额外开销;
* - 校验 key_id 严格升序,确保二分查找行为正确;
* - 校验 report_id 只落在当前模块支持的 Keyboard/Consumer 两类。
*/
static void hid_keymap_init_local(void)
{
if (!IS_ENABLED(CONFIG_ASSERT) || hid_keymap_initialized) {
return;
}
for (size_t i = 0; i < ARRAY_SIZE(hid_keymap); i++) {
if (i > 0U) {
__ASSERT(hid_keymap[i - 1].key_id < hid_keymap[i].key_id,
"hid_keymap must be sorted by key_id");
}
__ASSERT((hid_keymap[i].report_id == REPORT_ID_KEYBOARD) ||
(hid_keymap[i].report_id == REPORT_ID_CONSUMER),
"hid_keymap uses unsupported report_id");
}
hid_keymap_initialized = true;
}
/* 查询指定 key_id 的 HID 映射,查不到返回 NULL。 */
static const struct hid_keymap *hid_keymap_get_local(uint16_t key_id)
{
if (ARRAY_SIZE(hid_keymap) == 0U) {
return NULL;
}
struct hid_keymap key = {
.key_id = key_id,
.usage_id = 0U,
.report_id = 0U,
};
return bsearch(&key,
hid_keymap,
ARRAY_SIZE(hid_keymap),
sizeof(hid_keymap[0]),
hid_keymap_compare);
}
/* Report 协议键盘 payload: modifier(1) + usage bitset(0..0xE7 => 29B)。 */
#define KEYBOARD_USAGE_MAX 0x00E7
#define KEYBOARD_BITMAP_SIZE DIV_ROUND_UP(KEYBOARD_USAGE_MAX + 1, 8)
#define KEYBOARD_REPORT_PAYLOAD (1 + KEYBOARD_BITMAP_SIZE)
/* Boot 协议键盘 payload: modifier(1) + reserved(1) + 6 keys。 */
#define BOOT_KEYBOARD_PAYLOAD 8
/* Consumer payload 固定 16-bit usage。 */
#define CONSUMER_PAYLOAD 2
struct keyboard_state {
uint8_t modifier_bm;
uint8_t usage_bm[KEYBOARD_BITMAP_SIZE];
enum hid_protocol_type ble_protocol;
enum hid_protocol_type usb_protocol;
mode_type_t current_mode;
uint16_t consumer_usage;
};
static struct keyboard_state ks = {
.ble_protocol = HID_PROTO_REPORT,
.usb_protocol = HID_PROTO_REPORT,
.current_mode = MODE_TYPE_COUNT,
.consumer_usage = 0,
};
/* 依据当前 mode 选择生效的 HID 协议来源BLE 或 USB。 */
static enum hid_protocol_type active_protocol_get(void)
{
return (ks.current_mode == MODE_TYPE_USB) ? ks.usb_protocol : ks.ble_protocol;
}
/* 查询某 usage 位在当前键盘位图里是否处于按下状态。 */
static bool usage_pressed(uint16_t usage)
{
if (usage > KEYBOARD_USAGE_MAX) {
return false;
}
return (ks.usage_bm[usage / 8] & BIT(usage % 8)) != 0U;
}
/*
* 更新键盘 usage 位图与 modifier 状态。
* 返回 true 表示状态有变化,需要向传输层同步新报告。
*/
static bool keyboard_usage_update(uint16_t usage_id, bool pressed)
{
if (usage_id > KEYBOARD_USAGE_MAX) {
LOG_WRN("Unsupported usage_id=0x%04x", usage_id);
return false;
}
uint8_t idx = usage_id / 8;
uint8_t mask = BIT(usage_id % 8);
bool changed = false;
if (pressed) {
if ((ks.usage_bm[idx] & mask) == 0U) {
ks.usage_bm[idx] |= mask;
changed = true;
}
} else {
if ((ks.usage_bm[idx] & mask) != 0U) {
ks.usage_bm[idx] &= (uint8_t)~mask;
changed = true;
}
}
/* modifier(E0~E7) 额外维护一份 bitmask便于 Boot/Report 复用。 */
if ((usage_id >= 0x00E0) && (usage_id <= 0x00E7)) {
uint8_t mod_mask = BIT(usage_id - 0x00E0);
if (pressed) {
ks.modifier_bm |= mod_mask;
} else {
ks.modifier_bm &= (uint8_t)~mod_mask;
}
}
return changed;
}
/*
* 提交 HID 报告事件:
* - Report 协议编码为 [report_id | payload]
* - Boot 协议编码为 [payload](不含 report_id
*/
static void submit_hid_report(enum hid_protocol_type protocol,
uint8_t report_id,
const uint8_t *payload,
size_t payload_len)
{
size_t report_len = (protocol == HID_PROTO_REPORT) ? (payload_len + 1U) : payload_len;
struct hid_report_event *event = new_hid_report_event(report_len);
event->protocol = protocol;
if (protocol == HID_PROTO_REPORT) {
event->dyndata.data[0] = report_id;
memcpy(&event->dyndata.data[1], payload, payload_len);
} else {
memcpy(event->dyndata.data, payload, payload_len);
}
APP_EVENT_SUBMIT(event);
}
/*
* 组包并提交键盘报告:
* - Report 协议发送 NKRO payload
* - Boot 协议降级为 6KRO 固定 8 字节格式。
*/
static void submit_keyboard_report_payload(enum hid_protocol_type protocol)
{
if (protocol == HID_PROTO_REPORT) {
uint8_t payload[KEYBOARD_REPORT_PAYLOAD];
payload[0] = ks.modifier_bm;
memcpy(&payload[1], ks.usage_bm, sizeof(ks.usage_bm));
submit_hid_report(HID_PROTO_REPORT, REPORT_ID_KEYBOARD,
payload, sizeof(payload));
return;
}
/*
* Boot 协议只支持 6KRO。
* 从 usage 位图中按升序提取最多 6 个普通键modifier 走独立字节。
*/
uint8_t payload[BOOT_KEYBOARD_PAYLOAD] = { 0 };
size_t key_pos = 2;
payload[0] = ks.modifier_bm;
for (uint16_t usage = 0x04; usage <= 0x65; usage++) {
if (!usage_pressed(usage)) {
continue;
}
payload[key_pos++] = (uint8_t)usage;
if (key_pos >= ARRAY_SIZE(payload)) {
break;
}
}
submit_hid_report(HID_PROTO_BOOT, REPORT_ID_KEYBOARD, payload, sizeof(payload));
}
/* 组包并提交 consumer 报告16-bit usage。 */
static void submit_consumer_report_payload(void)
{
uint8_t payload[CONSUMER_PAYLOAD];
payload[0] = ks.consumer_usage & 0xFF;
payload[1] = (ks.consumer_usage >> 8) & 0xFF;
submit_hid_report(HID_PROTO_REPORT, REPORT_ID_CONSUMER, payload, sizeof(payload));
}
/*
* 处理键盘类 usage
* - 仅在按键状态实际变化时提交报告,避免无效重复上报。
*/
static bool handle_keyboard_usage_event(const struct hid_keymap *map, bool pressed)
{
if (!keyboard_usage_update(map->usage_id, pressed))
return false;
submit_keyboard_report_payload(active_protocol_get());
return false;
}
/*
* 处理 consumer 类 usage
* - Boot 协议不发送 consumer 报告;
* - 按下时上报 usage抬起时上报 0 清状态。
*/
static bool handle_consumer_usage_event(const struct hid_keymap *map, bool pressed)
{
if (active_protocol_get() == HID_PROTO_BOOT)
return false;
if (pressed) {
if (ks.consumer_usage == map->usage_id)
return false;
ks.consumer_usage = map->usage_id;
submit_consumer_report_payload();
return false;
}
if (ks.consumer_usage != map->usage_id)
return false;
ks.consumer_usage = 0U;
submit_consumer_report_payload();
return false;
}
/*
* 处理 button_event
* - 先查 key_id 映射;
* - 再按 report_id 分派到键盘/consumer 分支。
*/
static bool handle_button_event(const struct button_event *event)
{
const struct hid_keymap *map = hid_keymap_get_local(event->key_id);
if (!map) {
return false;
}
if (map->report_id == REPORT_ID_KEYBOARD)
return handle_keyboard_usage_event(map, event->pressed);
if (map->report_id == REPORT_ID_CONSUMER)
return handle_consumer_usage_event(map, event->pressed);
LOG_WRN("Unsupported report_id=%u key_id=0x%04x", map->report_id, event->key_id);
return false;
}
/* 同步 BLE/USB 传输层上报的当前协议。 */
static bool handle_hid_protocol_event(const struct hid_protocol_event *event)
{
if (event->transport == HID_TRANSPORT_BLE) {
ks.ble_protocol = event->protocol;
} else if (event->transport == HID_TRANSPORT_USB) {
ks.usb_protocol = event->protocol;
} else {
__ASSERT_NO_MSG(false);
}
return false;
}
/* 更新当前激活模式,决定协议来源取 BLE 还是 USB。 */
static bool handle_mode_event(const struct mode_event *event)
{
ks.current_mode = event->mode_type;
return false;
}
/* 模块总事件分发入口。 */
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_button_event(aeh)) {
return handle_button_event(cast_button_event(aeh));
}
if (is_hid_protocol_event(aeh)) {
return handle_hid_protocol_event(cast_hid_protocol_event(aeh));
}
if (is_mode_event(aeh)) {
return handle_mode_event(cast_mode_event(aeh));
}
if (is_module_state_event(aeh)) {
const struct module_state_event *event = cast_module_state_event(aeh);
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
/* 主模块 ready 后做一次 keymap 结构校验。 */
hid_keymap_init_local();
module_set_state(MODULE_STATE_READY);
}
return false;
}
__ASSERT_NO_MSG(false);
return false;
}
APP_EVENT_LISTENER(MODULE, app_event_handler);
APP_EVENT_SUBSCRIBE(MODULE, button_event);
APP_EVENT_SUBSCRIBE(MODULE, hid_protocol_event);
APP_EVENT_SUBSCRIBE(MODULE, mode_event);
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);

View File

@@ -13,6 +13,8 @@
#include <caf/events/module_state_event.h>
#include "hid_report_descriptor.h"
#include "hid_protocol_event.h"
#include "hid_report_event.h"
#include "mode_event.h"
#include "usb_hid_event.h"
@@ -48,14 +50,16 @@ struct usb_hid_ctx {
bool boot_iface_ready;
bool nkro_iface_ready;
bool raw_iface_ready;
bool boot_in_flight;
bool nkro_in_flight;
enum usb_hid_usbd_state usbd_state;
enum usb_hid_stack_state hid_state;
enum hid_protocol_type current_protocol;
};
static struct usb_hid_ctx g_usb_hid = {
.usbd_state = USB_HID_USBD_DISCONNECTED,
.hid_state = USB_HID_STACK_OFF,
.current_protocol = HID_PROTO_REPORT,
};
USBD_DEVICE_DEFINE(new_kbd_usbd,
@@ -78,37 +82,13 @@ static void publish_usb_hid_state(void)
event->evt_type = USB_HID_EVT_STATE_REPORT;
event->enable = g_usb_hid.stack_enabled;
event->usbd_state = g_usb_hid.usbd_state;
event->hid_state = g_usb_hid.hid_state;
APP_EVENT_SUBMIT(event);
}
static void recompute_hid_state(void)
{
enum usb_hid_stack_state new_hid_state;
if (g_usb_hid.stack_error) {
new_hid_state = USB_HID_STACK_ERROR;
} else if (g_usb_hid.pm_suspended && g_usb_hid.stack_enabled) {
new_hid_state = USB_HID_STACK_SUSPENDED;
} else if (!g_usb_hid.stack_initialized) {
new_hid_state = USB_HID_STACK_OFF;
} else if (g_usb_hid.stack_enabled &&
(g_usb_hid.boot_iface_ready ||
g_usb_hid.nkro_iface_ready ||
g_usb_hid.raw_iface_ready)) {
new_hid_state = USB_HID_STACK_ACTIVE;
} else {
/*
* 栈已初始化但未进入 ACTIVE例如刚 enable 还未配置、或 mode 切走后 disable
* 用 READY 表示“协议栈可用但当前未承载有效 HID 会话”。
*/
new_hid_state = USB_HID_STACK_READY;
}
if (g_usb_hid.hid_state != new_hid_state) {
g_usb_hid.hid_state = new_hid_state;
/* 兼容现有调用点:对外仅发布 enable + usbd 状态。 */
publish_usb_hid_state();
}
}
static void set_usbd_state(enum usb_hid_usbd_state state)
@@ -160,13 +140,49 @@ static uint32_t hid_stub_get_idle(const struct device *dev, uint8_t id)
static void hid_stub_set_protocol(const struct device *dev, uint8_t proto)
{
ARG_UNUSED(dev);
ARG_UNUSED(proto);
enum hid_protocol_type new_protocol =
(proto == HID_PROTOCOL_BOOT) ? HID_PROTO_BOOT : HID_PROTO_REPORT;
if (g_usb_hid.current_protocol == new_protocol) {
return;
}
g_usb_hid.current_protocol = new_protocol;
/*
* 按需求USB HID 在连接后收到 set_protocol 时上报 hid_protocol_event。
* 这里额外检查接口 ready避免在未枚举完成阶段上报无意义协议切换。
*/
if (g_usb_hid.boot_iface_ready || g_usb_hid.nkro_iface_ready) {
struct hid_protocol_event *event = new_hid_protocol_event();
event->transport = HID_TRANSPORT_USB;
event->protocol = new_protocol;
APP_EVENT_SUBMIT(event);
}
}
static void hid_stub_input_done(const struct device *dev, const uint8_t *report)
{
ARG_UNUSED(dev);
ARG_UNUSED(report);
/*
* 发送完成回调:
* - 仅在这里清除“在途发送”标志,确保“上一包未完成则丢弃新包”的策略可闭环;
* - 若收到未知 dev 的回调,仅记录告警,避免静默状态错乱。
*/
if (dev == g_usb_hid.boot_dev) {
g_usb_hid.boot_in_flight = false;
return;
}
if (dev == g_usb_hid.nkro_dev) {
g_usb_hid.nkro_in_flight = false;
return;
}
LOG_WRN("input_done from unknown HID dev: %p", (void *)dev);
}
static void hid_stub_output_report(const struct device *dev, uint16_t len, const uint8_t *buf)
@@ -180,14 +196,26 @@ static void hid_iface_ready_cb(const struct device *dev, bool ready)
{
if (dev == g_usb_hid.boot_dev) {
g_usb_hid.boot_iface_ready = ready;
if (!ready) {
g_usb_hid.boot_in_flight = false;
}
} else if (dev == g_usb_hid.nkro_dev) {
g_usb_hid.nkro_iface_ready = ready;
if (!ready) {
g_usb_hid.nkro_in_flight = false;
}
} else if (dev == g_usb_hid.raw_dev) {
g_usb_hid.raw_iface_ready = ready;
}
if (ready) {
set_usbd_state(USB_HID_USBD_CONNECTED);
/* 连接可用后同步一次当前协议,让 keyboard_module 与传输侧编码一致。 */
struct hid_protocol_event *event = new_hid_protocol_event();
event->transport = HID_TRANSPORT_USB;
event->protocol = g_usb_hid.current_protocol;
APP_EVENT_SUBMIT(event);
}
recompute_hid_state();
@@ -210,6 +238,7 @@ static const struct hid_device_ops report_hid_ops = {
.set_report = hid_stub_set_report,
.set_idle = hid_stub_set_idle,
.get_idle = hid_stub_get_idle,
.set_protocol = hid_stub_set_protocol,
.input_report_done = hid_stub_input_done,
.output_report = hid_stub_output_report,
};
@@ -435,6 +464,8 @@ static int usb_hid_set_enabled(bool enable)
g_usb_hid.boot_iface_ready = false;
g_usb_hid.nkro_iface_ready = false;
g_usb_hid.raw_iface_ready = false;
g_usb_hid.boot_in_flight = false;
g_usb_hid.nkro_in_flight = false;
set_usbd_state(USB_HID_USBD_DISCONNECTED);
}
@@ -517,6 +548,80 @@ static bool handle_wake_up_event(void)
return false;
}
static bool handle_hid_report_event(const struct hid_report_event *event)
{
/*
* USB 侧仅在 active 条件满足时发送:
* - 当前 mode 为 USB
* - USB HID 栈已启用且对应接口 ready。
*/
if (!g_usb_hid.usb_mode_selected || !g_usb_hid.stack_enabled) {
return false;
}
uint8_t report_id;
if (event->protocol != g_usb_hid.current_protocol) {
return false;
}
if (event->protocol == HID_PROTO_BOOT) {
const uint8_t *payload = event->dyndata.data;
size_t payload_len = event->dyndata.size;
report_id = REPORT_ID_KEYBOARD;
if (!g_usb_hid.boot_iface_ready || !g_usb_hid.boot_dev) {
return false;
}
if (report_id != REPORT_ID_KEYBOARD) {
return false;
}
if (g_usb_hid.boot_in_flight) {
LOG_WRN("Drop boot report: previous report not sent");
return false;
}
int err = hid_device_submit_report(g_usb_hid.boot_dev,
payload_len,
payload);
if (err) {
LOG_WRN("USB boot report send failed err=%d", err);
} else {
g_usb_hid.boot_in_flight = true;
}
return false;
}
if (event->dyndata.size < 1U) {
return false;
}
report_id = event->dyndata.data[0];
if (!g_usb_hid.nkro_iface_ready || !g_usb_hid.nkro_dev) {
return false;
}
if ((report_id != REPORT_ID_KEYBOARD) && (report_id != REPORT_ID_CONSUMER)) {
return false;
}
if (g_usb_hid.nkro_in_flight) {
LOG_WRN("Drop report id=0x%02x: previous report not sent", report_id);
return false;
}
/* Report 协议下 dyndata 是 [report_id|payload],可直接透传。 */
int err = hid_device_submit_report(g_usb_hid.nkro_dev, event->dyndata.size, event->dyndata.data);
if (err) {
LOG_WRN("USB report send failed id=0x%02x err=%d", report_id, err);
} else {
g_usb_hid.nkro_in_flight = true;
}
return false;
}
static bool app_event_handler(const struct app_event_header *aeh)
{
if (is_module_state_event(aeh)) {
@@ -535,6 +640,10 @@ static bool app_event_handler(const struct app_event_header *aeh)
return handle_wake_up_event();
}
if (is_hid_report_event(aeh)) {
return handle_hid_report_event(cast_hid_report_event(aeh));
}
__ASSERT_NO_MSG(false);
return false;
}
@@ -544,3 +653,4 @@ APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
APP_EVENT_SUBSCRIBE(MODULE, mode_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);
APP_EVENT_SUBSCRIBE_EARLY(MODULE, hid_report_event);