一、系统节拍
FreeRTOS 实时操作系统需要一个时钟节拍,以供系统处理诸如延时、超时、软件定时器等与时间相关的事件。
时钟节拍是周期性定时中断,这个中断可以看做是系统心跳。中断时间间隔取决于不同的应用,一般是 1ms -- 100ms。时钟的节拍中断使得内核可以将任务延迟若干个时钟节拍,以及当任务等待事件发生时,提供等待超时等依据。时钟节拍率越快,系统响应越快,系统的额外开销也越大。
对于 Cortex-M3 内核的 STM32F103 ,教程配套的例子都是用滴答定时器来实现系统时钟节拍的。
- 滴答定时器 Systick
SysTick 定时器位于 NVIC 中,用于产生 SysTick 异常(异常号:15),滴答定时器是一个 24 位的递减计数器,支持中断。使用比较简单,专门用于给操作系统提供时钟节拍。
FreeRTOS 的系统时钟节拍可以在配置文件 FreeRTOSConfig.h 里面设置:
#define configTICK_RATE_HZ(( TickType_t) 1000 )
如上所示的宏定义配置表示系统时钟节拍是 1KHz,即 1ms。
二、延时相关函数
2.1、作用
FreeRTOS 中的时间延迟函数主要有以下两个作用:
- 为周期性执行的任务提供延迟。
- 对于抢占式调度器,让高优先级任务可以通过时间延迟函数释放 CPU 使用权,从而让低优先级任务可以得到执行。
2.2、相关函数
FreeRTOS 时间相关的函数主要有以下 4 个:
- vTaskDelay ()
- vTaskDelayUntil ()
- xTaskGetTickCount()
- xTaskGetTickCountFromISR()
下面我们对这 4 个函数依次进行说明:
① 函数原型:void vTaskDelay(const TickType_t xTicksToDelay);
函数描述:函数 vTaskDelay 用于任务的延迟。 属于相对延时,指每次延时都是从执行函数 vTaskDelay() 开始,直到延时指定的时间(参数:滴答值)结束。
参数 xTicksToDelay 用于设置延迟的时钟节拍个数,范围 1- 0xFFFFFFFF。
延迟时间的最大值在 portmacro.h 文件里面有定义:
c
typedef uint32_t TickType_t;
#define portMAX_DELAY (TickType_t)0xffffffffUL
即延迟时间的范围是:1- 0xFFFFFFFF
② 函数原型:
void vTaskDelayUntil(TickType_t *pxPreviousWakeTime, const TickType_t xTimeIncrement);
函数描述:函数 vTaskDelayUntil 用于周期性延迟。 属于绝对延时,指间隔指定的时间(参数:滴答值),执行一次调用 vTaskDelayUntil() 函数的任务。
第 1 个参数,存储任务最后一次解除阻塞的时间
第 2 个参数,周期性延迟时间。
使用这个函数要注意以下问题:
- 使用此函数需要在 FreeRTOSConfig.h 配置文件中配置如下宏定义为 1
#define INCLUDE_vTaskDelayUntil 1
③ 函数原型:
volatile TickType_t xTaskGetTickCount(void);
函数描述:函数 xTaskGetTickCount 用于获取系统当前运行的时钟节拍数。
使用这个函数要注意以下问题:
- 此函数用于在任务代码里面调用,如果在中断服务程序里面调用的话,需要使用函数 xTaskGetTickCountFromISR,这两个函数切不可混用。
④ 函数原型:
volatile TickType_t xTaskGetTickCountFromISR(void);
函数描述:函数 xTaskGetTickCountFromISR 用于获取系统当前运行的时钟节拍数。
使用这个函数要注意以下问题:
- 此函数用于在中断服务程序里面调用,如果在任务里面调用的话,需要使用函数 xTaskGetTickCount,这两个函数切不可混用。
三、相对延时与绝对延时的区别与编程测试
1、问题:周期性去处理某一件事情。你会通过什么方式去实现?
比如:间隔 10ms 去采集传感器的数据,然后通过一种算法计算出一个结果,最后通过串口发送出去。对于裸机编程,首先想到的是:利用定时器,定时 10ms 中断,在中断里面处理。但中断函数适合处理简单数据,要求快进快出,不适合算法、通信等需要长时间占用 CPU 的处理。对计时精度要求比较高的地方适合定时器,像本节说的周期性采集传感器数据,要求不适合很高,那么就引入本文说的绝对延时。
2、 相对延时:指每次延时都是从执行函数 vTaskDelay() 开始,直到延时指定的时间(参数:滴答值)结束。
绝对延时:指间隔指定的时间(参数:滴答值),执行一次调用 vTaskDelayUntil() 函数的任务。
3、通过编程测试相对延时和绝对延时的区别
创建 2 个任务,情况如下:
任务 1:HAL_Delay 延时 50ms,模拟传感器采集数据与被中断或高优先级任务打断的时间,printf 打印任务运行次数,再通过 vTaskDelay 相对延时 200ms;
任务 2:HAL_Delay 延时 50ms,模拟传感器采集数据与被中断或高优先级任务打断的时间,printf 打印任务运行次数,再通过 vTaskDelayUntil 就绝对延时 200ms;
实验分析:
任务 1 由于采用相对延时,printf 间隔 250ms(50ms+200ms) 打印信息
任务 2 由于采用绝对延时,printf 间隔 200ms(50ms+150ms) 打印信息
**疑问:**为什么采用绝对延时,printf 间隔为 50ms+150ms。
**解答:**任务执行时,先花 50ms 执行模拟采集数据与被中断或高优先级任务打断的时间, 接着,调用 vTaskDelayUntil, 此函数会根据第 1 个实参 (存储任务最后一次解除阻塞的时间) 与当前系统时间计算出模拟采集数据与被中断或高优先级任务打断的 50ms 时间,此时只延时 200ms-50ms=150ms,以确保任务周期性执行,所以 vTaskDelayUntil 实际只阻塞了 150ms。