本文参考自[野火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。
rt_soft_timer_list 存放定时器任务,里面按照时间升序存放着所有被激活的定时器。
每新增一个定时器 ,其超时函数唤醒时间 timeout 就等于当前系统时间 rt_tick + 定时时间 time。
定时器控制块
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;
};
定时器的超时函数
定时器超时函数是定时器在计数值到达设定值时会自动运行的函数,这个函数需要自己定义,定时器超时函数存在两种情况:
- 超时函数在(系统时钟)中断上下文环境中执行(硬件定时器),需要在定时器控制块中指定RT_TIMER_FLAG_HARD_TIMER;
- 超时函数在线程的上下文环境中运行,需要在定时器控制块中指定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链表中删除,然后释放相应的定 时器控制块占有的内存。
初始化定时器 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队列链表中。
停止定时器 rt_timer_stop
rt_err_t rt_timer_stop (rt_timer_t timer);
调用定时器停止函数接口后,定时器状态将更改为停止状态,并从rt_timer_list链表中脱离出来不参与定时器超时检查。当一个(周期性)定时器超时时,也可以调用这个函数接口停止这个(周期性)定时器本身。
控制定时器 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;
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);
}
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)
{
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超时函数在周期性运行。
|