当前位置:网站首页>FreeRTOS个人笔记-软件定时器
FreeRTOS个人笔记-软件定时器
2022-07-26 20:45:00 【Couvrir洪荒猛兽】
根据个人的学习方向,学习FreeRTOS。由于野火小哥把FreeRTOS讲得比较含蓄,打算在本专栏尽量细化一点。作为个人笔记,仅供参考或查阅。
配套资料:FreeRTOS内核实现与应用开发实战指南、野火FreeRTOS配套视频源码、b站野火FreeRTOS视频。搭配来看更佳哟!!!
软件定时器
定时器,是指从指定的时刻开始,经过一个指定时间,然后触发一个超时事件,用户可以自定义定时器的周期与频率。
定时器有硬件定时器和软件定时器之分:
硬件定时器是芯片本身提供的定时功能。一般是由外部晶振提供给芯片输入时钟,芯片向软件模块提供一组配置寄存器,接受控制输入,到达设定时间值后芯片中断控制器产生时钟中断。
硬件定时器的精度一般很高,可以达到纳秒级别,并且是中断触发方式。
软件定时器,软件定时器是由操作系统提供的一类系统接口,它构建在硬件定时器基础之上,使系统能够提供不受硬件定时器资源限制的定时器服务,它实现的功能与硬件定时器也是类似的。
使用硬件定时器时,每次在定时时间到达之后就会自动触发一个中断,用户在中断中处理信息;
而使用软件定时器时,需要我们在创建软件定时器时指定时间到达后要调用的函数(也称超时函数/回调函数),在回调函数中处理信息。
注意:软件定时器回调函数的上下文是任务。软件定时器在被创建之后,当经过设定的时钟计数值后会触发用户定义的回调函数。定时精度与系统时钟的周期有关。
一般系统利用 SysTick 作为软件定时器的基础时钟,软件定时器的回调函数类似硬件的中断服务函数,所以,回调函数也要快进快出,而且回调函数中不能有任何阻塞任务运行的情况(软件定时器回调函数的上下文环境是任务),比如 vTaskDelay() 以及其它能阻塞任务运行的函数,两次触发回调函数的时间间隔 xTimerPeriodInTicks 叫定时器的定时周期。
FreeRTOS 操作系统提供软件定时器功能,软件定时器的使用相当于扩展了定时器的数量,允许创建更多的定时业务。
FreeRTOS 软件定时器功能上支持:
裁剪:能通过宏关闭软件定时器功能。
软件定时器创建。
软件定时器启动。
软件定时器停止。
软件定时器复位。
软件定时器删除。
FreeRTOS 提供的软件定时器支持单次模式和周期模式,单次模式和周期模式的定时时间到之后都会调用软件定时器的回调函数,用户可以在回调函数中加入要执行的工程代码。
单次模式:当用户创建了定时器并启动了定时器后,定时时间到了,只执行一次回调函数之后就将该定时器进入休眠状态,不再重新执行。
周期模式:这个定时器会按照设置的定时时间循环执行回调函数,直到用户将定时器删除。

FreeRTOS 通过一个 prvTimerTask 任务(也叫守护任务 Daemon)管理软件定时器,它是在启动调度器时自动创建的,为了满足用户定时需求。
prvTimerTask 任务会在其执行期间检查用户启动的时间周期溢出的定时器,并调用其回调函数。只有设置 FreeRTOSConfig.h中的宏定义 configUSE_TIMERS 设置为 1 ,将相关代码编译进来,才能正常使用软件定时器相关功能。
应用场景
在很多应用中,我们需要一些定时器任务,硬件定时器受硬件的限制,数量上不足以满足用户的实际需求,无法提供更多的定时器,那么可以采用软件定时器来完成,由软件定时器代替硬件定时器任务。
但需要注意的是软件定时器的精度是无法和硬件定时器相比的, 而且在软件定时器的定时过程中是极有可能被其它中断所打断,因为软件定时器的执行上下文环境是任务。
所以,软件定时器更适用于对时间精度要求不高的任务,一些辅助型的任务。
软件定时器的精度
在操作系统中,通常软件定时器以系统节拍周期为计时单位。系统节拍是系统的心跳节拍,表示系统时钟的频率,就类似人的心跳,1s 能跳动多少下,系统节拍配置为configTICK_RATE_HZ,该宏在 FreeRTOSConfig.h 中有定义,默认是 1000。
那么系统的时钟节拍周期就为 1ms(1s 跳动 1000 下,每一下就为 1ms)。软件定时器的所定时数值必须是这个节拍周期的整数倍,例如节拍周期是 10ms,那么上层软件定时器定时数值只能是10ms, 20ms, 100ms 等,而不能取值为 15ms。
由于节拍定义了系统中定时器能够分辨的精确度,系统可以根据实际系统 CPU 的处理能力和实时性需求设置合适的数值,系统节拍周期的值越小,精度越高,但是系统开销也将越大,因为这代表在 1 秒中系统进入时钟中断的次数也就越多。
运作机制
软件定时器是可选的系统资源,在创建定时器的时候会分配一块内存空间。
当用户创建并启动一个软件定时器时, FreeRTOS 会根据当前系统时间及用户设置的定时确定该定时器唤醒时间,并将该定时器控制块挂入软件定时器列表,
FreeRTOS 中采用两个定时器列表维护软件定时器, pxCurrentTimerList 与 pxOverflowTimerList 是列表指针,在初始化的时候分别指向 xActiveTimerList1 与 xActiveTimerList2。
PRIVILEGED_DATA static List_t xActiveTimerList1;
PRIVILEGED_DATA static List_t xActiveTimerList2;
PRIVILEGED_DATA static List_t *pxCurrentTimerList;
PRIVILEGED_DATA static List_t *pxOverflowTimerList;pxCurrentTimerList:系统新创建并激活的定时器都会以超时时间升序的方式插入到 pxCurrentTimerList 列表中。
系统在定时器任务中扫描 pxCurrentTimerList 中的第一个定时器,看是否已超时,若已经超时了则调用软件定时器回调函数。
否则将定时器任务挂起,因为定时时间是升序插入软件定时器列表的,列表中第一个定时器的定时时间都还没到的话,那后面的定时器定时时间自然没到。
pxOverflowTimerList 列表是在软件定时器溢出的时候使用, 作用与 pxCurrentTimerList 一致。
同时,FreeRTOS 的软件定时器还有采用消息队列进行通信,利用“定时器命令队列”向软件定时器任务发送一些命令,任务在接收到命令就会去处理命令对应的程序,比如启动定时器,停止定时器等。
假如定时器任务处于阻塞状态,我们又需要马上再添加一个软件定时器的话,就是采用这种消息队列命令的方式进行添加,才能唤醒处于等待状态的定时器任务,并且在任务中将新添加的软件定时器添加到软件定时器列表中,
所以,在定时器启动函数中,FreeRTOS 是采用队列的方式发送一个消息给软件定时器任务,任务被唤醒从而执行接收到的命令。
例如:系统当前时间 xTimeNow 值为 0,注意: xTimeNow 其实是一个局部变量, 是根据 xTaskGetTickCount()函数获取的,实际它的值就是全局变量 xTickCount 的值,下文都采用它表示当前系统时间。
在当前系统中已经创建并启动了 1 个定时器 Timer1;系统继续运行,当系统的时间 xTimeNow 为 20 的时候,用户创建并且启动一个定时时间为 100 的定时器 Timer2,
此时 Timer2 的溢出时间 xTicksToWait 就为定时时间 + 系统当前时间(100+20=120),然后将 Timer2 按 xTicksToWait 升序插入软件定时器列表中;
假设当前系统时间 xTimeNow 为 40 的时候,用户创建并且启动了一个定时时间为 50 的定时器Timer3 , 那么此时 Timer3 的溢出时间 xTicksToWait 就为 40+50=90 ,
同样安装 xTicksToWait 的数值升序插入软件定时器列表中。同理创建并且启动在已有的两个定时器中间的定时器也是一样的。


系统在不断运行,而 xTimeNow(xTickCount)随着 SysTick 的触发一直在增长(每一次硬件定时器中断来临,xTimeNow 变量会加 1),
在软件定时器任务运行的时候会获取下一个要唤醒的定时器,比较当前系统时间xTimeNow 是否大于或等于下一个定时器唤醒时间 xTicksToWait。
若大于则表示已经超时,定时器任务将会调用对应定时器的回调函数,否则将软件定时器任务挂起,直至下一个要唤醒的软件定时器时间到来或者接收到命令消息。
在创建定时器 Timer1 并且启动后,假如系统经过了 50 个 tick, xTimeNow 从 0 增长到 50,与 Timer1 的 xTicksToWait 值相等,
这时会触发与 Timer1 对应的回调函数,从而转到回调函数中执行用户代码,同时将 Timer1 从软件定时器列表删除。
如果软件定时器是周期性的,那么系统会根据 Timer1 下一次唤醒时间重新将 Timer1 添加到软件定时器列表中,按照 xTicksToWait 的升序进行排列。
同理,在 xTimeNow=40 的时候创建的 Timer3,在经过 130 个 tick 后(此时系统时间 xTimeNow 是 40, 130 个 tick 就是系统时间 xTimeNow 为 170 的时候),与 Timer3 定时器对应的回调函数会被触发,
接着将 Timer3 从软件定时器列表中删除,如果是周期性的定时器,还会按照 xTicksToWait 升序重新添加到软件定时器列表中。
使用软件定时器时候要注意以下几点:
软件定时器的回调函数中应快进快出,绝对不允许使用任何可能引软件定时器起任务挂起或者阻塞的 API 接口,在回调函数中也绝对不允许出现死循环。
软件定时器使用了系统的一个队列和一个任务资源,软件定时器任务的优先级默认为 configTIMER_TASK_PRIORITY,为了更好响应,该优先级应设置为所有任务中最高的优先级。
创建单次软件定时器,该定时器超时执行完回调函数后,系统会自动删除该软件定时器,并回收资源。
定时器任务的堆栈大小默认为 configTIMER_TASK_STACK_DEPTH 个字节。
软件定时器控制块
typedef struct tmrTimerControl
{
const char *pcTimerName; //软件定时器名称,一般用于调试,RTOS 使用定时器是通过其句柄,并不是使用其名称
ListItem_t xTimerListItem; //软件定时器列表项,用于插入定时器列表
TickType_t xTimerPeriodInTicks; //软件定时器的周期,单位为系统节拍周期(即 tick),pdMS_TO_TICKS()可以把时间单位从 ms 转换为系统节拍周期
UBaseType_t uxAutoReload; //软件定时器是否自动重置,pdFalse,单次模式。pdTRUE,周期模式。
void *pvTimerID; //软件定时器 ID,数字形式。该 ID 典型的用法是当一个回调函数分配给一个或者多个软件定时器时,在回调函数里面根据 ID 号来处理不同的软件定时器
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;软件定时器的功能是在定时器任务(或者叫定时器守护任务) 中实现的。 软件定时器的很多 API 函数通过一个名字叫“定时器命令队列” 的队列来给定时器守护任务发送命令。
该定时器命令队列由 RTOS 内核提供,且应用程序不能够直接访问, 其消息队列的长度由宏 configTIMER_QUEUE_LENGTH 定义。
创建软件定时器
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
TimerHandle_t xTimerCreate( const char * const pcTimerName, //软件定时器名称
const TickType_t xTimerPeriodInTicks, //软件定时器的周期,单位为系统节拍周期(即 tick),用法如pdMS_TO_TICKS(500)
const UBaseType_t uxAutoReload, //pdFalse,单次模式。pdTRUE,周期模式。
void * const pvTimerID, //软件定时器 ID,数字形式。
TimerCallbackFunction_t pxCallbackFunction ) //软件定时器的回调函数
{
Timer_t *pxNewTimer;
pxNewTimer = ( Timer_t * ) pvPortMalloc( sizeof( Timer_t ) ); //为软件定时器申请一块内存
if( pxNewTimer != NULL )
{
prvInitialiseNewTimer( pcTimerName, xTimerPeriodInTicks, uxAutoReload, pvTimerID, pxCallbackFunction, pxNewTimer ); //初始化软件定时器
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
{
pxNewTimer->ucStaticallyAllocated = pdFALSE;
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
}
return pxNewTimer;
}
#endif /* configSUPPORT_DYNAMIC_ALLOCATION */static void prvInitialiseNewTimer( const char * const pcTimerName,
const TickType_t xTimerPeriodInTicks,
const UBaseType_t uxAutoReload,
void * const pvTimerID,
TimerCallbackFunction_t pxCallbackFunction,
Timer_t *pxNewTimer )
{
/* 断言,判断定时器的周期是否大于 0 */
configASSERT( ( xTimerPeriodInTicks > 0 ) );
if ( pxNewTimer != NULL )
{
/* 初始化软件定时器列表与创建软件定时器消息队列 */
prvCheckForValidListAndQueue();
/* 初始化软件定时信息,这些信息保存在软件定时器控制块中 */
pxNewTimer->pcTimerName = pcTimerName;
pxNewTimer->xTimerPeriodInTicks = xTimerPeriodInTicks;
pxNewTimer->uxAutoReload = uxAutoReload;
pxNewTimer->pvTimerID = pvTimerID;
pxNewTimer->pxCallbackFunction = pxCallbackFunction;
vListInitialiseItem( &( pxNewTimer->xTimerListItem ) ); //初始化定时器列表项
traceTIMER_CREATE( pxNewTimer );
}
}xTimerCreate()实例
static TimerHandle_t Swtmr1_Handle =NULL; /* 软件定时器句柄 */
static TimerHandle_t Swtmr2_Handle =NULL; /* 软件定时器句柄 */
/* 周期模式的软件定时器 1,定时器周期 1000(tick)*/
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)
{
/********************************************************************
* xTicksToWait:如果在调用 xTimerStart()时队列已满,则以 tick 为单位指定调用任务应保持
* 在 Blocked(阻塞)状态以等待 start 命令成功发送到 timer 命令队列的时间。
* 如果在启动调度程序之前调用 xTimerStart(),则忽略 xTicksToWait。在这里设置等待时间为 0.
**********************************************************************/
xTimerStart(Swtmr1_Handle,0); //开启周期定时器
}
/* 单次模式的软件定时器 2,定时器周期 5000(tick)*/
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); //开启单次定时器
}
static void Swtmr1_Callback(void* parameter)
{
/* 软件定时器的回调函数,用户自己实现 */
}
static void Swtmr2_Callback(void* parameter)
{
/* 软件定时器的回调函数,用户自己实现 */
}
边栏推荐
- Drag and drop table rows
- HTTP cache browser cache that rabbits can understand
- 七、微信小程序运行报错:Error: AppID 不合法,invalid appid
- 如何借助自动化工具落地DevOps|含低代码与DevOps应用实践
- Serial port communication failure
- Flash source code outline
- Retrieve the parameters in this method in idea for our use -- 1. Class diagram. 2. Double click shift
- Deployment of kubernetes
- Six instructions of Memcache based caching mechanism
- (C language) a brief introduction to define
猜你喜欢
![[HCIA security] bidirectional nat](/img/e3/ba0bada1235ac92e626ae2d972ad09.png)
[HCIA security] bidirectional nat

From manual test to automatic test, it only took me a few months to double my salary

【HCIE安全】双机热备-主备备份

We were tossed all night by a Kong performance bug

JVM learning - memory structure - program counter & virtual machine stack & local method stack & heap & method area

Selenium自动化测试面试题全家桶

Solution to the problem of sticking and unpacking TCP

留存收益率计算公式

The hardest lesson we learned from the crypto Market

Summary of common interview questions of computer composition principle, including answers
随机推荐
Props with type Object/Array must...
任正非再谈美国打压:活下去就是胜利,要战胜美国
JVM learning - memory structure - program counter & virtual machine stack & local method stack & heap & method area
[HCIA security] NAT network address translation
微服务化解决文库下载业务问题实践
功能尝鲜 | 解密 Doris 复杂数据类型 ARRAY
Valley segment coverage - (summary of interval sequencing problem)
js中join方法
7、 Wechat applet running error: error: illegal appid, invalid appid
Summary of common interview questions on computer network, including answers
HTTP cache browser cache that rabbits can understand
洛谷-线段覆盖-(区间排序问题总结)
Arm Mali GPU的噩梦:三星、华为纷纷转向自研!
Serial port communication failure
【HCIA安全】双向NAT
七、微信小程序运行报错:Error: AppID 不合法,invalid appid
滤波及失真
1-《PyTorch深度学习实践》-线性模型
获取文本选择的方向
CFdiv1+2-Pathwalks-(树状数组+线性dp)