详解FreeRTOS中的软件定时器

news/2024/11/26 1:54:00/

软件定时器用于让某个任务定时执行,或者周期性执行。比如设定某个时间后执行某个函数,或者每隔一段时间执行某个函数。由软件定时器执行的函数称为软件定时器的回调函数

参考资料:

《Mastering the FreeRTOS™ Real Time Kernel》——Chapter 5 Software Timer Management

FreeRTOS全解析-6.软件定时器

目录

1.软件定时器的属性和状态

1.1软件定时器的周期

1.2软件定时器状态

1.3软件定时器的执行环境(上下文Context)

1.3.1RTOS守护任务(Daemon Task)(定时器服务Time Sevice)

1.3.2定时器命令队列

1.3.3守护任务调度

2.创建并启动软件定时器

3.定时器ID(Timer ID)

4.修改定时器的周期和重置软件定时器


1.软件定时器的属性和状态

在FreeRTOS中开启软件定时器功能:

1.构建FreeRTOS源文件FreeRTOS/ source /timers.c作为项目的一部分。

2. 在“FreeRTOSConfig.h”中将“configUSE_TIMERS”设置为1。

软件定时器回调函数

void ATimerCallback(TimerHandle_t xTimer)

软件定时器回调函数是在定时器服务中执行的,它们应该保持简短,并且不能进入阻塞态。定时器服务阻塞会影响内核,因此不能调用任何会导致阻塞的函数,比如vTaskDelay()。可以调用xQueueReceive()等函数,但前提是函数的xTicksToWait参数(指定函数的阻塞时间)设置为0。

1.1软件定时器的周期

一个软件定时器的“周期”是指软件定时器被启动和软件定时器的回调函数执行之间的时间。

单次定时器(一次性 one-shot)和周期性定时器(自动重载 Auto/-reload):

1.单次定时器只执行一次回调函数。可以手动重启,但不会自动重启。

2. 周期性定时器将在每次到期时重新启动自己,从而周期性地执行其回调函数。

1.2软件定时器状态

软件定时器可以处于以下两种状态之一:

1.休眠

休眠状态的软件定时器,是指一个软件定时器存在,且可以通过定时器句柄被引用,但是它并没有运行,所以它的回调函数不会执行。

2.运行

运行状态的软件定时器,根据设定的参数,到期运行一次或者周期性运行回调函数。

周期性定时器执行了回调函数后自动重新进入运行状态。

单次定时器执行过回调函数后就会进入休眠状态

1.3软件定时器的执行环境(上下文Context)

1.3.1RTOS守护任务(Daemon Task)(定时器服务Time Sevice)

所有软件定时器回调函数都在同一个RTOS守护(或'定时器服务')任务的上下文中执行。(在Linux上叫守护进程,FreeRTOS里称作任务)

守护任务是一个标准的FreeRTOS任务,在启动调度器时自动创建。它的优先级和堆栈大小分别由FreeRTOSConfig.h中的configTIMER_TASK_PRIORITY和configTIMER_TASK_STACK_DEPTH设置。

软件定时器回调函数不能调用会导致调用任务进入阻塞状态的FreeRTOS API函数,因为这样会导致守护任务进入阻塞态。

1.3.2定时器命令队列

一个任务调用软件定时器的API函数向守护任务发送命令,这个命令会被存在一个队列里,这个队列就叫定时器命令队列

命令示例“启动计时器”、“停止计时器”和“重置计时器”。

定时器命令队列是一个标准的FreeRTOS队列,在启动调度器时自动创建。定时器命令队列的长度由FreeRTOSConfig.h中的configTIMER_QUEUE_LENGTH设置。

1.3.3守护任务调度

守护任务像任何其他FreeRTOS任务一样调度。它只处理命令,或者当它是能够运行的最高优先级任务时,执行定时器回调函数。

如图,Task1运行在Task1中调用定时器API函数向守护任务发送启动定时器命令。因为守护任务优先级没有Task1高,所以守护任务不会立即处理命令,而是等到t4时,Task1进入阻塞态,守护任务才开始处理命令

假如守护任务优先级高的话,一旦发送命令,就切换到守护任务了,所以定时器也就立即启动了。

注意了,定时器的超时时间不是从守护任务接收到命令开始算的,而是从发送时间开始算的。

实际上发送的命令里包含了一个时间戳。时间戳记录了发送时间。例如,如果发送一个启动一个周期为10ms的定时器的命令,时间戳可以保证是发送后的10ms而不是守护任务处理命令后的10ms。

2.创建并启动软件定时器

xTimerCreate()用于创建一个软件计时器,并返回一个TimerHandle_t(软件定时器句柄)。软件定时器创建的时候是休眠状态,并没有立即启动。

软件计时器可以在调度程序运行之前创建,也可以在启动调度程序之后从任务中创建。

TimerHandle_t xTimerCreate( const char * const pcTimerName,                            TickType_t xTimerPeriodInTicks,                            UBaseType_t uxAutoReload,                            void * pvTimerID,                            TimerCallbackFunction_t pxCallbackFunction );
参数作用
pcTimerName软件定时器的名字,FreeRTOS不会用到,便于自己记忆就行
xTimerPeriodInTicks以tick为单位指定的计时器周期。pdMS_TO_TICKS()宏可用于将以毫秒为单位指定的时间转换为以tick为单位指定的时间。
uxAutoReload设置为pdTRUE创建周期(自动重载)计时器。设置为pdFALSE以创建单次(一次性)计时器。
pvTimerID每个软件定时器都有一个ID值。ID是一个空指针,应用程序编写人员可以将其用于任何目的。当同一个回调函数被多个软件计时器使用时,ID特别有用,因为它可以用于提供计时器特定的存储。后面演示。
pxCallbackFunction回调函数指针
返回值如果返回NULL,则不能创建软件计时器,因为没有足够的堆内存。返回非NULL值表示软件计时器已经创建成功。返回值是已创建计时器的句柄。

xTimerStart()用于启动处于休眠状态的软件定时器,或重置(重新启动)处于运行状态的软件定时器。可以在启动调度器之前调用xTimerStart(),但是软件定时器在启动调度器之前不会实际启动。

BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait );

这个函数的底层其实就是上期讲的队列发送FreeRTOS全解析-5.队列(Queue)

参数的意思也就显而易见了。

参数作用
xTimer软件定时器句柄。就是创建定时器的返回值。
xTicksToWait

指定如果队列已满,则调用任务应保持在Blocked状态等待的最大时间。

如果xTicksToWait为零且定时器命令队列已满,xTimerStart()将立即返回。

那么将xTicksToWait设置为portMAX_DELAY将导致调用任务无限期地保持在Blocked状态(没有超时),以等待timer命令队列中的可用空间。

和队列一样要使用portMAX_DELAY宏就要先FreeRTOSConfig.ht中的INCLUDE_vTaskSuspend设置为1

如果在启动调度器之前调用xTimerStart(),那么xTicksToWait的值将被忽略,xTimerStart()的行为就像xTicksToWait已被设置为零一样。

返回值

1.pdPASS命令成功发送。

2.pdFALSE队列已满无法写入。

xTimerStop()用于停止处于运行状态的软件定时器。停止软件计时器与将计时器转换为休眠状态相同。

例子如下:

程序创建了两个定时器,一个是单次的,一个是周期性的,回调函数里打印时间。

#define mainONE_SHOT_TIMER_PERIOD pdMS_TO_TICKS( 3333 )#define mainAUTO_RELOAD_TIMER_PERIOD pdMS_TO_TICKS( 500 )static void prvOneShotTimerCallback( TimerHandle_t xTimer ){  TickType_t xTimeNow;  xTimeNow = xTaskGetTickCount();  vPrintStringAndNumber( "One-shot timer callback executing", xTimeNow );  ulCallCount++;}static void prvAutoReloadTimerCallback( TimerHandle_t xTimer )  TickType_t xTimeNow;  xTimeNow = uxTaskGetTickCount();  vPrintStringAndNumber( "Auto-reload timer callback executing", xTimeNow );  ulCallCount++;}int main( void ){  TimerHandle_t xAutoReloadTimer, xOneShotTimer;  BaseType_t xTimer1Started, xTimer2Started;  xOneShotTimer = xTimerCreate("OneShot",mainONE_SHOT_TIMER_PERIOD,pdFALSE,0,prvOneShotTimerCallback );  xAutoReloadTimer = xTimerCreate("AutoReload",mainAUTO_RELOAD_TIMER_PERIOD,pdTRUE,0,prvAutoReloadTimerCallback );  if( ( xOneShotTimer != NULL ) && ( xAutoReloadTimer != NULL ) )  {    xTimer1Started = xTimerStart( xOneShotTimer, 0 );    xTimer2Started = xTimerStart( xAutoReloadTimer, 0 );      if( ( xTimer1Started == pdPASS ) && ( xTimer2Started == pdPASS ) )      {        vTaskStartScheduler();      }    }  for( ;; );}

效果:

3.定时器ID(Timer ID)

前文讲了每个软件定时器都有一个ID值。ID是一个空指针,应用程序编写人员可以将其用于任何目的。因为ID存储在void指针(void *)中,因此可以直接存储整数值,指向任何其他对象,或用作函数指针。

在 用函数xTimerCreate创建软件计时器时,会为ID分配一个初始值。在此之后,可以使用vTimerSetTimerlD() API函数更新ID,并使用pvTimerGetTimerID()来查询ID。

与其他软件定时器API函数不同,vTimerSetTimerlD()和pvTimerGetTimerlD()直接访问软件定时器——它们不向定时器命令队列发送命令。

void vTimerSetTimerID( const TimerHandle_t xTimer, void *pvNewID );void *pvTimerGetTimerID( TimerHandle_t xTimer );

例子:

static void prvTimerCallback( TimerHandle_t xTimer ){  TickType_t xTimeNow;  uint32_t ulExecutionCount;  ulExecutionCount = ( uint32_t ) pvTimerGetTimerID( xTimer );  ulExecutionCount++;  vTimerSetTimerID( xTimer, ( void * ) ulExecutionCount );  xTimeNow = xTaskGetTickCount();  if( xTimer == xOneShotTimer ) {    vPrintStringAndNumber( "One-shot timer callback executing", xTimeNow );  } else {    vPrintStringAndNumber( "Auto-reload timer callback executing", xTimeNow );    if( ulExecutionCount == 5 ) {      xTimerStop( xTimer, 0 );    }  }}

把定时器回调函数改成如上,把ID当做回调函数运行次数的计数,每次运行都取出ID并且加一,然后更新ID,当等于五时停止定时器,效果如下:

4.修改定时器的周期和重置软件定时器

软件定时器的周期可以使用xTimerChangePeriod()函数来改变。

如果使用xTimerChangePeriod()来更改已经在运行的计时器的周期,则计时器将使用新的周期值重新计算到期时间。重新计算的到期时间相对于调用xTimerChangePeriod()的时间,而不是相对于最初启动计时器的时间。

如果使用xTimerChangePeriod()来改变处于休眠状态(未运行的计时器)的周期,那么计时器将计算到期时间,并转换到运行状态(计时器将开始运行)。

BaseType_t xTimerChangePeriod( TimerHandle_t xTimer,                              TickType_t xNewTimerPeriodInTicks,                              TickType_t xTicksToWait );

xTimerReset()用于重置定时器,重置软件定时器意味着重新启动定时器;计时器的到期时间被重新计算为相对于计时器重置的时间,而不是计时器最初启动的时间。

BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait );

往期精彩:

嵌入式C语言几个重点(const、static、voliatile、位运算)

交叉编译环境、bootloader、kernel、根文件系统是什么?有什么联系?

嵌入式Linux驱动学习-7.什么是设备树?

从Linux内核中学习高级C语言宏技巧

嵌入式Linux驱动学习-5.驱动的分层分离思想


http://www.ppmy.cn/news/43151.html

相关文章

溯源取证-内存取证 高难度篇

今天的场景依然是windows场景,只不过此次场景分为两个镜像,本次学习主要学习如何晒别钓鱼邮件、如何提取钓鱼邮件、如何修复损坏的恶意文件、如何提取DLL动态链接库文件 本次需要使用的工具: volatility_2.6_lin64_standalone readpst clams…

SpringCloudalibaba微服务工具集

版本: Hoxton SR6 1.什么是微服务 官网 In short, the microservice architectural(架构) style is an approach to developing a single application as a suite(系列) of small services, each running in its own process(进程) and communicating with lightweight mech…

Windows系统生产力工具介绍

介绍 本文主要介绍在windows系统上如何安装一些常用的生产力软件,这些软件大多数都是开源免费使用的,包括markdown编辑器、知识管理软件、图片和视频工具、系统工具等,以及程序员专用的开发工具。根据本人的使用经验,将会不定期更…

PROFINET1.8.0.5协议移植问题汇总

注:记录个人移植过程遇到的问题,正在更新。。。 PROFINET1.8.0.5协议移植遇到问题汇总: 软件环境:TIA_V17 硬件环境:stm32F205_ZET6主控芯片TPS-1 PROFINET IO 20500PF00(芯片物料编码B0001.0.2&#xff09…

浙江海發進出口股份有限公司官网上线|LTD五金技术行业案例分享

​浙江海發進出口股份有限公司 (以下简称海發)是一家多元化的国际贸易企业。拥有自己的工厂,稳定的资金储备和最好的服务,在商业领域赢得了很高的声誉。地处长江三角洲交通经济中心嘉兴市。 浙江海發進出口股份有限公司 (以下简称海發)是一家多元化的国…

NLP / LLMs中的Temperature 是什么?

ChatGPT, GPT-3, GPT-3.5, GPT-4, LLaMA, Bard等大型语言模型的一个重要的超参数 大型语言模型能够根据给定的上下文或提示生成新文本,由于神经网络等深度学习技术的进步,这些模型越来越受欢迎。可用于控制生成语言模型行为的关键参数之一是Temperature …

Hadoop集群启动后,在web:50070端口只有两个datanode节点

spark集群部署规划: hadoop1 master worker datanode namenode secondarynamenode(hadoop) resourcemanager nodemanager(yarn) hadoop2 worker datanode nodemanager hadoop3 worker datanode nodemanager 问题引出:​​​​​​Hadoop集群启…

【活动】高效学习方法分享

1 写在前面 当今社会,学习已成为每个人不可避免的事情。无论是在校生还是职场人士,我们都需要不断地更新自己的知识和技能。而如何高效地学习,则成为了许多人迫切需要解决的问题。本文将分享一些高效学习方法,帮助你更快、更好地…