Files
blinky/docs/nrf_desktop_architecture.md
skiinder 2c421b23b6 feat(mini_keyboard): 添加CAF按钮模块支持并完善项目配置
- 添加external目录到.gitignore排除列表
- 在CMakeLists.txt中添加inc目录包含路径
- 更新DTS文件启用gpio0状态
- 创建CAF按钮定义头文件buttons_def.h,配置4x6矩阵键盘引脚
- 在prj.conf中启用CAF按钮模块及相关配置
- 添加详细的CAF官方模块清单文档caf_stock_modules_guide.md
- 添加nRF Desktop架构说明文档nrf_desktop_architecture.md,为后续
  键盘功能开发提供架构参考
2026-04-07 14:26:59 +08:00

501 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`