feat(hids): 添加HID服务模块支持键盘和多媒体功能
- 新增hids_module.c实现蓝牙HID服务,支持键盘NKRO和Consumer控制 - 添加hid_report_descriptor.h定义统一的HID描述符,包括键盘、多媒体和RAW HID - 在CMakeLists.txt中注册hids模块源文件 - 配置prj.conf启用蓝牙HID相关配置项,设置设备名称和外观 - 修改main.c移除启动LED效果,简化主函数逻辑 - 添加settings_loader_def.h确保模块依赖正确加载 - 配置pm_static.yml分配flash存储空间给mcuboot和settings - 调整电源管理超时时间从20秒增加到300秒 - 启用MCUBOOT引导加载器支持
This commit is contained in:
@@ -18,4 +18,5 @@ target_sources(app PRIVATE
|
||||
src/modules/battery_module.c
|
||||
src/modules/button_map_module.c
|
||||
src/modules/mode_switch_module.c
|
||||
src/modules/hids_module.c
|
||||
)
|
||||
|
||||
104
inc/hid_report_descriptor.h
Normal file
104
inc/hid_report_descriptor.h
Normal file
@@ -0,0 +1,104 @@
|
||||
#ifndef HID_REPORT_DESCRIPTOR_H_
|
||||
#define HID_REPORT_DESCRIPTOR_H_
|
||||
|
||||
#include "hid_types.h"
|
||||
#include <zephyr/usb/class/usbd_hid.h>
|
||||
|
||||
/*
|
||||
* HID_USAGE_PAGE() 只支持 1 字节 Usage Page。
|
||||
* Vendor Defined Page(0xFF00) 需要 2 字节编码,因此在本地补一个 16 位版本,
|
||||
* 避免在描述符里混用裸字节,后续维护时可以一眼看出字段语义。
|
||||
*/
|
||||
#define HID_USAGE_PAGE16(page_lsb, page_msb) \
|
||||
HID_ITEM(HID_ITEM_TAG_USAGE_PAGE, HID_ITEM_TYPE_GLOBAL, 2), page_lsb, page_msb
|
||||
|
||||
/*
|
||||
* 键盘(NKRO) + Consumer 的复合 Report 描述符:
|
||||
* - USB Report 接口和 BLE HIDS Report Map 统一使用这份定义,
|
||||
* 避免两边手写常量后长期演进出现不一致。
|
||||
*/
|
||||
#define HID_DESC_KEYBOARD_NKRO_CONSUMER() \
|
||||
{ \
|
||||
/* Generic Desktop 页:声明这是一个 Keyboard Application 集合。 */ \
|
||||
HID_USAGE_PAGE(HID_USAGE_GEN_DESKTOP), \
|
||||
HID_USAGE(HID_USAGE_GEN_DESKTOP_KEYBOARD), \
|
||||
HID_COLLECTION(HID_COLLECTION_APPLICATION), \
|
||||
HID_REPORT_ID(REPORT_ID_KEYBOARD), \
|
||||
\
|
||||
/* Keyboard/Keypad 页:先定义 8bit Modifier,再定义 232bit NKRO 位图。 */ \
|
||||
HID_USAGE_PAGE(HID_USAGE_GEN_DESKTOP_KEYPAD), \
|
||||
HID_USAGE_MIN8(0xE0), \
|
||||
HID_USAGE_MAX8(0xE7), \
|
||||
HID_LOGICAL_MIN8(0), \
|
||||
HID_LOGICAL_MAX8(1), \
|
||||
HID_REPORT_SIZE(1), \
|
||||
HID_REPORT_COUNT(8), \
|
||||
HID_INPUT(0x02), \
|
||||
HID_USAGE_MIN8(0x00), \
|
||||
HID_USAGE_MAX8(0xE7), \
|
||||
HID_LOGICAL_MIN8(0), \
|
||||
HID_LOGICAL_MAX8(1), \
|
||||
HID_REPORT_SIZE(1), \
|
||||
HID_REPORT_COUNT(0xE7 + 1), \
|
||||
HID_INPUT(0x02), \
|
||||
\
|
||||
/* Report 协议下键盘 LED 输出(NumLock/CapsLock/ScrollLock/Compose/Kana)。 */ \
|
||||
HID_USAGE_PAGE(0x08U), \
|
||||
HID_USAGE_MIN8(0x01), \
|
||||
HID_USAGE_MAX8(0x05), \
|
||||
HID_LOGICAL_MIN8(0), \
|
||||
HID_LOGICAL_MAX8(1), \
|
||||
HID_REPORT_SIZE(1), \
|
||||
HID_REPORT_COUNT(5), \
|
||||
HID_OUTPUT(0x02), \
|
||||
/* 补齐到 1 字节:3bit padding,标记为常量。 */ \
|
||||
HID_REPORT_SIZE(3), \
|
||||
HID_REPORT_COUNT(1), \
|
||||
HID_OUTPUT(0x01), \
|
||||
HID_END_COLLECTION, \
|
||||
\
|
||||
/* Consumer 页:使用 16bit Usage 承载多媒体按键(音量/播放/亮度等)。 */ \
|
||||
HID_USAGE_PAGE(0x0CU), \
|
||||
HID_USAGE(0x01U), \
|
||||
HID_COLLECTION(HID_COLLECTION_APPLICATION), \
|
||||
HID_REPORT_ID(REPORT_ID_CONSUMER), \
|
||||
HID_LOGICAL_MIN8(0), \
|
||||
HID_LOGICAL_MAX16(0xEA, 0x00), \
|
||||
HID_USAGE_MIN16(0x00, 0x00), \
|
||||
HID_USAGE_MAX16(0xEA, 0x00), \
|
||||
HID_REPORT_SIZE(16), \
|
||||
HID_REPORT_COUNT(1), \
|
||||
HID_INPUT(0x00), \
|
||||
HID_END_COLLECTION, \
|
||||
}
|
||||
|
||||
/*
|
||||
* RAW HID 的固定 64 字节输入/输出描述符。
|
||||
* 设计意图:
|
||||
* - 采用 Vendor Defined(0xFF00) 页,避免与标准键盘/多媒体语义冲突;
|
||||
* - IN/OUT 都固定 64 字节,便于固件与上位机用定长帧做双向透传;
|
||||
* - 不使用 Report ID,接口只承载一个 RAW Report,减少主机端解析分支。
|
||||
*/
|
||||
#define HID_DESC_RAW_64() \
|
||||
{ \
|
||||
/* Vendor Defined 页(0xFF00):供厂商私有协议传输,不绑定标准 HID 语义。 */ \
|
||||
HID_USAGE_PAGE16(0x00, 0xFF), \
|
||||
HID_USAGE(0x01), \
|
||||
HID_COLLECTION(HID_COLLECTION_APPLICATION), \
|
||||
HID_LOGICAL_MIN8(0), \
|
||||
HID_LOGICAL_MAX16(0xFF, 0x00), \
|
||||
HID_REPORT_SIZE(8), \
|
||||
\
|
||||
/* 输入页:定义 64 字节 Input 报文,Data|Var|Abs(0x02) 与原描述符一致。 */ \
|
||||
HID_REPORT_COUNT(0x40), \
|
||||
HID_USAGE(0x01), \
|
||||
HID_INPUT(0x02), \
|
||||
\
|
||||
/* 输出页:定义 64 字节 Output 报文,与输入长度对齐,简化双向协议。 */ \
|
||||
HID_REPORT_COUNT(0x40), \
|
||||
HID_USAGE(0x01), \
|
||||
HID_OUTPUT(0x02), \
|
||||
HID_END_COLLECTION, \
|
||||
}
|
||||
|
||||
#endif
|
||||
17
inc/settings_loader_def.h
Normal file
17
inc/settings_loader_def.h
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Defines modules that must reach READY before CAF settings_loader
|
||||
* calls settings_load().
|
||||
*/
|
||||
|
||||
/* Enforce single inclusion in the final link unit. */
|
||||
const struct {} settings_loader_def_include_once;
|
||||
|
||||
#include <caf/events/module_state_event.h>
|
||||
|
||||
static inline void get_req_modules(struct module_flags *mf)
|
||||
{
|
||||
module_flags_set_bit(mf, MODULE_IDX(main));
|
||||
#ifdef CONFIG_CAF_BLE_STATE
|
||||
module_flags_set_bit(mf, MODULE_IDX(ble_state));
|
||||
#endif
|
||||
}
|
||||
39
pm_static.yml
Normal file
39
pm_static.yml
Normal file
@@ -0,0 +1,39 @@
|
||||
mcuboot:
|
||||
address: 0x0
|
||||
end_address: 0xc000
|
||||
region: flash_primary
|
||||
size: 0xc000
|
||||
|
||||
mcuboot_pad:
|
||||
address: 0xc000
|
||||
end_address: 0xc200
|
||||
region: flash_primary
|
||||
size: 0x200
|
||||
|
||||
app:
|
||||
address: 0xc200
|
||||
end_address: 0x82000
|
||||
region: flash_primary
|
||||
size: 0x75e00
|
||||
|
||||
mcuboot_primary:
|
||||
address: 0xc000
|
||||
end_address: 0x82000
|
||||
orig_span: &id001
|
||||
- mcuboot_pad
|
||||
- app
|
||||
region: flash_primary
|
||||
size: 0x76000
|
||||
span: *id001
|
||||
|
||||
mcuboot_secondary:
|
||||
address: 0x82000
|
||||
end_address: 0xf8000
|
||||
region: flash_primary
|
||||
size: 0x76000
|
||||
|
||||
settings_storage:
|
||||
address: 0xf8000
|
||||
end_address: 0x100000
|
||||
region: flash_primary
|
||||
size: 0x8000
|
||||
36
prj.conf
36
prj.conf
@@ -1,6 +1,38 @@
|
||||
CONFIG_CAF=y
|
||||
CONFIG_HEAP_MEM_POOL_SIZE=2048
|
||||
CONFIG_LOG=y
|
||||
CONFIG_BOOTLOADER_MCUBOOT=y
|
||||
|
||||
CONFIG_BT=y
|
||||
CONFIG_BT_PERIPHERAL=y
|
||||
CONFIG_BT_SMP=y
|
||||
CONFIG_BT_DEVICE_NAME="new_kbd"
|
||||
CONFIG_BT_DEVICE_APPEARANCE=961
|
||||
CONFIG_BT_MAX_CONN=1
|
||||
CONFIG_SETTINGS=y
|
||||
CONFIG_SETTINGS_NVS=y
|
||||
CONFIG_NVS=y
|
||||
CONFIG_FLASH=y
|
||||
CONFIG_FLASH_MAP=y
|
||||
CONFIG_BT_SETTINGS=y
|
||||
|
||||
CONFIG_CAF_BLE_STATE=y
|
||||
CONFIG_CAF_BLE_ADV=y
|
||||
CONFIG_CAF_SETTINGS_LOADER=y
|
||||
CONFIG_BT_ADV_PROV_FLAGS=y
|
||||
CONFIG_BT_ADV_PROV_GAP_APPEARANCE=y
|
||||
CONFIG_BT_ADV_PROV_DEVICE_NAME=y
|
||||
CONFIG_BT_ADV_PROV_SWIFT_PAIR=y
|
||||
|
||||
CONFIG_BT_HIDS=y
|
||||
CONFIG_BT_CONN_CTX=y
|
||||
CONFIG_BT_GATT_POOL=y
|
||||
CONFIG_BT_GATT_CHRC_POOL_SIZE=16
|
||||
CONFIG_BT_GATT_UUID16_POOL_SIZE=24
|
||||
CONFIG_BT_HIDS_ATTR_MAX=32
|
||||
CONFIG_BT_HIDS_INPUT_REP_MAX=2
|
||||
CONFIG_BT_HIDS_OUTPUT_REP_MAX=1
|
||||
CONFIG_BT_HIDS_FEATURE_REP_MAX=0
|
||||
|
||||
CONFIG_LED=y
|
||||
CONFIG_LED_GPIO=y
|
||||
@@ -9,7 +41,7 @@ CONFIG_CAF_LEDS_GPIO=y
|
||||
CONFIG_CAF_LEDS_PM_EVENTS=y
|
||||
|
||||
CONFIG_CAF_POWER_MANAGER=y
|
||||
CONFIG_CAF_POWER_MANAGER_TIMEOUT=20
|
||||
CONFIG_CAF_POWER_MANAGER_TIMEOUT=300
|
||||
CONFIG_CAF_POWER_MANAGER_ERROR_TIMEOUT=10
|
||||
CONFIG_REBOOT=y
|
||||
CONFIG_CAF_KEEP_ALIVE_EVENTS=y
|
||||
@@ -22,3 +54,5 @@ CONFIG_CAF_BUTTONS_DEBOUNCE_INTERVAL=10
|
||||
CONFIG_ADC=y
|
||||
CONFIG_I2C=y
|
||||
CONFIG_IP5305=y
|
||||
|
||||
CONFIG_SEGGER_RTT_BUFFER_SIZE_UP=4096
|
||||
|
||||
19
src/main.c
19
src/main.c
@@ -3,33 +3,14 @@
|
||||
#define MODULE main
|
||||
#include <caf/events/module_state_event.h>
|
||||
|
||||
#include <caf/events/led_event.h>
|
||||
#include <caf/led_effect.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(MODULE);
|
||||
|
||||
/*
|
||||
* 通过 CAF leds 模块控制第 0 号 LED 常亮。
|
||||
* 颜色值在单色 LED 上会被折算为亮度,因此这里使用白色全亮。
|
||||
*/
|
||||
static const struct led_effect startup_led_effect =
|
||||
LED_EFFECT_LED_ON(LED_COLOR(255, 255, 255));
|
||||
|
||||
int main(void)
|
||||
{
|
||||
if (app_event_manager_init()) {
|
||||
LOG_ERR("Application Event Manager not initialized");
|
||||
} else {
|
||||
/*
|
||||
* 先提交 led_event,再上报 main ready。
|
||||
* CAF leds 模块会缓存 effect,并在收到 main ready 完成初始化后开始输出。
|
||||
*/
|
||||
struct led_event *event = new_led_event();
|
||||
|
||||
event->led_id = 0;
|
||||
event->led_effect = &startup_led_effect;
|
||||
APP_EVENT_SUBMIT(event);
|
||||
|
||||
module_set_state(MODULE_STATE_READY);
|
||||
}
|
||||
|
||||
|
||||
193
src/modules/hids_module.c
Normal file
193
src/modules/hids_module.c
Normal file
@@ -0,0 +1,193 @@
|
||||
#include <bluetooth/services/hids.h>
|
||||
|
||||
#include <app_event_manager.h>
|
||||
|
||||
#define MODULE hids
|
||||
#include <caf/events/module_state_event.h>
|
||||
#include <caf/events/ble_common_event.h>
|
||||
|
||||
#include "hid_types.h"
|
||||
#include "hid_report_descriptor.h"
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
|
||||
|
||||
#define INPUT_REPORT_COUNT 2
|
||||
#define OUTPUT_REPORT_COUNT 1
|
||||
#define KEYBOARD_REPORT_LEN 30
|
||||
#define CONSUMER_REPORT_LEN 2
|
||||
#define KEYBOARD_LED_REPORT_LEN 1
|
||||
|
||||
/* 注册 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 void pm_evt_handler(enum bt_hids_pm_evt evt, struct bt_conn *conn)
|
||||
{
|
||||
ARG_UNUSED(conn);
|
||||
|
||||
switch (evt) {
|
||||
case BT_HIDS_PM_EVT_BOOT_MODE_ENTERED:
|
||||
current_pm = BT_HIDS_PM_BOOT;
|
||||
LOG_INF("HIDS protocol: boot");
|
||||
break;
|
||||
case BT_HIDS_PM_EVT_REPORT_MODE_ENTERED:
|
||||
current_pm = BT_HIDS_PM_REPORT;
|
||||
LOG_INF("HIDS protocol: report");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void report_notify_handler(enum bt_hids_notify_evt evt)
|
||||
{
|
||||
ARG_UNUSED(evt);
|
||||
}
|
||||
|
||||
static void boot_keyboard_notif_handler(enum bt_hids_notify_evt evt)
|
||||
{
|
||||
ARG_UNUSED(evt);
|
||||
}
|
||||
|
||||
static void boot_keyboard_output_report_handler(struct bt_hids_rep *rep,
|
||||
struct bt_conn *conn,
|
||||
bool write)
|
||||
{
|
||||
ARG_UNUSED(conn);
|
||||
|
||||
/* Basic boot protocol support: accept host LED writes and keep state locally. */
|
||||
if (!write || !rep || (rep->size == 0) || !rep->data) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DBG("Boot KB out report 0x%02x", rep->data[0]);
|
||||
}
|
||||
|
||||
static void keyboard_output_report_handler(struct bt_hids_rep *rep,
|
||||
struct bt_conn *conn,
|
||||
bool write)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
static int hids_service_init(void)
|
||||
{
|
||||
static const uint8_t report_map[] = HID_DESC_KEYBOARD_NKRO_CONSUMER();
|
||||
struct bt_hids_init_param init_param = { 0 };
|
||||
struct bt_hids_inp_rep *input_report = &init_param.inp_rep_group_init.reports[0];
|
||||
struct bt_hids_outp_feat_rep *output_report = &init_param.outp_rep_group_init.reports[0];
|
||||
|
||||
init_param.info.bcd_hid = 0x0101;
|
||||
init_param.info.b_country_code = 0x00;
|
||||
init_param.info.flags = BT_HIDS_REMOTE_WAKE | BT_HIDS_NORMALLY_CONNECTABLE;
|
||||
|
||||
init_param.rep_map.data = report_map;
|
||||
init_param.rep_map.size = sizeof(report_map);
|
||||
|
||||
input_report[0].id = REPORT_ID_KEYBOARD;
|
||||
input_report[0].size = KEYBOARD_REPORT_LEN;
|
||||
input_report[0].handler = report_notify_handler;
|
||||
|
||||
input_report[1].id = REPORT_ID_CONSUMER;
|
||||
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;
|
||||
|
||||
init_param.inp_rep_group_init.cnt = INPUT_REPORT_COUNT;
|
||||
init_param.outp_rep_group_init.cnt = OUTPUT_REPORT_COUNT;
|
||||
init_param.pm_evt_handler = pm_evt_handler;
|
||||
init_param.is_kb = true;
|
||||
init_param.boot_kb_notif_handler = boot_keyboard_notif_handler;
|
||||
init_param.boot_kb_outp_rep_handler = boot_keyboard_output_report_handler;
|
||||
|
||||
return bt_hids_init(&hids_obj, &init_param);
|
||||
}
|
||||
|
||||
static void handle_ble_peer_event(const struct ble_peer_event *event)
|
||||
{
|
||||
switch (event->state) {
|
||||
case PEER_STATE_CONNECTED:
|
||||
__ASSERT_NO_MSG(active_conn == NULL);
|
||||
active_conn = event->id;
|
||||
if (bt_hids_connected(&hids_obj, active_conn)) {
|
||||
LOG_WRN("bt_hids_connected failed");
|
||||
}
|
||||
break;
|
||||
|
||||
case PEER_STATE_DISCONNECTED:
|
||||
if (active_conn == event->id) {
|
||||
if (bt_hids_disconnected(&hids_obj, active_conn)) {
|
||||
LOG_WRN("bt_hids_disconnected failed");
|
||||
}
|
||||
active_conn = NULL;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool app_event_handler(const struct app_event_header *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)) {
|
||||
static bool initialized;
|
||||
|
||||
__ASSERT_NO_MSG(!initialized);
|
||||
initialized = true;
|
||||
|
||||
if (hids_service_init()) {
|
||||
LOG_ERR("Cannot initialize HIDS service");
|
||||
module_set_state(MODULE_STATE_ERROR);
|
||||
} else {
|
||||
module_set_state(MODULE_STATE_READY);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_ble_peer_event(aeh)) {
|
||||
handle_ble_peer_event(cast_ble_peer_event(aeh));
|
||||
return false;
|
||||
}
|
||||
|
||||
__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);
|
||||
1
sysbuild.conf
Normal file
1
sysbuild.conf
Normal file
@@ -0,0 +1 @@
|
||||
SB_CONFIG_BOOTLOADER_MCUBOOT=y
|
||||
Reference in New Issue
Block a user