FreeRTOS软件定时器,相对前面的内容来说,软件定时器还是比较简单的,我们简单测试一下
因为是简单介绍,所以原理和源码的分析不会那么详细,具体可以根据文中API查看源码
使用起来记住创建,启动,回调函数核心步骤即可
..增加测试Demo,再次遇到溢出问题说明,定时器数量问题说明                 2021/12/2

目录

  • 问:什么时候使用软件定时器,什么时候使用硬件定时器?
  • 一、FreeRTOS软件定时器基础
    • 1.1 时钟来源
    • 1.2 运行原理
    • 1.3 使用注意事项
  • 二、API介绍
    • 2.1 创建定时器
    • 2.2 开始定时器(为什么开启定时器还需要等待时间)
    • 2.3 获取定时器ID
    • 2.4 停止/删除定时器
  • 三、测试Demo
    • 3.1 简单测试
      • 3.1.1 再遇溢出问题
      • 3.1.2 定时器数量问题
    • 3.2 做一个时钟(待更新...)

问:什么时候使用软件定时器,什么时候使用硬件定时器?

软件定时器可以解决硬件定时器数量不够的问题,理论上软件定时器可以很多,每个芯片的定时器外设是有限的,如果硬件定时器不够用,可以使用软件定时器。

但是,软件定时器相对硬件定时器来说,精度没有那么高(为什么不高?因为它以系统时钟为基准,系统时钟中断优先级又是最低,容易被打断)。 对于需要高精度要求的场合,不建议使用软件定时器。

同时软件定时器 是需要占用一部分内存空间的,用到的软件定时器数量越多,内存占用越大。 如果RAM空间不够用,不能使用软件定时器。

当然,使用软件定时器的程序还有一个好处就是方便移植,不同芯片的硬件定时器的设置代码是不一样的,在条件允许的情况下使用软件定时器,那么不同平台之间的的代码移植起来相对方便一点。

一、FreeRTOS软件定时器基础

1.1 时钟来源

系统的时钟周期,对于FreeRTOS而言,就是 TICK_RATE_HZ 对应的值。在以上的测试我们使用都是设置为默认的1000,那么系统的时钟节拍周期就为 1ms(1s 跳动 1000 下,每一下就为 1ms)。

1.2 运行原理

FreeRTOS 所创建的软件定时器共用一个任务prvTimerTask(也叫守护任务 Daemon)和队列,定时器处理API函数最终都是通过给队列发送信息,在任务中接收处理。

创建prvTimerTask任务:

在文章 FreeRTOS记录(六、FreeRTOS消息队列—Enocean模块串口通讯、RAM空间不足问题分析) 中的 第 5小结:5 、RAM空间不足问题 中讲到过只要使能了使用定时器,系统就会自动产生一个任务,占用内存空间,说的就是这个任务:

prvTimerTask任务:

  • 优先级为我们定时器的配置 configTIMER_TASK_PRIORITY

  • 任务的堆栈大小为 configTIMER_TASK_STACK_DEPTH

队列:

  • 队列的长度由配置 configTIMER_QUEUE_LENGTH 决定

prvTimerTask任务会在其执行期间检查用户启动的时间周期溢出的定时器,并调用其回调函数。在任务中最后会调用prvProcessReceivedCommands();函数:

定时器消息队列的命令在这个函数中进行处理:

在定时器创建好了以后,定时器并不会运行。在prvTimerTask任务中,如果暂时没有运行中的定时器,任务会进入阻塞态等待命令。 使用xTimerStart才会开始定时器的运行,启动函数通过“定时器命令队列” 向定时器任务发送一个启动命令,这个命令最终就在prvProcessReceivedCommands();函数中解析,定时器任务获得命令就解除阻塞,然后执行启动软件定时器命令。

1.3 使用注意事项

  • 软件定时器的定时时间必须是系统时钟周期的整数倍,如果我们定义的 TICK_RATE_HZ 为100,系统的时钟周期为10ms,必须为10的整数倍。那么5ms,15ms的延时是无法实现的。

  • 软件定时器使用了系统的一个队列和一个任务资源,软件定时器任务的优先级默认为configTIMER_TASK_PRIORITY,为了更好响应,该优先级应设置为所有任务中最高的优先级

  • 软件定时器的回调函数中应快进快出,切不可在定时器回调函数中调用任何将定时器任务挂起的函数,比如vTaskDelay(), vTaskDelayUntil()以及非零延迟的消息队列和信号量相关的函数,也绝对不允许出现死循环。

二、API介绍

FreeRTOS软件定时器的API所有的都可以在FreeRTOS驱动文件timers.h文件中找到,这里介绍常用的基本的几个:

2.1 创建定时器

  • xTimerCreate()xTimerCreateStatic()

创建以 xTimerCreate()为例介绍:

TimerHandle_t xTimerCreate( const char * const pcTimerName, /* 定时器名字,方便识别不同的定时器 */const TickType_t xTimerPeriod, /* 定时器周期,单位系统时钟节拍 */const UBaseType_t uxAutoReload, /* 若参数为 pdTRUE,则表示选择周期模式,若参数为pdFALSE,则表示选择单次模式 */void * const pvTimerID, /* 创建不同的定时器,但使用相同的回调函数时,在回调函数中通过不同的ID 号来区分不同的定时器。 */TimerCallbackFunction_t pxCallbackFunction ); /* 定时器回调函数 */...
void pxCallbackFunction (xTimerHandle pxTimer){...}

在CubeMX中封装后的函数为osTimerCreate

2.2 开始定时器(为什么开启定时器还需要等待时间)

  • xTimerStart()xTimerStartFromISR()

xTimerStart():

BaseType_t xTimerStart( TimerHandle_t xTimer, /* 定时器句柄 */TickType_t xBlockTime ); /* 成功启动定时器前的最大等待时间设置,单位系统时钟节拍 如果在 FreeRTOS 调度器开启之前调用 xTimerStart(),该形参将不起作用*/

说明:为什么开启定时器还需要等待时间?

前面 运行原理 已经介绍过,FreeRTOS的软件定时器是通过消息队列给定时器任务发消息来实现的,此参数设置的等待时间就是当消息队列已经满的情况下,等待消息队列有空间时的最大等待时间。

定时器任务实际执行消息队列发来的命令依赖于定时器任务的优先级,如果定时器任务是高优先级会及时得到执行,如果是低优先级,就要等待其余高优先级任务释放 CPU 权才可以得到执行。

对于已经被激活的定时器,即调用过函数 xTimerStart进行启动,再次调用此函数相当于调用了函数xTimerReset对定时器时间进行了复位。

如果在启动 FreeRTOS 调度器前调用了此函数,定时器是不会立即执行的,需要等到启动了 FreeRTOS调度器才会得到执行,即从此刻开始计时,达到xTimerCreate 中设置的单次或者周期性延迟时间才 会执行相应的回调函数。

xTimerStartFromISR():

/********************************************************************************************************@ 函数功能:在中断中启动一个软件定时器。*@ 函数参数:xTimer:软件定时器句柄  pxHigherPriorityTaskWoken:定时器守护任务的大部分时间都在阻塞态等待定时器命令队列的命令。 调用函数 xTimerStartFromISR()将会往定时器的命令队列发送一个启动命令,这很有可能会将定时器任务从阻塞除。 如果调用函数xTimerStartFromISR()让定时器任务脱离阻塞态, 且定时器守护任务的优先级大于或者等于当前被中断的任务的优先级,那么 pxHigherPriorityTaskWoken 的值会在函数xTimerStartFromISR()内部设置为 pdTRUE,然后在中断退出之前执行一次上下文切换。pxHigherPriorityTaskWoken:pxHigherPriorityTaskWoken 在使用之前必须初始化成pdFALSE。调用xEventGroupSetBitsFromISR()会给守护任务发送一个消息,如果守护任务的优先级高于当前被中断的任务的优先级的话(一般情况下都需要将守护任务的优先级设置为所有任务中最高优先级),pxHigherPriorityTaskWoken 会被置为 pdTRUE, 然后在中断退出前执行一次上下文切换。*@ 返回值:如果启动命令无法成功地发送到定时器命令队列则返回 pdFAILE,成功发送则返回pdPASS。软件定时器成功发送的命令是否真正的被执行也还要看定时器守护任务的优先级,其优先级由宏 configTIMER_TASK_PRIORITY 定义。
*******************************************************************************************************/
#define xTimerStartFromISR( xTimer, pxHigherPriorityTaskWoken )    xTimerGenericCommand( ( xTimer ), tmrCOMMAND_START_FROM_ISR,( xTaskGetTickCountFromISR() ),( pxHigherPriorityTaskWoken ), 0U )

2.3 获取定时器ID

  • pvTimerGetTimerID
void *pvTimerGetTimerID( const TimerHandle_t xTimer ) /* 定时器句柄 */

2.4 停止/删除定时器

  • xTimerStop()xTimerStopFromISR()

xTimerStop():

/********************************************************************************************************@ 函数功能:停止一个软件定时器, 让其进入休眠态。*@ 函数参数:xTimer:软件定时器句柄xBlockTime:用户指定超时时间, 单位为系统节拍周期(即 tick)。 如果在 FreeRTOS 调度器开启之前调用 xTimerStart(),形参将不起作用。*@ 返回值:如果启动命令在超时时间之前无法成功地发送到定时器命令队列则返回 pdFAILE,成功发送则返回 pdPASS。软件定时器成功发送的命令是否真正的被执行也还要看定时器守护任务的优先级,其优先级由宏 configTIMER_TASK_PRIORITY 定义。
*******************************************************************************************************/
BaseType_t xTimerStop( TimerHandle_t xTimer, TickType_t xBlockTime );

xTimerStopFromISR():

/********************************************************************************************************@ 函数功能:在中断中停止一个软件定时器, 让其进入休眠态。*@ 函数参数:xTimer:软件定时器句柄pxHigherPriorityTaskWoken:定时器守护任务的大部分时间都在阻塞态等待定时器命令队列的命令。调用函数 xTimerStopFromISR()将会往定时器的命令队列发送一个停止命令,这很有可能会将定时器任务从阻塞态移除 。 如果调用函数xTimerStopFromISR()让定时器任务脱离阻塞态,且定时器守护任务的优先级大于或者等于当前被中断的任务的优先级,那么 pxHigherPriorityTaskWoken 的值会在函数xTimerStopFromISR()内部设置为 pdTRUE, 然后在中断退出之前执行一次上下文切换。*@ 返回值:如果停止命令在超时时间之前无法成功地发送到定时器命令队列则返回pdFAILE,成功发送则返回 pdPASS。 软件定时器成功发送的命令是否真正的被执行也还要看定时器守护任务的优先级,其优先级由宏 configTIMER_TASK_PRIORITY 定义。
*******************************************************************************************************/
BaseType_t xTimerStopFromISR(TimerHandle_t xTimer,BaseType_t *pxHigherPriorityTaskWoken);
  • xTimerDelete()
/********************************************************************************************************@ 函数功能:删除一个已经被创建成功的软件定时器*@ 函数参数:xTimer:软件定时器句柄xBlockTime:用户指定的超时时间, 单位为系统节拍周期(即 tick), 如果在 FreeRTOS调度器开启之前调用 xTimerStart(), 该形参将不起作用。*@ 返回值:如果删除命令在超时时间之前无法成功地发送到定时器命令队列则返回 pdFAILE, 成功发送则返回 pdPASS。
*******************************************************************************************************/
#define xTimerDelete( xTimer, xTicksToWait )    xTimerGenericCommand( ( xTimer ),tmrCOMMAND_DELETE,0U, NULL, ( xTicksToWait ) )

三、测试Demo

3.1 简单测试

在CubeMX中定时器配置:

添加软件定时器:

程序设计:

  1. 生成的代码中需要定义一下 ID(上图的Parameter是NULL的,后来我改成了no1 和 no2,需要对no1 和 no2定义一下);
  2. 不能在调度前使用xTimerStart() 函数,所以单独创建了一个任务启动定时器,然后删除;
  3. 回调函数中发送任务通知给温湿度读取任务;
  4. 温湿度读取任务接收任务通知执行任务;
/*1.定义一下 ID*/#define no1 1#define no2 2../* Create the timer(s) *//* definition and creation of myTimer01 */osTimerDef(myTimer01, myTimeCallback);myTimer01Handle = osTimerCreate(osTimer(myTimer01), osTimerOnce, (void*) no1);/* definition and creation of myTimer02 */osTimerDef(myTimer02, myTimeCallback);myTimer02Handle = osTimerCreate(osTimer(myTimer02), osTimerPeriodic, (void*) no2);.../*2.创建了一个任务启动定时器*/
/* USER CODE END Header_StartTimerTask */
void StartTimerTask(void const * argument)
{/* USER CODE BEGIN StartTimerTask *//* Infinite loop */for(;;){osTimerStart(myTimer01Handle,5000);osTimerStart(myTimer02Handle,3000);vTaskDelete(NULL);osDelay(1);}/* USER CODE END StartTimerTask */
}
.../*
myTimeCallback function
3.回调函数中发送任务通知给温湿度读取任务
测试用,实际使用不能加printf在回调函数
*/
void myTimeCallback(void const * argument)
{/* USER CODE BEGIN myTimeCallback */uint8_t myTimerID;myTimerID = (uint8_t)pvTimerGetTimerID(argument);if (myTimerID == no1){osSignalSet(THreadHandle,test_signal1);printf("ulTimerID = %d,send a Signal_1 to threadtask!\r\n",myTimerID);}else if(myTimerID == no2){osSignalSet(THreadHandle,test_signal2); printf("ulTimerID = %d,send a Signal_2 to threadtask!\r\n",myTimerID);} /* USER CODE END myTimeCallback */
}/*4、温湿度读取函数,还是老样子,和第FreeRTOS记录(5、任务通知)中的函数一样*/
...

3.1.1 再遇溢出问题

本来是个简单的测试,测试结束完结散花,没想到又遇到了溢出问题:

遇到这个问题那可就不能不管了,为了项目中能够更加合理的分配RAM空间,问题必须深究到底!!!
问题一点一点剥开来测试!!!

我们看一下溢出情况下 定时器控制任务的 情况:

在CubeMX中能看到2个定时器需要的内存大小(目前的设置的configTIMER_TASK_STACK_DEPTH 的大小为 128 字,512Bytes)这样子看的话足够用的啊?:

接下来测试,把 configTIMER_TASK_STACK_DEPTH 改成 256 字后,运行起来是正常的:

此时再来看一下任务栈剩余情况(开始剩余2字,多了128字,剩余130,这点OK!):

那我始终觉得128字,应该是足够了,我把 configTIMER_TASK_STACK_DEPTH 改回128字,把myTimeCallback函数中的 printf 语句去掉,因为正常使用肯定不能带的(快进快出):

测试是能够正常工作的(128字去掉printf的情况下)看任务栈剩余结果:

还有测试Demo中我们创建了2个定时器,定时器创建完了就占用了内存空间,其中有一个单次的定时器,虽然只运行一次,但是他只是出于休眠状态,随时可以启动运行(虽然每次都是运行一次),如果我们只想他开机运行一次,那么我们可以在回调函数中删除此定时器,那么其所占用的内存就会释放:

测试结果:

通过上面我们也可以算出,定时器1占用了 78-26=52 字的空间。

3.1.2 定时器数量问题

可创建的软件定时器的数量是由什么决定的呢?

这个问题推测是和定义的队列长度有关,因为一个队列可以控制一个定时器。

我们做如下测试,把队列的长度,configTIMER_QUEUE_LENGTH 改为1,然后再运行上述代码:

测试结果如下(下图中红色部分的疑问暂时未解决):


上面测试看上去好像是 configTIMER_QUEUE_LENGTH 为1的话只能有一个任务了,为了确定确实是这样,继续测试,configTIMER_QUEUE_LENGTH 改为2,添加第三个任务:



在回调函数中测试一下(下图定时器3的回调函数,printf忘了在后面跟打印的变量了,这里不影响结果,后面的测试我添加上去了):

测试结果如下:

那最后的测试就是,把 configTIMER_QUEUE_LENGTH 改成3,不出意外,3个定时器就能正常运行了:

测试结果如下:

问题:为什么3个定时器开启了,和2个定时器占用的栈空间一样?(未解决)

3.2 做一个时钟(待更新…)

FreeRTOS记录(八、用软件定时器?还是硬件定时器?)相关推荐

  1. ESP-12S学习(3)--软件定时器和硬件定时器500ms点亮LED

    一.软件定时器的使用 ESP8266的软件定时器不是特别的精准,想要精准的定时器,那么最好是使用硬件定时器,这一点在SDK手册上面有说到 软定时有5个函数和一个结构体 结构体:os_timer_t t ...

  2. ESP32-C3入门教程 基础篇(六、TIMG 硬件定时器 与 软件定时器)

    到了测试第6课,还没有玩过ESP32-C3的基本定时器,虽然FreeRTOS,可以使用软件定时器 但是软件定时器毕竟也有不适用的时候,这个在我FreeRTOS博文中有单独说明. 所以硬件定时器也得熟悉 ...

  3. rtthread studio与正点原子apollo(3)--硬件定时器HTIMER

    rtthread studio与正点原子apollo[3]--硬件定时器HTIMER 前言 一.软件定时器和硬件定时器? 二.HTIMER使用详解 1.RT-Thread studio配置 2.功能代 ...

  4. esp8266~GPIO中断和硬件定时器的正确使用

    最近项目需要检测IO口下降沿信号和定时计数,于是就用到了GPIO中断和硬件定时器.有点可惜,github上面RTOS版sdk没有硬件定时器的使用方法,能够参考的只有1.5的sdk,然而我不会移植,只能 ...

  5. 【龙芯1c库】封装硬件定时器接口和使用示例

    龙芯1c库是把龙芯1c的常用外设的常用功能封装为一个库,类似于STM32库.完整源码请移步到https://gitee.com/caogos/OpenLoongsonLib1c 龙芯1c库中硬件定时器 ...

  6. 基于硬件定时器的软件定时器

    概括 硬件定时器很精确,软件定时器无论如何都有延迟,主要用在不需要精确定时的地方,而且软件定时比较浪费单片机资源. 梳理 讲到定时器,大家多多少少都会接触到硬件定时器,但是由于有时候资源的限制,又难免 ...

  7. FreeRTOS学习六(软件定时器)

    软件定时器允许设置一段时间,当设置的时间到达之后就执行指定的功能函数,被定时器调用的这个功能函数叫做定时器的回调函数.回调函数的两次执行间隔叫做定时器的定时周期,简而言之,当定时器的定时周期到了以后就 ...

  8. FreeRTOS记录(九、一个裸机工程转FreeRTOS的实例)

    记录一下一个实际项目由裸机程序改成FreeRTOS,以前产品的平台还是C8051单片机上面的程序, 硬件平台改成了STM32L051,同时使用STM32CubeMX生成的工程,使用FreeRTOS系统 ...

  9. FreeRTOS记录(四、FreeRTOS任务堆栈溢出问题和临界区)

    本来计划是消息队列.信号量.任务通知.事件集.邮件的文章 但是因为自己调试的时候遇到了一个问题,还是把堆栈溢出问题放到前面来说 ..增加临界区的使用说明 2021/11/7 ..临界区的使用部分增加任 ...

最新文章

  1. python3.6.5下载-python3.6.5下载
  2. 42翻转单词顺序列+注意该题找单词的方法
  3. mysql 重复字段查询及排除重复值
  4. 在VMware开启此虚拟机时出现内部错误
  5. GitHub Action + ACK:云原生 DevOps 落地利
  6. 开发者论坛一周精粹(第五十七期) 阿里云免费套餐 个人备案备注
  7. MATLAB基础学习笔记02:掌握MATLAB运算
  8. 百度地图开发的时候遇到的问题(二)
  9. [蓝桥杯历届试题] 汉诺塔计数
  10. Session Cookie 之我见
  11. 安装破解版的edraw max
  12. stata 将数据集变量名称导出_Stata 15 统计数据分析软件
  13. Java游戏编程不完全详解-2(1万2千字吐血推荐)
  14. java开发autocad_.NET AutoCAD二次开发之路(四、文字篇)
  15. Mac 好用的 Android 模拟器整理(玩游戏、装应用、支持咸鱼、拼多多...)
  16. 雪鹰领主手游战力提升辅助攻略 雪鹰领主手游脚本工具介绍
  17. 每日一诗词 —— 将进酒
  18. 学习Python处理Excel 难度0级别 多表合并、多条件筛选、找出重复项、去重
  19. Qt 界面获取键盘Enter键
  20. D38 Java智能电话项目本————创建实体类

热门文章

  1. 笔记本重新启动计算机,电脑正在重新启动很久了怎么办
  2. virtualbox 虚拟机里支持 USB3.0 , 无线网卡
  3. 机器人学习笔记(一)
  4. 30岁女IT月薪3W的背后:从数据报表到数仓、中台,这工具帮了大忙
  5. Java中的测不准原理
  6. web前端工具(配色图片图标)
  7. 我的docker随笔38:用 registry 搭建私有仓库
  8. linux scp 指令使用
  9. Delphi ListView的用法(常用技巧) 作者:蓝色忧郁
  10. git报错:[0x7FF8A8967EA0] ANOMALY: meaningless REX prefix used