基于STM32F103的FreeRTOS系列(十四)·软件定时器
目录
1. 简介
2. 软件定时器的精度
3. 软件定时器控制块
4. 软件定时器API函数
4.1 软件定时器创建函数 xTimerCreate()
4.2 软件定时器启动函数
4.2.1 xTimerStart()
4.2.2 xTimerStartFromISR()
4.3 软件定时器停止函数
4.3.1 xTimerStop()
4.3.2 xTimerStopFromISR()
4.4 软件定时器删除函数 xTimerDelete()
5. 代码编写
5.1 开始前准备
5.2 应用任务创建
5.2.1 LED闪烁任务
5.2.2 定时器1回调函数
5.2.3 定时器2回调函数
5.2.4 宏定义相关
5.3 开始任务
5.3.1 开始任务函数
5.3.2 宏定义相关
5.4 主函数
1. 简介
定时器,是指从指定的时刻开始,经过一个指定时间,然后触发一个超时事件,用户可以自定义定时器的周期与频率。相当于一个闹钟,到点干什么。
软件定时器并不依赖于硬件定时器的中断信号,而是通过系统定期的时间片或任务调度机制来检查定时器是否到期。当软件定时器到期时,会触发预定的回调函数或执行相应的任务。
注意,回调函数不要使用带有阻塞状态的函数。
FreeRTOS操作系统提供软件定时器功能,软件定时器的使用相当于扩展了定时器的数量,允许创建更多的定时业务。
FreeRTOS软件定时器功能上支持:
- 裁剪:能通过宏关闭软件定时器功能。
- 软件定时器创建。
- 软件定时器启动。
- 软件定时器停止。
- 软件定时器复位。
- 软件定时器删除。
软件定时器有单次和周期两种模式:
2. 软件定时器的精度
通常软件定时器以系统节拍周期为计时单位,系统节拍配置为configTICK_RATE_HZ,该宏在FreeRTOSConfig.h中有定义,默认是1000,意为1s内跳动1000次,也就是系统节拍周期为1ms。
软件定时器的所定时数值必须是这个节拍周期的整数倍。系统节拍越小,精度也就越高,但是系统开销也将越大,因为这代表在 1 秒中系统进入时钟中断的次数也就越多。
使用软件定时器时候要注意以下几点:
(1)软件定时器的回调函数中应快进快出,绝对不允许使用任何可能引软件定时器起任务挂起或者阻塞的API接口,在回调函数中也绝对不允许出现死循环。
(2)软件定时器使用了系统的一个队列和一个任务资源,软件定时器任务的优先级默认为configTIMER_TASK_PRIORITY,为了更好响应,该优先级应设置为所有任务中最高的优先级。
(3)创建单次软件定时器,该定时器超时执行完回调函数后,系统会自动删除该软件定时器,并回收资源。
(4)定时器任务的堆栈大小默认为 configTIMER_TASK_STACK_DEPTH 个字节。
3. 软件定时器控制块
找到timers.c文件:
翻译一下:
/* 定时器结构体定义 */
typedef struct tmrTimerControl
{
const char *pcTimerName; /*<< 定时器名称文本。这在内核中没有被使用,仅仅是为了便于调试。 */ /*lint !e971 不合格的字符类型仅允许用于字符串和单个字符。*/
ListItem_t xTimerListItem; /*<< 标准链表项,所有内核功能中都使用链表项来管理事件。 */
TickType_t xTimerPeriodInTicks; /*<< 定时器的周期,以滴答(tick)为单位。即定时器到期的频率。 */
UBaseType_t uxAutoReload; /*<< 如果定时器应该在过期后自动重新加载(即变为周期性定时器),则设置为 pdTRUE;如果是一次性定时器,则设置为 pdFALSE。 */
void *pvTimerID; /*<< 定时器的标识符,可以用来区分多个定时器,即使它们使用相同的回调函数。 */
TimerCallbackFunction_t pxCallbackFunction; /*<< 定时器到期时调用的回调函数。 */
#if( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxTimerNumber; /*<< 由跟踪工具(如 FreeRTOS+Trace)分配的 ID,用于定时器的跟踪。 */
#endif
#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
uint8_t ucStaticallyAllocated; /*<< 如果定时器是静态分配的,设置为 pdTRUE。这样就不会在定时器被删除时尝试再次释放内存。 */
#endif
} xTIMER;
每个成员所代表的含义:
- pcTimerName:定时器的名称,仅用于调试,内核代码不会使用。
- xTimerListItem:定时器所在的链表项,使用链表来管理定时器。
- xTimerPeriodInTicks:定时器的周期,以滴答(tick)为单位,表示定时器多久会过期。
- uxAutoReload:标记定时器是否为自动重载定时器(周期性定时器)。pdTRUE 表示自动重载,pdFALSE 表示一次性定时器。
- pvTimerID:定时器的自定义 ID,可以用来在回调中识别不同的定时器。
- pxCallbackFunction:定时器过期时调用的回调函数。
- uxTimerNumber(条件编译):如果启用了跟踪功能(configUSE_TRACE_FACILITY == 1),则为定时器分配一个 ID,用于跟踪工具。
- ucStaticallyAllocated(条件编译):如果定时器是静态分配的(即在创建时就分配了内存),则设置为 pdTRUE,以避免在定时器删除时释放内存。
4. 软件定时器API函数
4.1 软件定时器创建函数 xTimerCreate()
TimerHandle_t xTimerCreate( const char * const pcTimerName,
const TickType_t xTimerPeriodInTicks,
const UBaseType_t uxAutoReload,
void * const pvTimerID,
TimerCallbackFunction_t pxCallbackFunction )
函数原型 | TimerHandle_t xTimerCreate( const char * const pcTimerName, const TickType_t xTimerPeriodInTicks, const UBaseType_t uxAutoReload, void * const pvTimerID, TimerCallbackFunction_t pxCallbackFunction ) | |
功能 | 软件定时器创建函数 | |
参数 | pcTimerName | 软件定时器的名字,调试用的 |
xTimerPeriodInTicks | 软件定时器周期,单位为系统节拍周期 | |
uxAutoReload | 自动重装,即设置定时器模式,周期运行还是单次运行。pdTRUE —— 周期运行,pdFALSE —— 单次运行 | |
pvTimerID | 软件定时器 ID, 数字形式。 该 ID 典型的用法是当一个回调函数分配给一个或者多个软件定时器时,在回调函数里面根据 ID 号来处理不同的软件定时器 | |
pxCallbackFunction | 软件定时器的回调函数, 当定时时间到达的时候就会调用这个函数 |
注意,这是动态创建方法,静态创建函数:xTimerCreateStatic()
4.2 软件定时器启动函数
4.2.1 xTimerStart()
软件定时器创建完成后处于休眠状态,需要启动函数将软件定时器活动起来:
#define xTimerStart( xTimer, xTicksToWait )
xTimerGenericCommand( ( xTimer ), //要启动的定时器句柄
tmrCOMMAND_START, //表示启动定时器的命令
( xTaskGetTickCount() ), //该函数返回当前系统的 tick 值,即系统自启动以来的时钟节拍数。
NULL,
( xTicksToWait ) )//这个参数指定从现在开始,等待多少个系统 ticks 后启动定时器。
4.2.2 xTimerStartFromISR()
#define xTimerStartFromISR( xTimer, pxHigherPriorityTaskWoken )
xTimerGenericCommand( ( xTimer ),
tmrCOMMAND_START_FROM_ISR,
( xTaskGetTickCountFromISR() ),
( pxHigherPriorityTaskWoken ),
0U )
函数原型 | #define xTimerStartFromISR( xTimer, pxHigherPriorityTaskWoken ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_START_FROM_ISR, ( xTaskGetTickCountFromISR() ), ( pxHigherPriorityTaskWoken ), 0U ) | |
功能 | 在中断中启动一个软件定时器 | |
参数 | xTimer | 软件定时器句柄 |
pxHigherPriority TaskWoken | 定时器守护任务的大部分时间都在阻塞态等待定时器命令队列的命令。调用函数 xTimerStartFromISR()将会往定时器的命令队列发送一个启动命令,这很有可能会将定时器任务从阻塞态移除。如果调用函数xTimerStartFromISR()让定时器任务脱离阻塞态,且定时器守护任务的优先级大于或者等于当前被中断的任务的优先级,那么pxHigherPriorityTaskWoken的值会在函数xTimerStartFromISR()内部设置为pdTRUE,然后在中断退出之前执行一次上下文切换。 | |
返回值 | 如果启动命令无法成功地发送到定时器命令队列则返回pdFAILE,成功发送则返回pdPASS。软件定时器成功发送的命令是否真正的被执行也还要看定时器守护任务的优先级,其优先级由宏configTIMER_TASK_PRIORITY定义。 |
4.3 软件定时器停止函数
4.3.1 xTimerStop()
#define xTimerStop( xTimer, xTicksToWait ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_STOP, 0U, NULL, ( xTicksToWait ) )
函数原型 | BaseType_txTimerStop(TimerHandle_txTimer, TickType_t xBlockTime); | |
功能 | 停止一个软件定时器,让其进入休眠态。 | |
形参 | xTimer | 软件定时器句柄 |
xBlockTime | 用户指定超时时间,单位为系统节拍周期(即tick)。如果在FreeRTOS调度器开启之前调用 xTimerStart(),形参将不起作用 | |
返回值 | 如果启动命令在超时时间之前无法成功地发送到定时器命令队列则返回pdFAILE,成功发送则返回 pdPASS。软件定时器成功发送的命令是否真正的被执行也还要看定时器守护任务的优先级,其优先级由宏configTIMER_TASK_PRIORITY定义。 |
4.3.2 xTimerStopFromISR()
#define xTimerStopFromISR( xTimer, pxHigherPriorityTaskWoken ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_STOP_FROM_ISR, 0, ( pxHigherPriorityTaskWoken ), 0U )
函数原型 | BaseType_txTimerStopFromISR(TimerHandle_txTimer, BaseType_t*pxHigherPriorityTaskWoken); | |
功能 | 在中断中停止一个软件定时器,让其进入休眠态。 | |
形参 | xTimer | 软件定时器句柄。 |
pxHigherPriority TaskWoken | 定时器守护任务的大部分时间都在阻塞态等待定时器命令队列的命令。调用函数 xTimerStopFromISR()将会往定时器的命令队列发送一个停止命令,这很有可能会将定时器任务从阻塞态移除。如果调用函数xTimerStopFromISR()让定时器任务脱离阻塞态,且定时器守护任务的优先级大于或者等于当前被中断的任务的优先级,那么pxHigherPriorityTaskWoken的值会在函数xTimerStopFromISR()内部设置为 pdTRUE,然后在中断退出之前执行一次上下文切换。 | |
返回值 | 如果停止命令在超时时间之前无法成功地发送到定时器命令队列则返回pdFAILE,成功发送则返回pdPASS。软件定时器成功发送的命令是否真正的被执行也还要看定时器守护任务的优先级,其优先级由宏configTIMER_TASK_PRIORITY定义。 |
4.4 软件定时器删除函数 xTimerDelete()
#define xTimerDelete( xTimer, xTicksToWait ) xTimerGenericCommand( ( xTimer ), tmrCOMMAND_DELETE, 0U, NULL, ( xTicksToWait ) )
函数原型 | #define xTimerDelete( xTimer, xTicksToWait) xTimerGenericCommand((xTimer), tmrCOMMAND_DELETE, OU, NULL, (xTicksToWait)) | |
功能 | 删除一个已经被创建成功的软件定时器。 | |
形参 | xTimer | 软件定时器句柄。 |
xBlockTime | 用户指定的超时时间,单位为系统节拍周期(即tick),如果在FreeRTOS调度器开启之前调用xTimerStart(),该形参将不起作用。 | |
返回值 | 如果删除命令在超时时间之前无法成功地发送到定时器命令队列则返回pdFAILE,成功发送则返回pdPASS。 |
5. 代码编写
主要创建两个任务,一个软件定时器是单次模式,5000个tick调用一次回调函数,另一个软件定时器是周期模式,1000个tick调用一次回调函数,在回调函数中输出相关信息。
5.1 开始前准备
由于我们使用的是动态存储,因此需要找到FreeRTOSConfig将动态内存申请函数置1:
//支持动态内存申请
#define configSUPPORT_DYNAMIC_ALLOCATION 1
找到软件定时器,将其置1,将优先级设到最大,配备队列长度和堆栈大小:
//启用软件定时器
#define configUSE_TIMERS 1
//软件定时器优先级
#define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES-1)
//软件定时器队列长度
#define configTIMER_QUEUE_LENGTH 10
//软件定时器任务堆栈大小
#define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE*2)
5.2 应用任务创建
5.2.1 LED闪烁任务
void led1_task(void *pvParameters)
{
while(1)
{
LED1=0;
vTaskDelay(200);
LED1=1;
vTaskDelay(800);
}
}
5.2.2 定时器1回调函数
//定时器1回调函数
//软件定时器不要调用阻塞函数,也不要进行死循环,应快进快出
void Swtmr1_Callback(void* parameter)
{
TickType_t tick_num1;
TmrCb_Count1++; /* 每回调一次加一 */
tick_num1 = xTaskGetTickCount(); /* 获取滴答定时器的计数值 */
LED2=!LED2;
printf("Swtmr1_Callback函数执行 %d 次\n", TmrCb_Count1);
printf("滴答定时器数值=%d\n", tick_num1);
}
5.2.3 定时器2回调函数
//定时器2回调函数
//软件定时器不要调用阻塞函数,也不要进行死循环,应快进快出
void Swtmr2_Callback(void* parameter)
{
TickType_t tick_num2;
TmrCb_Count2++; /* 每回调一次加一 */
tick_num2 = xTaskGetTickCount(); /* 获取滴答定时器的计数值 */
printf("Swtmr2_Callback函数执行 %d 次\n", TmrCb_Count2);
printf("滴答定时器数值=%d\n", tick_num2);
}
5.2.4 宏定义相关
//任务优先级
#define LED1_TASK_PRIO 2
//任务堆栈大小
#define LED1_STK_SIZE 50
//任务句柄
TaskHandle_t LED1Task_Handler;
//任务函数
void led1_task(void *pvParameters);
5.3 开始任务
5.3.1 开始任务函数
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建定时器1
Swtmr1_Handle=xTimerCreate((const char* )"AutoReloadTimer",
(TickType_t )1000,/* 定时器周期 1000(tick) */
(UBaseType_t )pdTRUE,/* 周期模式 */
(void* )1,/* 为每个计时器分配一个索引的唯一ID */
(TimerCallbackFunction_t)Swtmr1_Callback);
if(Swtmr1_Handle != NULL)
{
xTimerStart(Swtmr1_Handle,0); //开启周期定时器
}
//创建定时器2
Swtmr2_Handle=xTimerCreate((const char* )"OneShotTimer",
(TickType_t )5000,/* 定时器周期 5000(tick) */
(UBaseType_t )pdFALSE,/* 单次模式 */
(void* )2,/* 为每个计时器分配一个索引的唯一ID */
(TimerCallbackFunction_t)Swtmr2_Callback);
if(Swtmr2_Handle != NULL)
{
xTimerStart(Swtmr2_Handle,0); //开启周期定时器
}
//创建LED1任务
xTaskCreate((TaskFunction_t )led1_task,
(const char* )"led1_task",
(uint16_t )LED1_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED1_TASK_PRIO,
(TaskHandle_t* )&LED1Task_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
5.3.2 宏定义相关
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
TimerHandle_t Swtmr1_Handle =NULL;
TimerHandle_t Swtmr2_Handle =NULL;
static uint32_t TmrCb_Count1 = 0; /* 记录软件定时器1回调函数执行次数 */
static uint32_t TmrCb_Count2 = 0; /* 记录软件定时器2回调函数执行次数 */
static void Swtmr1_Callback(void* parameter);
static void Swtmr2_Callback(void* parameter);
5.4 主函数
#include "timers.h"
int main()
{
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
LED_Init();
KEY_Init();
USART1_Init(115200);
printf("FreeRTOS事件标志组实验\r\n");
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}
需要注意的是:
对于周期性执行在执行到第五次时,因为此时单次执行也启动了,所以此次可能有些细微的误差,这属于正常现象,单次执行,执行完一次,下一次就没有了,因此周期性的下一个就会恢复了。
5.5 完整代码
基于STM32F103C8T6的FreeRTOS的软件定时器资源-CSDN文库
FreeRTOS实时操作系统_时光の尘的博客-CSDN博客