定时器,是指从指定的时刻开始,经过一定的指定时间后触发一个事件,例如定个时间提醒第二天能够按时起床。定时器有硬件定时器和软件定时器之分:
1)硬件定时器是芯片本身提供的定时功能。一般是由外部晶振提供给芯片输入时钟,芯片向软件模块提供一组配置寄存器,接受控制输入,到达设定时间值后芯片中断控制器产生时钟中断。硬件定时器的精度一般很高,可以达到纳秒级别,并且是中断触发方式。
2)软件定时器是由操作系统提供的一类系统接口,它构建在硬件定时器基础之上,使系统能够提供不受数目限制的定时器服务。
RT-Thread 操作系统提供软件实现的定时器,以时钟节拍(OS Tick)的时间长度为单位,即定时数值必须是 OS Tick 的整数倍,例如一个 OS Tick 是 10ms,那么上层软件定时器只能是 10ms,20ms,100ms 等,而不能定时为 15ms。RT-Thread 的定时器也基于系统的节拍,提供了基于节拍整数倍的定时能力。
RT-Thread 定时器介绍
RT-Thread 的定时器提供两类定时器机制:第一类是单次触发定时器,这类定时器在启动后只会触发一次定时器事件,然后定时器自动停止。第二类是周期触发定时器,这类定时器会周期性的触发定时器事件,直到用户手动的停止,否则将永远持续执行下去。
另外,根据超时函数执行时所处的上下文环境,RT-Thread 的定时器可以分为 HARD_TIMER 模式与 SOFT_TIMER 模式,如下图。
HARD_TIMER 模式的定时器超时函数在中断上下文环境中执行,可以在初始化 / 创建定时器时使用参数 RT_TIMER_FLAG_HARD_TIMER 来指定。
在中断上下文环境中执行时,对于超时函数的要求与中断服务例程的要求相同:执行时间应该尽量短,执行时不应导致当前上下文挂起、等待。例如在中断上下文中执行的超时函数它不应该试图去申请动态内存、释放动态内存等。
RT-Thread 定时器默认的方式是 HARD_TIMER 模式,即定时器超时后,超时函数是在系统时钟中断的上下文环境中运行的。在中断上下文中的执行方式决定了定时器的超时函数不应该调用任何会让当前上下文挂起的系统函数;也不能够执行非常长的时间,否则会导致其他中断的响应时间加长或抢占了其他线程执行的时间。
SOFT_TIMER 模式可配置,通过宏定义 RT_USING_TIMER_SOFT 来决定是否启用该模式。该模式被启用后,系统会在初始化时创建一个 timer 线程,然后 SOFT_TIMER 模式的定时器超时函数在都会在 timer 线程的上下文环境中执行。可以在初始化 / 创建定时器时使用参数 RT_TIMER_FLAG_SOFT_TIMER 来指定设置 SOFT_TIMER 模式。
1.定时器总体的框图
2.创建与删除定时器(分动态创建与静态)
创建动态定时器
当动态创建一个定时器时,可使用下面的函数接口:
rt_timer_t rt_timer_create(const char* name,
void (*timeout)(void* parameter),
void* parameter,
rt_tick_t time,
rt_uint8_t flag);
调用该函数接口后,内核首先从动态内存堆中分配一个定时器控制块,然后对该控制块进行基本的初始化。其中的各参数和返回值说明详见下表:
参数 | 描述 |
---|---|
name | 定时器的名称 |
void (timeout) (void parameter) | 定时器超时函数指针(当定时器超时时,系统会调用这个函数) |
parameter | 定时器超时函数的入口参数(当定时器超时时,调用超时回调函数会把这个参数做为入口参数传递给超时函数) |
time | 定时器的超时时间,单位是时钟节拍 |
flag | 定时器创建时的参数,支持的值包括单次定时、周期定时、硬件定时器、软件定时器等(可以用 “或” 关系取多个值) |
返回 | —— |
RT_NULL | 创建失败(通常会由于系统内存不够用而返回 RT_NULL) |
定时器的句柄 | 定时器创建成功 |
删除动态定时器
系统不再使用动态定时器时,可使用下面的函数接口:
rt_err_t rt_timer_delete(rt_timer_t timer);
调用这个函数接口后,系统会把这个定时器从 rt_timer_list 链表中删除,然后释放相应的定时器控制块占有的内存,其中的各参数和返回值说明详见下表:
参数 | 描述 |
---|---|
timer | 定时器句柄,指向要删除的定时器 |
返回 | —— |
RT_EOK | 删除成功(如果参数 timer 句柄是一个 RT_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 | 定时器的名称 |
void (timeout) (void parameter) | 定时器超时函数指针(当定时器超时时,系统会调用这个函数) |
parameter | 定时器超时函数的入口参数(当定时器超时时,调用超时回调函数会把这个参数做为入口参数传递给超时函数) |
time | 定时器的超时时间,单位是时钟节拍 |
flag | 定时器创建时的参数,支持的值包括单次定时、周期定时、硬件定时器、软件定时器(可以用 “或” 关系取多个值),详见创建定时器小节 |
脱离静态定时器(删除)
当一个静态定时器不需要再使用时,可以使用下面的函数接口:
rt_err_t rt_timer_detach(rt_timer_t timer);
脱离定时器时,系统会把定时器对象从内核对象容器中脱离,但是定时器对象所占有的内存不会被释放,其中的各参数和返回值说明详见表下表:
参数 | 描述 |
---|---|
timer | 定时器句柄,指向要脱离的定时器控制块 |
返回 | —— |
RT_EOK | 脱离成功 |
3.启动定时器
当定时器被创建或者初始化以后,并不会被立即启动,必须在调用启动定时器函数接口后,才开始工作,启动定时器函数接口如下:
rt_err_t rt_timer_start(rt_timer_t timer);
调用定时器启动函数接口后,定时器的状态将更改为激活状态(RT_TIMER_FLAG_ACTIVATED),并按照超时顺序插入到 rt_timer_list 队列链表中,其中的各参数和返回值说明详见下表:
参数 | 描述 |
---|---|
timer | 定时器句柄,指向要启动的定时器控制块 |
返回 | —— |
RT_EOK | 启动成功 |
4.停止定时器
启动定时器以后,若想使它停止,可以使用下面的函数接口:
rt_err_t rt_timer_stop(rt_timer_t timer);
调用定时器停止函数接口后,定时器状态将更改为停止状态,并从 rt_timer_list 链表中脱离出来不参与定时器超时检查。当一个(周期性)定时器超时时,也可以调用这个函数接口停止这个(周期性)定时器本身,其中的各参数和返回值说明详见下表:
参数 | 描述 |
---|---|
timer | 定时器句柄,指向要停止的定时器控制块 |
返回 | —— |
RT_EOK | 成功停止定时器 |
- RT_ERROR | timer 已经处于停止状态 |
5.定时器的控制
RT-Thread 也额外提供了定时器控制函数接口,以获取或设置更多定时器的信息。控制定时器函数接口如下:
rt_err_t rt_timer_control(rt_timer_t timer, rt_uint8_t cmd, void* arg);
控制定时器函数接口可根据命令类型参数,来查看或改变定时器的设置,其中的各参数和返回值说明详见下表:
参数 | 描述 |
---|---|
timer | 定时器句柄,指向要控制的定时器控制块 |
cmd | 用于控制定时器的命令,当前支持四个命令,分别是设置定时时间,查看定时时间,设置单次触发,设置周期触发 |
arg | 与 cmd 相对应的控制命令参数 比如,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 /* 设置定时器为周期型定时器 */