diff --git a/.gitignore b/.gitignore index 635a99b..4c45b7a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ # build /build*/ +/external/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 4de34fb..7b142ba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,4 +4,6 @@ cmake_minimum_required(VERSION 3.20.0) find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) project(blinky) +zephyr_include_directories(inc) + target_sources(app PRIVATE src/main.c) diff --git a/boards/atguigu/mini_keyboard/mini_keyboard.dts b/boards/atguigu/mini_keyboard/mini_keyboard.dts index ad93fe6..e25be1b 100644 --- a/boards/atguigu/mini_keyboard/mini_keyboard.dts +++ b/boards/atguigu/mini_keyboard/mini_keyboard.dts @@ -52,6 +52,10 @@ }; }; +&gpio0 { + status = "okay"; +}; + &gpio1 { status = "okay"; }; diff --git a/docs/caf_stock_modules_guide.md b/docs/caf_stock_modules_guide.md new file mode 100644 index 0000000..dbfb5a3 --- /dev/null +++ b/docs/caf_stock_modules_guide.md @@ -0,0 +1,782 @@ +# CAF 官方现成模块清单与使用方法 + +本文基于本地 `NCS v3.2.3` 的官方源码与文档整理,范围以以下目录为准: + +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf` +- `C:\ncs\v3.2.3\nrf\subsys\caf\modules` + +目标是回答两个问题: + +1. CAF 官方现成提供了哪些模块可以直接用。 +2. 这些模块最小要怎么启用、怎么接入。 + +## 1. CAF 是什么 + +CAF, Common Application Framework,是 Nordic 基于 `app_event_manager` 封装的一组现成模块和事件。 + +它的基本模式是: + +- 你启用某个 CAF 模块 +- 模块监听 CAF 事件或应用自定义事件 +- 模块自己和别的模块通过事件解耦通信 + +因此 CAF 更像一套“事件驱动应用积木”,而不是单个库函数。 + +## 2. 使用 CAF 的最小前提 + +在使用任何 CAF 模块前,建议先完成这 3 件事。 + +### 2.1 打开 CAF + +在 `prj.conf` 中至少启用: + +```conf +CONFIG_CAF=y +``` + +### 2.2 启用并初始化 Application Event Manager + +你的应用需要正常使用 `app_event_manager`。 + +### 2.3 在 `main()` 中发出第一条 `module_state_event` + +CAF 模块在收到 `main` 模块的 `MODULE_STATE_READY` 后才会继续初始化。 + +典型写法: + +```c +#include + +#define MODULE main +#include + +int main(void) +{ + if (app_event_manager_init()) { + return 0; + } + + module_set_state(MODULE_STATE_READY); + return 0; +} +``` + +可以参考: + +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\caf_overview.rst` +- `C:\ncs\v3.2.3\nrf\applications\nrf_desktop\src\main.c` + +## 3. CAF 官方现成模块列表 + +按 `C:\ncs\v3.2.3\nrf\subsys\caf\modules\Kconfig`,NCS 3.2.3 中 CAF 官方模块包括: + +1. `CAF_BLE_ADV` +2. `CAF_BLE_BOND` +3. `CAF_BLE_SMP` +4. `CAF_BLE_STATE` +5. `CAF_BLE_STATE_PM` +6. `CAF_BUTTONS` +7. `CAF_BUTTONS_PM_KEEP_ALIVE` +8. `CAF_CLICK_DETECTOR` +9. `CAF_FACTORY_RESET_REQUEST` +10. `CAF_LEDS` +11. `CAF_NET_STATE` +12. `CAF_POWER_MANAGER` +13. `CAF_SENSOR_DATA_AGGREGATOR` +14. `CAF_SENSOR_MANAGER` +15. `CAF_SETTINGS_LOADER` +16. `CAF_SHELL` + +## 4. 模块总览表 + +| 模块 | 主要作用 | 典型输入 | 典型输出 | 最小启用点 | +| --- | --- | --- | --- | --- | +| `CAF_BUTTONS` | 扫描按键/矩阵键盘 GPIO | GPIO 变化 | `button_event` | `CONFIG_CAF_BUTTONS=y` | +| `CAF_BUTTONS_PM_KEEP_ALIVE` | 按键活动保持系统唤醒 | `button_event` | `keep_alive_event` | `CONFIG_CAF_BUTTONS_PM_KEEP_ALIVE=y` | +| `CAF_CLICK_DETECTOR` | 将按键动作识别为短按/长按/双击 | `button_event` | `click_event` | `CONFIG_CAF_CLICK_DETECTOR=y` | +| `CAF_LEDS` | 根据 LED effect 控制 LED | `led_event` | LED 状态变化 | `CONFIG_CAF_LEDS=y` | +| `CAF_POWER_MANAGER` | 管理挂起/唤醒/关机 | keep-alive、restriction、error | `power_down_event`、`wake_up_event`、`power_off_event` | `CONFIG_CAF_POWER_MANAGER=y` | +| `CAF_BLE_STATE` | 打开 BLE、管理连接回调 | 蓝牙栈回调 | `ble_peer_event`、`ble_peer_conn_params_event` | `CONFIG_CAF_BLE_STATE=y` | +| `CAF_BLE_STATE_PM` | BLE 连接存在时限制省电级别 | `ble_peer_event` | power restriction | `CONFIG_CAF_BLE_STATE_PM=y` | +| `CAF_BLE_ADV` | Peripheral 侧广播控制 | BLE 状态、advertising provider 数据 | 广播行为、`force_power_down_event` | `CONFIG_CAF_BLE_ADV=y` | +| `CAF_BLE_BOND` | 默认 BLE bond 管理 | `click_event`、settings | bond erase 行为 | `CONFIG_CAF_BLE_BOND=y` | +| `CAF_BLE_SMP` | BLE 上 MCUmgr DFU | MCUmgr 传输 | `ble_smp_transfer_event` | `CONFIG_CAF_BLE_SMP=y` | +| `CAF_SETTINGS_LOADER` | 在合适时机调用 `settings_load()` | `module_state_event` | settings 已装载 | `CONFIG_CAF_SETTINGS_LOADER=y` | +| `CAF_NET_STATE` | 上报网络连接状态 | LTE / OpenThread backend | `net_state_event` | `CONFIG_CAF_NET_STATE=y` | +| `CAF_SENSOR_MANAGER` | 周期采样传感器 | sensor driver | `sensor_event`、`sensor_state_event` | `CONFIG_CAF_SENSOR_MANAGER=y` | +| `CAF_SENSOR_DATA_AGGREGATOR` | 聚合 sensor_event 数据块 | `sensor_event` | `sensor_data_aggregator_event` | `CONFIG_CAF_SENSOR_DATA_AGGREGATOR=y` | +| `CAF_FACTORY_RESET_REQUEST` | 上电窗口内按键触发恢复出厂请求 | `button_event` | `factory_reset_event` | `CONFIG_CAF_FACTORY_RESET_REQUEST=y` | +| `CAF_SHELL` | 通过 shell 手动发 CAF 事件 | shell 命令 | `button_event` 等 | `CONFIG_CAF_SHELL=y` | + +## 5. 各模块使用方法 + +下面按“用途、最小接入方法、何时使用”来总结。 + +### 5.1 `CAF_BUTTONS` + +**用途** + +- 读取独立按键或矩阵键盘 +- 统一生成 `button_event` + +**最小接入** + +1. 在 `prj.conf` 打开: + +```conf +CONFIG_CAF_BUTTONS=y +CONFIG_GPIO=y +``` + +2. 新建按钮定义头文件,例如 `buttons_def.h`,定义: + +- `row[]` +- `col[]` + +3. 用 `CONFIG_CAF_BUTTONS_DEF_PATH` 指向这个配置文件。 + +例如: + +```conf +CONFIG_CAF_BUTTONS_DEF_PATH="buttons_def.h" +``` + +**何时使用** + +- 你要做按键输入,基本都会先用它 +- 对键盘项目最常用 + +**补充** + +- 支持矩阵键盘和直接 GPIO 按键 +- 支持去抖、扫描周期、按键极性配置 +- 可选支持 PM 事件和唤醒 + +参考: + +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\buttons.rst` + +### 5.2 `CAF_BUTTONS_PM_KEEP_ALIVE` + +**用途** + +- 按键按下时自动发 `keep_alive_event` +- 常配合 `CAF_POWER_MANAGER` + +**最小接入** + +```conf +CONFIG_CAF_BUTTONS=y +CONFIG_CAF_POWER_MANAGER=y +CONFIG_CAF_BUTTONS_PM_KEEP_ALIVE=y +``` + +**何时使用** + +- 设备需要超时休眠 +- 但用户按键活动应重置休眠计时 + +参考: + +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\buttons_pm_keep_alive.rst` + +### 5.3 `CAF_CLICK_DETECTOR` + +**用途** + +- 从 `button_event` 识别短按、长按、双击 +- 生成 `click_event` + +**最小接入** + +1. 打开: + +```conf +CONFIG_CAF_CLICK_DETECTOR=y +``` + +2. 新建 click 配置头文件,定义 `click_detector_config[]`。 + +至少要给出: + +- `key_id` +- `consume_button_event` + +3. 用 `CONFIG_CAF_CLICK_DETECTOR_DEF_PATH` 指向该文件。 + +**何时使用** + +- 一个键要复用多个动作 +- 如长按进入配对、双击切层、长按恢复出厂 + +参考: + +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\click_detector.rst` + +### 5.4 `CAF_LEDS` + +**用途** + +- 接收 `led_event` +- 用 PWM 或 GPIO 驱动 LED +- 支持 LED effect + +**最小接入** + +PWM 方案示例: + +```conf +CONFIG_CAF_LEDS=y +CONFIG_CAF_LEDS_PWM=y +CONFIG_LED=y +CONFIG_LED_PWM=y +CONFIG_PWM=y +``` + +GPIO 方案示例: + +```conf +CONFIG_CAF_LEDS=y +CONFIG_CAF_LEDS_GPIO=y +CONFIG_LED=y +CONFIG_LED_GPIO=y +CONFIG_GPIO=y +``` + +同时还需要: + +- 在 DTS 或 overlay 中定义 LED 节点 +- 在应用里由别的模块发 `led_event` + +**何时使用** + +- 做状态灯、层指示、配对指示、电量指示 + +**补充** + +- 想做平滑呼吸灯,优先用 PWM +- GPIO 版只适合开关式 LED + +参考: + +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\leds.rst` +- `C:\ncs\v3.2.3\nrf\samples\caf` + +### 5.5 `CAF_POWER_MANAGER` + +**用途** + +- 管理系统从 idle 到 suspended/off 的切换 +- 对外广播 `power_down_event`、`wake_up_event`、`power_off_event` + +**最小接入** + +```conf +CONFIG_CAF_POWER_MANAGER=y +``` + +常用附加项: + +```conf +CONFIG_CAF_POWER_MANAGER_TIMEOUT=120 +CONFIG_CAF_POWER_MANAGER_ERROR_TIMEOUT=30 +``` + +**何时使用** + +- 电池设备 +- 需要空闲休眠/关机 +- 多模块都需要统一响应省电状态 + +**补充** + +- 常和 `CAF_BUTTONS_PM_KEEP_ALIVE` 配合 +- 其他模块可用 `power_manager_restrict_event` 限制最大省电级别 + +参考: + +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\power_manager.rst` + +### 5.6 `CAF_BLE_STATE` + +**用途** + +- 启动 BLE +- 处理连接和参数回调 +- 向应用广播 BLE 连接状态事件 + +**最小接入** + +```conf +CONFIG_BT=y +CONFIG_BT_SMP=y +CONFIG_CAF_BLE_STATE=y +``` + +常见附加项: + +```conf +CONFIG_CAF_BLE_STATE_SECURITY_REQ=y +CONFIG_CAF_BLE_USE_LLPM=y +``` + +**何时使用** + +- 只要 CAF 体系下要做 BLE,几乎都先启用它 +- 它本身不负责广播或扫描 + +参考: + +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\ble_state.rst` + +### 5.7 `CAF_BLE_STATE_PM` + +**用途** + +- 当 BLE 连接存在时,阻止系统进入过深省电级别 + +**最小接入** + +```conf +CONFIG_CAF_BLE_STATE=y +CONFIG_CAF_POWER_MANAGER=y +CONFIG_CAF_BLE_STATE_PM=y +``` + +**何时使用** + +- BLE 外设连接后不能立即休眠 +- 要让 BLE 连接期间系统维持可通信 + +参考: + +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\ble_state_pm.rst` + +### 5.8 `CAF_BLE_ADV` + +**用途** + +- 作为 BLE Peripheral 控制广播 + +**最小接入** + +1. 启用 `CAF_BLE_STATE` +2. 启用: + +```conf +CONFIG_CAF_BLE_ADV=y +``` + +3. 配置 advertising data provider 和 scan response provider + +**何时使用** + +- 你的设备要作为 BLE Peripheral 被手机/PC/主机发现 + +**补充** + +- 支持快慢广播 +- 支持 directed advertising +- 支持 suspend/resume +- 支持 grace period + +参考: + +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\ble_adv.rst` + +### 5.9 `CAF_BLE_BOND` + +**用途** + +- 提供默认 BLE bond 管理 +- 可通过特定点击动作执行 bond erase + +**最小接入** + +```conf +CONFIG_CAF_BLE_BOND=y +CONFIG_BT_BONDABLE=y +CONFIG_BT_SETTINGS=y +CONFIG_CAF_SETTINGS_LOADER=y +CONFIG_CAF_BLE_COMMON_EVENTS=y +``` + +如果要支持按键清除 bond,还要配置: + +```conf +CONFIG_CAF_BLE_BOND_PEER_ERASE_CLICK=y +CONFIG_CAF_BLE_BOND_PEER_ERASE_CLICK_KEY_ID=0x0000 +``` + +并选择触发类型之一: + +- `CONFIG_CAF_BLE_BOND_PEER_ERASE_CLICK_SHORT` +- `CONFIG_CAF_BLE_BOND_PEER_ERASE_CLICK_LONG` +- `CONFIG_CAF_BLE_BOND_PEER_ERASE_CLICK_DOUBLE` + +**何时使用** + +- 简单 BLE 应用需要默认配对/绑定位管理 + +**补充** + +- 只适合简单应用 +- 更复杂的多 identity / 多 peer 管理,通常要自己实现 + +参考: + +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\ble_bond.rst` + +### 5.10 `CAF_BLE_SMP` + +**用途** + +- 通过 BLE 做 MCUmgr DFU + +**最小接入** + +```conf +CONFIG_CAF_BLE_STATE=y +CONFIG_CAF_BLE_SMP=y +CONFIG_MCUMGR_GRP_IMG=y +CONFIG_MCUMGR_MGMT_NOTIFICATION_HOOKS=y +CONFIG_MCUMGR_GRP_IMG_UPLOAD_CHECK_HOOK=y +CONFIG_MCUMGR_TRANSPORT_BT=y +CONFIG_BOOTLOADER_MCUBOOT=y +``` + +**何时使用** + +- 需要 BLE OTA/DFU + +**补充** + +- 依赖 MCUboot +- 构建后会在 build 目录生成 `dfu_application.zip` + +参考: + +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\ble_smp.rst` + +### 5.11 `CAF_SETTINGS_LOADER` + +**用途** + +- 在合适的初始化时机调用 `settings_load()` + +**最小接入** + +```conf +CONFIG_CAF_SETTINGS_LOADER=y +CONFIG_SETTINGS=y +``` + +还需要: + +- 新建配置头文件 +- 实现 `get_req_modules(struct module_flags *mf)` + +这个函数用于告诉 settings loader: + +- 哪些模块 ready 以后再加载 settings + +**何时使用** + +- 你的应用用了 settings/NVS/BT settings +- 比如 BLE bond、用户配置、持久化参数 + +参考: + +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\settings_loader.rst` + +### 5.12 `CAF_NET_STATE` + +**用途** + +- 上报网络连接状态 +- 提供 LTE / OpenThread backend + +**最小接入** + +```conf +CONFIG_CAF_NET_STATE=y +``` + +具体 backend 由链路层决定,例如: + +- `CONFIG_CAF_NET_STATE_LTE` +- `CONFIG_CAF_NET_STATE_OT` + +**何时使用** + +- 不是键盘/鼠标常用模块 +- 更适合蜂窝或 Thread 设备 + +参考: + +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\net_state.rst` + +### 5.13 `CAF_SENSOR_MANAGER` + +**用途** + +- 周期采样传感器 +- 统一生成 `sensor_event` 和 `sensor_state_event` + +**最小接入** + +```conf +CONFIG_CAF_SENSOR_MANAGER=y +CONFIG_SENSOR=y +``` + +还需要: + +1. 在 DTS/overlay 中启用传感器 +2. 打开对应传感器驱动 Kconfig +3. 新建 `sm_sensor_config[]` 配置文件 +4. 用 `CONFIG_CAF_SENSOR_MANAGER_DEF_PATH` 指向该文件 + +配置项通常至少包括: + +- `dev_name` +- `event_descr` +- `chans` +- `chan_cnt` +- `sampling_period_ms` +- `active_events_limit` + +**何时使用** + +- 有 IMU、加速度计、光传感器、环境传感器等 + +**补充** + +- 有独立采样线程 +- 支持 trigger +- 支持 PM + +参考: + +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\sensor_manager.rst` +- `C:\ncs\v3.2.3\nrf\samples\caf_sensor_manager` + +### 5.14 `CAF_SENSOR_DATA_AGGREGATOR` + +**用途** + +- 将多个 `sensor_event` 聚合成更大的数据包 +- 多核 SoC 下可降低核间唤醒频率 + +**最小接入** + +```conf +CONFIG_CAF_SENSOR_DATA_AGGREGATOR=y +``` + +如果另一核心要接收聚合事件,还可启用: + +```conf +CONFIG_CAF_SENSOR_DATA_AGGREGATOR_EVENTS=y +``` + +还需要: + +- 在 DTS/overlay 里添加 `compatible = "caf,aggregator"` 的节点 + +关键属性: + +- `sensor_descr` +- `buf_data_length` +- `sample_size` +- `buf_count` + +**何时使用** + +- 传感器数据量较大 +- 需要批处理 +- 或者多核 SoC 上做功耗优化 + +参考: + +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\sensor_data_aggregator.rst` + +### 5.15 `CAF_FACTORY_RESET_REQUEST` + +**用途** + +- 在上电初始化窗口中检测某个按键是否被按住 +- 若满足条件,发出 `factory_reset_event` + +**最小接入** + +```conf +CONFIG_CAF_FACTORY_RESET_REQUEST=y +CONFIG_CAF_FACTORY_RESET_REQUEST_BUTTON=0x0000 +CONFIG_CAF_FACTORY_RESET_REQUEST_DELAY=50 +``` + +它依赖: + +- `CAF_BUTTONS` +- `button_event` + +**何时使用** + +- 开机长按某键请求恢复出厂 +- 需要给上层模块一个统一 factory-reset 事件入口 + +**补充** + +- 这个模块在 `NCS v3.2.3` 源码中存在 +- 但没有找到与其他模块同等级的独立官方 `.rst` 页面 +- 当前说明主要依据: + - `C:\ncs\v3.2.3\nrf\subsys\caf\modules\Kconfig.factory_reset_request` + - `C:\ncs\v3.2.3\nrf\subsys\caf\modules\factory_reset_request.c` + +### 5.16 `CAF_SHELL` + +**用途** + +- 通过 Zephyr Shell 手工触发 CAF 事件 +- 便于调试 + +**最小接入** + +```conf +CONFIG_CAF_SHELL=y +CONFIG_SHELL=y +CONFIG_CAF=y +``` + +常见命令: + +```text +caf_events button_event [button_id] [pressed] +``` + +例如: + +```text +caf_events button_event 1 y +caf_events button_event 1 n +``` + +**何时使用** + +- 没有真实按键硬件时做联调 +- 验证 button/click/LED/状态机逻辑 + +参考: + +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\caf_shell.rst` + +## 6. 对键盘项目最常用的 CAF 组合 + +如果你的项目是 `C:\projects\blinky` 这种自定义键盘方向,最常见的组合通常是: + +### 6.1 纯输入层 + +- `CAF_BUTTONS` +- `CAF_CLICK_DETECTOR` + +适合: + +- 扫描键盘按键 +- 做短按/长按/双击功能键 + +### 6.2 带状态灯 + +- `CAF_BUTTONS` +- `CAF_CLICK_DETECTOR` +- `CAF_LEDS` + +适合: + +- Caps Lock 指示 +- 蓝牙通道指示 +- 层状态指示 + +### 6.3 电池设备 + +- `CAF_BUTTONS` +- `CAF_POWER_MANAGER` +- `CAF_BUTTONS_PM_KEEP_ALIVE` +- `CAF_LEDS` + +适合: + +- 一段时间无操作后休眠 +- 按键恢复活跃状态 + +### 6.4 BLE 键盘 + +- `CAF_BLE_STATE` +- `CAF_BLE_ADV` +- `CAF_BLE_BOND` +- `CAF_SETTINGS_LOADER` + +按需叠加: + +- `CAF_BLE_STATE_PM` +- `CAF_BLE_SMP` + +适合: + +- 支持广播、连接、加密、绑定 +- 支持设置装载 +- 需要时支持 OTA + +## 7. 推荐的接入顺序 + +如果你要在 `blinky` 里逐步引入 CAF,建议顺序如下: + +1. 先接 `CAF_BUTTONS` +2. 再接 `CAF_LEDS` +3. 然后根据需要加 `CAF_CLICK_DETECTOR` +4. 如果是电池设备,再接 `CAF_POWER_MANAGER` +5. 如果要做 BLE,再接 `CAF_BLE_STATE`、`CAF_BLE_ADV`、`CAF_BLE_BOND` +6. 如果要做 OTA,再接 `CAF_BLE_SMP` + +这样改动面最小,也最容易定位问题。 + +## 8. 官方参考路径 + +建议优先阅读这些本地官方文件: + +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\caf_overview.rst` +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\buttons.rst` +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\click_detector.rst` +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\leds.rst` +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\power_manager.rst` +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\ble_state.rst` +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\ble_state_pm.rst` +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\ble_adv.rst` +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\ble_bond.rst` +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\ble_smp.rst` +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\settings_loader.rst` +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\net_state.rst` +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\sensor_manager.rst` +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\sensor_data_aggregator.rst` +- `C:\ncs\v3.2.3\nrf\doc\nrf\libraries\caf\caf_shell.rst` + +同时可参考源码: + +- `C:\ncs\v3.2.3\nrf\subsys\caf\modules` +- `C:\ncs\v3.2.3\nrf\samples\caf` +- `C:\ncs\v3.2.3\nrf\samples\caf_sensor_manager` + +## 9. 一句话结论 + +如果只看键盘/鼠标类项目,CAF 里最值得优先使用的官方模块通常是: + +- `CAF_BUTTONS` +- `CAF_CLICK_DETECTOR` +- `CAF_LEDS` +- `CAF_POWER_MANAGER` +- `CAF_BLE_STATE` +- `CAF_BLE_ADV` +- `CAF_BLE_BOND` +- `CAF_SETTINGS_LOADER` + +它们已经覆盖了一个 BLE/USB 输入设备项目里最常见的基础设施。 diff --git a/docs/nrf_desktop_architecture.md b/docs/nrf_desktop_architecture.md new file mode 100644 index 0000000..e793f6b --- /dev/null +++ b/docs/nrf_desktop_architecture.md @@ -0,0 +1,500 @@ +# nRF Desktop 官方程序架构说明 + +本文基于 `C:\ncs\v3.2.3\nrf\applications\nrf_desktop` 中的官方源码与文档整理,目的是帮助在 `C:\projects\blinky` 中开发自定义键盘或 HID 设备时,理解 `nrf_desktop` 的整体设计思路。 + +## 1. nRF Desktop 是什么 + +`nrf_desktop` 不是一个单体应用,而是 Nordic 在 NCS 中提供的一个参考级 HID 框架。它可以通过不同配置,工作成以下几类设备: + +- 鼠标 +- 键盘 +- Dongle + +它同时支持以下传输方式: + +- Bluetooth Low Energy +- USB +- BLE + USB 并存 + +官方文档的核心描述是:这个应用是一个基于 CAF 和 Application Event Manager 的模块化、事件驱动架构。 + +对应源码和文档位置: + +- `C:\ncs\v3.2.3\nrf\applications\nrf_desktop\src\main.c` +- `C:\ncs\v3.2.3\nrf\applications\nrf_desktop\description.rst` +- `C:\ncs\v3.2.3\nrf\applications\nrf_desktop\modules.rst` + +## 2. 整体设计思想 + +它的设计目标主要有三个: + +- 高性能,尤其是 HID report rate 和输入延迟 +- 可配置,不同板子和不同产品形态共用同一套代码骨架 +- 可扩展,通过增加模块或替换模块实现新功能 + +`nrf_desktop` 的关键点在于: + +- `main()` 几乎不做业务逻辑 +- 功能被拆成很多独立模块 +- 模块之间主要通过事件通信,而不是直接互相调用 +- 不同产品形态通过 Kconfig、DTS overlay 和配置头文件组合出来 + +因此它更像一个“产品框架”,而不是一个简单示例。 + +## 3. 启动流程 + +`src/main.c` 非常简单,核心逻辑只有两步: + +1. 初始化 `app_event_manager` +2. 发送 `module_state_event(MODULE_STATE_READY)` + +也就是说,`main()` 只是启动系统并广播“主模块已经准备好”。其他模块监听这个事件后,再分别完成自己的初始化。 + +这和传统的串行初始化方式不同: + +- 传统方式:`main -> init_ble -> init_usb -> init_keys -> init_hid` +- `nrf_desktop` 方式:`main` 只发启动事件,各模块自己响应并进入就绪状态 + +这种模式的优点是模块之间耦合更低,方便裁剪和重用。 + +## 4. 源码目录分层 + +`nrf_desktop` 的目录结构本身就体现了它的架构分层: + +### 4.1 `src/events` + +这里定义事件类型,相当于模块间通信协议。 + +常见事件包括: + +- `motion_event` +- `hid_report_event` +- `hid_report_sent_event` +- `ble_event` +- `usb_event` +- `battery_event` +- `config_event` + +这些事件不是“业务实现”,而是模块之间交换信息的数据载体。 + +### 4.2 `src/hw_interface` + +这一层负责直接接触硬件,把硬件输入转换为内部事件。 + +例如: + +- `board.c` +- `motion_sensor.c` +- `motion_buttons.c` +- `wheel.c` +- `battery_meas.c` +- `passkey_buttons.c` + +这一层可以理解成“硬件抽象输入层”。 + +### 4.3 `src/modules` + +这是最核心的一层,负责系统行为和业务逻辑。 + +例如: + +- `hid_state.c` +- `hids.c` +- `usb_state.c` +- `hid_forward.c` +- `ble_scan.c` +- `ble_discovery.c` +- `ble_bond.c` +- `led_state.c` +- `dfu.c` +- `qos.c` + +如果说 `hw_interface` 负责“采集输入”,那么 `modules` 负责“处理输入并把它交付给主机或其他设备”。 + +### 4.4 `src/util` + +这一层放通用工具和公共基础能力。 + +例如: + +- `hid_reportq.c` +- `hid_eventq.c` +- `hid_keymap.c` +- `hwid.c` +- `config_channel_transport.c` + +这一层是支撑模块工作的辅助库。 + +## 5. 事件驱动架构 + +`nrf_desktop` 的核心是事件驱动。 + +典型模式是: + +1. 某个模块监听一个或多个事件 +2. 收到事件后更新内部状态 +3. 如有需要,再提交新的事件 +4. 其他模块继续响应 + +例如: + +- 按键模块产生 `button_event` +- HID provider 收到后更新 report 数据 +- `hid_state` 请求生成 HID report +- `usb_state` 或 `hids` 把 report 发送出去 +- 发送完成后提交 `hid_report_sent_event` +- 上游模块再决定是否采下一帧输入 + +所以它的整体运行更像“事件链路”,而不是“函数调用链”。 + +官方对这种模式的说明可以参考: + +- `description.rst` +- `doc/event_propagation.rst` + +## 6. 两种核心设备角色 + +`nrf_desktop` 架构里最重要的划分不是“鼠标还是键盘”,而是以下两个角色: + +- HID Peripheral +- HID Dongle + +这两个角色决定了系统的核心数据流完全不同。 + +### 6.1 HID Peripheral + +这种角色下,设备本身就是输入设备,比如键盘或鼠标。 + +它的职责是: + +- 采集本地硬件输入 +- 生成 HID report +- 通过 BLE 或 USB 发给主机 + +典型模块包括: + +- `buttons` +- `motion` +- `wheel` +- `hid_provider_*` +- `hid_state` +- `hids` +- `usb_state` + +### 6.2 HID Dongle + +这种角色下,设备本身不直接生成输入,而是做桥接转发。 + +它的职责是: + +- 作为 BLE Central 连接外部 HID 外设 +- 接收这些外设通过 HOGP 发来的 HID report +- 再通过 USB 转发给 PC 主机 + +典型模块包括: + +- `ble_scan` +- `ble_discovery` +- `ble_conn_params` +- `hid_forward` +- `usb_state` + +所以: + +- Peripheral 模式更像“输入源” +- Dongle 模式更像“协议桥” + +## 7. Peripheral 模式的核心链路 + +在 Peripheral 模式下,系统最核心的中心模块是 `hid_state`。 + +它负责: + +- 管理哪些 HID subscriber 当前有效 +- 决定 report 应该发给 USB 还是 BLE +- 和各类 `hid_provider` 交互 +- 处理 HID output report,例如键盘 LED 状态 + +你可以把它理解成“本地 HID 数据总调度器”。 + +### 7.1 典型数据流 + +以键盘或鼠标为例,数据流大致是: + +`buttons / motion / wheel` +-> 产生原始输入事件 +-> `hid_provider_*` +-> `hid_state` +-> `usb_state` 或 `hids` +-> 主机 + +其中: + +- `hid_provider_mouse` 负责组装鼠标输入 report +- `hid_provider_keyboard` 负责组装键盘 report +- `hid_provider_consumer_ctrl` 负责多媒体键 +- `hid_provider_system_ctrl` 负责系统控制键 + +它们都不是最终传输模块,而是“report 生成器”。 + +### 7.2 `hid_state` 为什么重要 + +`hid_state` 的价值在于把“输入生成”和“传输介质”解耦: + +- 上游模块只关心输入语义 +- 下游模块只关心通过 USB 或 BLE 发送 +- `hid_state` 负责把两边连起来 + +这样键盘逻辑不需要知道当前是 USB 主机在收,还是 BLE 主机在收。 + +## 8. 鼠标高性能链路 + +`nrf_desktop` 对鼠标场景做了专门优化,尤其是高 report rate。 + +官方文档里说明得很清楚: + +- Motion sensor 的采样和 HID report 的发送是同步的 +- `hid_report_sent_event` 会触发下一次采样 +- 在 BLE 或某些 USB 配置下,会建立两个 report 的 pipeline + +原因是: + +- BLE 通知完成的确认存在一个连接间隔延迟 +- USB poll 也可能有时间抖动 + +因此系统不是“采样器一直跑,report 有空再发”,而是“根据发送节奏反推采样节奏”。 + +这是一种很典型的低延迟输入设备设计。 + +对键盘来说,这种 pipeline 不一定像鼠标那样关键,但它说明官方非常重视链路级时序设计。 + +## 9. BLE 传输层模块 + +在 Peripheral 角色下,BLE 相关模块大致分为几类: + +- `ble_state` +- `ble_adv` +- `ble_bond` +- `ble_latency` +- `hids` +- `bas` +- `dev_descr` + +职责可以简单理解为: + +- `ble_state`:打开蓝牙、处理连接状态和参数回调 +- `ble_adv`:负责广播 +- `ble_bond`:负责绑定和身份管理 +- `ble_latency`:在配置或升级场景下降低连接延迟 +- `hids`:真正承载 HID over GATT +- `bas`:电池服务 +- `dev_descr`:设备描述和硬件 ID + +其中真正“把 HID report 发到 BLE 主机”的是 `hids`。 + +## 10. USB 传输层模块 + +USB 侧的核心模块是 `usb_state`。 + +它负责: + +- 跟踪 USB 连接状态 +- 注册 HID class 实例 +- 接收内部 `hid_report_event` +- 把 report 送入 USB 栈 +- 发送完成后产生 `hid_report_sent_event` +- 在需要时处理 output report + +它既是传输模块,也是系统状态模块。 + +官方还支持: + +- legacy USB stack +- USB next stack + +而且不同设备还能配置: + +- 单个 HID USB 实例 +- 多个 HID USB 实例 +- boot protocol +- report protocol + +这说明 `usb_state` 设计得非常通用,并不是只为单一 demo 服务。 + +## 11. Dongle 模式的核心链路 + +如果系统工作在 Dongle 模式,最核心的模块不再是 `hid_state`,而是 `hid_forward`。 + +它负责: + +- 从 BLE 外设接收 HID report +- 必要时用队列缓存 report +- 转成内部事件 +- 再发给 `usb_state` +- 把主机下发的 output report 再转发回 BLE 外设 + +典型数据流是: + +BLE HID Peripheral +-> `ble_discovery` +-> `hid_forward` +-> `usb_state` +-> PC 主机 + +在这个角色里,设备自己不再生产键值或鼠标移动,而是充当“BLE 到 USB 的协议转换桥”。 + +## 12. 配置通道和 DFU + +`nrf_desktop` 很强的一点是,它不仅有“输入数据面”,还有“控制面”。 + +控制面主要由 `config_channel` 提供,基于 HID feature report 实现。 + +它可以做: + +- 读取设备信息 +- 修改模块参数 +- 调节传感器 CPI +- 下发 LED 数据 +- 执行 DFU + +Dongle 还可以把这些请求继续转发给 BLE Peripheral。 + +这意味着官方架构已经把“量产设备常见需求”纳入了统一框架,而不是把配置、升级、输入完全割裂开。 + +## 13. 线程模型 + +`nrf_desktop` 总体上是少线程设计。 + +官方文档说明,大多数逻辑都运行在: + +- system workqueue +- App Event Manager 回调上下文 + +只有少量功能单独开线程,例如: + +- motion sensor sampling thread +- settings loader thread +- QoS sampling thread + +这种设计的直接好处是: + +- 降低并发复杂度 +- 大部分路径不需要显式资源保护 +- 更有利于保持时序可控 + +对于嵌入式 HID 设备,这是非常务实的选择。 + +## 14. 配置目录如何决定产品形态 + +`nrf_desktop` 的另一个重要架构点是“同一套源码,多套产品配置”。 + +配置目录位于: + +- `C:\ncs\v3.2.3\nrf\applications\nrf_desktop\configuration` + +每个板子一个目录,目录内通过这些文件控制构建结果: + +- `prj.conf` +- `prj_keyboard.conf` +- `prj_dongle.conf` +- `app.overlay` +- 各种 `*_def.h` +- `sysbuild.conf` +- `pm_static.yml` + +例如在 `nrf52840dk_nrf52840` 中: + +- `prj_keyboard.conf` 把它构造成键盘 +- `prj_dongle.conf` 把它构造成 dongle + +也就是说,架构的复用不是靠复制工程,而是靠配置裁剪模块集合。 + +## 15. 对你的 `blinky` 项目的启发 + +你的项目路径是: + +- `C:\projects\blinky` + +如果你后续想把它做成一个更完整的自定义键盘,而不是只停留在简单 GPIO 读取和发送,那么 `nrf_desktop` 最值得借鉴的不是某一个文件,而是下面这些架构思想。 + +### 15.1 把硬件输入和 HID 输出分层 + +建议至少拆成两层: + +- 输入层:按键扫描、编码器、LED、传感器 +- HID 层:按键状态汇总、键值映射、report 生成、USB/BLE 发送 + +不要让扫描函数直接拼 USB report。 + +### 15.2 用事件或消息队列解耦模块 + +即使不完全照搬 CAF,也可以参考它的思路: + +- 输入模块只上报事件 +- HID 模块只处理事件 +- 传输模块只发送 report + +这样后面加 BLE、加层切换、加宏录制时,不容易改崩整套逻辑。 + +### 15.3 先定义“角色”和“能力” + +在正式扩展前,先明确你的设备属于哪一类: + +- 纯 USB 键盘 +- BLE 键盘 +- 双模键盘 +- 带配置通道的键盘 + +不同目标会直接影响: + +- 模块边界 +- report 设计 +- 状态管理 +- 功耗策略 + +### 15.4 把板级配置独立出来 + +你现在已经有: + +- `boards/atguigu/mini_keyboard/mini_keyboard.dts` +- `boards/atguigu/mini_keyboard/Kconfig.mini_keyboard` + +这条路是对的。后面建议继续把下列内容尽量配置化: + +- matrix 行列定义 +- LED 引脚 +- 特殊按键定义 +- 默认 keymap + +这样应用逻辑就不会和某一块板子绑定太死。 + +## 16. 一句话总结 + +`nrf_desktop` 的官方架构可以概括为: + +一个基于 CAF 和事件总线的模块化 HID 产品框架,通过配置来组合出键盘、鼠标、dongle 等不同设备形态,并在输入采集、HID report 生成、BLE/USB 传输、配置通道、DFU 和状态管理之间建立清晰分层。 + +如果你后续想把 `C:\projects\blinky` 往自定义键盘方向继续演进,那么最值得学习的是它的: + +- 模块分层 +- 事件驱动 +- 角色化配置 +- 传输层与输入层解耦 +- 控制面与数据面分离 + +## 17. 参考源码位置 + +建议重点阅读以下路径: + +- `C:\ncs\v3.2.3\nrf\applications\nrf_desktop\src\main.c` +- `C:\ncs\v3.2.3\nrf\applications\nrf_desktop\description.rst` +- `C:\ncs\v3.2.3\nrf\applications\nrf_desktop\modules.rst` +- `C:\ncs\v3.2.3\nrf\applications\nrf_desktop\bluetooth.rst` +- `C:\ncs\v3.2.3\nrf\applications\nrf_desktop\board_configuration.rst` +- `C:\ncs\v3.2.3\nrf\applications\nrf_desktop\doc\event_propagation.rst` +- `C:\ncs\v3.2.3\nrf\applications\nrf_desktop\doc\hid_state.rst` +- `C:\ncs\v3.2.3\nrf\applications\nrf_desktop\doc\hids.rst` +- `C:\ncs\v3.2.3\nrf\applications\nrf_desktop\doc\hid_forward.rst` +- `C:\ncs\v3.2.3\nrf\applications\nrf_desktop\doc\motion.rst` +- `C:\ncs\v3.2.3\nrf\applications\nrf_desktop\doc\usb_state.rst` +- `C:\ncs\v3.2.3\nrf\applications\nrf_desktop\doc\config_channel.rst` diff --git a/inc/buttons_def.h b/inc/buttons_def.h new file mode 100644 index 0000000..f6f8122 --- /dev/null +++ b/inc/buttons_def.h @@ -0,0 +1,34 @@ +/* + * CAF buttons 矩阵引脚定义 + * + * 设计说明: + * - 本文件被 CAF buttons 模块通过 CONFIG_CAF_BUTTONS_DEF_PATH 直接包含; + * - 行列引脚顺序必须与板级 DTS 中 my_keyboard 的 row-gpios/col-gpios 保持一致; + * - key_id 的行列编号完全基于这里的数组下标,不依赖 input-keymap 节点。 + */ + +#include + +/* + * 该符号用于保证配置文件只被链接一次: + * 若被重复包含到多个编译单元,会在链接阶段报重复定义,避免静默错配。 + */ +const struct {} buttons_def_include_once; + +/* 列引脚:对应 atguigu_mini_keyboard.dts 中 my_keyboard/col-gpios 顺序。 */ +static const struct gpio_pin col[] = { + { .port = 0, .pin = 5 }, + { .port = 0, .pin = 6 }, + { .port = 0, .pin = 26 }, + { .port = 0, .pin = 30 }, +}; + +/* 行引脚:对应 atguigu_mini_keyboard.dts 中 my_keyboard/row-gpios 顺序。 */ +static const struct gpio_pin row[] = { + { .port = 0, .pin = 15 }, + { .port = 0, .pin = 7 }, + { .port = 0, .pin = 12 }, + { .port = 0, .pin = 4 }, + { .port = 1, .pin = 9 }, + { .port = 0, .pin = 8 }, +}; diff --git a/prj.conf b/prj.conf index e7af48a..01d59b5 100644 --- a/prj.conf +++ b/prj.conf @@ -1,4 +1,7 @@ CONFIG_CAF=y +CONFIG_CAF_BUTTONS=y +CONFIG_CAF_BUTTONS_DEF_PATH="buttons_def.h" +CONFIG_GPIO=y CONFIG_HEAP_MEM_POOL_SIZE=2048 CONFIG_LOG=y CONFIG_ASSERT=y