1.开发背景
基于上一篇指引,已经了解了线程的挂起和恢复,这个篇章主要测试线程阻塞的方法
2.开发需求
线程串行阻塞和周期阻塞
3.开发环境
window10 + MDK + STM32F429 + FreeRTOS10.3.1
4.实现步骤
4.1 线程串行阻塞
其实在上一篇已经使用了线程阻塞,如果高优先级线程一直不阻塞占用 CPU,程序会直接卡死在高优先级线程中,常用的阻塞方式 vTaskDelay,这个在 FreeRTOS 中非常常用,需要配置宏 INCLUDE_vTaskDelay
#define INCLUDE_vTaskDelay 1
下面实现 vTaskDelay 实现阻塞延时,定期 1000ms 唤醒一次
/* 串行延时任务 */
static void TaskDelay(void *pvParameters)
{
for ( ; ; )
{
vTaskDelay(1000);
mspDwt_DelayMs(10);
Log_Debug("%s\r\n", __func__);
}
}
结果显示
由于我们设置的时钟节拍是 1ms,而 vTaskDelay 的输入参数就是时钟节拍,FreeRTOS 的参数基本都是以时钟节拍为单位,如果时钟节拍不是 1ms,则需要注意换算。
由上述图示可以看出,基本都是每 1000ms 输出一次打印,值得注意的是由于我们测试插入了 mspDwt_DelayMs(10); 的耗时测试操作,每次打印的时间间隔大概是 1010ms,明显会有串行时间累计误差。因此,这种阻塞方式只适用不需要周期唤醒的使用场景。
4.2 线程周期阻塞
基于上一个执行试验,发现了累计误差,所以当我们需要用到周期唤醒线程时,可以基于 vTaskDelay 的基础上记录每次唤醒的时间点再决定下一次使用 vTaskDelay 的延时参数给多少。事实上,FreeRTOS 已经考虑到了这一点,所以有了 vTaskDelayUntil,可以实现周期性唤醒。
首先还是需要配置宏
#define INCLUDE_vTaskDelayUntil 1
下面是实现代码,先用 xTaskGetTickCount 获取当前系统的时钟计数值,再定期阻塞
/* 周期延时任务 */
static void TaskDelayUntil(void *pvParameters)
{
TickType_t xLastWakeTime;
const TickType_t xFrequency = 1000;
xLastWakeTime = xTaskGetTickCount();
for( ;; )
{
vTaskDelayUntil(&xLastWakeTime, xFrequency);
mspDwt_DelayMs(10);
Log_Debug("%s\r\n", __func__);
}
}
结果显示:
由图示可以看出,定期 1000ms 输出一个打印,即使加了 mspDwt_DelayMs(10); 耗时操作也没有影响到线程的周期性唤醒。
4.3 线程退出阻塞
线程处于阻塞状态有没有办法解除阻塞能,FreeRTOS 作为完善的实时操作系统,当然提供了这样的接口,那就是 xTaskAbortDelay。
1)配置宏开关,打开配置
#define INCLUDE_xTaskAbortDelay 1
2)阻塞一个线程,一直阻塞
/* 串行延时任务 */
static void TaskDelay(void *pvParameters)
{
for ( ; ; )
{
vTaskDelay(portMAX_DELAY);
Log_Debug("%s\r\n", __func__);
}
}
3)控制线程定期解除阻塞,这里引入了一个接口 eTaskGetState,可以获取指定线程的状态。
/* 串行延时任务 */
static void TaskCtrl(void *pvParameters)
{
for ( ; ; )
{
vTaskDelay(1000);
if (eTaskGetState(p->taskDelay) == eBlocked)
{
Log_Debug("%s 解除 taskDelay 线程阻塞\r\n", __func__);
xTaskAbortDelay(p->taskDelay);
}
}
}
4)执行现象
每间隔 1000ms 控制线程就强制解除串行延时线程一次,测试结果符合预期。