系列文章目录
前言
硬件定时器
CPU内部自带的定时器模块,通过初始化、配置可以实现定时,定时时间到以后就会执行相应的定时器中断处理函数。硬件定时器一般都带有其它功能,比如PWM输出、输入捕获等等功能。但是缺点是硬件定时器数量少!!
软件定时器
软件定时器允许设置一段时间,当设置的时间到达之后就执行指定的功能函数,被定时器调用的这个功能函数叫做定时器的回调函数。回调函数的两次执行间隔叫做定时器的定时周期,简而言之,当定时器的定时周期到了以后就会执行回调函数。
一、回调函数
软件定时器的回调函数是在定时器服务任务中执行的,所以一定不能在回调函数中调用任
何会阻塞任务的 API 函数!
FreeRTOS 提供了很多定时器有关的 API 函数,这些 API 函数大多都使用 FreeRTOS
的队列发送命令给定时器服务任务。这个队列叫做定时器命令队列。定时器命令队列是提供给FreeRTOS 的软件定时器使用的,用户不能直接访问!
二、定时器的配置
1.软件定时器分类
软件定时器分两种:单次定时器和周期定时器,单次定时器的话定时器回调函数就执行一
次,比如定时 1s,当定时时间到了以后就会执行一次回调函数,然后定时器就会停止运行。对于单次定时器我们可以再次手动重新启动(调用相应的 API 函数即可),但是单次定时器不能自动重启。相反的,周期定时器一旦启动以后就会在执行完回调函数以后自动的重新启动,这样回调函数就会周期性的执行。
2.定时器相关API函数
1.创建软件定时器
1、函数 xTiemrCreate()
此函数用于创建一个软件定时器,所需要的内存通过动态内存管理方法分配。新创建的软
件 定 时 器 处 于 休 眠 状 态 , 也 就 是 未 运 行 的 。 函 数 xTimerStart() 、 xTimerReset() 、xTimerStartFromISR() 、 xTimerResetFromISR() 、 xTimerChangePeriod() 和xTimerChangePeriodFromISR()可以使新创建的定时器进入活动状态
TimerHandle_t xTimerCreate( const char * const pcTimerName,
TickType_t xTimerPeriodInTicks,
UBaseType_t uxAutoReload,
void * pvTimerID,
TimerCallbackFunction_t pxCallbackFunction )
参数:
pcTimerName: 软件定时器名字,名字是一串字符串,用于调试使用。
xTimerPeriodInTicks : 软件定时器的定时器周期, 单位是时钟节拍数。可以借助
portTICK_PERIOD_MS 将 ms 单位转换为时钟节拍数。举个例子,定时器的周期为 100 个时钟节拍的话,那么 xTimerPeriodInTicks 就为100,当定时器周期为 500ms 的时候 xTimerPeriodInTicks 就可以设置为(500/ portTICK_PERIOD_MS)。
uxAutoReload: 设置定时器模式,单次定时器还是周期定时器?当此参数为 pdTRUE
的时候表示创建的是周期定时器。如果为 pdFALSE 的话表示创建的是单次定时器。
pvTimerID: 定时器 ID 号,一般情况下每个定时器都有一个回调函数,当定时器定
时周期到了以后就会执行这个回调函数。但是 FreeRTOS 也支持多个定时器共用同一个回调函数,在回调函数中根据定时器的 ID 号来处理不同的定时器。
pxCallbackFunction: 定时器回调函数,当定时器定时周期到了以后就会调用这个函数。
返回值:
NULL: 软件定时器创建失败。
其他值: 创建成功的软件定时器句柄。
2.开启软件定时器
前面说过,最开始设置的软件定时器是处于休眠状态的,所有需要开启软件定时器。
1、函数 xTimerStart()
启动软件定时器,函数 xTimerStartFromISR()是这个函数的中断版本,可以用在中断服务函
数中。如果软件定时器没有运行的话调用函数 xTimerStart()就会计算定时器到期时间,如果软件定时器正在运行的话调用函数 xTimerStart()的结果和 xTimerReset()一样。
BaseType_t xTimerStart( TimerHandle_t xTimer,
TickType_t xTicksToWait )
参数:
xTimer: 要开启的软件定时器的句柄。
xTicksToWait: 设置阻塞时间(涉及到入队的问题),调用函数 xTimerStart()开启软件定时器其实就是向定时器命令队列发送一条 tmrCOMMAND_START 命令,既然是向队列发送消息,那肯定会涉及到入队阻塞时间的设置。
返回值:
pdPASS: 软件定时器开启成功,其实就是命令发送成功。
pdFAIL: 软件定时器开启失败,命令发送失败。
2、函数 xTimerStartFromISR()
此函数是函数 xTimerStart()的中断版本,用在中断服务函数中。
BaseType_t xTimerStartFromISR( TimerHandle_t xTimer,
BaseType_t * pxHigherPriorityTaskWoken );
参数:
xTimer: 要开启的软件定时器的句柄。
pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值函数会自动设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。
返回值:
pdPASS: 软件定时器开启成功,其实就是命令发送成功。
pdFAIL: 软件定时器开启失败,命令发送失败。
3.停止软件定时器
1、函数 xTimerStop()
此函数用于停止一个软件定时器,此函数用于任务中,不能用在中断服务函数中!
BaseType_t xTimerStop ( TimerHandle_t xTimer,
TickType_t xTicksToWait )
参数:
xTimer: 要停止的软件定时器的句柄。
xTicksToWait: 设置阻塞时间,调用函数 xTimerStop()停止软件定时器其实就是向定时器命令队列发送一条 tmrCOMMAND_STOP 命令,既然是向队列发送消息,那肯定
会涉及到入队阻塞时间的设置。
返回值:
pdPASS: 软件定时器停止成功,其实就是命令发送成功。
pdFAIL: 软件定时器停止失败,命令发送失败。
2、函数 xTimerStopFromISR()
此函数是 xTimerStop()的中断版本,此函数用于中断服务函数中!
BaseType_t xTimerStopFromISR( TimerHandle_t xTimer,
BaseType_t * pxHigherPriorityTaskWoken );
参数:
xTimer: 要停止的软件定时器句柄。
pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值函数会
自动设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。
返回值:
pdPASS: 软件定时器停止成功,其实就是命令发送成功。
pdFAIL: 软件定时器停止失败,命令发送失败。
4.复位软件定时器
复位很简单,就是重新运行,持续5秒。
1、函数 xTimerReset()
复位一个软件定时器,此函数只能用在任务中,不能用于中断服务函数!
BaseType_t xTimerReset( TimerHandle_t xTimer,
TickType_t xTicksToWait )
参数:
xTimer: 要复位的软件定时器的句柄。
xTicksToWait: 设置阻塞时间,调用函数 xTimerReset ()开启软件定时器其实就是向定时器命令队列发送一条 tmrCOMMAND_RESET 命令,既然是向队列发送消息,那肯定会涉及到入队阻塞时间的设置。
返回值:
pdPASS: 软件定时器复位成功,其实就是命令发送成功。
pdFAIL: 软件定时器复位失败,命令发送失败。
2、函数 xTimerResetFromISR()
此函数是 xTimerReset()的中断版本,此函数用于中断服务函数中!
BaseType_t xTimerResetFromISR( TimerHandle_t xTimer,
BaseType_t * pxHigherPriorityTaskWoken );
参数:
xTimer: 要复位的软件定时器的句柄。
pxHigherPriorityTaskWoken: 记退出此函数以后是否进行任务切换,这个变量的值函数会自动设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。
返回值:
pdPASS: 软件定时器复位成功,其实就是命令发送成功。
pdFAIL: 软件定时器复位失败,命令发送失败
三、软件定时器实验
创建定时器:
开启定时器:
定时器回调函数:
//¿ªÊ¼ÈÎÎñÈÎÎñº¯Êý
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //½øÈëÁÙ½çÇø
//´´½¨Èí¼þÖÜÆÚ¶¨Ê±Æ÷
AutoReloadTimer_Handle=xTimerCreate((const char* )"AutoReloadTimer",
(TickType_t )1000,
(UBaseType_t )pdTRUE,
(void* )1,
(TimerCallbackFunction_t)AutoReloadCallback); //ÖÜÆÚ¶¨Ê±Æ÷£¬ÖÜÆÚ1s(1000¸öʱÖÓ½ÚÅÄ)£¬ÖÜÆÚģʽ
//´´½¨µ¥´Î¶¨Ê±Æ÷
OneShotTimer_Handle=xTimerCreate((const char* )"OneShotTimer",
(TickType_t )2000,
(UBaseType_t )pdFALSE,
(void* )2,
(TimerCallbackFunction_t)OneShotCallback); //µ¥´Î¶¨Ê±Æ÷£¬ÖÜÆÚ2s(2000¸öʱÖÓ½ÚÅÄ)£¬µ¥´Îģʽ
//´´½¨¶¨Ê±Æ÷¿ØÖÆÈÎÎñ
xTaskCreate((TaskFunction_t )timercontrol_task,
(const char* )"timercontrol_task",
(uint16_t )TIMERCONTROL_STK_SIZE,
(void* )NULL,
(UBaseType_t )TIMERCONTROL_TASK_PRIO,
(TaskHandle_t* )&TimerControlTask_Handler);
vTaskDelete(StartTask_Handler); //ɾ³ý¿ªÊ¼ÈÎÎñ
taskEXIT_CRITICAL(); //Í˳öÁÙ½çÇø
}
//TimerControlµÄÈÎÎñº¯Êý
void timercontrol_task(void *pvParameters)
{
u8 key,num;
while(1)
{
//Ö»ÓÐÁ½¸ö¶¨Ê±Æ÷¶¼´´½¨³É¹¦Á˲ÅÄܶÔÆä½øÐвÙ×÷
if((AutoReloadTimer_Handle!=NULL)&&(OneShotTimer_Handle!=NULL))
{
key = KEY_Scan(0);
switch(key)
{
case WKUP_PRES: //µ±key_up°´ÏµĻ°´ò¿ªÖÜÆÚ¶¨Ê±Æ÷
xTimerStart(AutoReloadTimer_Handle,0); //¿ªÆôÖÜÆÚ¶¨Ê±Æ÷
printf("¿ªÆô¶¨Ê±Æ÷1\r\n");
break;
case KEY0_PRES: //µ±key0°´ÏµĻ°´ò¿ªµ¥´Î¶¨Ê±Æ÷
xTimerStart(OneShotTimer_Handle,0); //¿ªÆôµ¥´Î¶¨Ê±Æ÷
printf("¿ªÆô¶¨Ê±Æ÷2\r\n");
break;
case KEY1_PRES: //µ±key1°´Ï»°¾Í¹Ø±Õ¶¨Ê±Æ÷
xTimerStop(AutoReloadTimer_Handle,0); //¹Ø±ÕÖÜÆÚ¶¨Ê±Æ÷
xTimerStop(OneShotTimer_Handle,0); //¹Ø±Õµ¥´Î¶¨Ê±Æ÷
printf("¹Ø±Õ¶¨Ê±Æ÷1ºÍ2\r\n");
break;
}
}
num++;
if(num==50) //ÿ500msLED0ÉÁ˸һ´Î
{
num=0;
LED0=!LED0;
}
vTaskDelay(10); //ÑÓʱ10ms£¬Ò²¾ÍÊÇ10¸öʱÖÓ½ÚÅÄ
}
}
//ÖÜÆÚ¶¨Ê±Æ÷µÄ»Øµ÷º¯Êý
void AutoReloadCallback(TimerHandle_t xTimer)
{
static u8 tmr1_num=0;
tmr1_num++; //ÖÜÆÚ¶¨Ê±Æ÷Ö´ÐдÎÊý¼Ó1
LCD_ShowxNum(70,111,tmr1_num,3,16,0x80); //ÏÔʾÖÜÆÚ¶¨Ê±Æ÷µÄÖ´ÐдÎÊý
LCD_Fill(6,131,114,313,lcd_discolor[tmr1_num%14]);//Ìî³äÇøÓò
}
//µ¥´Î¶¨Ê±Æ÷µÄ»Øµ÷º¯Êý
void OneShotCallback(TimerHandle_t xTimer)
{
static u8 tmr2_num = 0;
tmr2_num++; //ÖÜÆÚ¶¨Ê±Æ÷Ö´ÐдÎÊý¼Ó1
LCD_ShowxNum(190,111,tmr2_num,3,16,0x80); //ÏÔʾµ¥´Î¶¨Ê±Æ÷Ö´ÐдÎÊý
LCD_Fill(126,131,233,313,lcd_discolor[tmr2_num%14]); //Ìî³äÇøÓò
LED1=!LED1;
printf("¶¨Ê±Æ÷2ÔËÐнáÊø\r\n");
}
周期定时器开启后会周期性执行,单次定时器开启一次只会执行一次。
总结
软件定时器分类周期定时器和单次定时器,一定要知道是如何创立的,其功能是什么,怎么开启,怎么关闭,以及相对应的API函数怎么使用。