Env

esp-idf: v5.3-stable

数据结构介绍

事件处理函数

当事件到达后,运行的函数名为事件处理函数

事件处理函数要在事件循环创建完成之后调用注册函数注册进入事件循环

事件处理函数定义如下:

typedef void (*esp_event_handler_t)(void* event_handler_arg,
esp_event_base_t event_base,
int32_t event_id,
void* event_data);
/**< function called when an event is posted to the queue */

事件循环句柄

事件循环句柄的内存API决定,用户并不参与,所以用户在创建事件循环句柄的时候应该创建为指针,调用esp_event_loop_create函数创建事件循环句柄,经该函数创建的事件循环句柄称为用户事件循环句柄

事件循环句柄类型:esp_event_loop_handle_t
PS: 这个类型已经为一个指针。

e.g.:

esp_event_loop_handle_t loop_handle;

事件循环配置项

上面提到事件循环句柄由API创建,那么如果想配置事件循环就要用到事件循环配置项

事件循环配置项定义如下:

/// Configuration for creating event loops
typedef struct {
int32_t queue_size; /**< size of the event loop queue */
const char *task_name; /**< name of the event loop task; if NULL,
a dedicated task is not created for event loop*/
UBaseType_t task_priority; /**< priority of the event loop task, ignored if task name is NULL */
uint32_t task_stack_size; /**< stack size of the event loop task, ignored if task name is NULL */
BaseType_t task_core_id; /**< core to which the event loop task is pinned to,
ignored if task name is NULL */
} esp_event_loop_args_t;

配置项解析如下:

  • queue_size: 事件循环队列大小,指在事件没发送到事件处理函数之前,该事件循环最多能容纳的事件数量。
  • task_name: 是否选择定义一个线程去读取事件循环的队列,并且在读取到数据后执行事件处理函数,如果该参数为NULL,则没有专用线程读取事件并执行事件处理函数,需要手动调用esp_event_loop_run函数去读取队列执行事件处理函数,并且下面三个成员配置项无效。
  • task_priority: 事件循环专用线程的优先级配置,可使用uxTaskPriorityGet(NULL)函数获取当前线程优先级附加给它。
  • task_stack_size: 该专用线程的堆栈大小,不建议太小,会溢出,3072是一个普遍安全值。
  • task_core_id: 该事件循环专用线程在哪个核心上执行,应该≥0 && < CONFIG_FREERTOS_NUMBER_OF_CORES,该宏在sdkconfig定义了最大核心数。也可以使用tskNO_AFFINITY宏定义不指定任何一个核心,由FreeRTOS指定。

EVENT_BASE与EVENT_ID

每个事件循环都应该有事件处理函数,否则事件循环毫无意义。

事件处理函数中区别事件的唯一方式就是通过EVENT_BASEEVENT_ID

这两个类似于Linux的major与minor。

EVENT_BASE可以是WIFI_EVENTEVENT_ID可以是CONNECTEDGOT_IP等。

EVENT_BASE定义方式如下:

ESP_EVENT_DECLARE_BASE(MY_EVENT_BASE);
ESP_EVENT_DEFINE_BASE(MY_EVENT_BASE);

EVENT_ID推荐通过enum定义:

enum {
MY_EVENT_ID1,
MY_EVENT_ID2,
MY_EVENT_ID3,
};

API

该章节涵盖了常用API以及简介,欲知更详细,请见[1]

  • esp_event_loop_create: 该函数创建了事件循环,两个入参一个是事件循环句柄,一个是事件循环参数
  • esp_event_loop_create_default: 该函数创建了默认的事件循环,由于一些事件的推送不能由用户提交到队列,如wifi连接事件,获取ip事件等,所以便有了默认的事件循环,具体见[4]
  • esp_event_loop_delete: 删除事件循环,入参是事件循环句柄
  • esp_event_loop_delete_default: 删除默认事件循环
  • esp_event_handler_register_with: 注册事件处理函数到某个事件循环,需要提供该事件处理函数所处理的EVENT_BASEEVENT_ID,一个事件处理函数可用不同的EVENT注册多次,也可以使用ESP_EVENT_ANY_ID去处理整个EVENT_BASE下的ID
  • esp_event_handler_unregister_with: 注销某个事件处理函数
  • esp_event_handler_register: 为默认事件循环注册事件处理函数,除了不需要提供事件循环句柄以外,与esp_event_handler_register无异。
  • esp_event_handler_unregister: 为默认事件循环注销事件处理函数,除了不需要提供事件循环句柄以外,与esp_event_handler_register无异。
  • esp_event_post_to: 向指定的事件循环发送事件。
  • esp_event_post: 向默认的事件循环发送事件
  • esp_event_loop_run: 当在事件循环参数内没有配置task_name时,就不用有专有线程去读取队列并且执行事件处理函数,此时需要调用esp_event_loop_run手动读取并调用事件处理函数,该函数两个入参分别为事件循环句柄运行tick

Example

用户事件循环

专属线程

/* 事件循环句柄 */
esp_event_loop_handle_t loop_with_task;

ESP_EVENT_DECLARE_BASE(TASK_EVENTS); // declaration of the task events family

enum {
TASK_ITERATION_EVENT // raised during an iteration of the loop within the task
};

static void task_iteration_handler(void* handler_args, esp_event_base_t base, int32_t id, void* event_data)
{
// Two types of data can be passed in to the event handler: the handler specific data and the event-specific data.
//
// The handler specific data (handler_args) is a pointer to the original data, therefore, the user should ensure that
// the memory location it points to is still valid when the handler executes.
//
// The event-specific data (event_data) is a pointer to a deep copy of the original data, and is managed automatically.
int iteration = *((int*) event_data);

char* loop;

if (handler_args == loop_with_task) {
loop = "loop_with_task";
} else {
loop = "loop_without_task";
}

ESP_LOGI(TAG, "handling %s:%s from %s, iteration %d", base, "TASK_ITERATION_EVENT", loop, iteration);
}

void app_main(void) {
/* 事件循环参数 有专属任务*/
esp_event_loop_args_t loop_with_task_args = {
/* 队列大小 */
.queue_size = 5,
.task_name = "loop_task", // task will be created
/* 当前创建事件循环的任务的优先级 */
.task_priority = uxTaskPriorityGet(NULL),
/* 堆栈大小 */
.task_stack_size = 3072,
/* 不指定任何核心,由系统指派 */
.task_core_id = tskNO_AFFINITY
};

/* 创建事件循环 */
ESP_ERROR_CHECK(esp_event_loop_create(&loop_with_task_args, &loop_with_task));

/* 注册事件处理函数 */
ESP_ERROR_CHECK(esp_event_handler_instance_register_with(loop_with_task, TASK_EVENTS, TASK_ITERATION_EVENT, task_iteration_handler, loop_with_task, NULL));
}

没有专属线程

/* 事件循环句柄 */
esp_event_loop_handle_t loop_without_task;

ESP_EVENT_DECLARE_BASE(TASK_EVENTS); // declaration of the task events family

enum {
TASK_ITERATION_EVENT // raised during an iteration of the loop within the task
};

static void task_iteration_handler(void* handler_args, esp_event_base_t base, int32_t id, void* event_data)
{
// Two types of data can be passed in to the event handler: the handler specific data and the event-specific data.
//
// The handler specific data (handler_args) is a pointer to the original data, therefore, the user should ensure that
// the memory location it points to is still valid when the handler executes.
//
// The event-specific data (event_data) is a pointer to a deep copy of the original data, and is managed automatically.
int iteration = *((int*) event_data);

char* loop;

if (handler_args == loop_with_task) {
loop = "loop_with_task";
} else {
loop = "loop_without_task";
}

ESP_LOGI(TAG, "handling %s:%s from %s, iteration %d", base, "TASK_ITERATION_EVENT", loop, iteration);
}

void app_main(void) {
/* 事件循环参数 没有专属任务
在post_to之后需要在线程中
调用esp_event_loop_run(loop_without_task, 100);
否则无法进入事件处理函数
*/
esp_event_loop_args_t loop_without_task_args = {
.queue_size = 5,
.task_name = NULL // no task will be created
};

/* 创建事件循环 */
ESP_ERROR_CHECK(esp_event_loop_create(&loop_without_task_args, &loop_without_task));

/* 注册事件处理函数 */
ESP_ERROR_CHECK(esp_event_handler_instance_register_with(loop_with_task, TASK_EVENTS, TASK_ITERATION_EVENT, task_iteration_handler, loop_with_task, NULL));
}

Ref

[1]https://docs.espressif.com/projects/esp-idf/zh_CN/v5.3/esp32/api-reference/system/esp_event.html
[2]https://github.com/espressif/esp-idf/tree/v5.3/examples/system/esp_event/user_event_loops
[3]https://github.com/espressif/esp-idf/tree/v5.3/examples/system/esp_event/default_event_loop
[4]https://docs.espressif.com/projects/esp-idf/zh_CN/v5.3/esp32/api-reference/system/esp_event.html#esp-event-default-loops