详解FreeRTOS中的软件定时器

news2024/11/17 3:50:43

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

参考资料:

《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.coloradmin.cn/o/419845.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue2、vue3使用i18n实现国际化

一、目标效果 源码地址:git clone i18n-demo: vue2、vue3实现i18n国际化功能 默认语言是中文: 点击语言切换,变成英文(这里简单设置了中文、英文两种语言) 注意:vue2支持 vue-i18n 版本8.0 ,vu…

本轮沙尘已跨过长江!这些地区请注意加强防范

气象台4月12日06时继续发布沙尘暴蓝色预警:受冷空气大风影响,预计4月12日08时至13日08时,新疆南疆盆地和东部、内蒙古中西部、甘肃河西和中部、宁夏、陕西中部、山西、河北、辽宁、北京、天津、山东、河南、安徽、江苏、上海、湖北中北部、浙…

漏洞利用之弱口令

漏洞利用之弱口令1.弱口令的定义2.弱口令字典生成收集用户信息,组成爆破字典特殊字符增强的字典生成字典生成项目3.利用 Burp Suite 进行密码爆破1.弱口令的定义 弱口令没有严格和准确的定义,通常认为容易被别人猜测到或被破解工具破解的口令均为弱口令…

【Mysql】mysql8.0.26解压包部署方式

版本背景: 操作系统:centos7.3 mysql版本:mysql-8.0.26-linux-glibc2.12-x86_64.tar 一、前期准备 1、检测操作系统自带安装的mysql和mariadb服务,如存在,需卸载 rpm -qa | grep mysql rpm -qa | grep mariadb 卸载…

vim编辑器使用

目录 vim基本概念 vim基本操作 插入模式 命令模式 移动光标 删除字符 复制剪切 替换 撤销 更改 移动指定行 底行模式 命令集 vim简单配置 vim基本概念 vim编辑器有三种模式: 命令模式:光标移动,字符或行移动&#x…

iTOP-RK3568开发板Android kernel移植-单独编译内核

此方法常用于 kernel 的开发和调试,以下的方法既编译 kernel 部分时, 同 时打包成 boot.img, 这样加快了我们开发的速度; 进入内核目录下, 输入以下命令: cd kernel make ARCHarm64 CC../prebuilts/cla…

Vue:axios

1、 第三方库方式, 基于 Promise 的 HTTP 库:axios (对 XMLHttpRequest进行的封装) 即: axios.get().then() 2、跨域访问 定义:在 a 页面中想获取 b 页面中的资源,如果 a 页面和 b 页面所处的协…

Java基础从入门到精通系列(一)

1、Java简介 Java语言是一种高级、并发、面向对象的计算机编程语言,由Sun Microsystems公司于1995年推出。Java广泛应用于Web应用程序开发、移动应用程序开发以及大型企业级应用程序的开发中。Java语言具有跨平台、安全性好、可移植性强、易学易用等优点&#xff0…

JavaSE学习进阶day04_01 Date类

第一章 Date类 首先对时间的几个补充有必要知道: 时间起点: 心得: 如果以后我们仅仅要展示时间,那么可以用Date和SimpleDateFormat(格式化) 如果我们要拿着两个时间进行计算,用Date和SimpleD…

【C++升级之路】第十二篇:模板进阶 | 非类型模板参数 模板特化 类模板的分离编译 模板优缺点总结

🌟hello,各位读者大大们你们好呀🌟 🍭🍭系列专栏:【C学习与应用】 ✒️✒️本篇内容:非类型模板参数,函数模板特化,类模板特化(全特化,部分特化&a…

【系统集成项目管理工程师】信息系统集成专业知识

信息系统集成专业技术 一 信息系统建设 1 信息系统生命周期 立项阶段:即概念阶段或需求阶段,这一阶段根据用户业务发展和经营管理的需要,提出建设信息系统的初步构想,然后对企业信息系统的需求进行深入调研和分析,形…

K8s结合docker部署

原生安装步骤 安装必要的环境依赖与工具 sudo apt-get install \apt-transport-https \ca-certificates \curl \gnupg \lsb-release下载证书更新 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-key…

Linux基础命令-seq打印数字序列

Linux基础命令-sed流编辑器 前言 seq命令通常是用来打印一串有规律的数字,常与其他命令搭配使用,一起来看下它的用法。 一. 命令介绍 在doc文档中查看seq命令的含义 NAMEseq - print a sequence of numbers DESCRIPTIONPrint numbers from FIRST to…

4.14-4.16学习总结

多线程: 线程: 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。 进程: 进程是程序的基本执行实体 举个例子:360运行之后,它就可以看做是一个进程,但…

【UE4】关卡流送的demo

关卡流送功能可以将地图文件加载到内存中,或者从内存中卸载,并在游戏过程中切换地图的可视性。 这样一来,场景便能拆分为较小的地图块,并且只有相关部分才会占用资源并被渲染。 正确设置后,开发者便能创建大型、无缝衔…

c++STL容器之序列式容器

目录 vector容器 vector对象的默认构造 vector的初始化 vector的遍历 vector的增删改查 vector末尾的添加移除操作 vector的数据存取 deque容器 deque对象的默认构造 deque末尾的添加移除操作 deque的数据存取 stack容器 stack对象的默认构造 stack的push()与po…

Midjourney科普介绍

Midjourney是啥? Midjourney是一个由Midjourney研究实验室开发的人工智能程序,可根据文本生成图像,于2022年7月12日进入公开测试阶段,使用者可通过Discord的机器人指令进行操作,可以创作出很多的图像作品。 以下这些…

ROS2中将octomap发布到Moveit中作为碰撞物体

1.安装准备 这里假设你已经装好了ROS2以及Moveit2(都用二进制安装就行,不用从源码安转),没有安装好的,可以按照鱼香ROS的教程安装,两三行命令就搞定了。 我的ROS2版本为humble,请根据你使用的实…

银行数字化转型导师坚鹏:银行同业核心产品与营销策略解读

数字化背景下银行同业核心产品与营销策略解读课程背景: 数字化背景下,很多银行存在以下问题: 不清楚银行同业核心产品发展现状? 不清楚如何银行同业产品营销策略? 不知道如何更好地挖掘他行优质客户? 课…

国产化系统改造实践(未完)

一、项目背景 2020 年,红帽公司宣布,将在 2021 年 12 月 31 日和 2024 年 6 月 30 日分别终止对 CentOS 8 和 CentOS 7 的服务支持,把 CentOS 项目的工作和投资集中在CentOS Stream 上。 CentOS Linux 8已于2021年12月31日停止维护,CentOS Linux7也 将于2024年6月停服。s所…