文章目录
- 一、软件定时器简介
- 二、定时器服务/Daemon 任务
- 三、单次定时器和周期定时器
- 四、复位软件定时器
- 1. 函数 xTimerReset()
- 2. 函数 xTimerResetFromISR()
- 五、创建软件定时器
- 1. 函数 xTiemrCreate()
- 2. 函数 xTimerCreateStatic()
- 六、开启软件定时器
- 1. 函数 xTimerStart()
- 2. 函数 xTimerStartFromISR()
- 七、停止软件定时器
- 1. 函数 xTimerStop()
- 2. 函数 xTimerStopFromISR()
一、软件定时器简介
1. 软件定时器概述
软件定时器允许设置一段时间,当设置的时间到达之后就执行指定的功能函数,被定时器调用的这个功能函数叫做定时器的回调函数。回调函数的两次执行间隔叫做定时器的定时周期,简而言之,当定时器的定时周期到了以后就会执行回调函数。
2. 编写回调函数的注意事项
软件定时器的回调函数是在定时器服务任务中执行的,所以一定不能在回调函数中调用任何会阻塞任务的 API 函数!比如,定时器回调函数中千万不能调用 vTaskDelay()、vTaskDelayUnti(),还有一些访问队列或者信号量的非零阻塞时间的 API 函数也不能调用。
二、定时器服务/Daemon 任务
1. 定时器服务任务与队列
定时器是一个可选的、不属于 FreeRTOS 内核的功能,它是由定时器服务(或 Daemon)任务来提供的。FreeRTOS 提供了很多定时器有关的 API 函数,这些 API 函数大多都使用 FreeRTOS的队列发送命令给定时器服务任务。这个队列叫做定时器命令队列。定时器命令队列是提供给FreeRTOS 的软件定时器使用的,用户不能直接访问!下图描述了这个过程:
上图左侧部分属于用户应用程序的一部分,并且会在某个用户创建的用户任务中调用。图中右侧部分是定时器服务任务的任务函数,定时器命令队列将用户应用任务和定时器服务任务连接在一起。在这个例子中,应用程序调用了函数 xTimerReset(),结果就是复位命令会被发送到定时器命令队列中,定时器服务任务会处理这个命令。应用程序是通过函数 xTimerReset()间接的向定时器命令队列发送了复位命令,并不是直接调用类似 xQueueSend()这样的队列操作函数发送的。
2. 定时器相关配置
由上面知道软件定时器有一个定时器服务任务和定时器命令队列,这两个东西肯定是要配置的,配置方法和我们前面讲解的FreeRTOSCofig.h 一样,而且相关的配置也是放到文件 FreeRTOSConfig.h 中的,涉及到的配置如下:
(1)configUSE_TIMERS
如果要使用软件定时器的话宏 configUSE_TIMERS 一定要设置为 1,当设置为 1 的话定时器服务任务就会在启动 FreeRTOS 调度器的时候自动创建。
(2)configTIMER_TASK_PRIORITY
设置软件定时器服务任务的任务优先级,可以为 0~(configMAX_PRIORITIES-1)。优先级一定要根据实际的应用要求来设置。如果定时器服务任务的优先级设置的高的话,定时器命令
队列中的命令和定时器回调函数就会及时的得到处理。
(3)configTIMER_QUEUE_LENGTH
此宏用来设置定时器命令队列的队列长度。
(4)configTIMER_TASK_STACK_DEPTH
此宏用来设置定时器服务任务的任务堆栈大小,单位为字,不是字节!,对于 STM32 来说一个字是 4 字节。由于定时器服务任务中会执行定时器的回调函数,因此任务堆栈的大小一定要根据定时器的回调函数来设置。
三、单次定时器和周期定时器
软件定时器分两种:单次定时器和周期定时器,单次定时器的话定时器回调函数就执行一次,比如定时 1s,当定时时间到了以后就会执行一次回调函数,然后定时器就会停止运行。对于单次定时器我们可以再次手动重新启动(调用相应的 API 函数即可),但是单次定时器不能自动重启。相反的,周期定时器一旦启动以后就会在执行完回调函数以后自动的重新启动,这样回调函数就会周期性的执行。下图描述了单次定时器和周期定时器的不同:
图中 Timer1 为单次定时器,定时器周期为 100,Timer2 为周期定时器,定时器周期为 200。
四、复位软件定时器
有时候我们可能会在定时器正在运行的时候需要复位软件定时器,复位软件定时器的话会重新计算定时周期到达的时间点,这个新的时间点是相对于复位定时器的那个时刻计算的,并不是第一次启动软件定时器的那个时间点。下图演示了这个过程,Timer1 是单次定时器,定时周期是 5s:
在上图中我们展示了定时器复位过程,这是一个通过按键打开 LCD 背光的例子,我们假定当唤醒键被按下的时候应用程序打开 LCD 背光,当 LCD 背光点亮以后如果 5s 之内唤醒键没有再次按下就自动熄灭。如果在这 5s 之内唤醒键被按下了,LCD 背光就从按下的这个时刻起再亮 5s。
FreeRTOS 提供了两个 API 函数来完成软件定时器的复位,如下表所示:
1. 函数 xTimerReset()
复位一个软件定时器,此函数只能用在任务中,不能用于中断服务函数!此函数是一个宏,真正执行的是函数 xTimerGenericCommand(),函数原型如下:
BaseType_t xTimerReset( TimerHandle_t xTimer,
TickType_t xTicksToWait )
参数:
xTimer: 要复位的软件定时器的句柄。
xTicksToWait: 设置阻塞时间,调用函数 xTimerReset ()开启软件定时器其实就是向定时器命令队列发送一条tmrCOMMAND_RESET 命令,既然是向队列发送消息,那肯定会涉及到入队阻塞时间的设置。
返回值:
pdPASS: 软件定时器复位成功,其实就是命令发送成功。
pdFAIL: 软件定时器复位失败,命令发送失败。
2. 函数 xTimerResetFromISR()
此函数是 xTimerReset()的中断版本,此函数用于中断服务函数中!此函数是一个宏,真正执行的是函数 xTimerGenericCommand(),函数原型如下:
BaseType_t xTimerResetFromISR( TimerHandle_t xTimer,
BaseType_t * pxHigherPriorityTaskWoken );
参数:
xTimer: 要复位的软件定时器的句柄。
pxHigherPriorityTaskWoken: 记退出此函数以后是否进行任务切换,这个变量的值函数会自动设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。
返回值:
pdPASS: 软件定时器复位成功,其实就是命令发送成功。
pdFAIL: 软件定时器复位失败,命令发送失败。
五、创建软件定时器
使用软件定时器之前要先创建软件定时器,软件定时器创建函数如下表所示:
1. 函数 xTiemrCreate()
此函数用于创建一个软件定时器,所需要的内存通过动态内存管理方法分配。新创建的软件 定 时 器 处 于 休 眠 状 态 , 也 就 是 未 运 行 的 。 函 数 xTimerStart() 、 xTimerReset() 、xTimerStartFromISR() 、 xTimerResetFromISR() 、xTimerChangePeriod() 和xTimerChangePeriodFromISR()可以使新创建的定时器进入活动状态,此函数的原型如下:
TimerHandle_t xTimerCreate( const char * const pcTimerName,
TickType_t xTimerPeriodInTicks,
UBaseType_t uxAutoReload,
void * pvTimerID,
TimerCallbackFunction_t pxCallbackFunction )
参数:
pcTimerName: 软件定时器名字,名字是一串字符串,用于调试使用。
xTimerPeriodInTicks : 软件定时器的定时器周期, 单位是时钟节拍数。可以借助portTICK_PERIOD_MS 将 ms 单位转换为时钟节拍数。举个例子,定时器的周期为 100 个时钟节拍的话,那么 xTimerPeriodInTicks 就为100,当定时器周期为 500ms 的时候 xTimerPeriodInTicks 就可以设置为(500/portTICK_PERIOD_MS)。
uxAutoReload: 设置定时器模式,单次定时器还是周期定时器?当此参数为 pdTRUE的时候表示创建的是周期定时器。如果为 pdFALSE 的话表示创建的是单次定时器。
pvTimerID: 定时器 ID 号,一般情况下每个定时器都有一个回调函数,当定时器定时周期到了以后就会执行这个回调函数。但是 FreeRTOS 也支持多个定时器共用同一个回调函数,在回调函数中根据定时器的 ID 号来处理不同的定时器。
pxCallbackFunction: 定时器回调函数,当定时器定时周期到了以后就会调用这个函数。
返回值:
NULL: 软件定时器创建失败。
其他值: 创建成功的软件定时器句柄。
2. 函数 xTimerCreateStatic()
此函数用于创建一个软件定时器,所需要的内存需要用户自行分配。新创建的软件定时器处于休眠状态,也就是未运行的。函数 xTimerStart()、xTimerReset()、xTimerStartFromISR()、xTimerResetFromISR()、xTimerChangePeriod()和xTimerChangePeriodFromISR()可以使新创建的定时器进入活动状态,此函数的原型如下:
TimerHandle_t xTimerCreateStatic(const char * const pcTimerName,
TickType_t xTimerPeriodInTicks,
UBaseType_t uxAutoReload,
void * pvTimerID,
TimerCallbackFunction_t pxCallbackFunction,
StaticTimer_t * pxTimerBuffer )
参数:
pcTimerName: 软件定时器名字,名字是一串字符串,用于调试使用。
xTimerPeriodInTicks : 软件定时器的定时器周期, 单位是时钟节拍数。可以借助portTICK_PERIOD_MS 将 ms 单位转换为时钟节拍数。举个例子,定时器的周期为 100 个时钟节拍的话,那么 xTimerPeriodInTicks 就为100,当定时器周期为 500ms 的时候 xTimerPeriodInTicks 就可以设置为(500/portTICK_PERIOD_MS)。
uxAutoReload: 设置定时器模式,单次定时器还是周期定时器?当此参数为 pdTRUE的时候表示创建的是周期定时器。如果为 pdFALSE 的话表示创建的是单次定时器。
pvTimerID: 定时器 ID 号,一般情况下每个定时器都有一个回调函数,当定时器定时周期到了以后就会执行这个回调函数。当时 FreeRTOS 也支持多个定时器共用同一个回调函数,在回调函数中根据定时器的 ID 号来处理不同的定时器。
pxCallbackFunction: 定时器回调函数,当定时器定时周期到了以后就会调用这个函数。
pxTimerBuffer: 参数指向一个 StaticTimer_t 类型的变量,用来保存定时器结构体。
返回值:
NULL: 软件定时器创建失败。
其他值: 创建成功的软件定时器句柄。
六、开启软件定时器
如果软件定时器停止运行的话可以使用 FreeRTOS 提供的两个开启函数来重新启动软件定时器,这两个函数如下表所示:
1. 函数 xTimerStart()
启动软件定时器,函数 xTimerStartFromISR()是这个函数的中断版本,可以用在中断服务函数中。如果软件定时器没有运行的话调用函数 xTimerStart()就会计算定时器到期时间,如果软件定时器正在运行的话调用函数 xTimerStart()的结果和 xTimerReset()一样。此函数是个宏,真正执行的是函数 xTimerGenericCommand,函数原型如下:
BaseType_t xTimerStart( TimerHandle_t xTimer,
TickType_t xTicksToWait )
参数:
xTimer: 要开启的软件定时器的句柄。
xTicksToWait: 设置阻塞时间,调用函数 xTimerStart()开启软件定时器其实就是向定时器命令队列发送一条tmrCOMMAND_START 命令,既然是向队列发送消息,那肯定会涉及到入队阻塞时间的设置。
返回值:
pdPASS: 软件定时器开启成功,其实就是命令发送成功。
pdFAIL: 软件定时器开启失败,命令发送失败。
2. 函数 xTimerStartFromISR()
此函数是函数 xTimerStart()的中断版本,用在中断服务函数中,此函数是一个宏,真正执行的是函数 xTimerGenericCommand(),此函数原型如下:
BaseType_t xTimerStartFromISR( TimerHandle_t xTimer,
BaseType_t * pxHigherPriorityTaskWoken );
参数:
xTimer: 要开启的软件定时器的句柄。
pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值函数会自动设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。
返回值:
pdPASS: 软件定时器开启成功,其实就是命令发送成功。
pdFAIL: 软件定时器开启失败,命令发送失败。
七、停止软件定时器
既然有开启软件定时器的 API 函数,那么肯定也有停止软件定时器的函数,FreeRTOS 也提供了两个用于停止软件定时器的 API 函数,如下表所示:
1. 函数 xTimerStop()
此函数用于停止一个软件定时器,此函数用于任务中,不能用在中断服务函数中!此函数是一个宏,真正调用的是函数 xTimerGenericCommand(),函数原型如下:
BaseType_t xTimerStop ( TimerHandle_t xTimer,
TickType_t xTicksToWait )
参数:
xTimer: 要停止的软件定时器的句柄。
xTicksToWait: 设置阻塞时间,调用函数 xTimerStop()停止软件定时器其实就是向定时器命令队列发送一条tmrCOMMAND_STOP 命令,既然是向队列发送消息,那肯定会涉及到入队阻塞时间的设置。
返回值:
pdPASS: 软件定时器停止成功,其实就是命令发送成功。
pdFAIL: 软件定时器停止失败,命令发送失败。
2. 函数 xTimerStopFromISR()
此函数是 xTimerStop()的中断版本,此函数用于中断服务函数中!此函数是一个宏,真正执行的是函数 xTimerGenericCommand(),函数原型如下:
BaseType_t xTimerStopFromISR( TimerHandle_t xTimer,
BaseType_t * pxHigherPriorityTaskWoken );
参数:
xTimer: 要停止的软件定时器句柄。
pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值函数会自动设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。
返回值:
pdPASS: 软件定时器停止成功,其实就是命令发送成功。
pdFAIL: 软件定时器停止失败,命令发送失败。