esp32关于esp_timer回调函数使用的注意事项(esp-idf5.2.3)
这是最近在做项目的过程中,遇到的一个问题。
问题背景
初始化两个esp_timer定时器,并分别注册了周期回调函数,当定时器溢出后被调用。在定时器1回调函数中获取sntp网络时间并打印;在定时器2回调函数中发送https请求获取数据包。此外主函数中还创建了一个跑LVGL的任务。具体代码如下:
#include <stdio.h>
#include <time.h>
#include "incl/myWIFI.h"
#include "incl/Ntp_time.h"
#include "incl/https_reqst.h"
#include "incl/lv_port.h"
#include "lvgl/lvgl.h"
#include "lv_demos.h"
static const char* TAG = "main";
// sntp时间获取完成回调
void mySntp_done_cb(void)
{
ESP_LOGI(TAG, "mySntp_done, Beijing time was successfully obtained");
}
void lvgl_task_run(void * param)
{
lv_demo_widgets();
while (1)
{
lv_task_handler();
vTaskDelay(pdMS_TO_TICKS(10));
}
}
void app_main(void)
{
wifi_sta_init(); // wifi设置为sta模式
mySntp_ready();
lv_port_init(); // 初始化lvgl
xTaskCreatePinnedToCore(lvgl_task_run, "lvgl_task", 8192, NULL, 3, NULL, 1);
while (1)
{
mySntp_start(mySntp_done_cb);
https_request_start();
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
以上是主函数,下面是创建esp_timer的代码:
void mySntp_start(mySntp_done_t f)
{
if(!get_wifi_status())
return;
if(false == mySntp_start_flag) // 仅当第一次发起sntp同步时创建定时函数和任务
{
f_mySntp_done = f;
ESP_LOGI(TAG, "mySntp start");
mySntp_start_flag = true;
static uint32_t timer_period = 60000000ull; // 单位us, 即定时器周期为1分钟
const esp_timer_create_args_t mySntp_periodic_timer_args = {
.arg = NULL,
.callback = print_now_time,
.name = "print_time",
};
esp_timer_handle_t print_timer_handle;
esp_timer_create(&mySntp_periodic_timer_args, &print_timer_handle);
esp_timer_start_periodic(print_timer_handle, timer_period);
xTaskCreatePinnedToCore(sntp_task, "sntp_task", 4096, NULL, 5, NULL, 1);
}
}
static void https_request_task(void *pvparameters)
{
static uint64_t timer_period = 120000000ull; // 2分钟发送一次https请求
const esp_timer_create_args_t https_request_timer_args = {
.callback = send_https_request,
};
esp_timer_handle_t https_request_timer;
ESP_ERROR_CHECK(esp_timer_create(&https_request_timer_args, &https_request_timer));
ESP_ERROR_CHECK(esp_timer_start_periodic(https_request_timer, timer_period));
vTaskDelete(NULL);
}
两个定时器回调函数
// 获取sntp网络时间回调函数
static void print_now_time(void* args)
{
time_t now;
struct tm timeinfo;
static uint16_t cnt = 0;
static bool is_fristPrint = true;
time(&now);
localtime_r(&now, &timeinfo);
// Is time set? If not, tm_year will be (1970 - 1900).
if (is_fristPrint || timeinfo.tm_year < (2016 - 1900)) {
is_fristPrint = false;
ESP_LOGI(TAG, "Time is not set yet. Connecting to WiFi and getting time over NTP.");
if(!sntp_inited_flag)
{
if(mySntp_init() == ESP_OK)
sntp_inited_flag = 1;
}
if(obtain_time() != ESP_OK)
{
ESP_LOGE(TAG, "obtain time failed!");
}
// update 'now' variable with current time
time(&now);
}
char strftime_buf[64];
// Set timezone to China Standard Time
setenv("TZ", "CST-8", 1);
tzset();
localtime_r(&now, &timeinfo);
// 将时间戳写入nvs
nvs_handle_t my_handle = 0;
xSemaphoreTakeRecursive(nvs_MuxSem, portMAX_DELAY); // 获取锁
if(tim_write_into_nvs(&my_handle, &now) == ESP_OK)
ESP_LOGI(TAG, "tim_write_into_nvs successful");
else ESP_LOGE(TAG, "tim_write_into_nvs failed");
xSemaphoreGiveRecursive(nvs_MuxSem); // 释放锁
// 打印时间
strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
ESP_LOGI(TAG, "%dth: The current date/time in BeiJing is: %s", ++cnt, strftime_buf);
}
// 发送https请求回调函数
static void send_https_request(void* args)
{
static uint16_t req_cnt = 0; // 请求次数
ESP_LOGI(TAG, "Start %dth https_request", ++req_cnt);
if (esp_reset_reason() == ESP_RST_POWERON) { // 从nvs中更新系统时间, 用于https验证
ESP_LOGI(TAG, "Updating time from NVS");
ESP_ERROR_CHECK(update_time_from_nvs());
}
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE && CONFIG_EXAMPLE_USING_ESP_TLS_MBEDTLS
https_get_request_using_crt_bundle();
#endif
ESP_LOGI(TAG, "Minimum free heap size: %" PRIu32 " bytes", esp_get_minimum_free_heap_size());
ESP_LOGI(TAG, "Finish https_request");
}
问题描述
当定时器2回调函数发送https请求时,其他任务全部阻塞,lvgl界面卡住,待获取数据包完成后恢复正常。
问题分析
经过调试以及查看相关资料得出:当esp_timer定时器溢出时,会触发一个高优先级的中断,然后在中断上下文中调度回调函数的执行。具体来说这个回调函数不是直接在中断上下文中调用,而是在中断中将回调函数的执行调度到一个高优先级的任务(
esp_timer任务)中,意思就是将这个回调函数放在系统级的高优先级任务中执行。那么我们知道,在rtos中高优先级的任务会抢占低优先级的任务执行权,如果我们在这个回调函数中执行了过于繁琐的流程,那么其他低优先级任务就会得不到执行,所以导致我们的其它任务都阻塞,UI界面也卡住。
接下来我们来看上面的定时器2回调函数,在回调函数中是发送https请求。由于HTTPS请求涉及复杂的操作,包括DNS解析、TCP连接、TLS握手、数据发送和接收等,所以这些操作可能会占用较长时间,导致用户创建的应用级任务(低优先级任务)得不到执行,等https请求完成后,又恢复正常。
还需要注意的是,esp32具有两个CPU核心,分别为Core0和Core1,Core0一般是运行系统协议的任务比如wifi和蓝牙等等,而esp_timer的回调函数默认也是运行在Core0上面的,
这个可以通过将下面这句代码放在回调函数中打印得出,这里自行实验
printf("coreID: %d, line%d\r\n", xPortGetCoreID(), __LINE__);所以在使用WIFI或蓝牙的情况下,请尽量不要调用核心0来执行任务,不然会影响通讯。
解决方案
既然不能在回调函数中执行过多的操作,那么我们可以创建一个任务来执行发送https请求的相关操作,然后在回调函数中使用信号量、队列或者事件等用来通知任务的执行,这样在回调函数中仅执行一行代码就能执行https请求,具体代码如下:
// 定时器回调函数
static void access_https_request(void* args)
{
xEventGroupSetBits(reqEventGp_handle, START_REQ_EVENTBIT);
}
// 发送https请求任务
static void send_https_request_task(void* params)
{
static uint16_t req_cnt = 0; // 请求次数
static EventBits_t wait_bit;
while(1)
{
wait_bit = xEventGroupWaitBits(reqEventGp_handle, START_REQ_EVENTBIT, pdTRUE, pdTRUE, portMAX_DELAY);
if(wait_bit & START_REQ_EVENTBIT)
{
ESP_LOGI(TAG, "Start %dth https_request", ++req_cnt);
if (esp_reset_reason() == ESP_RST_POWERON) { // 从nvs中更新系统时间, 用于https验证
ESP_LOGI(TAG, "Updating time from NVS");
ESP_ERROR_CHECK(update_time_from_nvs());
}
#if CONFIG_MBEDTLS_CERTIFICATE_BUNDLE && CONFIG_EXAMPLE_USING_ESP_TLS_MBEDTLS
https_get_request_using_crt_bundle();
#endif
ESP_LOGI(TAG, "Minimum free heap size: %" PRIu32 " bytes", esp_get_minimum_free_heap_size());
ESP_LOGI(TAG, "Finish https_request");
}
}
}
// http请求任务和esp_timer的创建、事件标志组的创建
static void https_request_task(void *pvparameters)
{
static uint64_t timer_period = 120000000ull; // 2分钟发送一次https请求
reqEventGp_handle = xEventGroupCreate();
xEventGroupSetBits(reqEventGp_handle, START_REQ_EVENTBIT); // 默认启动后发送一次https请求
const esp_timer_create_args_t https_request_timer_args = {
.callback = access_https_request,
};
esp_timer_handle_t https_request_timer;
ESP_ERROR_CHECK(esp_timer_create(&https_request_timer_args, &https_request_timer));
ESP_ERROR_CHECK(esp_timer_start_periodic(https_request_timer, timer_period));
xTaskCreatePinnedToCore(send_https_request_task, "send_https_req_task", 4096, NULL, 5, NULL, 1);
vTaskDelete(NULL);
}
修改后成功解决卡顿问题。这里由于连接了wifi, 指定Core1创建https请求任务。至于另外一个定时器回调函数print_now_timer同理使用相同的方法即可,尽量不要在esp_timer回调中执行过多的操作。
总结
就像中断中的处理一样,不允许在中断中使用延时函数或者执行过多的操作,否则会导致系统的实时性降低。
更多推荐


所有评论(0)