- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
为什么 FreeRTOS简单内核实现3 任务管理 文章中实现的 RTOS 内核不能看起来并行运行呢?
Task1 延时 100ms 之后执行 taskYIELD() 切换到 Task2,Task2 延时 500ms 之后执行 taskYIELD() 再次切换 Task1 ,在延时期间两个任务均占用 MCU ,所以只能一个任务执行完再执行另外一任务,可以看出 MCU 处理这两个任务的大部分时间浪费在了无用的延时上 。
有什么方法解决吗?
引入空闲任务,当有任务执行延时操作时产生任务调度,但因为 MCU 时刻在运行程序,不会说中途休息一会儿,所以当所有任务都处于延时状态时,MCU 必须要有一个空闲任务来执行,当有任务从延时阻塞状态恢复时,再次产生任务调度执行从阻塞状态恢复的任务 。
具体怎么实现呢?
下面我们就来逐点实现以上 4 点内容 。
空闲任务和普通任务一样,只不过任务函数为空而已,由于是静态创建任务,所以需要提前定义好任务栈,任务控制块、任务句柄和任务函数,如下所示 。
/* task.c */
// 空闲任务参数
TCB_t IdleTaskTCB;
#define confgiMINIMAL_STACK_SIZE 128
StackType_t IdleTasKStack[confgiMINIMAL_STACK_SIZE];
// 空闲任务函数体
void prvIdleTask(void *p_arg)
{
for(;;){}
}
由于空闲任务始终要被创建,因此一般选择将其放在启动调度器 vTaskStartScheduler() 函数中,如下所示 。
/* task.c */
// 启动任务调度器
void vTaskStartScheduler(void)
{
// 创建空闲任务
TaskHandle_t xIdleTaskHandle = xTaskCreateStatic((TaskFunction_t)prvIdleTask,
(char *)"IDLE",
(uint32_t)confgiMINIMAL_STACK_SIZE,
(void *)NULL,
(StackType_t *)IdleTasKStack,
(TCB_t *)&IdleTaskTCB);
// 将空闲任务插入到就绪链表中
vListInsertEnd(&(pxReadyTasksLists),
&(((TCB_t *)(&IdleTaskTCB))->xStateListItem));
pxCurrentTCB = &Task1TCB;
if(xPortStartScheduler() != pdFALSE){}
}
首先需要在任务控制块结构体中增加一个变量用于记录任务阻塞延时的时间 。
/* task.h */
// 任务控制块
typedef struct tskTaskControlBlock
{
// 省略之前的结构体成员定义
TickType_t xTicksToDelay; // 用于延时
}tskTCB;
阻塞延时与普通延时的区别就是普通延时会一直占用 MCU ,而阻塞延时执行后会产生任务调度暂时让出 MCU ,让其执行处于运行状态的任务,阻塞延时函数如下所示 。
/* task.c */
// 阻塞延时函数
void vTaskDelay(const TickType_t xTicksToDelay)
{
TCB_t *pxTCB = NULL;
// 获取当前要延时的任务 TCB
pxTCB = (TCB_t *)pxCurrentTCB;
// 记录延时时间
pxTCB->xTicksToDelay = xTicksToDelay;
// 主动产生任务调度,让出 MCU
taskYIELD();
}
/* task.h */
// 函数声明
void vTaskDelay(const TickType_t xTicksToDelay);
注意:需要明白的很重要的一点是任务调度策略是寻找合适的 pxCurrentTCB 指针 。
根据目前实现的 RTOS 内核,发生任务调度有如下两种情况 。
taskYIELD()
函数vTaskDelay()
阻塞延时函数之前的任务调度策略为 Task1 和 Task2 两个任务轮流执行,现在加入了空闲任务和阻塞延时后需要修改任务调度策略,目前理想的任务调度策略应该如下所示 。
上述步骤 1 ~ 3 中笔者描述的尝试执行的任务是有顺序的,比如步骤 2 会先尝试执行未阻塞的 Task2,不满足才会尝试执行未阻塞的 Task1,这样在手动调用 taskYIELD() 函数发生任务调度时才会切换任务,否则达不到任务切换的目的 。
具体的任务调度函数如下所示 。
/* task.c */
// 任务调度函数
void vTaskSwitchContext(void)
{
if(pxCurrentTCB == &IdleTaskTCB)
{
if(Task1TCB.xTicksToDelay == 0)
{
pxCurrentTCB = &Task1TCB;
}
else if(Task2TCB.xTicksToDelay == 0)
{
pxCurrentTCB = &Task2TCB;
}
else
{
return;
}
}
else
{
if(pxCurrentTCB == &Task1TCB)
{
if(Task2TCB.xTicksToDelay == 0)
{
pxCurrentTCB = &Task2TCB;
}
else if(pxCurrentTCB->xTicksToDelay != 0)
{
pxCurrentTCB = &IdleTaskTCB;
}
else
{
return;
}
}
else if(pxCurrentTCB == &Task2TCB)
{
if(Task1TCB.xTicksToDelay == 0)
{
pxCurrentTCB = &Task1TCB;
}
else if(pxCurrentTCB->xTicksToDelay != 0)
{
pxCurrentTCB = &IdleTaskTCB;
}
else
{
return;
}
}
}
}
阻塞延时本质是延时函数,涉及到时间就需要提供时间基准,我们在任务控制块结构体中使用了一个名为 xTicksToDelay 的 uint32_t 类型的变量记录每个任务的延时时间,那这个延时时间什么时候递减呢?
通常 MCU 都有一个名为 SysTick 的滴答定时器,其会按照某一固定周期产生中断,一般用来为 MCU 提供时间基准,对于 STM32 HAL 库来说,其滴答定时器只用于 HAL_Delay() 延时函数,我们可以在其中断 SysTick_Handler() 函数中对任务的延时时间进行递减操作,那如何控制滴答定时器产生中断的周期呢?
对于配置好时钟树,然后由 STM32CubeMX 生成的代码中,滴答定时器会自动初始化并启动滴答定时器中断,初始化流程如下所示 。
初始化流程中有一个重要的参数用于配置滴答定时器的中断周期(频率),默认为 1KHZ(1ms),读者可按需要对其做相应修改,具体定义如下所示 。
/* stm32f4xx_hal.c */
HAL_TickFreqTypeDef uwTickFreq = HAL_TICK_FREQ_DEFAULT; /* 1KHz */
/* stm32f4xx_hal.h */
typedef enum
{
HAL_TICK_FREQ_10HZ = 100U,
HAL_TICK_FREQ_100HZ = 10U,
HAL_TICK_FREQ_1KHZ = 1U,
HAL_TICK_FREQ_DEFAULT = HAL_TICK_FREQ_1KHZ
} HAL_TickFreqTypeDef;
xPortSysTickHandler() 本质是滴答定时器中断服务函数,作为 RTOS 的心跳在其中对任务的阻塞延时参数做处理,每次心跳一次就将阻塞延时参数递减,直到减到 0 之后使任务从阻塞状态恢复,具体如下所示 。
/* port.c */
// SysTick 中断服务函数
void xPortSysTickHandler(void)
{
// 关中断
vPortRaiseBASEPRI();
// 更新任务延时参数
xTaskIncrementTick();
// 开中断
vPortSetBASEPRI(0);
}
/* portMacro.h */
#define xPortSysTickHandler SysTick_Handler
注意:由于我们重新实现了 SysTick 中断服务函数,因此在 stm32f4xx_it.c 中自动生成的 SysTick_Handler 函数需要注释或者直接删除 。
该函数为具体的处理函数,其遍历链表中每个链表项(任务),如果链表项的延时参数不为 0 就将其递减,直到减少到 0 表示该任务延时阻塞到期,然后产生任务调度,具体如下所示 。
/* task.c */
// 滴答定时器计数值
static volatile TickType_t xTickCount = (TickType_t)0U;
// 更新任务延时参数
void xTaskIncrementTick(void)
{
TCB_t *pxTCB = NULL;
ListItem_t *pxListItem = NULL;
List_t *pxList = &pxReadyTasksLists;
uint8_t xSwitchRequired = pdFALSE;
// 更新 xTickCount 系统时基计数器
const TickType_t xConstTickCount = xTickCount + 1;
xTickCount = xConstTickCount;
// 检查就绪链表是否为空
if(listLIST_IS_EMPTY(pxList) == pdFALSE)
{
// 不为空获取链表头链表项
pxListItem = listGET_HEAD_ENTRY(pxList);
// 迭代就绪链表所有链表项
while(pxListItem != (ListItem_t *)&(pxList->xListEnd))
{
// 获取每个链表项的任务控制块 TCB
pxTCB = (TCB_t *)listGET_LIST_ITEM_OWNER(pxListItem);
// 延时参数递减
if(pxTCB->xTicksToDelay > 0){
pxTCB->xTicksToDelay--;
}
else{
xSwitchRequired = pdTRUE;
}
// 移动到下一个链表项
pxListItem = listGET_NEXT(pxListItem);
}
}
// 如果就绪链表中有任务从阻塞状态恢复就产生任务调度
if(xSwitchRequired == pdTRUE){
// 产生任务调度
taskYIELD();
}
}
/* task.h */
// 函数声明
void xTaskIncrementTick(void);
测试程序与 FreeRTOS简单内核实现3 任务管理 几乎一致,主要是将任务函数体内的延时由 HAL_Delay() 修改为本文创建的阻塞延时 vTaskDelay() 函数,然后删除掉 taskYIELD() 函数即可,具体如下所示 。
/* main.c */
/* USER CODE BEGIN Includes */
#include "FreeRTOS.h"
/* USER CODE END Includes */
/* USER CODE BEGIN PV */
extern List_t pxReadyTasksLists;
TaskHandle_t Task1_Handle;
#define TASK1_STACK_SIZE 128
StackType_t Task1Stack[TASK1_STACK_SIZE];
TCB_t Task1TCB;
TaskHandle_t Task2_Handle;
#define TASK2_STACK_SIZE 128
StackType_t Task2Stack[TASK2_STACK_SIZE];
TCB_t Task2TCB;
// 任务 1 入口函数
void Task1_Entry(void *parg)
{
for(;;)
{
HAL_GPIO_TogglePin(GREEN_LED_GPIO_Port, GREEN_LED_Pin);
vTaskDelay(100);
}
}
// 任务 2 入口函数
void Task2_Entry(void *parg)
{
for(;;)
{
HAL_GPIO_TogglePin(ORANGE_LED_GPIO_Port, ORANGE_LED_Pin);
vTaskDelay(500);
}
}
/* USER CODE END PV */
/* USER CODE BEGIN 2 */
// 使用链表前手动初始化
prvInitialiseTaskLists();
// 创建任务 1 和 2
Task1_Handle = xTaskCreateStatic((TaskFunction_t)Task1_Entry,
(char *)"Task1",
(uint32_t)TASK1_STACK_SIZE,
(void *)NULL,
(StackType_t *)Task1Stack,
(TCB_t *)&Task1TCB);
Task2_Handle = xTaskCreateStatic((TaskFunction_t)Task2_Entry,
(char *)"Task2",
(uint32_t)TASK2_STACK_SIZE,
(void *) NULL,
(StackType_t *)Task2Stack,
(TCB_t *)&Task2TCB );
// 将两个任务插入到就绪链表中
vListInsertEnd(&(pxReadyTasksLists),&(((TCB_t *)(&Task1TCB))->xStateListItem));
vListInsertEnd(&(pxReadyTasksLists),&(((TCB_t *)(&Task2TCB))->xStateListItem));
// 启动任务调度器,永不返回
vTaskStartScheduler();
/* USER CODE END 2 */
启动任务调度器后的程序执行流程如下所示 。
pxCurrentTCB = &Task1TCB;
,让 Task1 成为第一个被运行的任务使用逻辑分析仪捕获 GREEN_LED 和 ORANGE_LED 两个引脚的电平变化,具体如下图所示 。
从图上可以发现两个任务几乎是并行运行的,和我们期待的 Task1 引脚电平每隔 100 ms 翻转一次,Task2 引脚电平每隔 500ms 翻转一次效果一致 。
当前 RTOS 简单内核已实现的功能有 。
当前 RTOS 简单内核存在的缺点有 。
最后此篇关于FreeRTOS简单内核实现5阻塞延时的文章就讲到这里了,如果你想了解更多关于FreeRTOS简单内核实现5阻塞延时的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我正在使用 http://www.freertos.org/对于应用程序,但我找不到自启动以来如何获取系统时间。我可以创建一个任务并不断更新计数器,但我认为这不是一件好事,因为调度程序可能会安排我的任
我正在开始使用 freeRTOS。我浏览了 freeRTOS.org 中提供的文档,并对一些演示项目进行了一些练习。我的问题是如何在不使用 win32 端口的情况下安装 freeRTOS(因为它只是一
我正在开始使用 freeRTOS。我浏览了 freeRTOS.org 中提供的文档,并对一些演示项目进行了一些练习。我的问题是如何在不使用 win32 端口的情况下安装 freeRTOS(因为它只是一
当使用依赖于 WFI 指令的无滴答空闲功能时,我在 FreeRTOS 的 Cortex-M 端口中看到以下几行 __asm volatile( "dsb" ); __asm volatile( "wf
我在 Linux 下使用 C 编写了一个应用程序,其中使用自定义结构创建队列。代码运行得很好。现在我想修改该代码以使用 freeRTOS 运行它。这是我第一次接触 RTO。在浏览文档时,我发现 fre
哪些参数定义了 FreeRTOS 中的时间片持续时间,以及如何计算在切换一个任务并切换到另一个任务之前它将运行多长时间。 我找到了 configTICK_RATE_HZ,它设置了每秒的滴答数,但它与任
我开始使用 FreeRTOS,我想要一个中断来抢占任何即将运行的任务并运行我需要关键运行的任务。 有没有办法在 FreeRTOS 中执行此操作? (这是通过任务优先级实现的吗?) 最佳答案 不!以上两
我有 STM32F746ZG Nucleo-144pin 板并使用 STMCubeMx 生成代码。我选择了CubeMx提供的10.0.0版本的FreeRTOS,工具链是SW4STM32。 我做了两个任
我正在学习 freeRTOS。我需要在 PIC32 平台(cerebot Mx7ck)的 freeRTOS 中编写软件中断 ISR 处理程序。我浏览了文档,但没有帮助。请有人帮忙。 最佳答案 您是在寻
FreeRTOS 使用什么样的调度程序? 我在某处读到它是一个运行完成调度程序,但另一方面,我也看到它与并行任务一起使用,所以它不会是一个循环调度程序? 最佳答案 最高优先级的任务被授予CPU时间。如
FreeRTOS 使用什么样的调度程序? 我在某处读到它是一个运行完成调度程序,但另一方面,我也看到它与并行任务一起使用,所以它不会是一个循环调度程序? 最佳答案 最高优先级的任务被授予CPU时间。如
我正在构建一个 FreeRTOS 应用程序。我创建了一个模块,它从另一个模块注册 freeRTOS 队列句柄,当该模块中发生中断时,它会向所有注册的队列发送一条消息。但似乎我能够从队列发送消息,但无法
在 cortex M0 MCU 上的一些 FreeRTOS 演示中 configMINIMAL_STACK_SIZE设置为 60,而其他一些设置为 70。使用 STM32Cube 软件将其设置为 12
我想创建两个在 FreeRTOS 中同时运行的任务。第一个任务将处理 LED,第二个任务将监视温度。我有两个问题: 这段代码会创建两个同时运行的任务吗? 如何在任务之间发送数据,例如:如果温度超过 x
我正在读取 dsPIC30F6014A 上 ADC channel 上的一些数据。为此,我为每个 ADC 实现了一个单独的任务(例如 7 个 channel -7 个任务)。 我只在开始时创建了所有任
我写了一个简单的例子,包括 2 个任务:任务 1 和任务 2。任务 1 的优先级高于任务 2。在任务 1 函数中,我增加了任务 2 的优先级,使其优先级等于 (任务 1 的优先级 + 1)。此外,在任
我有一个任务在 freeRTOS 上运行,我正在检查该应用程序中有多少应用程序堆栈未使用。我看到的是,可用堆栈内存在一段时间后会减少,并在很长一段时间内保持在该值。该任务有一个 while(1) 循环
让一个线程修改原子单元(例如字符)而另一个线程仅读取它是否安全?当然,在允许这样做的环境中,例如 freertos。 我得到了一些执行此操作的代码,它使用 bool (定义为 char)作为信号量,我
我正在使用 ARM Cortex-M4 微 Controller 来开发带有 FreeRTOS 的应用程序。 为了精确计时,我想使用基于中断的计时器。中断具有适当的优先级,因此它应该能够调用 Free
我是 FreeRTOS (ARM CM3) 的初学者,我的问题是: 当你创建一个内部有一些局部变量的任务时,将这些变量存储在 RAM 中,堆栈中(由 main() 使用)就像局部变量的一般情况一样,或
我是一名优秀的程序员,十分优秀!