FreeRTOS软件定时器
原文链接:https://blog.csdn.net/qq_18150497/article/details/52874310
1 简述
考虑平台硬件定时器个数限制的, FreeRTOS 通过一个 Daemon 任务(启动调度器时自动创建)管理软定时器, 满足用户定时需求. Daemon 任务会在其执行期间检查用户启动的时间周期溢出的定时器,并调用其回调函数。
对于硬件定时器的中断服务程序, 我们知道不应该在里面执行复杂,可能导致阻塞的工作,相应的, 虽然软定时器实际是在定时Daemon 任务中执行,但是阻塞的话会导致其他定时器调用被延时, 所以实际使用也应该避免。
软定时器是通过一个任务来辅助实现,该功能时刻裁剪的 , 只有设置 FreeRTOSConfig.h 中configUSE_TIMERS == 1 将相关代码编译进来, 才能正常使用相关功能。
分析的源码版本是 v9.0.0
2 使用定时器
软件定时器的周期
软件定时器的“周期”是软件计时器启动到执行软件定时器的回调函数之间的时间。
单次和自动重装定时器
1. 单次定时器
一旦启动,单次定时器将仅执行一次其回调函数。单次定时器可以手动重启,但不会自动重启。
2. 自动重装定时器
一旦启动,自动重载定时器将在每次到期时自动重新启动,从而导致其回调函数的定期执行。
下图显示了单触发定时器和自动重载定时器之间的行为差异。垂直虚线表示滴答中断发生的时间。
2.1 配置定时器服务任务
程序中需要使用到软件定时器, 需要先在 FreeRTOSConfig.h 中正确配置如下宏 :
- configUSE_TIMERS
是否编译定时器相关代码, 如需要使用定时器, 设置为 1 - configTIMER_TASK_PRIORITY
设置定时器Daemon 任务优先级, 如果优先级太低, 可能导致定时器无法及时执行 - configTIMER_QUEUE_LENGTH
设置定时器Daemon 任务的命令队列深度, 设置定时器都是通过发送消息到该队列实现的。 - configTIMER_TASK_STACK_DEPTH
设置定时器Daemon 任务的栈大小
2.2 创建 启动 停止定时器
TimerHandle_t xTimerUser; // 定义句柄
// 定时器回调函数格式
void vTimerCallback( TimerHandle_t xTimer )
{
// do something no block
// 获取溢出次数
static uin32_t ulCount = ( uint32_t ) pvTimerGetTimerID( xTimer );
// 累积溢出次数
++ulCount;
// 更新溢出次数
vTimerSetTimerID( xTimer, ( void * ) ulCount );
if (ulCount == 10) {
// 停止定时器
xTimerStop( xTimer, 0 );
}
}
void fun()
{
// 申请定时器, 配置
xTimerUser = xTimerCreate
/*调试用, 系统不用*/
("Timer's name",
/*定时溢出周期, 单位是任务节拍数*/
100,
/*是否自动重载, 此处设置周期性执行*/
pdTRUE,
/*记录定时器溢出次数, 初始化零, 用户自己设置*/
( void * ) 0,
/*回调函数*/
vTimerCallback);
if( xTimerUser != NULL ) {
// 启动定时器, 0 表示不阻塞
xTimerStart( xTimerUser, 0 );
}
}
如上所示, 调用函数 xTimerCreate申请,配置定时器, 通过 xTimerStart 启动定时器, 当定时器计数溢出时, 系统回调注册的函数。
定时器可以设置为一次性 One-shot 或者自动重载 Auto-reload 两种, 第一种溢出后停止定时器, 第二种溢出后会再次启动定时器。
2.3 修改定时器
在申请定时器的时候设置的定时器周期, 可以通过函数 xTimerChangePeriod 修改, 如下示例 :
void vAFunction_2( TimerHandle_t xTimer )
{
// 判断定时器是否处于运行状态
if( xTimerIsTimerActive( xTimer ) != pdFALSE )
{
/* xTimer is active, do something. */
}
else
{
// 处于这个状态的定时器, 可能由于 :
// 1 定时器 create 后没有start
// 2 一次性定时器执行溢出后
// 修改定时器周期
if( xTimerChangePeriod( xTimer,
/*修改定时周期*/
500 / portTICK_PERIOD_MS,
/*允许阻塞最大时间 100 ticks*/
100 ) == pdPASS )
{
// update fail
// 阻塞 100 tick 仍然无法发送命令
// 删除定时器 释放对应内存!
xTimerDelete( xTimer );
}
else
{
// 定时器配置更新成功, 并已经启动 !!
}
}
}
如上, 该函数会修改定时器并使定时器 开始运行!!!
另外, 可以通过函数 xTimerReset 重启定时器, 如果已经启动计数, 重新开始计数; 如果没有启动,启动定时器。
定时器使用系统提供 API,涉及 Queue 操作, 如果是在中断程序中调用,需要调用对应带 FromISR的接口。
2.4 获取定时器状态
其他获取定时器信息的函数
// 获取名称 , 申请是设置的字符串
pcTimerGetName()
// 定时器溢出周期
xTimerGetPeriod()
// 返回定时器溢出的时间点 (--> xTaskGetTickCount())
xTimerGetExpiryTime()
2.5 定时器实现
FreeRTOS 软定时器的实现在源码目录 Source/include/timers.h, 涉及 链表 和 消息队列(后续文章分析)。
2.6 数据结构
使用定时器前,需要先申请定时器, 见 配置定时器服务任务 中, 通过函数 xTimerCreate获取一个定时器, 实际上是向系统申请了一块内存存储定时器控制块的数据结构, 并将参数填写到该结构体中。
2.7 定时器控制块
typedef struct tmrTimerControl
{
// 定时器名 方便调试
const char *pcTimerName;
// 链表项 用于插入定时链表
ListItem_t xTimerListItem;
// 定时器中断周期
TickType_t xTimerPeriodInTicks;
// 是否自动重置, 如果 =pdFalse 为一次性
UBaseType_t uxAutoReload;
// 溢出计数 需自己设置
void *pvTimerID;
// 定时器溢出回调函数
TimerCallbackFunction_t pxCallbackFunction;
#if( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxTimerNumber;
#endif
#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) &&
( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
// 标记定时器使用的内存, 删除时判断是否需要释放内存
uint8_t ucStaticallyAllocated; #endif
} xTIMER;
成功申请定时器后, 定时器并没有开始工作, 需要调用函数将该定时器中的 xTimerListItem 插入到定时器管理链表中, Daemon 任务才能在该定时器设定的溢出时刻调用其回调函数。
2.8 定时器管理链表
timers.c 中定义了如下几个链表变量用于管理定时器, 定时器根据其溢出时刻从小到大插入链表进行管理。
使用两个链表是为了应对系统 TickCount 溢出的问题,在 FreeRTOS 任务调度 系统节拍 介绍过。
PRIVILEGED_DATA static List_t xActiveTimerList1;
PRIVILEGED_DATA static List_t xActiveTimerList2;
// 当前节拍计数器对应的定时器管理链表指针
PRIVILEGED_DATA static List_t *pxCurrentTimerList;
// 溢出时间到了下一个节拍计数阶段(当前节拍计数器溢出后)的定时器管理链表指针
PRIVILEGED_DATA static List_t *pxOverflowTimerList;
2.9 命令队列
文章开头提到的使用定时器的函数, 大部分都带有一个参数,用于设置调用后允许阻塞的最大时间, 原因是, 这些函数并没有直接操作定时器管理链表, 而是向定时器Daemon 任务的消息队列 xTimerQueue 发送消息命令。 之后, 定时器Daemon 任务会从消息队列取出消息并响应操作。
命令格式
命令类型
2.10定时器服务任务
此处,从系统启动的定时器Daemon 任务展开分析 FreeRTOS 的软定时器的实现 。
该任务主体的执行流程如下所示 :
永久循环部分的代码 :
for( ;; )
{
// 读取定时器队列第一个链表项的值 -> 即将溢出的定时器时间(ticks)
// 如果链表空, 返回的是 0
xNextExpireTime = prvGetNextExpireTime( &xListWasEmpty );
// 处理溢出的定时器
// 阻塞直到下一个定时器溢出 或 消息队列有新命令
prvProcessTimerOrBlockTask( xNextExpireTime, xListWasEmpty );
// 读取消息队列,执行命令
prvProcessReceivedCommands();
}
2.11 回调定时器
定时器任务中, 取出下一个定时器溢出的时间,并把它传递给函数prvProcessTimerOrBlockTask, 该函数负责处理溢出定时器, 应对节拍计数器溢出问题等, 并设置合适的时间阻塞 Daemon 任务, 让出 CPU 使用权直到下一个定时器溢出或者接收到新的命令。
static void prvProcessTimerOrBlockTask(
const TickType_t xNextExpireTime,
BaseType_t xListWasEmpty )
{
TickType_t xTimeNow;
BaseType_t xTimerListsWereSwitched;
// 挂起调度器 避免任务切换
vTaskSuspendAll();
{
// 判断系统节拍计数是否溢出
// 如果是,处理溢出定时器, 并切换定时器链表
xTimeNow = prvSampleTimeNow( &xTimerListsWereSwitched );
// 系统节拍计数器没有溢出
if( xTimerListsWereSwitched == pdFALSE )
{
// 判断是否有定时器溢出
if( ( xListWasEmpty == pdFALSE ) && ( xNextExpireTime <= xTimeNow ) )
{
// 恢复调度
( void ) xTaskResumeAll();
//执行相应定时器的回调函数
// 对于需要自动重载的定时器, 更新下一次溢出时间, 插回链表
prvProcessExpiredTimer( xNextExpireTime, xTimeNow );
}
else
{
// 当前链表没有定时器
if( xListWasEmpty != pdFALSE )
{
// 判断溢出链表上是否有定时器
xListWasEmpty = listLIST_IS_EMPTY( pxOverflowTimerList );
}
// 阻塞挂起直到 : 下一个定时器溢出 或 新命令消息
// 下面这个queue函数是内核专用, 调用后不会直接阻塞,但是会把任务加入到阻塞链表中
vQueueWaitForMessageRestricted( xTimerQueue,
( xNextExpireTime - xTimeNow ), /*转换阻塞时间*/
xListWasEmpty );
if( xTaskResumeAll() == pdFALSE )
{
// 触发任务切换
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
else
{
// 恢复调度
( void ) xTaskResumeAll();
}
}
}
2.12 处理节拍计数器溢出
上面提到, 通过函数 prvSampleTimeNow判断节拍计数器是否发发生溢出, 并执行相应处理, 此处看看该函数内容 :
static TickType_t prvSampleTimeNow( BaseType_t * const pxTimerListsWereSwitched )
{
TickType_t xTimeNow;
// 静态变量 记录上一次调用时系统节拍值
PRIVILEGED_DATA static TickType_t xLastTime = ( TickType_t ) 0U;
// 获取本次调用节拍结束器值
xTimeNow = xTaskGetTickCount();
// 判断节拍计数器是否溢出过
// 比如 8bit : 0xFF+1 -> 0
if( xTimeNow < xLastTime )
{
// 发生溢出, 处理当前链表上所有定时器并切换管理链表
prvSwitchTimerLists();
*pxTimerListsWereSwitched = pdTRUE;
}
else
{
*pxTimerListsWereSwitched = pdFALSE;
}
// 更新记录
xLastTime = xTimeNow;
return xTimeNow;
}
可以看到, 该函数每次调用都会记录节拍值, 下一次调用,通过比较相邻两次调用的值判断节拍计数器是否溢出过。
当节拍计数器溢出, 需要处理掉当前链表上的定时器(应为这条链表上的定时器都已经溢出了), 然后切换链表。
对于处理这部分任务的函数, 主要要注意其对于需要重载的定时器的处理 :
类比一下 , 一个自动重载的定时器, 每月需要执行一次, 上次调用是2016 年6月, 之后由于优先级问题,导致下一次调用时间等到第二年2017年 1月了,也就是跨年了(节拍计数器溢出了), 切换日历(链表)前, 需要把旧的先处理掉, 那么实际该定时器在2016年 7~ 12月每月都需要执行一次,所以要补偿回来,直到第二年1月, 才发送消息,插到新日历里面(链表)。
即使时间延迟了,但是该调用几次,是保证的!!
static void prvSwitchTimerLists( void )
{
TickType_t xNextExpireTime, xReloadTime;
List_t *pxTemp;
Timer_t *pxTimer;
BaseType_t xResult;
// 切换链表前, 需要先处理当前链表上的所有执行定时器
while( listLIST_IS_EMPTY( pxCurrentTimerList ) == pdFALSE )
{
// 获取第一个定时器溢出时间
xNextExpireTime = listGET_ITEM_VALUE_OF_HEAD_ENTRY( pxCurrentTimerList );
// 取出定时器并从链表移除
pxTimer = ( Timer_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxCurrentTimerList );
( void ) uxListRemove( &( pxTimer->xTimerListItem ) );
traceTIMER_EXPIRED( pxTimer );
// 执行定时器回调函数
pxTimer->pxCallbackFunction( ( TimerHandle_t ) pxTimer );
// 对于自动重载的定时器 计算下一次溢出时间
if( pxTimer->uxAutoReload == ( UBaseType_t ) pdTRUE )
{
// 如果重载后定时器的时间没有溢出, 还在当前链表范围内, 继续插回到当前链表
// 保证执行的次数
xReloadTime = ( xNextExpireTime + pxTimer->xTimerPeriodInTicks );
if( xReloadTime > xNextExpireTime )
{
// 设置下一次溢出时间
listSET_LIST_ITEM_VALUE( &( pxTimer->xTimerListItem ), xReloadTime );
listSET_LIST_ITEM_OWNER( &( pxTimer->xTimerListItem ), pxTimer );
vListInsert( pxCurrentTimerList, &( pxTimer->xTimerListItem ) );
}
else
{
// 重载后定时器的时间同节拍计数器一样溢出了
// 需要插入到新的链表中, 通过消息发送
// 等到处理消息时,链表已经切换了
xResult = xTimerGenericCommand(
pxTimer,
tmrCOMMAND_START_DONT_TRACE,
xNextExpireTime,
NULL,
tmrNO_DELAY );
configASSERT( xResult );
( void ) xResult;
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
// 切换链表
pxTemp = pxCurrentTimerList;
pxCurrentTimerList = pxOverflowTimerList;
pxOverflowTimerList = pxTemp;
}
函数 prvProcessTimerOrBlockTask 中, 当节拍计数器没有溢出, 判断当前管理链表上溢出定时器并进行处理的函数 prvProcessExpiredTimer 整体和上面介绍差别不大, 执行函数回调, 判断是否需要重载等。
2.13 命令处理
用户将需要处理的定时器命令发送到定时器的消息队列, Daemon 任务每次执行期间回去读取并执行, 这部分工作有任务主体中的函数 prvProcessReceivedCommands完成, 下面看看这个函数如何实现, 对应平时使用定时器控制函数更加有底。
以下代码做了简化
static void prvProcessReceivedCommands( void )
{
DaemonTaskMessage_t xMessage;
Timer_t *pxTimer;
BaseType_t xTimerListsWereSwitched, xResult;
TickType_t xTimeNow;
while( xQueueReceive( xTimerQueue, &xMessage, tmrNO_DELAY ) != pdFAIL )
{
#if ( INCLUDE_xTimerPendFunctionCall == 1 )
// 延期执行函数命令
// 执行注册的函数
#endif
// 定时器命令消息
if( xMessage.xMessageID >= ( BaseType_t ) 0 )
{
// 命令处理的定时器
pxTimer = xMessage.u.xTimerParameters.pxTimer;
if( listIS_CONTAINED_WITHIN( NULL, &( pxTimer->xTimerListItem ) ) == pdFALSE )
{
// 如果定时器已经在链表中, 不管37 21, 移除
( void ) uxListRemove( &( pxTimer->xTimerListItem ) );
}
// 判断节拍计数器是否溢出过 处理 切换
// 因为下面可能有新项插入 确保链表对应
xTimeNow = prvSampleTimeNow( &xTimerListsWereSwitched );
switch( xMessage.xMessageID )
{
case tmrCOMMAND_START :
case tmrCOMMAND_START_FROM_ISR :
case tmrCOMMAND_RESET :
case tmrCOMMAND_RESET_FROM_ISR :
case tmrCOMMAND_START_DONT_TRACE :
// 以上 ,都是让定时器跑起来
// 设置定时器溢出时间并插到链表中
if( prvInsertTimerInActiveList( pxTimer,
xMessage.u.xTimerParameters.xMessageValue +
pxTimer->xTimerPeriodInTicks, xTimeNow,
xMessage.u.xTimerParameters.xMessageValue )
!= pdFALSE )
{
// 处理定时器慢了, 该定时器已经溢出
// 赶紧执行其回调就看看函数
pxTimer->pxCallbackFunction( ( TimerHandle_t ) pxTimer );
// 重载定时器 重新启动
if( pxTimer->uxAutoReload
== ( UBaseType_t ) pdTRUE )
{
xResult = xTimerGenericCommand( pxTimer,
tmrCOMMAND_START_DONT_TRACE,
xMessage.u.xTimerParameters.xMessageValue+pxTimer->xTimerPeriodInTicks,
NULL, tmrNO_DELAY );
configASSERT( xResult );
( void ) xResult;
}
}
break;
case tmrCOMMAND_STOP :
case tmrCOMMAND_STOP_FROM_ISR :
// 停止定时器 开头已经从链表移除
// 不需要做其他
break;
case tmrCOMMAND_CHANGE_PERIOD :
case tmrCOMMAND_CHANGE_PERIOD_FROM_ISR :
// 更新定时器配置
pxTimer->xTimerPeriodInTicks =
xMessage.u.xTimerParameters.xMessageValue;
// 插入到管理链表 也就启动了定时器
( void ) prvInsertTimerInActiveList( pxTimer,
( xTimeNow + pxTimer->xTimerPeriodInTicks ),
xTimeNow, xTimeNow );
break;
case tmrCOMMAND_DELETE :
// 删除定时器
// 判断定时器内存是否需要释放(动态的释放)
break;
default :
/* Don't expect to get here. */
break;
}
}
}
}
函数处理定时器,开头不管后面命令是什么,如果定时器原本在运行, 直接移除。
3 软件定时器运用示例
3.1 开启定时器
BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait );
3.2 关闭定时器
BaseType_t xTimerStop( TimerHandle_t xTimer, TickType_t xTicksToWait );
原文链接:https://blog.csdn.net/qq_33194301/article/details/103758176
#include "rtos_test.h"
//one-shot & periodic
u32 ulCallCount=0;
static void prvOneShotTimerCallback( TimerHandle_t xTimer )
{
TickType_t xTimeNow;
/* Obtain the current tick count. */
xTimeNow = xTaskGetTickCount();
/* Output a string to show the time at which the callback was executed. */
vPrintStringAndNumber( "One-shot timer callback executing :%d", xTimeNow );
/* File scope variable. */
ulCallCount++;
}
static void prvAutoReloadTimerCallback( TimerHandle_t xTimer )
{
TickType_t xTimeNow;
/* Obtain the current tick count. */
xTimeNow = xTaskGetTickCount();
/* Output a string to show the time at which the callback was executed. */
vPrintStringAndNumber( "Auto-reload timer callback executing :%d", xTimeNow );
ulCallCount++;
}
/* The periods assigned to the one-shot and auto-reload timers are 3.333 second and half a
second respectively. */
#define mainONE_SHOT_TIMER_PERIOD pdMS_TO_TICKS( 3333 )
#define mainAUTO_RELOAD_TIMER_PERIOD pdMS_TO_TICKS( 500 )
int main( void )
{
TimerHandle_t xOneShotTimer, xAutoReloadTimer;
BaseType_t xTimer1Started, xTimer2Started;
BSP_Init();
/* Create the one shot timer, storing the handle to the created timer in xOneShotTimer. */
xOneShotTimer = xTimerCreate(
/* Text name for the software timer - not used by FreeRTOS. */
"OneShot",
/* The software timer's period in ticks. */
mainONE_SHOT_TIMER_PERIOD,
/* Setting uxAutoRealod to pdFALSE creates a one-shot software timer. */
pdFALSE,
/* This example does not use the timer id. */
0,
/* The callback function to be used by the software timer being created. */
prvOneShotTimerCallback );
/* Create the auto-reload timer, storing the handle to the created timer in xAutoReloadTimer. */
xAutoReloadTimer = xTimerCreate(
/* Text name for the software timer - not used by FreeRTOS. */
"AutoReload",
/* The software timer's period in ticks. */
mainAUTO_RELOAD_TIMER_PERIOD,
/* Setting uxAutoRealod to pdTRUE creates an auto-reload timer. */
pdTRUE,
/* This example does not use the timer id. */
0,
/* The callback function to be used by the software timer being created. */
prvAutoReloadTimerCallback );
/* Check the software timers were created. */
if( ( xOneShotTimer != NULL ) && ( xAutoReloadTimer != NULL ) )
{
/* Start the software timers, using a block time of 0 (no block time). The scheduler has
not been started yet so any block time specified here would be ignored anyway. */
xTimer1Started = xTimerStart( xOneShotTimer, 0 );
xTimer2Started = xTimerStart( xAutoReloadTimer, 0 );
/* The implementation of xTimerStart() uses the timer command queue, and xTimerStart()
will fail if the timer command queue gets full. The timer service task does not get
created until the scheduler is started, so all commands sent to the command queue will
stay in the queue until after the scheduler has been started. Check both calls to
xTimerStart() passed. */
if( ( xTimer1Started == pdPASS ) && ( xTimer2Started == pdPASS ) )
{
/* Start the scheduler. */
vTaskStartScheduler();
}
}
/* As always, this line should not be reached. */
for( ;; );
}
3.3 给定时器分配ID
void vTimerSetTimerID( const TimerHandle_t xTimer, void *pvNewID );
通过定时器ID在回调函数中对其进行操作。
#include "rtos_test.h"
TimerHandle_t xOneShotTimer, xAutoReloadTimer;
static void prvTimerCallback( TimerHandle_t xTimer )
{
TickType_t xTimeNow;
uint32_t ulExecutionCount;
/* A count of the number of times this software timer has expired is stored in the timer's
ID. Obtain the ID, increment it, then save it as the new ID value. The ID is a void
pointer, so is cast to a uint32_t. */
ulExecutionCount = ( uint32_t ) pvTimerGetTimerID( xTimer );
ulExecutionCount++;
vTimerSetTimerID( xTimer, ( void * ) ulExecutionCount );
/* Obtain the current tick count. */
xTimeNow = xTaskGetTickCount();
/* The handle of the one-shot timer was stored in xOneShotTimer when the timer was created.
Compare the handle passed into this function with xOneShotTimer to determine if it was the
one-shot or auto-reload timer that expired, then output a string to show the time at which
the callback was executed. */
if( xTimer == xOneShotTimer )
{
vPrintStringAndNumber( "One-shot timer callback executing :%d", xTimeNow );
}
else
{
/* xTimer did not equal xOneShotTimer, so it must have been the auto-reload timer that
expired. */
vPrintStringAndNumber( "Auto-reload timer callback executing :%d", xTimeNow );
if( ulExecutionCount == 5 )
{
/* Stop the auto-reload timer after it has executed 5 times. This callback function
executes in the context of the RTOS daemon task so must not call any functions that
might place the daemon task into the Blocked state. Therefore a block time of 0 is
used. */
xTimerStop( xTimer, 0 );
}
}
}
/* The periods assigned to the one-shot and auto-reload timers are 3.333 second and half a
second respectively. */
#define mainONE_SHOT_TIMER_PERIOD pdMS_TO_TICKS( 3333 )
#define mainAUTO_RELOAD_TIMER_PERIOD pdMS_TO_TICKS( 500 )
int main( void )
{
BaseType_t xTimer1Started, xTimer2Started;
BSP_Init();
/* Create the one shot timer, storing the handle to the created timer in xOneShotTimer. */
/* Create the one shot timer software timer, storing the handle in xOneShotTimer. */
xOneShotTimer = xTimerCreate( "OneShot",
mainONE_SHOT_TIMER_PERIOD,
pdFALSE,
/* The timer’s ID is initialized to 0. */
0,
/* prvTimerCallback() is used by both timers. */
prvTimerCallback );
/* Create the auto-reload software timer, storing the handle in xAutoReloadTimer */
xAutoReloadTimer = xTimerCreate( "AutoReload",
mainAUTO_RELOAD_TIMER_PERIOD,
pdTRUE,
/* The timer’s ID is initialized to 0. */
0,
/* prvTimerCallback() is used by both timers. */
prvTimerCallback );
/* Check the software timers were created. */
if( ( xOneShotTimer != NULL ) && ( xAutoReloadTimer != NULL ) )
{
/* Start the software timers, using a block time of 0 (no block time). The scheduler has
not been started yet so any block time specified here would be ignored anyway. */
xTimer1Started = xTimerStart( xOneShotTimer, 0 );
xTimer2Started = xTimerStart( xAutoReloadTimer, 0 );
/* The implementation of xTimerStart() uses the timer command queue, and xTimerStart()
will fail if the timer command queue gets full. The timer service task does not get
created until the scheduler is started, so all commands sent to the command queue will
stay in the queue until after the scheduler has been started. Check both calls to
xTimerStart() passed. */
if( ( xTimer1Started == pdPASS ) && ( xTimer2Started == pdPASS ) )
{
/* Start the scheduler. */
vTaskStartScheduler();
}
}
/* As always, this line should not be reached. */
for( ;; );
}
3.4 更改定时器周期
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer,
TickType_t xNewTimerPeriodInTicks,
TickType_t xTicksToWait );
3.5 重启定时器
BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait );
3.6 应用示例2
/*
*************************************************************************
* 包含的头文件
*************************************************************************
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "event_groups.h"
#include "task.h"
/* 开发板硬件bsp头文件 */
#include "bsp_led.h"
#include "bsp_usart.h"
/**************************** 任务句柄 ********************************/
/*
* 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
* 这个句柄可以为NULL。
*/
/* 创建任务句柄 */
static TaskHandle_t AppTaskCreate_Handle = NULL;
/* LED任务句柄 */
static TaskHandle_t LED_Task_Handle = NULL;
static TaskHandle_t Task1_Handle = NULL;
static TaskHandle_t Task2_Handle = NULL;
static TaskHandle_t TaskWDG_Handle = NULL;
static TaskHandle_t TaskTimer_Handle = NULL; //定时器任务句柄
void TimerControlTask(void); //定时器任务函数
TimerHandle_t AutoReloadTimer_Handle; //周期定时器句柄
TimerHandle_t OneShotTimer_Handle; //单次定时器句柄
void AutoReloadTimerCallback(TimerHandle_t handle); //周期定时器回调函数
void OneShotTimerCallback(TimerHandle_t handle); //单次定时器回调函数
EventGroupHandle_t xMyEventGroup;
#define WDG_BIT_DOWN_TASK1 (1<<0)
#define WDG_BIT_DOWN_TASK2 (1<<1)
#define WDG_BIT_TASK_ALL ( WDG_BIT_DOWN_TASK1 | WDG_BIT_DOWN_TASK2)
/********************************** 内核对象句柄 *********************************/
/*
* 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核
* 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我
* 们就可以通过这个句柄操作这些内核对象。
*
* 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,
* 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数
* 来完成的
*
*/
/******************************* 全局变量声明 ************************************/
/*
* 当我们在写应用程序的时候,可能需要用到一些全局变量。
*/
/*
*************************************************************************
* 函数声明
*************************************************************************
*/
static void AppTaskCreate(void);/* 用于创建任务 */
static void LED_Task(void* pvParameters);/* LED_Task任务实现 */
static void vTask1(void* pvParameters);/* Task1任务实现 */
static void vTask2(void* pvParameters);/* Task2任务实现 */
static void vTaskWDG(void* pvParameters);/* TaskWDG任务实现 */
static void BSP_Init(void);/* 用于初始化板载相关资源 */
/*****************************************************************
* @brief 主函数
* @param 无
* @retval 无
* @note 第一步:开发板硬件初始化
第二步:创建APP应用任务
第三步:启动FreeRTOS,开始多任务调度
****************************************************************/
int main(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
/* 开发板硬件初始化 */
BSP_Init();
printf("这是一个[野火]-STM32全系列开发板-FreeRTOS-动态创建任务!\r\n");
/* 创建AppTaskCreate任务 */
xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /* 任务入口函数 */
(const char* )"AppTaskCreate",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL,/* 任务入口函数参数 */
(UBaseType_t )1, /* 任务的优先级 */
(TaskHandle_t* )&AppTaskCreate_Handle);/* 任务控制块指针 */
/* 启动任务调度 */
if(pdPASS == xReturn)
vTaskStartScheduler(); /* 启动任务,开启调度 */
else
return -1;
while(1); /* 正常不会执行到这里 */
}
/***********************************************************************
* @ 函数名 : AppTaskCreate
* @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面
* @ 参数 : 无
* @ 返回值 : 无
**********************************************************************/
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
taskENTER_CRITICAL(); //进入临界区
/* 创建LED_Task任务 */
xReturn = xTaskCreate((TaskFunction_t )LED_Task, /* 任务入口函数 */
(const char* )"LED_Task",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )2, /* 任务的优先级 */
(TaskHandle_t* )&LED_Task_Handle);/* 任务控制块指针 */
if(pdPASS == xReturn)
printf("创建LED_Task任务成功!\r\n");
//创建软件周期定时器,周期1秒钟(1000个时钟节拍),周期模式
AutoReloadTimer_Handle = xTimerCreate((const char *)"AutoReloadTimer",
(TickType_t )1000,
(UBaseType_t )pdTRUE,
(void *)1,
(TimerCallbackFunction_t)AutoReloadTimerCallback);
if(NULL != AutoReloadTimer_Handle)
printf("创建周期定时器成功\r\n");
//创建单次定时器,周期2秒钟(2000个时钟节拍),单次模式
OneShotTimer_Handle = xTimerCreate((const char *)"OneShotTimer",
(TickType_t )2000,
(UBaseType_t )pdFALSE,
(void *)2,
(TimerCallbackFunction_t)OneShotTimerCallback);
if(NULL != OneShotTimer_Handle)
printf("创建单次定时器成功\r\n");
/* 创建TimerControlTask任务 */
xReturn = xTaskCreate((TaskFunction_t )TimerControlTask, /* 任务入口函数 */
(const char* )"TimerControlTask",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )13, /* 任务的优先级 */
(TaskHandle_t* )&TaskTimer_Handle);/* 任务控制块指针 */
if(pdPASS == xReturn)
printf("创建TimerControlTask任务成功!\r\n");
vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
taskEXIT_CRITICAL(); //退出临界区
}
/**********************************************************************
* @ 函数名 : LED_Task
* @ 功能说明: LED_Task任务主体
* @ 参数 :
* @ 返回值 : 无
********************************************************************/
static void LED_Task(void* parameter)
{
while (1)
{
LED1_ON;
vTaskDelay(500); /* 延时500个tick */
taskENTER_CRITICAL();
printf("LED_Task Running,LED1_ON\r\n");
taskEXIT_CRITICAL();
LED1_OFF;
vTaskDelay(500); /* 延时500个tick */
taskENTER_CRITICAL();
printf("LED_Task Running,LED1_OFF\r\n");
taskEXIT_CRITICAL();
}
}
void TimerControlTask(void)
{
static uint16_t count = 0;
static bool flag1 = false;
static bool flag2 = false;
xTimerStart(AutoReloadTimer_Handle, 0);
xTimerStart(OneShotTimer_Handle, 0);
while (1)
{
vTaskDelay(300);
taskENTER_CRITICAL();
printf("TimerControlTask Running\r\n");
taskEXIT_CRITICAL();
count++;
if(count > 10 && count < 20 && (flag1==false))
{
xTimerStop(AutoReloadTimer_Handle, 0);
taskENTER_CRITICAL();
printf("Stop AutoReloadTimer@@@@@@@@@@@@@@@@@@@######################\r\n");
taskEXIT_CRITICAL();
flag1 = true;
}
if(count > 20 && (flag2==false))
{
xTimerStart(AutoReloadTimer_Handle, 0);
taskENTER_CRITICAL();
printf("Start AutoReloadTimer@@@@@@@@@@@@@@@@@@@######################\r\n");
taskEXIT_CRITICAL();
flag2 = true;
}
}
}
void AutoReloadTimerCallback(TimerHandle_t handle)
{
taskENTER_CRITICAL();
printf("AutoReloadTimerCallback&&&&&&&&&&&&&&&&&&&&&\r\n");
taskEXIT_CRITICAL();
}
void OneShotTimerCallback(TimerHandle_t handle)
{
taskENTER_CRITICAL();
printf("OneShotTimerCallback#######################\r\n");
taskEXIT_CRITICAL();
}
static void vTask1(void* pvParameters) /* Task1任务实现 */
{
uint32_t count = 0;
while (1)
{
vTaskDelay(100);
xEventGroupSetBits(xMyEventGroup, WDG_BIT_DOWN_TASK1); //设置任务1标志位
taskENTER_CRITICAL();
printf("WDG_BIT_DOWN_TASK1\r\n");
taskEXIT_CRITICAL();
count++;
if(count > 10 && count < 20)
vTaskDelay(1500);
}
}
static void vTask2(void* pvParameters) /* Task2任务实现 */
{
while (1)
{
vTaskDelay(100);
xEventGroupSetBits(xMyEventGroup, WDG_BIT_DOWN_TASK2); //设置任务1标志位
taskENTER_CRITICAL();
printf("WDG_BIT_DOWN_TASK2\r\n");
taskEXIT_CRITICAL();
}
}
static void vTaskWDG(void* pvParameters) /* TaskWDG任务实现 */
{
EventBits_t uxBits;
//创建事件标志组
xMyEventGroup = xEventGroupCreate();
if(NULL == xMyEventGroup)
{
//xMyEventGroup没有创建成功,用户可以在这里加入创建失败的机制
printf("创建事件标志组xMyEventGroup失败!");
return ;
}
while (1)
{
//等待所有任务发来事件标志
uxBits = xEventGroupWaitBits(xMyEventGroup, //事件标志组句柄
WDG_BIT_TASK_ALL, //等待WDG_BIT_TASK_ALL被设置
pdTRUE, //退出前WDG_BIT_TASK_ALL被清楚,这里是WDG_BIT_TASK_ALL都被设置才表示“退出”
pdTRUE, //设置为pdTRUE表示等待WDG_BIT_TASK_ALL都被设置
1000); //等待延迟时间为1000ticks,即1秒钟
if((uxBits & WDG_BIT_TASK_ALL) == WDG_BIT_TASK_ALL)
{
//vWDG_Feed(); 喂狗操作
taskENTER_CRITICAL();
printf("==========vWDG_Feed==========\r\n");
taskEXIT_CRITICAL();
}
else if(~(uxBits & WDG_BIT_DOWN_TASK1))
{
taskENTER_CRITICAL();
printf("==========TASK1喂狗超时==========\r\n");
taskEXIT_CRITICAL();
//xEventGroupClearBits(xMyEventGroup, WDG_BIT_TASK_ALL);
//通过变量uxBits简单的可以在此处检测到哪个任务长期没有发来运行标志
}
}
}
/***********************************************************************
* @ 函数名 : BSP_Init
* @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面
* @ 参数 :
* @ 返回值 : 无
*********************************************************************/
static void BSP_Init(void)
{
/*
* STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
* 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
* 都统一用这个优先级分组,千万不要再分组,切忌。
*/
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
/* LED 初始化 */
LED_GPIO_Config();
/* 串口初始化 */
USART_Config();
}
/********************************END OF FILE****************************/