解码LVGL定时器

定时器核心概念

  • LVGL 定时器是按指定毫秒(ms)周期执行回调函数的机制,依赖内置计时器系统调度
  • 非抢占式:多个定时器不会互相中断,一个定时器的回调执行完,才会执行下一个,因此回调中可安全调用 LVGL 相关函数或普通函数
  • 调度依赖 lv_timer_handler():所有定时器的回调都在该函数中触发,必须定期调用(如主循环、操作系统线程中),否则定时器无法执行

核心调度函数:lv_timer_handler ()

/**
 * LVGL 定时器核心调度函数(带属性标记,用于编译器优化/特性识别)
 * @brief 触发所有就绪的定时器回调,处理定时器周期计数、状态判断,是定时器工作的核心
 * @return uint32_t 下次需要调用该函数的最小延迟(单位:ms),用于优化调度(如OS中线程睡眠时长)
 *                  返回 0 表示需立即再次调用,避免定时器延迟
 * @note LV_ATTRIBUTE_TIMER_HANDLER 是LVGL内置属性宏,用于标记该函数为定时器处理入口,
 *       编译器可能根据该属性做优化(如避免内联、指定代码段等),不可省略
 *       必须定期调用:推荐调用周期 ≤ 5ms,调用频率直接决定定时器响应精度
 *       返回值用途:在操作系统中,可根据返回值让LVGL线程睡眠对应时长,减少CPU占用
 *       函数内部逻辑:遍历所有定时器 → 检查是否就绪(周期到达/被lv_timer_ready标记)→ 执行回调 → 更新计数
 *       若回调函数执行耗时过长,会导致返回值变大,甚至影响其他定时器的准时触发,需控制单个回调耗时
 *       线程安全:可在多线程环境中调用(LVGL内部有保护机制),但需确保所有LVGL操作(如控件修改)在同一线程
 */
LV_ATTRIBUTE_TIMER_HANDLER uint32_t lv_timer_handler(void);

定时器基本操作

创建定时器(核心接口)

/**
 * 创建一个定时器并返回其句柄
 * @param timer_cb  定时器回调函数(定时器触发时执行的函数)
 * @param period_ms 定时器执行周期(单位:ms),最小支持 1ms
 * @param user_data 用户自定义数据(回调中可通过 lv_timer_get_user_data 获取)
 * @return          成功返回定时器句柄(lv_timer_t*),失败返回 NULL(如内存不足)
 * @note            创建后定时器默认是启用状态(除非手动禁用)
 *                  回调函数原型必须符合 lv_timer_cb_t 定义
 */
lv_timer_t *lv_timer_create(lv_timer_cb_t timer_cb, uint32_t period_ms, void *user_data);

/**
 * 定时器回调函数原型
 * @param timer 触发回调的定时器句柄,可通过该句柄获取定时器信息(如用户数据、周期等)
 */
typedef void (*lv_timer_cb_t)(lv_timer_t *timer);

// 示例:创建定时器并传递用户数据
static void my_timer_cb(lv_timer_t *timer) {
    // 获取传递的用户数据
    void *user_data = lv_timer_get_user_data(timer);
    // 业务逻辑:如刷新控件显示、处理数据等
}

// 用法示例
lv_obj_t *label = lv_label_create(lv_scr_act()); // 创建一个标签控件
// 创建定时器,周期 500ms,将 label 作为用户数据传递
lv_timer_t *timer = lv_timer_create(my_timer_cb, 500, label);

就绪与重置定时器

/**
 * 强制定时器在下一次调用 lv_timer_handler() 时执行(忽略剩余周期)
 * @param timer 要设置的定时器句柄(非 NULL)
 * @note 用于需要立即触发一次定时器的场景,执行后仍按原周期继续计时
 */
void lv_timer_ready(lv_timer_t *timer);

/**
 * 重置定时器的周期计数(重新开始计时)
 * @param timer 要重置的定时器句柄(非 NULL)
 * @note 调用后,定时器需等待完整的 period_ms 时间才会再次触发
 *       例如:周期 1000ms,已计时 600ms 时调用 reset,需再等 1000ms 才触发
 */
void lv_timer_reset(lv_timer_t *timer);

修改定时器核心参数

/**
 * 设置定时器的新回调函数
 * @param timer 定时器句柄(非 NULL)
 * @param new_cb 新的回调函数(符合 lv_timer_cb_t 原型)
 * @note 修改后,下一次定时器触发时执行新回调
 */
void lv_timer_set_cb(lv_timer_t *timer, lv_timer_cb_t new_cb);

/**
 * 设置定时器的新执行周期
 * @param timer 定时器句柄(非 NULL)
 * @param new_period 新周期(单位:ms),最小 1ms
 * @note 修改后立即生效,下一次计时按新周期计算
 */
void lv_timer_set_period(lv_timer_t *timer, uint32_t new_period);

/**
 * 设置定时器的新用户数据
 * @param timer 定时器句柄(非 NULL)
 * @param new_user_data 新的用户数据(void* 类型,可传递任意数据)
 * @note 后续回调中通过 lv_timer_get_user_data 获取的是新数据
 */
void lv_timer_set_user_data(lv_timer_t *timer, void *new_user_data);

/**
 * 获取定时器的用户数据(回调中常用)
 * @param timer 定时器句柄(非 NULL)
 * @return 返回设置的用户数据(void* 类型,需手动强转成实际类型)
 */
void *lv_timer_get_user_data(const lv_timer_t *timer);

设置定时器重复次数

/**
 * 设置定时器的执行次数(默认无限重复)
 * @param timer 定时器句柄(非 NULL)
 * @param count 重复次数:-1 = 无限重复(默认值);≥0 = 执行 count 次后自动删除
 * @note 执行次数耗尽后,LVGL 会自动调用 lv_timer_del 删除定时器,无需手动删除
 *       示例:count=5 → 定时器触发 5 次后自动销毁
 *       若中途修改 count,以新值重新计数(如已执行 3 次,改为 count=2 → 再执行 2 次后删除)
 */
void lv_timer_set_repeat_count(lv_timer_t *timer, int32_t count);

启用与禁用定时器

/**
 * 启用定时器(恢复定时器计时和触发)
 * @param timer 定时器句柄(非 NULL)
 * @note 启用后,定时器按周期正常计时,到达周期触发回调
 */
void lv_timer_enable(lv_timer_t *timer);

暂停与恢复定时器

/**
 * 暂停定时器(暂停计时,区别于禁用)
 * @param timer 定时器句柄(非 NULL)
 * @note 暂停后,定时器停止计时,但保持启用状态
 *       恢复(lv_timer_resume)后,继续从暂停时的剩余周期计时(而非重新开始)
 *       示例:周期 1000ms,已计时 600ms 时暂停,恢复后再计时 400ms 触发回调
 */
void lv_timer_pause(lv_timer_t *timer);

/**
 * 恢复被暂停的定时器
 * @param timer 定时器句柄(非 NULL)
 * @note 仅对已暂停的定时器有效,对禁用状态的定时器无效
 */
void lv_timer_resume(lv_timer_t *timer);

其他常用接口

创建空定时器(无默认回调和用户数据)

/**
 * 创建一个空定时器(无回调、无用户数据、默认周期 0ms)
 * @return 成功返回定时器句柄,失败返回 NULL
 * @note 创建后需手动设置回调、周期、用户数据等参数才能使用
 *       用途:动态配置定时器参数的场景(如根据运行时状态设置不同回调)
 *       示例:
 *          lv_timer_t *timer = lv_timer_create_basic();
 *          lv_timer_set_cb(timer, my_cb);
 *          lv_timer_set_period(timer, 200);
 */
lv_timer_t *lv_timer_create_basic(void);

获取 lv_timer_handler() 空闲时间百分比

/**
 * 获取 lv_timer_handler() 函数执行时的空闲百分比(仅反映该函数内部的空闲状态)
 * @return 空闲百分比(0~100):0 = 完全忙碌,100 = 完全空闲
 * @note 不代表整个系统的空闲时间,仅反映 LVGL 定时器调度的繁忙程度
 *       在操作系统中使用时,该值可能有误导性(无法反映 OS 空闲线程的消耗)
 *       用途:判断 LVGL 定时器调度是否过载(如返回 0 持续一段时间,说明回调执行耗时过长)
 */
uint8_t lv_timer_get_idle(void);

异步调用(下一次 lv_timer_handler() 执行)

/**
 * 安排一个函数在下一次调用 **lv_timer_handler**() 时执行(仅执行一次)
 * @param func 要执行的函数(原型:void (*lv_async_cb_t)(void*))
 * @param data 传递给 func 的用户数据
 * @note 用于无法立即执行的操作(如避免阻塞当前流程、当前上下文不允许操作 LVGL 控件)
 *       执行后自动销毁,无需手动删除
 *       例:在中断中触发 LVGL 控件更新(中断中不能直接操作 LVGL,可通过该函数异步执行)
 */
void lv_async_call(lv_async_cb_t func, void *data);

/**
 * 异步调用的函数原型
 * @param data 传递的用户数据(void* 类型,需手动强转)
 */
typedef void (*lv_async_cb_t)(void *data);

定时器删除(关键知识点)

手动删除定时器(外部删除)

/**
 * 手动删除定时器(释放定时器占用的内存)
 * @param timer 要删除的定时器句柄(非 NULL)
 * @note 删除后,定时器句柄失效,不可再使用(避免野指针访问)
 *       若定时器正在执行回调,删除操作会在回调执行完后生效
 *       必须确保删除时定时器未被其他地方引用(如全局变量存储的句柄需置 NULL)
 *       示例:
 *          lv_timer_t *timer = lv_timer_create(my_cb, 500, NULL);
 *          // ... 业务逻辑 ...
 *          lv_timer_del(timer); // 删除定时器
 *          timer = NULL; // 避免野指针
 */
void lv_timer_del(lv_timer_t *timer);

回调函数中自删定时器

/**
 * 在定时器回调中删除自身(常用场景:执行一次后自动删除,无需设置 repeat_count)
 * @note 示例:
 * static void my_timer_cb(lv_timer_t *timer) {
 *     // 业务逻辑 ...
 *     lv_timer_del(timer); // 执行完业务后删除自身
 *     timer = NULL; // 可选:若句柄是全局变量,需置 NULL
 * }
 * 
 * // 创建时无需设置 repeat_count(默认无限重复,但回调中自删)
 * lv_timer_create(my_timer_cb, 1000, NULL);
 */

控件与定时器关联的安全删除

场景:定时器用户数据是 LVGL 控件(如 label),需安全删除定时器和控件,避免野指针。

安全删除步骤:

  • 先禁用定时器(防止删除过程中定时器触发,访问已销毁的控件)
  • 删除定时器(释放定时器内存)
  • 删除控件(释放控件内存)
  • 将定时器句柄和控件句柄置 NULL(避免野指针访问)

示例代码:

// 全局/局部静态变量存储句柄(方便删除时访问)
static lv_timer_t *label_timer = NULL;
static lv_obj_t *my_label = NULL;

// 定时器回调:刷新 label 显示
static void label_refresh_cb(lv_timer_t *timer) {
    // 先判断控件是否有效(避免已删除后访问)
    if(lv_obj_is_valid(my_label)) {
        static int count = 0;
        char buf[32];
        sprintf(buf, "Count: %d", count++);
        lv_label_set_text(my_label, buf);
    } else {
        // 控件已失效,删除定时器
        lv_timer_del(timer);
        label_timer = NULL;
    }
}

// 安全删除函数
void safe_delete_label_and_timer(void) {
    // 禁用定时器(防止删除过程中触发回调)
    if(label_timer != NULL) {
        lv_timer_enable(false);
    }
    
    // 删除定时器
    if(label_timer != NULL) {
        lv_timer_del(label_timer);
        label_timer = NULL; // 置 NULL,避免野指针
    }
    
    // 删除控件(判断控件是否有效)
    if(lv_obj_is_valid(my_label)) {
        lv_obj_del(my_label);
        my_label = NULL; // 置 NULL,避免野指针
    }
}

// 初始化
void init_label_and_timer(void) {
    // 创建控件
    my_label = lv_label_create(lv_scr_act());
    lv_label_set_text(my_label, "Count: 0");
    
    // 创建定时器,传递 label 作为用户数据
    label_timer = lv_timer_create(label_refresh_cb, 500, my_label);
}

关键注意点:

  • 回调中必须先判断控件是否有效(lv_obj_is_valid),避免控件已删除但定时器未删除时访问野指针
  • 删除顺序:先禁用定时器 → 再删除定时器 → 最后删除控件,确保回调不会访问已删除的控件
  • 句柄管理:删除后必须置 NULL,防止后续代码误使用失效句柄

常见问题与注意事项

  • 定时器不执行?
    • 检查 lv_timer_handler() 是否定期调用(如主循环中是否遗漏)
    • 检查定时器是否被禁用或暂停
    • 检查周期是否设置过小(如 0ms,会导致不执行)
  • 定时器执行延迟?
    • 增大 lv_timer_handler()的调用频率(推荐 ≤5ms)
    • 减少回调函数的执行耗时(非抢占式,单个回调耗时过长会阻塞其他定时器)
  • 野指针问题?
    • 定时器用户数据是堆内存 / 控件时,需确保数据 / 控件未被提前释放
    • 删除定时器后,立即将句柄置 NULL,避免后续访问
  • 中断中能否操作定时器?
    • 可以调用 lv_timer_createlv_timer_enable等接口(LVGL 定时器接口线程安全)
    • 不能在中断中直接操作 LVGL 控件,需通过 lv_async_call 异步执行控件操作
posted @ 2025-11-11 18:19  YouEmbedded  阅读(25)  评论(0)    收藏  举报