第04章 CAF
约 1527 字大约 5 分钟
2026-03-23
1 CAF 概述
CAF 全程 Common Application Framework
NCS集成了Zephyr实时操作系统,并且 Nordic 基于NCS,已经实现了一套键盘鼠标外设的示范项目;我们这个项目的目标,就是采用类似的架构自己将该项目实现一遍,从而理解USB HID,BLE HID以及NCS外设实现方案。
整个软件架构基于NCS自带的Common Application Framework(以下简称CAF),其架构如下:
我们可以按照一个发布——订阅模型理解CAF。CAF架构中核心叫Application Event Manager,基本的组件叫做模块(module),模块和模块之间不会直接产生函数调用,而是通过事件通信。下面我们通过一个demo快速理解该过程
1 模块创建
如果需要声明一个模块,只需要使用宏LOG_MODULE_REGISTER即可。最简单的模块声明示例如下:
#include <app_event_manager.h>
#define MODULE main
#include <caf/events/module_state_event.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE);
int main(void)
{
if (app_event_manager_init()) {
LOG_ERR("Application Event Manager not initialized");
} else {
module_set_state(MODULE_STATE_READY);
}
return 0;
}上面的示例中声明了一个main模块,并发布了一个main module ready事件。
2. 模块监听事件
模块彼此之间通信使用的是事件。例如上面的示例中,main模块发送了Ready事件,其他模块需要订阅该事件并作出反应。例如,下面的模块订阅了main模块的ready事件,并在收到该事件后执行了自己的逻辑:
#include <app_event_manager.h>
#define MODULE test
#include <caf/events/module_state_event.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE);
static int test_module_init(void)
{
LOG_INF("test module inited");
return 0;
}
static bool app_event_handler(const struct app_event_header *aeh)
{
// 检查事件是否为module状态事件
if (is_module_state_event(aeh))
{
const struct module_state_event *event = cast_module_state_event(aeh);
// 检查是否main模块ready
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY))
{
// 执行自己的初始化逻辑,并根据结果发布自己的模块状态事件
int err = test_module_init();
if (err)
{
module_set_state(MODULE_STATE_ERROR);
}
else
{
module_set_state(MODULE_STATE_READY);
}
}
// 将main module的事件传播(而不是消费)
return false;
}
__ASSERT_NO_MSG(false);
return false;
}
//注册模块监听回调函数
APP_EVENT_LISTENER(MODULE, app_event_handler);
//注册需要监听的事件,这里监听了所有模块的状态变化事件,这其中包含main模块的状态变化。
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);对于事件处理器回调函数app_event_handler来说,其处理完成收到的事件后,需要返回一个bool值。该值的含义是:当前你处理过的事件,是否需要继续向下传播。例如对于main模块的ready事件,一般来说所有模块都需要订阅这个ready事件(事实上这个ready事件就是程序的入口),那么所有模块在处理完这个事件口,就需要返回false,表示传播这个事件以供其他模块处理。如果某个模块返回true,那么main的ready事件就会在这个模块处理完成后被消费,导致之后的模块无法再接收到这个事件,也就实际没有函数入口了。
3. 执行Demo
如果需要运行上面的代码片段,需要在设置中开启CAF模块并开启动态内存分配。同时开启日志系统方便我们查看日志:
CONFIG_CAF=y
CONFIG_HEAP_MEM_POOL_SIZE=2048
CONFIG_LOG=y
CONFIG_ASSERT=y将上面的两个源文件编译并刷写后,运行并查看RTT Console,会看到如下信息:
SEGGER J-Link V9.28 - Real time terminal output
SEGGER J-Link (unknown) V1.0, SN=63728771
Process: JLink.exe
*** Booting nRF Connect SDK v3.2.3-6f8485d2890d ***
*** Using Zephyr OS v4.2.99-c4c75f71e709 ***
[00:00:00.266,876] <inf> app_event_manager: e:module_state_event module:main state:READY
[00:00:00.266,876] <inf> test: test module inited
[00:00:00.266,998] <inf> app_event_manager: e:module_state_event module:test state:READY4. 模块发布自定义事件
我们也可以自定义自己的事件类型,并在模块中发布并由其他模块订阅(自己订阅也可以),例如:
test_event.h
#ifndef TEST_EVENT_H__
#define TEST_EVENT_H__
#include <stdbool.h>
#include <stdint.h>
#include <app_event_manager.h>
#include <app_event_manager_profiler_tracer.h>
struct test_event {
struct app_event_header header;
int32_t message;
};
APP_EVENT_TYPE_DECLARE(test_event);
static inline void test_event_submit(int32_t message)
{
struct test_event *event = new_test_event();
event->message = message;
APP_EVENT_SUBMIT(event);
}
#endif /* TEST_EVENT_H__ */test_event.c
#include "test_event.h"
// 如果需要日志输出事件,实际的输出调用
static void log_test_event(const struct app_event_header *aeh)
{
const struct test_event *event = cast_test_event(aeh);
APP_EVENT_MANAGER_LOG(aeh, "message=%d", event->message);
}
// 事件profile追踪宏。如果我们不需要追踪,可以不写该实现
static void profile_test_event(struct log_event_buf *buf,
const struct app_event_header *aeh)
{
const struct test_event *event = cast_test_event(aeh);
nrf_profiler_log_encode_int32(buf, (uint8_t)event->message);
}
APP_EVENT_INFO_DEFINE(test_event,
ENCODE(NRF_PROFILER_ARG_S32),
ENCODE("message"),
profile_test_event);
// 事件定义宏,这个宏必须有,最后一项参数表示我们使用AEM内置的LOGGER记录并发布事件
// 如果没有写事件profile宏,那么第三个参数需要传NULL
APP_EVENT_TYPE_DEFINE(test_event,
log_test_event,
&test_event_info,
APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));定义完成自定义事件后,我们可以在test模块中发布该事件
#include <app_event_manager.h>
#define MODULE test
#include <caf/events/module_state_event.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE);
#include "test_event.h"
static int test_module_init(void)
{
// 发布test事件,并带上1234的payload
test_event_submit(1234);
LOG_INF("test module inited");
return 0;
}
static bool app_event_handler(const struct app_event_header *aeh)
{
// 检查事件是否为module状态事件
if (is_module_state_event(aeh))
{
const struct module_state_event *event = cast_module_state_event(aeh);
// 检查是否main模块ready
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY))
{
// 执行自己的初始化逻辑,并根据结果发布自己的模块状态事件
int err = test_module_init();
if (err)
{
module_set_state(MODULE_STATE_ERROR);
}
else
{
module_set_state(MODULE_STATE_READY);
}
}
// 将main module的事件传播(而不是消费)
return false;
}
__ASSERT_NO_MSG(false);
return false;
}
//注册模块监听回调函数
APP_EVENT_LISTENER(MODULE, app_event_handler);
//注册需要监听的事件,这里监听了所有模块的状态变化事件,这其中包含main模块的状态变化。
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);编译并执行,RTT Console能看到下面的结果:
SEGGER J-Link V9.28 - Real time terminal output
SEGGER J-Link (unknown) V1.0, SN=63728771
Process: JLink.exe
*** Booting nRF Connect SDK v3.2.3-6f8485d2890d ***
*** Using Zephyr OS v4.2.99-c4c75f71e709 ***
[00:00:00.353,271] <inf> app_event_manager: e:module_state_event module:main state:READY
[00:00:00.353,302] <inf> test: test module inited
[00:00:00.353,363] <inf> app_event_manager: e:test_event message=1234
[00:00:00.353,454] <inf> app_event_manager: e:module_state_event module:test state:READY能看到app_event_manager记录了test事件的发布,代表事件发布成功。
