定时器概述
定时器运行过程
定时器就像一个闹钟,它有超时时间、函数、是否为周期性这三个部分。
- 超时时间:什么时候到时间,就像闹钟响起
- 函数:闹钟响起,要干什么
- 是否为周期性:这个闹钟只响一次(单次触发)还是每天都响(周期性触发)
定时器的简化运行过程如下图:
- (t1)使用定时器,需要先创建一个定时器,创建时会规定以上的三个部分,但这时定时器并不会开始计时。
- (t2)之后需要开启定时器,并基于该时刻进行计时。
- (t2->t3过程)FreeRTOS有一个tick中断,每次进入中断,都会在中断中判断是否有定时器超时,如果超时就进行定时器的触发。
- (t3)当定时器触发之后,会唤醒一个任务(守护任务)来去执行创建定时器时传入的函数。至此一次定时完成,如果设置为了周期性触发,之后就会重复上述过程,如果设置为单次触发,之后将不会再触发。
定时器的状态转换
定时器只有两种状态:Dormant(休眠态)、Running(运行态),下面是定时器的状态转换过程
- 当创建定时器时,定时器进入的是休眠态,这时定时器不进行计时。
- 当开启定时器、复位定时器、重置定时器周期时,定时器从休眠态转换到了运行态,这时定时器开始计时。
- 当计时完成之后,如果是单次触发,定时器就会进入休眠态,不再计时;如果是周期性触发,定时器保持运行态,继续计时、触发。
- 除此之外,在定时器运行态时,也可以调用关闭定时器函数,来让定时器从运行态转为休眠态
定时器相关函数的实质
在上述的状态转换过程中,调用了很多的定时器相关的函数。这些函数实质是将一些命令发送给定时器命令队列,之后守护任务不再阻塞并读取定时器命令队列,根据这些命令执行相应的操作。具体的交互模型如下:
相关配置
在使用定时器相关函数之前,需要打开红开关,具体操作如下:
相关函数
创建定时器
函数声明如下:
TimerHandle_t xTimerCreate( const char * const pcTimerName,
const TickType_t xTimerPeriodInTicks,
const BaseType_t xAutoReload,
void * const pvTimerID,
TimerCallbackFunction_t pxCallbackFunction )
返回值:定时器句柄
pcTimerName:名字
xTimerPeriodInTicks:周期
xAutoReload:单次触发(pdFALSE)、周期性触发(pdTRUE)
pvTimerID:可写NULL
pxCallbackFunction:回调函数
启动定时器
/* 这是一个宏,开启定时器 */
xTimerStart( xTimer, xTicksToWait )
/* 宏定义 */
#define xTimerStart( xTimer, xTicksToWait ) \
xTimerGenericCommand( ( xTimer ), \
tmrCOMMAND_START, \
( xTaskGetTickCount() ), \
NULL, \
( xTicksToWait ) ) \
/* 实际调用函数 */
BaseType_t xTimerGenericCommand( TimerHandle_t xTimer,
const BaseType_t xCommandID,
const TickType_t xOptionalValue,
BaseType_t * const pxHigherPriorityTaskWoken,
const TickType_t xTicksToWait )
xTimer:定时器句柄
xTicksToWait:超时,有超时是因为函数实质是发送指令给队列
验证实验
实验内容为:开启定时器,每0.1s翻转一次变量值,用逻辑分析仪来查看变量状态。
具体代码实现如下:
/* 定时器回调函数 */
/* 这里面不用在while(1)中跑 */
char i;
void TimFun( TimerHandle_t xTimer ){
i = !i;
}
int main( void )
{
TimerHandle_t xTimeHandle_Test;/* 定时器句柄 */
prvSetupHardware();
SerialPortInit();
printf("UART TEST\r\n");
/* 创建定时器,周期为100*1ms = 0.1s */
xTimeHandle_Test = xTimerCreate("time",100,pdTRUE,NULL,TimFun);
/* 开启定时器 */
xTimerStart(xTimeHandle_Test,0);
xTaskCreate(TaskAFunction,"TaskA",100,(void*)NULL,2,NULL);/* taskA空跑while(1) */
vTaskStartScheduler();
//xTimerStart
return 0;
}
运行结果如下:
可以看到,变量i每隔0.1s进行了一次翻转,由于taskA空跑while(1),说明守护任务与TaskA是相互独立的。