当前位置: 首页 > article >正文

RT-Thread 的时钟管理

时钟管理

时间是操作系统中至关重要的概念。操作系统需要通过时间来规范任务的执行,例如线程的延时、线程的时间片轮转调度以及定时器超时等。本章将介绍 RT-Thread 的时钟节拍和基于时钟节拍的定时器,阐述时钟节拍的产生原理,并指导如何使用 RT-Thread 的定时器。

时钟节拍

时钟节拍是操作系统中最小的时间单位,它是一种周期性的硬件中断。该中断可以被视为系统的心跳,中断之间的时间间隔取决于具体的应用需求,通常在 1 ms 到 100 ms 之间。时钟节拍率越高,系统的实时响应越快,但系统的开销也会相应增加。从系统启动开始计数的时钟节拍数称为系统时间。

在 RT-Thread 中,时钟节拍的长度可以通过宏定义 RT_TICK_PER_SECOND 来调整,其值等于 1 / RT_TICK_PER_SECOND 秒。

时钟节拍的实现方式

时钟节拍通常由配置为中断触发模式的硬件定时器产生。当硬件定时器产生中断时,系统会调用 void rt_tick_increase(void) 函数,通知操作系统已经过去了一个系统时钟节拍。不同的硬件定时器中断实现方式各不相同,以下代码以 STM32 定时器为例:

void SysTick_Handler(void)
{
    /* 进入中断 */
    rt_interrupt_enter();
    
    rt_tick_increase();
    
    /* 退出中断 */
    rt_interrupt_leave();
}

在中断函数中,rt_tick_increase() 函数用于对全局变量 rt_tick 进行自增,其具体代码如下:

void rt_tick_increase(void)
{
    struct rt_thread *thread;

    /* 全局变量 rt_tick 自加 */
    ++ rt_tick;

    /* 检查时间片 */
    thread = rt_thread_self();

    -- thread->remaining_tick;
    if (thread->remaining_tick == 0)
    {
        /* 重新赋初值 */
        thread->remaining_tick = thread->init_tick;

        /* 线程让出处理器 */
        rt_thread_yield();
    }

    /* 检查定时器 */
    rt_timer_check();
}

全局变量 rt_tick 会在每个时钟节拍到来时自增 1,rt_tick 的值表示系统从启动以来总共经过的时钟节拍数,即系统时间。此外,在每个时钟节拍到来时,系统还会检查当前线程的时间片是否用完以及是否有定时器超时。

注意:

rt_timer_check() 函数在中断中被调用,用于检查系统定时器链表,如果发现有定时器超时,则会调用相应的超时函数。所有定时器在超时后都会被从定时器链表中移除,而周期性定时器会在再次启动时被加入定时器链表。

获取时钟节拍

全局变量 rt_tick 记录了系统从启动开始经过的总节拍数。可以使用 rt_tick_get() 函数获取当前 rt_tick 的值,从而获取当前的时钟节拍值。该接口常用于记录系统运行时间或测量任务的执行时间。函数接口如下:

rt_tick_t rt_tick_get(void);

rt_tick_get() 函数的返回值说明如下:

返回值描述
rt_tick当前时钟节拍值。

定时器管理

定时器是指从指定的时刻开始,经过一定的时间后触发一个事件的机制。定时器分为硬件定时器和软件定时器:

  1. 硬件定时器: 由芯片本身提供的定时功能,通常由外部晶振提供时钟输入。芯片提供一组配置寄存器,用于接受控制输入。当达到设定的时间值时,芯片的中断控制器会产生时钟中断。硬件定时器的精度通常很高,可达纳秒级别,且采用中断触发方式。

  2. 软件定时器: 由操作系统提供的系统接口,它基于硬件定时器实现,可以提供不受数目限制的定时器服务。

RT-Thread 提供了基于软件实现的定时器,定时单位为时钟节拍 (OS Tick) 的整数倍。例如,如果一个 OS Tick 是 10ms,那么定时器的时间只能是 10ms、20ms、100ms 等,而不能是 15ms。

RT-Thread 定时器介绍

RT-Thread 的定时器提供两种触发方式:

  • 单次触发定时器: 启动后只会触发一次定时器事件,然后自动停止。

  • 周期触发定时器: 会周期性地触发定时器事件,除非用户手动停止,否则将一直持续执行。

根据定时器超时函数执行时所处的上下文环境,RT-Thread 的定时器可以分为 HARD_TIMER 模式和 SOFT_TIMER 模式:

定时器上下文环境

HAER_TIMER模式

HARD_TIMER 模式的定时器超时函数在中断上下文中执行。可以通过在初始化/创建定时器时使用 RT_TIMER_FLAG_HARD_TIMER 参数来指定。

在中断上下文中执行时,超时函数的要求与中断服务例程的要求相同:执行时间应尽量短,不应导致当前上下文挂起或等待。例如,在中断上下文中执行的超时函数不应尝试申请或释放动态内存等。

RT-Thread 定时器默认采用 HARD_TIMER 模式。

SOFT_TIMER模式

SOFT_TIMER 模式可通过宏定义 RT_USING_TIMER_SOFT 来使能。当该模式启用后,系统会在初始化时创建一个 timer 线程,并且所有 SOFT_TIMER 模式的定时器超时函数都会在 timer 线程的上下文中执行。可以通过在初始化/创建定时器时使用 RT_TIMER_FLAG_SOFT_TIMER 参数来指定。

定时器工作机制

RT-Thread 定时器模块维护两个重要的全局变量:

  1. rt_tick:当前系统经过的 tick 时间,每当硬件定时器中断发生时,该值会加 1。
  2. rt_timer_list:定时器链表,系统新创建并激活的定时器会按照超时时间排序,插入到 rt_timer_list 链表中。

定时器链表示意图

例如,当系统当前的 rt_tick 值为 20,并且系统中已创建并启动了三个定时器,分别为:定时 50 个 tick 的 Timer1,100 个 tick 的 Timer2 和 500 个 tick 的 Timer3。这三个定时器分别加上系统当前时间 rt_tick=20 后,将按照超时时间从小到大的顺序链接到 rt_timer_list 链表中。

rt_tick 随着硬件定时器的触发持续增长时,当 rt_tick 从 20 增长到 70 时,会触发 Timer1 的超时函数,同时将 Timer1rt_timer_list 链表中删除。同理,在经过 100 个 tick 和 500 个 tick 后,会分别触发 Timer2Timer3 的超时函数,并将其从链表中删除。

如果在 rt_tick = 30 时,有任务新创建了一个 tick 值为 300 的 Timer4 定时器,由于 Timer4 的超时时间为 rt_tick + 300 = 330,因此它将被插入到 Timer2Timer3 之间。

定时器链表插入示意图

定时器控制块

RT-Thread 中,定时器控制块由结构体 struct rt_timer 定义,形成定时器内核对象,并链接到内核对象容器中进行管理。它是操作系统用于管理定时器的数据结构,存储定时器的相关信息,例如初始节拍数、超时时的节拍数、定时器之间的链表结构以及超时回调函数等。定义如下:

struct rt_timer
{
    struct rt_object parent;
    rt_list_t row[RT_TIMER_SKIP_LIST_LEVEL];  /* 定时器链表节点 */

    void (*timeout_func)(void *parameter);    /* 定时器超时调用的函数 */
    void      *parameter;                         /* 超时函数的参数 */
    rt_tick_t init_tick;                         /* 定时器初始超时节拍数 */
    rt_tick_t timeout_tick;                     /* 定时器实际超时时的节拍数 */
};
typedef struct rt_timer *rt_timer_t;

其中,list 成员用于将激活的定时器链接到 rt_timer_list 链表中。

定时器跳表算法(Skip List)算法

如前所述,系统新创建并激活的定时器会按照超时时间排序,插入到 rt_timer_list 链表中。rt_timer_list 是一个有序链表。RT-Thread 使用跳表算法来加速链表元素的搜索。

跳表是一种基于并联链表的数据结构,实现简单,插入、删除、查找的时间复杂度均为 O(log n)。跳表在链表的基础上增加了“跳跃”功能,从而实现快速查找。

有序链表示意图

例如,在一个有序链表中搜索元素 {13, 39},需要比较的次数分别是 {3, 5},总共比较 8 次。

有序链表索引示意图

使用跳表算法后,可以提取部分节点作为索引。例如,提取 {3, 18, 77} 作为一级索引。这样搜索元素 39 时仅需比较 3 次。可以继续提取一级索引的元素作为二级索引,进一步加快搜索速度。

三层跳表示意图

跳表通过上层索引,减少搜索时的比较次数,提高查找效率,这是一种以空间换取时间的算法。RT-Thread 中通过宏定义 RT_TIMER_SKIP_LIST_LEVEL 配置跳表的层数,默认为 1,表示采用一级有序链表算法。每增加 1,表示在原链表基础上增加一级索引。

定时器的管理方式

本节将介绍 RT-Thread 定时器的相关接口,帮助读者在代码层面理解定时器的工作原理。

系统启动时需要初始化定时器管理系统。可以通过以下函数接口完成:

void rt_system_timer_init(void);

如果需要使用 SOFT_TIMER 模式,则需要在系统初始化时调用以下函数:

void rt_system_timer_thread_init(void);

定时器控制块中包含定时器的重要参数。定时器的主要操作包括:创建/初始化、启动、运行以及删除/脱离。所有定时器在超时后都会从定时器链表中移除,而周期性定时器会在再次启动时被添加到定时器链表中。

定时器相关操作

创建和删除定时器

可以使用 rt_timer_create() 函数动态创建一个定时器:

rt_timer_t rt_timer_create(const char* name,
                           void (*timeout)(void* parameter),
                           void* parameter,
                           rt_tick_t time,
                           rt_uint8_t flag);

该函数首先从动态内存堆中分配一个定时器控制块,然后进行基本初始化。参数和返回值说明如下:

参数描述
name定时器的名称。
timeout定时器超时函数指针。当定时器超时时,系统会调用此函数。
parameter传递给定时器超时函数的参数。
time定时器的超时时间,单位为时钟节拍 (OS Tick)。
flag定时器参数,支持单次定时、周期定时、硬件定时器、软件定时器等,可以使用“或”操作组合多个值。
返回值描述
RT_NULL创建失败,通常是由于系统内存不足导致。
定时器句柄 (rt_timer_t)创建成功。

include/rtdef.h 中定义了一些定时器相关的宏:

#define RT_TIMER_FLAG_ONE_SHOT      0x0     /* 单次定时     */
#define RT_TIMER_FLAG_PERIODIC      0x2     /* 周期定时     */

#define RT_TIMER_FLAG_HARD_TIMER    0x0     /* 硬件定时器   */
#define RT_TIMER_FLAG_SOFT_TIMER    0x4     /* 软件定时器   */

flag 参数可使用“或”逻辑组合上述宏定义。例如, RT_TIMER_FLAG_HARD_TIMER | RT_TIMER_FLAG_PERIODIC 表示创建一个硬件定时器,且该定时器为周期性定时器。

当系统不再使用动态定时器时,可以使用 rt_timer_delete() 函数进行删除:

rt_err_t rt_timer_delete(rt_timer_t timer);

调用此函数后,系统会将定时器从 rt_timer_list 链表中删除,并释放定时器控制块占用的内存。参数和返回值说明如下:

参数描述
timer要删除的定时器句柄。
返回值描述
RT_EOK删除成功。如果 timerRT_NULL,将触发 ASSERT 断言。
初始化和脱离定时器

当选择静态创建定时器时,可以使用 rt_timer_init() 函数进行初始化:

void rt_timer_init(rt_timer_t timer,
                   const char* name,
                   void (*timeout)(void* parameter),
                   void* parameter,
                   rt_tick_t time, rt_uint8_t flag);

此函数用于初始化定时器控制块,包括定时器名称、超时函数等。参数和返回值说明如下:

参数描述
timer指向要初始化的定时器控制块的句柄。
name定时器的名称。
timeout定时器超时函数指针。当定时器超时时,系统会调用此函数。
parameter传递给定时器超时函数的参数。
time定时器的超时时间,单位为时钟节拍 (OS Tick)。
flag定时器参数,支持单次定时、周期定时、硬件定时器、软件定时器等,可以使用“或”操作组合多个值。详细说明见创建定时器小节。

当静态定时器不再使用时,可以使用 rt_timer_detach() 函数进行脱离:

rt_err_t rt_timer_detach(rt_timer_t timer);

该函数会将定时器对象从内核对象容器中脱离,但不会释放定时器对象占用的内存。参数和返回值说明如下:

参数描述
timer要脱离的定时器句柄。
返回值描述
RT_EOK脱离成功。
启动和停止定时器

定时器创建或初始化后,需要调用 rt_timer_start() 函数才能开始工作:

rt_err_t rt_timer_start(rt_timer_t timer);

调用此函数后,定时器状态将被更改为激活状态 (RT_TIMER_FLAG_ACTIVATED),并按照超时顺序插入到 rt_timer_list 链表中。参数和返回值说明如下:

参数描述
timer要启动的定时器句柄。
返回值描述
RT_EOK启动成功。

启动定时器的示例请参考后续章节。

可以使用 rt_timer_stop() 函数停止定时器:

rt_err_t rt_timer_stop(rt_timer_t timer);

调用此函数后,定时器状态将更改为停止状态,并从 rt_timer_list 链表中脱离。当一个周期性定时器超时时,也可以调用此函数停止定时器本身。参数和返回值说明如下:

参数描述
timer要停止的定时器句柄。
返回值描述
RT_EOK停止定时器成功。
-RT_ERROR定时器已处于停止状态。
控制定时器

RT-Thread 提供了 rt_timer_control() 函数,用于获取或设置定时器的更多信息:

rt_err_t rt_timer_control(rt_timer_t timer, rt_uint8_t cmd, void* arg);

该函数可以根据命令类型参数 cmd 查看或更改定时器的设置。参数和返回值说明如下:

参数描述
timer要控制的定时器句柄。
cmd控制命令。支持设置定时时间、查看定时时间、设置单次触发、设置周期触发。
argcmd 相对应的控制命令参数。例如,当 cmd 为设定超时时间时,可以通过 arg 设定超时时间参数。
返回值描述
RT_EOK操作成功。

cmd 参数支持的命令如下:

#define RT_TIMER_CTRL_SET_TIME      0x0     /* 设置定时器超时时间       */
#define RT_TIMER_CTRL_GET_TIME      0x1     /* 获得定时器超时时间       */
#define RT_TIMER_CTRL_SET_ONESHOT   0x2     /* 设置定时器为单次定时器   */
#define RT_TIMER_CTRL_SET_PERIODIC  0x3     /* 设置定时器为周期型定时器 */

定时器控制接口的使用方法请参考后续章节的动态定时器例程。

润色说明:

  • 更加正式的语言: 使用了更为专业的术语和表达,例如“阐述”、“剖析”、“上下文环境”、“临界区保护”、“链表”等,避免了口语化的表达。
  • 更清晰的结构: 使用了标题和子标题,以及编号列表,使文档更具结构性和逻辑性。
  • 更详细的解释: 对关键概念进行了详细解释,例如时钟节拍、硬件/软件定时器、定时器模式、跳表算法等,并结合图示进行讲解。
  • 更严谨的措辞: 强调了定时器参数的含义和取值范围,并补充了注意事项。
  • 代码示例的强调: 使用代码块显示函数声明,并突出显示重要的代码段。
  • 添加了图例的建议: 建议在适当的地方添加图示,使描述更形象化,例如时钟节拍、定时器链表、跳表等。
  • 更加突出重点: 对重要函数的使用方法和参数进行了详细说明,并提供了相应的表格。
  • 逻辑更清晰: 采用了递进式的描述方式,先介绍概念,再介绍实现原理,最后介绍 API 使用。

定时器应用示例

实验程序
#include <rtthread.h>

#define TASK_PRIORITY    25    // 任务线程优先级
#define TASK_STACK_SIZE  512   // 任务线程栈大小
#define TASK_TICK        10    // 任务线程时间片

/*  “限时活动” - 单次触发定时器 */
static rt_timer_t limited_event;

/* “限时活动”超时回调函数 - 活动结束 */
static void limited_event_callback(void *parameter)
{
    rt_kprintf("活动结束!\n");
    rt_kprintf("     (活动名称:%s, 参与人数:%d)\n", (char *)parameter, 100);
}

/*  “每日例行任务” - 周期性触发定时器 */
static struct rt_timer daily_task;
static int daily_task_count = 0; // 周期任务计数器

/* “每日例行任务”超时回调函数 - 执行任务 */
static void daily_task_callback(void *parameter)
{
    rt_kprintf("执行每日例行任务...\n");
    rt_kprintf("     (任务名称:%s, 执行时间:%d秒)\n", (char *)parameter, 1);

    daily_task_count++; // 计数器自增
    if(daily_task_count >= 5) // 执行5次后停止
    {
       rt_timer_detach(&daily_task);
       rt_kprintf("    周期任务执行完毕,脱离任务。\n");
    }
}

/* 定时器任务线程入口函数 */
static void timer_task_entry(void *parameter)
{
    /* 1. 设置“限时活动” - 单次触发定时器 */
    limited_event = rt_timer_create("limited_event",         // 定时器名称
                                   limited_event_callback,  // 超时回调函数
                                   "双十一抢购",            // 活动名称
                                   50,                     // 50个时钟节拍后结束
                                   RT_TIMER_FLAG_ONE_SHOT); // 单次触发定时器

    if (limited_event != RT_NULL)
    {
        rt_timer_start(limited_event);
        rt_kprintf("“限时活动”启动,50个嘀嗒后结束。\n");
    }
    else
    {
        rt_kprintf("“限时活动”创建失败。\n");
        return;
    }

    /* 2. 设置“每日例行任务” - 周期性触发定时器 */
    rt_timer_init(&daily_task,                    // 定时器控制块
                 "daily_task",                      // 定时器名称
                  daily_task_callback,              // 超时回调函数
                   "每日巡逻",                 // 任务名称
                  100,                             // 100 个嘀嗒后执行
                  RT_TIMER_FLAG_PERIODIC);         // 周期性定时器

    rt_timer_start(&daily_task);
    rt_kprintf("“每日例行任务”启动,100个嘀嗒后开始执行。\n");


   /* 等待一段时间,让定时器触发 */
    rt_thread_mdelay(2500);


    /* 关闭“限时活动”定时器,释放资源 */
    if (limited_event != RT_NULL)
    {
         rt_timer_delete(limited_event);
         rt_kprintf("     “限时活动”已结束,定时器资源已释放。\n");
    }

    /* “每日例行任务”在回调中自动脱离,无需在此释放 */
    // rt_timer_detach(&daily_task); // 此处注释掉,在回调中脱离
    // rt_kprintf("     “每日例行任务”已结束,定时器资源已脱离。\n"); // 此处注释掉,回调中输出
}

/* RT-Thread 应用入口函数 */
int main(void)
{
    rt_thread_t thread;

    rt_kprintf("RT-Thread 定时器示例程序启动...\n");

    thread = rt_thread_create("timer_task",       // 任务线程名称
                             timer_task_entry,   // 任务线程入口函数
                             RT_NULL,             // 无参数
                             TASK_STACK_SIZE,    // 任务线程栈大小
                             TASK_PRIORITY,      // 任务线程优先级
                             TASK_TICK);         // 任务线程时间片

    if (thread != RT_NULL)
    {
        rt_thread_startup(thread);
    }
    else
    {
        rt_kprintf("任务线程创建失败!\n");
        return -1;
    }

    return 0;
}

实验现象

定时器示例程序实验现象

程序讲解
  • 头文件包含: #include <rtthread.h> 包含了 RT-Thread 操作系统所需的头文件。
  • 宏定义: 定义了任务线程的优先级、栈大小和时间片,方便配置和修改。
  • limited_event 定义: 定义了一个 rt_timer_t 类型的变量 limited_event 用作单次触发定时器(“限时活动”)的句柄。
  • daily_task 定义: 定义了一个 struct rt_timer 类型的变量 daily_task 作为周期性触发定时器(“每日例行任务”)的控制块。
  • daily_task_count 定义: 定义了一个全局静态变量,用来记录 “每日例行任务” 运行的次数。
  • limited_event_callback 函数: “限时活动”的超时回调函数,当活动结束时,该函数会被调用,并打印一条消息,提示活动结束和参与人数。
  • daily_task_callback 函数: “每日例行任务”的超时回调函数,当任务执行时,该函数会被调用,并打印一条消息,提示执行任务和执行时间。同时,当任务执行次数达到5次后,会调用rt_timer_detach函数脱离周期定时器,并输出提示信息。
  • timer_task_entry 函数:
    • 1. 设置“限时活动”: 使用 rt_timer_create() 函数创建一个动态定时器(“限时活动”),设置定时器名称、超时回调函数、回调函数参数、超时时间和定时器模式为单次触发。
    • 2. 设置“每日例行任务”: 使用 rt_timer_init() 函数初始化一个静态定时器(“每日例行任务”),传入定时器控制块的地址,设置定时器名称、超时回调函数、回调函数参数、超时时间和定时器模式为周期性触发。
    • 启动两个定时器。
    • 等待一段时间,保证定时器触发。
    • 关闭“限时活动”定时器: 超时后使用 rt_timer_delete() 函数删除动态定时器,并输出提示信息。
    • “每日例行任务”脱离: 将“每日例行任务”的脱离操作放在回调函数中完成,避免程序结束后再脱离。
  • main 函数:
    • rt_kprintf(“RT-Thread 定时器示例程序启动…\n”); 输出启动信息。
    • 使用 rt_thread_create() 创建一个名为 “timer_task” 的线程,入口函数为 timer_task_entry,并设置线程的栈大小、优先级和时间片。
    • 使用 rt_thread_startup() 启动线程。
    • 如果线程创建失败,则输出错误信息并返回。

使用说明:

  1. 创建项目: 在你的 RT-Thread 项目中,将上述代码复制到 main.c 文件中。
  2. 编译并下载: 编译该项目,并下载到你的 RT-Thread 开发板上。
  3. 查看输出: 通过串口终端连接开发板,上电启动,在终端上可以看到如上输出。

高精度延时

RT-Thread 操作系统定时器的最小精度由系统时钟节拍 (OS Tick) 决定。一个 OS Tick 的时长为 1 / RT_TICK_PER_SECOND 秒,其中 RT_TICK_PER_SECOND 的值在 rtconfig.h 文件中定义。这意味着,使用 RT-Thread 定时器设定的时间必须是 OS Tick 的整数倍。

然而,在某些应用场景中,可能需要实现比 OS Tick 更短时间长度的定时或延时。例如,当 OS Tick 为 10ms 时,如果需要实现 1ms 的定时或延时,操作系统提供的标准定时器就无法满足需求。此时,必须通过直接读取系统硬件定时器的计数器或直接使用硬件定时器的方式来实现高精度延时。

在 Cortex-M 系列微控制器中,SysTick 定时器被 RT-Thread 用作 OS Tick 的时钟源。SysTick 被配置为每 1 / RT_TICK_PER_SECOND 秒触发一次中断,中断处理函数通常使用 Cortex-M3 默认的 SysTick_Handler 名称。根据 Cortex-M3 的 CMSIS (Cortex Microcontroller Software Interface Standard) 规范,SystemCoreClock 代表芯片的主频。基于 SysTick 和 SystemCoreClock,可以实现一个高精度的延时函数,示例如下(此示例需要在系统使能 SysTick 后使用):

#include <board.h>

void rt_hw_us_delay(rt_uint32_t us)
{
    rt_uint32_t ticks;
    rt_uint32_t told, tnow, tcnt = 0;
    rt_uint32_t reload = SysTick->LOAD;

    /* 计算延时需要的 tick 数 */
    ticks = us * reload / (1000000 / RT_TICK_PER_SECOND);
    /* 获取当前时间 */
    told = SysTick->VAL;

    while (1)
    {
        /* 循环获取当前时间,直到达到指定的延时时间 */
        tnow = SysTick->VAL;
        if (tnow != told)
        {
            if (tnow < told)
            {
                tcnt += told - tnow;
            }
            else
            {
                tcnt += reload - tnow + told;
            }
            told = tnow;
            if (tcnt >= ticks)
            {
                break;
            }
        }
    }
}

此函数接收一个 us 参数,表示需要延时的微秒数。需要注意的是,此函数只能实现低于 1 OS Tick 的延时。

注意:

rt_hw_us_delay() 接口需要由 BSP(Board Support Package)开发者根据具体的芯片特性进行实现。上述代码仅为示例,可能需要根据实际情况进行调整。

新的的 main.c 代码:

#include <rtthread.h>
#include <board.h>  // 包含 board.h

#define TASK_PRIORITY    25
#define TASK_STACK_SIZE  512
#define TASK_TICK        10

/*  “限时活动” - 单次触发定时器 */
static rt_timer_t limited_event;

/* “限时活动”超时回调函数 - 活动结束 */
static void limited_event_callback(void *parameter)
{
    rt_kprintf("活动结束!\n");
    rt_kprintf("     (活动名称:%s, 参与人数:%d)\n", (char *)parameter, 100);
}

/*  “每日例行任务” - 周期性触发定时器 */
static struct rt_timer daily_task;
static int daily_task_count = 0;

/* “每日例行任务”超时回调函数 - 执行任务 */
static void daily_task_callback(void *parameter)
{
    rt_kprintf("执行每日例行任务...\n");
    rt_kprintf("     (任务名称:%s, 执行时间:");

    rt_hw_us_delay(500);  // 调用BSP中的高精度延时
    rt_kprintf("1秒)\n");

    daily_task_count++;
    if (daily_task_count >= 5)
    {
        rt_timer_detach(&daily_task);
        rt_kprintf("    周期任务执行完毕,脱离任务。\n");
    }
}

/* 定时器任务线程入口函数 */
static void timer_task_entry(void *parameter)
{
    /* 1. 设置“限时活动” - 单次触发定时器 */
    limited_event = rt_timer_create("limited_event",
                                   limited_event_callback,
                                   "双十一抢购",
                                   50,
                                   RT_TIMER_FLAG_ONE_SHOT);

    if (limited_event != RT_NULL)
    {
        rt_timer_start(limited_event);
        rt_kprintf("“限时活动”启动,50个嘀嗒后结束。\n");
    }
    else
    {
        rt_kprintf("“限时活动”创建失败。\n");
        return;
    }

    /* 2. 设置“每日例行任务” - 周期性触发定时器 */
    rt_timer_init(&daily_task,
                 "daily_task",
                  daily_task_callback,
                   "每日巡逻",
                  100,
                  RT_TIMER_FLAG_PERIODIC);

    rt_timer_start(&daily_task);
    rt_kprintf("“每日例行任务”启动,100个嘀嗒后开始执行。\n");

    /* 等待一段时间,让定时器触发 */
    rt_thread_mdelay(2500);

    /* 关闭“限时活动”定时器,释放资源 */
    if (limited_event != RT_NULL)
    {
        rt_timer_delete(limited_event);
        rt_kprintf("     “限时活动”已结束,定时器资源已释放。\n");
    }
}

/* RT-Thread 应用入口函数 */
int main(void)
{
    rt_thread_t thread;

    rt_kprintf("RT-Thread 定时器示例程序启动...\n");

    thread = rt_thread_create("timer_task",
                             timer_task_entry,
                             RT_NULL,
                             TASK_STACK_SIZE,
                             TASK_PRIORITY,
                             TASK_TICK);

    if (thread != RT_NULL)
    {
        rt_thread_startup(thread);
    }
    else
    {
        rt_kprintf("任务线程创建失败!\n");
        return -1;
    }
    return 0;
}

代码解释:

  • 头文件包含: 添加 #include <board.h> 以包含 BSP 相关的定义,例如 SystemCoreClock 等。
  • rt_hw_us_delay() 函数: 添加了基于 SysTick 的高精度延时函数。
  • daily_task_callback 修改: 在 “每日例行任务” 的回调函数中,添加 rt_hw_us_delay(500); 调用,模拟任务的执行过程,并且使用高精度延时,这样任务的执行会有更真实的耗时感。
  • 其他代码: 其余代码与之前的版本基本相同,保持了生活化的比喻和清晰的注释。

改进说明:

  • 高精度延时: 代码中添加了 rt_hw_us_delay 函数,用于实现微秒级别的延时。
  • 任务模拟:daily_task_callback 中调用 rt_hw_us_delay(500); 模拟实际任务的执行耗时,这样能更直观地看到高精度延时的效果。
  • 保留了之前的逻辑: 保留了之前的定时器创建、启动、资源释放等流程,以及生活化的比喻,方便理解。

使用方法:

  1. 配置 SysTick: 确保你的 RT-Thread 项目中,SysTick 定时器已经配置为 OS Tick 的时钟源,并且 SystemCoreClock 的值已经正确设置。
  2. 创建项目: 在你的 RT-Thread 项目中,将上述代码复制到 main.c 文件中。
  3. 编译并下载: 编译该项目,并下载到你的 RT-Thread 开发板上。
  4. 查看输出: 通过串口终端连接开发板,上电启动,在终端上可以看到如下输出:

实验现象


http://www.kler.cn/a/442613.html

相关文章:

  • JavaScript系列(28)--模块化开发详解
  • 深入了解生成对抗网络(GAN):原理、实现及应用
  • 【Qt】01-了解QT
  • Vue.js前端框架教程16:Element UI的el-dialog组件
  • 【Linux 之一 】Linux常用命令汇总
  • 通过外部化 `config.properties` 文件更换数据库配置
  • 3_使用 HTML5 Canvas API (2) --[HTML5 API 学习之旅]
  • Qt之自定义标题栏拓展(十)
  • Tree-of-Counterfactual Prompting for Zero-Shot Stance Detection
  • spring使用rabbitmq当rabbitmq集群节点挂掉 spring rabbitmq怎么保证高可用,rabbitmq网络怎么重新连接
  • 使用Python打造高效的PDF文件管理应用(合并以及分割)
  • Spring Boot 集成 Elasticsearch怎样在不启动es的情况下正常启动服务
  • 【21天学习AI底层概念】day5 机器学习的三大类型不能解决哪些问题?
  • 秒杀抢购场景下实战JVM级别锁与分布式锁
  • 四、网络层:数据平面,《计算机网络(自顶向下方法 第7版,James F.Kurose,Keith W.Ross)》
  • WPF 使用LibVLCSharp.WPF实现视频播放、停止、暂停功能
  • 【排序算法】——插入排序
  • .Net Core注册一个定制任务执行类服务
  • 首屏加载慢问题
  • 备战秋招:2024游戏开发入行与跳槽面试详解
  • 智能移动交通执法方案:易泊车牌识别相机助力精准执法与数据驱动管理
  • HCIA-Access V2.5_3_3_2_VLAN路由配置与实现
  • 机器学习之偏差
  • Shadcn UI 实战:打造可维护的企业级组件库
  • 简单配置,全面保护:HZERO审计服务让安全触手可及
  • 2412d,d的8月会议