FreeRTOS-软件定时器使用注意事项
软件定时器可以识别的基本时间单位:
内核在每次tick中断
发生时,会更新其内部的tick
计数值加 1,来实现“感知时间”功能(更多信息可以参考文章:FreeRTOS-内核对时间的测量)。而FreeRTOS提供的软件定时器,就是基于内核的tick
计数值,来实现定时功能的。
例如,假设内核的tick中断
产生的周期为100 ms,即每100 ms产生一次tick中断
。如果一个定时器设定的超时时间为 1秒,那么识别该定时器超时,就是识别从该定时器启动后,tick
计数值是否增加了10。
因此,FreeRTOS的软件定时器,可以识别的基本时间单位,就是内核产生tick中断
的周期。tick中断
周期由工程配置文件FreeRTOSConfig.h
中定义的configTICK_RATE_HZ
决定:
1 |
如上配置,就是设置内核产生tick中断
的频率,为一秒产生100次tick中断
,即周期即为 10 ms。 那么软件定时器,可以识别的基本时间单位就是10ms,可以设置的超时时间需要是 10ms 的 n 倍(n为大于 0的整数)。
如果设定的超时时间小于10ms,对定时器来说就是0,这个参数是不合法的,如果我们在开发阶段使能了断言宏,就会立刻报错。
在开发阶段,我们应该实现工程配置文件FreeRTOSConfig.h
中的断言宏 configASSERT
。很多配置、参数错误的问题,内核都会通过configASSERT
给出错误的位置。例如一个简单的实现:
1 | extern void vAssertCalled( const char * pcFile, uint32_t ulLine ); |
然后在 main.c 或其它源文件中实现具体的函数,将出错的文件和具体行号信息输出:
1 | void vAssertCalled( const char * pcFile, uint32_t ulLine ){ |
如果定义了上述宏configASSERT
,那么在调用 API xTimerCreate
创建软件定时器时,如果传入的定时器超时时间,小于软件定时器可以识别的基本时间单位,就会立刻给出错误提示。
软件定时器服务任务(prvTimerTask)优先级的影响:
在之前的两篇文章FreeRTOS-软件定时器的实现原理和FreeRTOS-软件定时器的使用 中,介绍了FreeRTOS的软件定时器,是一种分离式的设计。如下图所示:
当我们调用软件定时器相关的 API 时,内部实际是发送了相应的定时器命令到软件定时器专用的消息队列
中,而软件定时器服务任务(或称为daemon task,使能FreeRTOS的软件定时器功能后,内核在启动时会自动创建该任务
),即上图中的prvTimerTask任务,会从软件定时器专用的消息队列中,提取定时器命令,并处理。
此外,每当有定时器的超时时间到达了,其超时回调函数(创建定时器时的参数pxCallbackFunction
)也是在prvTimerTask
任务中调用的。
prvTimerTask
任务的优先级,在工程配置文件FreeRTOSConfig.h
中定义,如下配置即将其优先级设置为 2
1 |
由于软件定时器的超时回调函数,是在prvTimerTask
任务中调用的。因此,不能让prvTimerTask
任务长时间得不到运行。否则,即使一些定时器超时了,由于prvTimerTask
任务得不到运行,那么也就不能调用那些已经超时的定时器 的超时回调函数。
一个简单的测试实验,配置定时器服务任务prvTimerTask
的优先级为2,并创建一个周期定时器,输出信息。再创建 2个优先级均为 3的任务task_a
和task_b
。task_a
无限循环一直空跑,task_b
则每秒输出一次信息。如下所示:
1 |
|
烧录程序运行,只能看到 task_b
的输出信息,看不到定时器超时回调函数的输出信息。
1 | start FreeRTOS |
这是因为,task_a
任务一直保持空转,没有调用任何会阻塞task_a
任务的api,所以它一直是就绪态(被运行时,就是运行态),任务调度时总是选择处于就绪状态且优先级最高的那个任务,由于task_a
优先级又比prvTimerTask
高,所以每次都不会选择prvTimerTask
任务。task_b
能得到运行,是因为它和task_a
优先级一样,所以task_b
和task_a
轮流运行(时间片调度)。
如果将task_a
、task_b
和prvTimerTask
优先级都设置相同,默认就是轮流调用,此时就能看到定时器超时回调函数的输出信息。
1 | start FreeRTOS |
不要在超时回调函数中调用会让任务阻塞的 API:
原因还是如上一节所述,软件定时器的超时回调函数,是在prvTimerTask
任务中调用的,如果你在某个定时器的超时回调函数中,调用了会让任务阻塞的API(如vTaskDelay
),那么prvTimerTask
任务就会进入阻塞态,会暂停运行,那么也就不能调用那些已经超时的定时器的超时回调函数了。超时回调函数需要等到prvTimerTask
任务恢复就绪态,且prvTimerTask
任务被调度时才能被调用。( 但对于类似xQueueSend有超时参数的 API,当超时参数设置为 0时,任务是不会阻塞的,所以可以使用
)
一个简单的测试例子:创建两个单次触发的定时器timer1
和timer2
,超时时间均为 1秒钟,启动时先启动timer1
。在timer1
的超时回调函数中调用vTaskDelay
,让其延迟 5秒钟。则可以观察到,timer2
的超时回调函数,需要等到 5秒延迟结束后才会被调用。
代码如下:
1 |
|
烧录运行可以看到如下输出信息:
1 | start FreeRTOS |
ps:需要注意文章代码中的日志输出函数,产品代码中如果需要使用的话,需要考虑线程安全性(多任务安全性),因为中断/任务切换可能发生在另一个任务正在输出日志但还未输出完的时候,这就可能造成日志错乱
FreeRTOS交流QQ群-663806972