RT-Thread 软件定时器(学习笔记)

本文参考自[野火EmbedFire]《RT-Thread内核实现与应用开发实战——基于STM32》,仅作为个人学习笔记。更详细的内容和步骤请查看原文(可到野火资料下载中心下载)

定时器的基本概念

定时器,是指从指定的时刻开始,经过一个指定时间,然后触发一个超时事件,用户可以自定义定时器的周期与频率。类似生活中的闹钟,我们可以设置闹钟每天什么时候响,还能设置响的次数,是响一次还是每天都响。

定时器有硬件定时器和软件定时器之分:

硬件定时器是芯片本身提供的定时功能。一般是由外部晶振提供给芯片输入时钟,芯片向软件模块提供一组配置寄存器,接受控制输入,到达设定时间值后芯片中断控制器产生时钟中断。硬件定时器的精度一般很高,可以达到纳秒级别,并且是中断触发方式。

软件定时器,软件定时器是由操作系统提供的一类系统接口,它构建在硬件定时器基础之上,使系统能够提供不受硬件定时器资源限制的定时器服务,它实现的功能与硬件定时器也是类似的。

RT-Thread 操作系统提供软件定时器功能,软件定时器的使用相当于扩展了定时器的数量,允许创建更多的定时业务。

——原文

定时器的应用场景

由于微处理器或微控制器的硬件定时器数量有限,当用户需要更多的定时器时,需要采用软件软件定时器来完成。

但是,软件定时器只适用于对时间精度要求不高的线程,因为与硬件定时器相比,软件定时器的精度很低,且软件定时器容易被其他线程打断(软件定时器是一个线程,优先级默认为4)。

定时器的精度

在操作系统中,通常软件定时器以系统节拍周期为计时单位。系统节拍是系统的心跳节拍,表示系统时钟的频率,就类似人的心跳,1s 能跳动多少下,系统节拍配置为 RT_TICK_PER_SECOND,该宏在 rtconfig.h 中有定义,默认是 1000。那么系统的时钟节拍周期就为 1ms(1s 跳动 1000 下,每一下就为 1ms)。软件定时器的所定时数值必须是这个节拍周期的整数 倍,例如节拍周期是 10ms,那么上层软件定时器定时数值只能是 10ms,20ms,100ms 等,而不能取值为 15ms。由于节拍定义了系统中定时器能够分辨的精确度,系统可以根据实际系统 CPU 的处理能力和实时性需求设置合适的数值,系统节拍周期的值越小,精度越高,但是系统开销也将越大,因为在 1 秒中系统进入时钟中断的次数也就越多。

——原文

定时器的运作机制

用户创建软件定时器时,系统会根据 rt_tick 时间及用户设置的定时确定该定时器的唤醒时间 timeout,并将该定时器控制块挂入软件定时器列表 rt_soft_timer_list。

rt_tick 是一个32位无符号变量,用于记录系统当前运行的时间,每经过一个时钟节拍,rt_tick 就加1。

扫描二维码关注公众号,回复: 13826530 查看本文章

rt_soft_timer_list 存放定时器任务,里面按照时间升序存放着所有被激活的定时器。

每新增一个定时器 ,其超时函数唤醒时间 timeout 就等于当前系统时间 rt_tick + 定时时间 time。

定时器控制块

struct rt_timer
{
    
    
    struct rt_object parent;                            /**< 继承自rt_object */

    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;                      /**< 定时器实际超时时的节拍数 */
};

定时器的超时函数

定时器超时函数是定时器在计数值到达设定值时会自动运行的函数,这个函数需要自己定义,定时器超时函数存在两种情况:

  1. 超时函数在(系统时钟)中断上下文环境中执行(硬件定时器),需要在定时器控制块中指定RT_TIMER_FLAG_HARD_TIMER;
  2. 超时函数在线程的上下文环境中运行,需要在定时器控制块中指定RT_TIMER_FLAG_SOFT_TIMER。

定时器函数接口

创建定时器 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);

调用该函数接口后,内核首先从动态内存堆中分配一个定时器控制块,然后对该控制块进行基本的初始化。
当指定的flag为RT_IMER_FLAG_HARD_TIMER时,如果定时器超时,定时器的回调函数将在时钟中断的服务例程上下文中被调用;当指定的flag为RT_TIMER_FLAG_SOFT_TIMER时,如果定时器超时,定时器的回调函数将在系统时钟timer线程的上下文中被调用。

参数 描述
name 定时器的名称
timeout 定时器超市化函数指针
parameter 定时器超时函数入口参数
time 超时时间,单位为系统节拍
flag 参数选项

删除定时器 rt_timer_delete

rt_err_t rt_timer_delete(rt_timer_t timer);

调用这个函数接口后,系统会把这个定时器从rt_timer_list链表中删除,然后释放相应的定 时器控制块占有的内存。

参数 描述
timer 定时器句柄

初始化定时器 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);

使用该函数接口时会初始化相应的定时器控制块,初始化相应的定时器名称,定时器超时函数等等。
当指定的flag为RT_IMER_FLAG_HARD_TIMER时,如果定时器超时,定时器的回调函数将在时钟中断的服务例程上下文中被调用;当指定的flag为RT_TIMER_FLAG_SOFT_TIMER时,如果定时器超时,定时器的回调函数将在系统时钟timer线程的上下文中被调用。

参数 描述
timer 定时器句柄
name 定时器的名称
timeout 定时器超市化函数指针
parameter 定时器超时函数入口参数
time 超时时间,单位为系统节拍
flag 参数选项

启动定时器 rt_timer_start

rt_err_t rt_timer_start(rt_timer_t timer);

调用定时器启动函数接口后,定时器的状态将更改为激活状态(RT_TIMER_FLAG_ACTIVATED),并按照超时顺序插入到rt_timer_list队列链表中。

参数 描述
timer 定时器句柄

停止定时器 rt_timer_stop

rt_err_t rt_timer_stop(rt_timer_t timer);

调用定时器停止函数接口后,定时器状态将更改为停止状态,并从rt_timer_list链表中脱离出来不参与定时器超时检查。当一个(周期性)定时器超时时,也可以调用这个函数接口停止这个(周期性)定时器本身。

参数 描述
timer 定时器句柄

控制定时器 rt_timer_control

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

控制定时器函数接口可根据命令类型参数,来查看或改变定时器的设置。当前支持四个命令接口,分别是设置定时时间,查看定时时间,设置单次触发,设置周期触发;

参数 描述
timer 定时器句柄
cmd 用于控制定时器的命令
arg 与cmd相对应的控制命令参数

定时器实验

实验比较简单,定义两个定时器,分别使用单次定时模式和周期定时模式。

#include "board.h"
#include "rtthread.h"


// 定义软件定时器控制块
static rt_timer_t tmr1 = RT_NULL;
static rt_timer_t tmr2 = RT_NULL;

// 可能用到的全局变量
static rt_uint32_t TmrCb_Cnt1 = 0;
static rt_uint32_t TmrCb_Cnt2 = 0;



/******************************************************************************
* @ 函数名  : timer1_callback
* @ 功  能  : 定时器1回调函数
* @ 参  数  : parameter 外部传入的参数
* @ 返回值  : 无
******************************************************************************/
static void timer1_callback(void *parameter)
{
    
    
	rt_uint32_t tick_num1 = 0;
	
	TmrCb_Cnt1++;
	
	tick_num1 = (rt_uint32_t)rt_tick_get();   // 获取滴答定时器的计数值
	
	rt_kprintf("timer1_callback 函数被执行%d次\n", TmrCb_Cnt1);
	rt_kprintf("滴答定时器的数值=%d\n", tick_num1);
}

/******************************************************************************
* @ 函数名  : timer2_callback
* @ 功  能  : 定时器2回调函数
* @ 参  数  : parameter 外部传入的参数
* @ 返回值  : 无
******************************************************************************/
static void timer2_callback(void *parameter)
{
    
    
		rt_uint32_t tick_num2 = 0;
	
	TmrCb_Cnt2++;
	
	tick_num2 = (rt_uint32_t)rt_tick_get();   // 获取滴答定时器的计数值
	
	rt_kprintf("timer2_callback 函数被执行%d次\n", TmrCb_Cnt2);
	rt_kprintf("滴答定时器的数值=%d\n", tick_num2);
}

int main(void)
{
    
    
	// 硬件初始化和RTT的初始化已经在component.c中的rtthread_startup()完成

	// 创建一个软件定时器
	tmr1 =  rt_timer_create("tmr1",                 // 软件定时器名称       
						timer1_callback,            // 软件定时器超时函数
						RT_NULL,                    // 超时函数参数
						3000,                       // 超时时间
						RT_TIMER_FLAG_ONE_SHOT |   
						RT_TIMER_FLAG_SOFT_TIMER);  // 软件定时器模式,一次模式
					
	
	// 启动定时器
	if(tmr1 != RT_NULL)
		rt_timer_start(tmr1);
							
	// 创建一个软件定时器
	tmr2 =  rt_timer_create("tmr2",                 // 软件定时器名称       
						timer2_callback,            // 软件定时器超时函数
						RT_NULL,                    // 超时函数参数
						1000,                       // 超时时间
						RT_TIMER_FLAG_PERIODIC |   
						RT_TIMER_FLAG_SOFT_TIMER);  // 软件定时器模式,周期模式
					
	
	// 启动定时器
	if(tmr2 != RT_NULL)
		rt_timer_start(tmr2);
}

试验现象

定时器1超时函数只执行了一次,而定时器2超时函数在周期性运行。

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_43772810/article/details/124191497