FreeRTOS 记录(八、用软件定时器?还是硬件定时器?)
说明:FreeRTOS 专栏与我的 RT-Thread 专栏不同,我的 RT-Thread 专栏是从理论学习一步一步循序渐进,从 0 起步的 完整教学,而 FreeRTOS 更偏向于 我直接拿来使用,需要用到什么,然后引出知识点,在使用中发现问题,解然后再解决问题。
本 FreeRTOS 专栏记录的开发环境:
FreeRTOS 记录(一、熟悉开发环境以及 CubeMX 下 FreeRTOS 配置)
https://xie.infoq.cn/article/e82c7e10af242105b509bee2e
FreeRTOS 记录(二、FreeRTOS 任务 API 认识和源码简析)
https://xie.infoq.cn/article/250c67b1cc68b91241347f8e6
FreeRTOS 记录(三、RTOS 任务调度原理解析 _Systick、PendSV、SVC)
https://xie.infoq.cn/article/e0c671e5ca2d472ce40d272d4
FreeRTOS 记录(四、FreeRTOS 任务堆栈溢出问题和临界区)
https://xie.infoq.cn/article/d3f7d46aa262b2118a544f250
FreeRTOS 记录(五、FreeRTOS 任务通知)
https://xie.infoq.cn/article/bd3a7f06b5148b665893b6ac6
FreeRTOS 记录(六、FreeRTOS 消息队列—Enocean 模块串口通讯、RAM 空间不足问题分析))
https://xie.infoq.cn/article/273f300ca626097eb6b44d6cb
FreeRTOS 记录(七、FreeRTOS 信号量、事件标志组、邮箱和消息队列、任务通知的关系)
问:什么时候使用软件定时器,什么时候使用硬件定时器?
软件定时器可以解决硬件定时器数量不够的问题,理论上软件定时器可以很多,每个芯片的定时器外设是有限的,如果硬件定时器不够用,可以使用软件定时器。
但是,软件定时器相对硬件定时器来说,精度没有那么高(为什么不高?因为它以系统时钟为基准,系统时钟中断优先级又是最低,容易被打断)。 对于需要高精度要求的场合,不建议使用软件定时器。
同时软件定时器 是需要占用一部分内存空间的,用到的软件定时器数量越多,内存占用越大。如果 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()
为例介绍:
在 CubeMX 中封装后的函数为osTimerCreate
:
2.2 开始定时器(为什么开启定时器还需要等待时间)
xTimerStart()
和xTimerStartFromISR()
xTimerStart():
说明:为什么开启定时器还需要等待时间?
前面 运行原理 已经介绍过,FreeRTOS 的软件定时器是通过消息队列给定时器任务发消息来实现的,此参数设置的等待时间就是当消息队列已经满的情况下,等待消息队列有空间时的最大等待时间。
定时器任务实际执行消息队列发来的命令依赖于定时器任务的优先级,如果定时器任务是高优先级会及时得到执行,如果是低优先级,就要等待其余高优先级任务释放 CPU 权才可以得到执行。
对于已经被激活的定时器,即调用过函数 xTimerStart
进行启动,再次调用此函数相当于调用了函数xTimerReset
对定时器时间进行了复位。
如果在启动 FreeRTOS 调度器前调用了此函数,定时器是不会立即执行的,需要等到启动了 FreeRTOS 调度器才会得到执行,即从此刻开始计时,达到xTimerCreate
中设置的单次或者周期性延迟时间才 会执行相应的回调函数。
xTimerStartFromISR():
2.3 获取定时器 ID
pvTimerGetTimerID
2.4 停止/删除定时器
xTimerStop()
和xTimerStopFromISR()
xTimerStop():
xTimerStopFromISR():
xTimerDelete()
三、测试 Demo
3.1 简单测试
在 CubeMX 中定时器配置:
添加软件定时器:
程序设计:
生成的代码中需要定义一下 ID(上图的 Parameter 是 NULL 的,后来我改成了 no1 和 no2,需要对 no1 和 no2 定义一下);
不能在调度前使用
xTimerStart()
函数,所以单独创建了一个任务启动定时器,然后删除;回调函数中发送任务通知给温湿度读取任务;
温湿度读取任务接收任务通知执行任务;
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,然后再运行上述代码:
测试结果如下<font color=#FF0033>(下图中红色部分的疑问暂时未解决):
上面测试看上去好像是 configTIMER_QUEUE_LENGTH 为 1 的话只能有一个任务了,为了确定确实是这样,继续测试,configTIMER_QUEUE_LENGTH 改为 2,添加第三个任务:
在回调函数中测试一下(下图定时器 3 的回调函数,printf 忘了在后面跟打印的变量了,这里不影响结果,后面的测试我添加上去了):
测试结果如下:
那最后的测试就是,把 configTIMER_QUEUE_LENGTH 改成 3,不出意外,3 个定时器就能正常运行了:
测试结果如下:
留一个问题:为什么 3 个定时器开启了,和 2 个定时器占用的栈空间一样?
版权声明: 本文为 InfoQ 作者【矜辰所致】的原创文章。
原文链接:【http://xie.infoq.cn/article/c665e5d9ad69431aebb44f51d】。文章转载请联系作者。
评论