细说STM32单片机FreeRTOS任务管理相关函数及多任务编程的实现方法

news2025/4/19 4:37:35

目录

一、FreeRTOS任务管理相关函数

1、FreeRTOS函数

2、FreeRTOS宏函数 

3、主要函数功能说明

(1)创建任务osThreadNew()

(2)删除任务vTaskDelete()

(3)挂起任务vTaskSuspend()

(4)恢复任务vTaskResume()

(5)启动任务调度器vTaskStartScheduler()

(6)延时函数vTaskDelay()

(7)绝对延时函数vTaskDelayUntil() 

二、多任务编程示例

1、示例功能

2、CubeMX项目设置

(1)RCC、SYS

(2)GPIO

(3)FreeRTOS

(4)NVIC

3、程序设计

(1)main()

(2)freertos.c

(3)编写任务函数代码

1)相同优先级的任务的执行

2)低优先级任务被“饿死”

3)高优先级任务主动进入阻塞状态


一、FreeRTOS任务管理相关函数

        在FreeRTOS中,任务的管理主要包括任务的创建、删除、挂起、恢复等操作,还包括任务调度器的启动、挂起与恢复,以及使任务进入阻塞状态的延迟函数等。

        FreeRTOS中任务管理相关的函数都在文件task.h中定义,在文件tasks.c中实现。在CMSIS-RTOS中还有一些函数,对FreeRTOS的函数进行了封装,也就是调用相应的FreeRTOS函数实现相同的功能,这些标准接口函数的定义在文件cmsis_os.h和cmsis_os2.h中。CubeMX生成的代码一般使用CMSIS-RTOS标准接口函数,在用户自己编写的程序中,一般直接使用FreeRTOS的函数。

1、FreeRTOS函数

        任务管理常用的一些函数及其功能描述见下。这里只列出了函数名,省略了输入/输出参数。 

分组

Free RTOS函数

函数功能描述

CMSIS-RTOS

封装函数

任务
管理

xTaskCreate()

创建一个任务,动态分配内存

osThreadNew()

xTaskCreateStatic()

创建一个任务,静态分配内存

osThreadNew()

vTaskDelete()

删除当前任务或另一个任务

osThreadTerminate()
osThreadExit()

vTaskSuspend()

挂起当前任务或另一个任务

osThreadSuspend()

vTaskResume()

恢复另一个挂起任务的运行

osThreadResume()

调度器
管理

vTaskStartScheduler()

开启任务调度器

osKernelStart()

vTaskSuspendAll()

挂起调度器,但不禁止中断。调度器被挂起后,不会再进行上下文切换

osKernelLock()

xTaskResumeAll()

恢复调度器的执行,但是不会解除用函数vTaskSuspend()单独挂起的任务的起状态

osKernelUnlock()

vTaskStepTick()

用于在tickless低功耗模式时补足系统时钟计数节拍

延时与
调度

vTaskDelay()

当前任务延时指定节拍数,并进入阻塞状态

osDelay()

vTaskDelayUntil()

当前任务延时到指定的时间,并进入阻塞状态,用于精确延时的周期性任务

osDelayUntil()

xTaskGetTickCount()

返回嘀嗒信号的当前计数值

osKernelGetTickCount()

xTaskAbortDelay()

终止另一个任务的延时,使其立刻退出阻塞状态

taskYIELD()

请求进行一次上下文切换

osThreadYield()

        FreeRTOS函数基本都有对应的CMSIS-RTOS标准函数,特别地:

  • FreeRTOS创建任务的函数有两个,xTaskCreate()用于创建动态分配内存的任务,xTaskCreateStatic()用于创建静态分配内存的任务。对应的CMSIS-RTOS标准函数osThreadNew()会根据任务的参数自动调用其中的某个函数。
  • 函数vTaskDelete()可以根据传递的参数不同,删除另一个任务或当前任务,对应的CMSIS-RTOS标准函数有两个,osThreadTerminate()用于删除另一个任务,osThreadExit()用于删除当前任务(即任务自身)。
  • 函数xTaskAbortDelay()用于终止另一个任务的延时,使其立刻退出阻塞状态,这个函数没有对应的CMSIS-RTOS函数。

2、FreeRTOS宏函数 

        除了表中的这些函数外,文件task.h中还有几个常用的宏函数,其定义代码如下,相应的函数功能见代码中的注释:

#define taskENTER_CRITICAL() portENTER_CRITICAL()			//开始临界代码段
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()

#define taskEXIT_CRITICAL() portEXIT_CRITICAL()				//结束临界代码段
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )

#define taskDISABLE_INTERRUPTS() portDISABLE_INTERRUPTS()	//关闭NCU的所有可屏蔽中断
#define taskENABLE_INTERRUPTS() portENABLE_INTERRUPTS()		//使能MCU的中断
  • 宏函数taskDISABLE_INTERRUPTS()和taskENABLE_INTERRUPTS()用于关闭和开启MCU的可屏蔽中断,用于界定不受其他中断干扰的代码段。只能关闭FreeRTOS可管理的中断优先级,即参数configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY定义的最高优先级。这两个函数必须成对使用,且不能嵌套使用。
  • 函数taskENTER_CRITICAL()和taskEXIT_CRITICAL()用于界定临界(Critical)代码段。在临界代码段内,任务不会被更高优先级的任务抢占,可以保证代码执行的连续性。例如,一段代码需要通过串口上传一批数据,如果被更高优先级的任务抢占了CPU,上传的过程被打断,上传数据就可能出现问题,这时就可以将这段代码界定为临界代码段。taskENTER_CRITICAL()内部会调用关闭可屏蔽中断的函数portDISABLE_INTERRUPTS(),与宏函数taskDISABLE_INTERRUPTS()实现的功能相似。函数taskENTER_CRITICAL()和taskEXIT_CRITICAL()必须成对使用,但可以嵌套使用。
  • taskENTER_CRITICAL_FROM_ISR()是taskENTER_CRITICAL()的ISR版本,用于在中断服务例程中调用。注意,FreeRTOS的所有API函数分为普通版本和ISR版本,如果要在ISR里调用FreeRTOS的API函数,必须使用其ISR版本。

        这些宏函数实际上是执行了另外一个函数,例如,taskENTER_CRITICAL()实际上就是执行了函数portENTER_CRITICAL()。跟踪代码会发现,这些port”前缀的函数是在文件portmacro.h中定义的宏函数。文件portmacro.h中的这些宏函数如下: 

#define portSET_INTERRUPT_MASK_FROM_ISR() 		ulPortRaiseBASEPRI()
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) 	vPortSetBASEPRI(x)
#define portDISABLE_INTERRUPTS() 				vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS() 				vPortSetBASEPRI(0)
#define portENTER_CRITICAL() 					vPortEnterCritical()
#define portEXIT_CRITICAL() 					vPortExitCritical()

        这些宏函数实际执行的函数是在文件port.c或portmacro.h中实现的,某些函数的实现代码完全是用汇编语言写的,它们是根据具体的MCU型号移植的代码。

3主要函数功能说明

(1)创建任务osThreadNew()

        例如,当CubeMX使用默认RTOS参数生成的代码时,使用函数osThreadNew()创建任务,根据任务的属性设置,osThreadNew()内部会自动调用xTaskCreate()以动态分配内存方式创建任务,或者调用xTaskCreateStatic()以静态分配内存方式创建任务。函数osThreadNew()的原型定义如下:

/// Create a thread and add it to Active Threads.
/// \param[in] func thread function.
/// \param[in] argument pointer that is passed to the thread function as start argument.
/// \param[in] attr thread attributes; NULL: default values.
/// \return thread ID for reference by other functions or NULL in case of error.
osThreadId_t osThreadNew (osThreadFunc_t func, void *argument, const osThreadAttr_t *attr);

         返回的数据是所创建任务的句柄,数据类型osThreadld_t的定义如下:

/// \details Thread ID identifies the thread.
typedef void *osThreadId_t;

        使用动态分配内存方式创建任务的函数是xTaskCreate(),其原型定义如下,每个参数的意义见代码注释:

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,	//任务函数名称
        const char * const pcName, 					//任务的备注名称
        const configSTACK_DEPTH_TYPE usStackDepth,	//栈空间大小,单位:字
        void * const pvParameters,					//传递给任务函数的参数
        UBaseType_t uxPriority,						//任务优先级
        TaskHandle_t * const pxCreatedTask )		//任务的句柄

        函数xTaskCreate()返回值的类型是BaseType_t,其值若是pdPASS,就表示任务创建成功。创建的任务的句柄是函数中的参数pxCreatedTask,其类型是TaskHandle_t。这个类型的定义与osThreadld_t的是相同的,定义如下:

typedef void TaskHandle_t;

        使用静态分配内存方式创建任务的函数是xTaskCreateStatic(),其原型定义如下,每个参数的意义见代码注释:

TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,	//任务函数名称
        const char * const pcName, 						    //任务的备注名称
        const uint32_t ulStackDepth,						//栈空间大小,单位:字
        void * const pvParameters,						    //传递给任务函数的参数
        UBaseType_t uxPriority,							    //任务优先级
        StackType_t * const puxStackBuffer,					//任务的栈空间数组
        StaticTask_t * const pxTaskBuffer )					//任务控制块存储空间

        函数xTaskCreateStatic()返回的数据类型是TaskHandle_t,返回的数据就是所创建任务的句柄。

        用户可以在启动任务调度器之前创建所有的任务,也可以在启动任务调度器之后,在一个任务的任务函数里创建其他任务。在实际编程中,若要手工编程创建任务,建议使用函数osThreadNew(),因为可以参考CubeMX生成的代码。

(2)删除任务vTaskDelete()

        删除任务的函数是vTaskDelete(),其原型定义如下:

void vTaskDelete( TaskHandle_t xTaskToDelete );

        TaskHandle_t类型的参数xTaskToDelete是需要删除的任务的句柄。如果要删除任务自己,则传递参数NULL即可。注意,如果要删除任务自己,必须在跳出任务死循环之后,在退出任务函数之前执行vTaskDelete(NULL)。

        删除任务时,FreeRTOS会自动释放系统自动分配的内存,如动态分配的栈空间和任务控制块,但是在任务内由用户自己分配的内存,需要在删除任务之前手工释放。

(3)挂起任务vTaskSuspend()

        挂起一个任务的函数是vTaskSuspend(),其原型定义如下:

void vTaskSuspend( TaskHandle_t xTaskToSuspend );

        参数xTaskToSuspend是需要挂起的任务的句柄,如果是要挂起任务自己,则传递参数UUL,被挂起的任务将不再参与任务调度,但是还存在于系统中,可以被恢复。

(4)恢复任务vTaskResume()

        恢复一个被挂起的任务的函数是vTaskResume(),其原型定义如下:

void vTaskResume( TaskHandle_t xTaskToResume );

        参数xTaskToResume是需要恢复的任务的句柄。一个被挂起的任务无法在任务函数里恢复自己。只能在其他任务的函数里恢复,所以参数不能是NULL。

(5)启动任务调度器vTaskStartScheduler()

        函数vTaskStartScheduler()可用于启动任务调度器,开始FreeRTOS的运行,其原型定义如下:

void vTaskStartScheduler( void );

        函数vTaskStartScheduler()会自动创建一个空闲任务,空闲任务的优先级为0,也就是最低先级。如果设置参数configUSE_TIMERS的值为1,也就是要使用软件定时器,还会自动创建一个时间守护任务。

(6)延时函数vTaskDelay()

        延时函数vTaskDelay()用于延时一定节拍数,它会使当前任务进入阻塞状态。任何任务都要在空闲的时候进入阻塞状态,以让出CPU的使用权,使其他低优先级的任务可以获得CPU在使用权,否则,一个高优先级的任务将总是占据CPU,导致其他低优先级的任务无法运行。

        函数vTaskDelay()的原型定义如下:

void vTaskDelay(const TickType_t xTicksToDelay);

        其中,参数xTicksToDelay是需要延时的节拍数,是基础时钟的节拍数。一般会结合宏函数pdMS_TO_TICKS(),将一个以毫秒为单位的时间转换为节拍数,然后调用vTaskDelay(),可以使延时时间不受FreeRTOS基础时钟频率变化的影响。一般地,使用延时函数进入阻塞状态的任务函数的基本代码结构如下:

void AppTask_Function(void *argument)
{
    /*任务内初始化*/
    TickType_t ticks2 = pdMS_TO_TICKS(500);		//延时时间500ms转换为节拍数
    for(;;)								        //死循环
    {
        /*死循环内的功能代码*/
        vTaskDelay(ticks2);				        //空闲的时候进行延时,进入阻塞状态
    }
    vTaskDelete(NULL);					        //如果跳出了死循环,需要在函数退出前删除任务自己
}

(7)绝对延时函数vTaskDelayUntil() 

        绝对延时函数vTaskDelayUntil()的功能与延时函数vTaskDelay()的相似,也用于延时,并且使任务进入阻塞状态。不同的是,函数vTaskDelay()的延时时间长度是相对于进入阻塞状态的时刻的,但是对于任务的死循环,一个循环的周期时间是不确定的,因为循环内执行的代码的时间长度是未知的,可能被其他任务抢占。

        若需要在任务函数内实现严格的周期性的循环,则可以使用绝对延时函数vTaskDelayUntil(),其原型定义如下:

void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement );

        其中,参数pxPreviousWakeTime表示上次任务唤醒时基础时钟计数器的值,参数xTimeIncrement表示相对于上次唤醒时刻延时的节拍数。函数vTaskDelayUntil()每次会自动更新pxPreviousWakeTime的值,但是在第一次调用时,需要给一个初值。

        函数vTaskDelay()和vTaskDelayUntil()的意义和区别可以用图表示:方波表示任务的周期循环,高电平表示任务运行时间,低电平表示阻塞时间,上跳沿表示唤醒时刻,下跳沿表示进入阻塞状态的时刻。

 

        可以通过函数xTaskGetTickCount()返回嘀嗒信号当前计数值,作为pxPreviousWakeTime的初值。使用vTaskDelayUntil()的任务函数的一般代码结构如下,示例代码可以使任务的循环周期为比较精确的1000ms:

void AppTask_Function(void *argument)
{
    /*任务内初始化*/
    TickType_t previouaWakeTime=xTaskGetTickCount();				//获得嘀嗒信号当前计数值
    for(;;)												            //死循环
    {
        /*死循环内的功能代码*/
        vTaskDelayUntil(&previousWakeTime,pdMS_TO_TICKS(1000));	    //循环周期1000ms
    }
    vTaskDelete(NULL);						                        //如果跳出了死循环,需要在函数退出前删除任务自己
}

二、多任务编程示例

1、示例功能

        设计一个示例以测试FreeRTOS的多任务功能。本示例继续使用旺宝红龙开发板STM32F407 ZGT6 KIT V1.0,并用到开发板上的LED1和LED2,在FreeRTOS中设计两个任务,在任务1里使LED1闪烁,在任务2里使LED2闪烁。

2、CubeMX项目设置

        在CubeMX中,我们选择STM32F407ZG创建一个项目,先完成如下的基本设置。

(1)RCC、SYS

        外部时钟,配置HCLK为168MHz;

        设置Debug接口为Serial Wire,设置基础时钟(Timebase Source)为TIM6。

(2)GPIO

         根据开发板原理图之LED电路,配置引脚PA6和PA4为GPIO推挽输出,无上拉或下拉。依次改名为LED1和LED2。

(3)FreeRTOS

  • 启用FreeRTOS并设置为CMSIS_V2接口。
  • Config parameters页面的设置默认值,默认状态下,参数configUSE_PREEMPTION的值是1;configUSE_TIME_SLICING的值,其值总是1。所以,默认状态下,FreeRTOS使用带时间片的抢占式任务调度方法。
  • Include parameters页面的设置也保持默认值,默认状态下,这一页面已经包含了vTaskDelete()、vTaskSuspend()、vTaskDelay()、vTaskDelayUntil()等函数,如果不需要使用某个,将相应的参数设置为Disabled即可。
  • 在FreeRTOS中创建两个任务,创建好的两个任务的基本参数如图。Add或Delete按钮,可以添加或删除任务;双击列表中的一个任务,就可以打开一个设置其属性的对话框。 

 

  • 两个任务的优先级都设置为osPriorityNormal,也就是具有相同的优先级。默认任务的优先级为Low;
  • 两个任务采用了不同的内存分配方式,LED1_Task使用动态分配内存,LED2_Task使用静态分配内存。使用静态分配内存时,需要设置作为栈空间的数组名称以及控制块名称。

(4)NVIC

        默认设置

 

3、程序设计

(1)main()

        一般,这段程序是自动生成的。 

/*删除无关的沙箱代码段*/

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "cmsis_os.h"
#include "gpio.h"

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void MX_FREERTOS_Init(void);

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* Configure the system clock */
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();

  /* Init scheduler */
  osKernelInitialize();

  /* Call init function for freertos objects (in cmsis_os2.c) */
  MX_FREERTOS_Init();

  /* Start scheduler */
  osKernelStart();

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/*省略以下代码*/

        main()函数的代码结构非常清晰,前面部分是MCU软硬件的初始化,包括HAL初始化、系统时钟配置和外设初始化。

        FreeRTOS的初始化和启动就是执行以下3个函数。

  • osKernelInitialize()是CMSIS-RTOS标准接口函数,用于初始化FreeRTOS的调度器。
  • MX_FREERTOS_Init()是FreeRTOS对象初始化函数,在freertos.c中实现,用于创建任务、信号量、互斥量等FreeRTOS中定义的对象。
  • osKernelStart()是CMSIS-RTOS标准接口函数,用于启动FreeRTOS的任务调度器。

        执行函数osKernelStart()启动FreeRTOS的任务调度器后,任务调度器就接管了CPU的控制权,函数osKernelStart()是永远不会退出的,所以不会执行后面的while()循环。

(2)freertos.c

        文件freertos.c是CubeMX生成代码时生成的文件,是实现用户功能的代码文件。在main()函数中调用的函数MX_FREERTOS_Init()就是在这个文件里实现的。

/* Private typedef -----------------------------------------------------------*/
typedef StaticTask_t osStaticThreadDef_t;				//类型符号定义

/* Definitions for defaultTask */
osThreadId_t defaultTaskHandle;
const osThreadAttr_t defaultTask_attributes = {
    .name = "defaultTask",
    .stack_size = 128 * 4,
    .priority = (osPriority_t) osPriorityLow,
};

/* Definitions for Task_LED1 */
osThreadId_t Task_LED1Handle;				        //任务Task_LED1的句柄变量
const osThreadAttr_t Task_LED1_attributes = {		//任务Task_LED1的属性
    .name = "Task_LED1",						    //任务名称
    .stack_size = 128 * 4,						    //栈空间大小,128×4字节
    .priority = (osPriority_t) osPriorityLow,		//任务名称/7任务优先级
};

/* Definitions for Task_LED2 */
osThreadId_t Task_LED2Handle;				        //任务Task_LED2的句柄变量
uint32_t Task_LED2Buffer[ 128 ];				    //任务Task_LED2的栈空间数组
osStaticThreadDef_t Task_LED2ControlBlock;		    //任务Task_LED2的任务控制块
const osThreadAttr_t Task_LED2_attributes = {		//任务Task_LED2的属性
    .name = "Task_LED2",						    //任务名称
    .cb_mem = &Task_LED2ControlBlock,			    //任务控制块
    .cb_size = sizeof(Task_LED2ControlBlock),		//任务控制块大小
    .stack_mem = &Task_LED2Buffer[0],			    //栈空间数组
    .stack_size = sizeof(Task_LED2Buffer),			//栈空间大小,单位:字节
    .priority = (osPriority_t) osPriorityLow,		//任务优先级
};

/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */

/* USER CODE END FunctionPrototypes */

void StartDefaultTask(void *argument);
void StartTask02(void *argument);				    //任务Task_LED1的任务函数
void StartTask03(void *argument);				    //任务Task_LED2的任务函数
void MX_FREERTOS_Init(void); 				        /* (MISRA C 2004 rule 8.1) */

/**
  * @brief  FreeRTOS initialization
  * @param  None
  * @retval None
  */
void MX_FREERTOS_Init(void) {
  /* Create the thread(s) */
  /* creation of defaultTask */
  defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);

  /* creation of Task_LED1 */
  Task_LED1Handle = osThreadNew(AppTask_LED1, NULL, &Task_LED1_attributes);

  /* creation of Task_LED2 */
  Task_LED2Handle = osThreadNew(AppTask_LED2, NULL, &Task_LED2_attributes);
}

/* USER CODE BEGIN Header_StartDefaultTask */
/**
  * @brief  Function implementing the defaultTask thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void *argument)
{
  /* USER CODE BEGIN StartDefaultTask */
  /* Infinite loop */
  for(;;)
  {
    osDelay(1);
  }
  /* USER CODE END StartDefaultTask */
}

/* USER CODE BEGIN Header_AppTask_LED1 */
/**
* @brief Function implementing the LED1_Task thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_AppTask_LED1 */
void AppTask_LED1(void *argument)
{
  /* USER CODE BEGIN AppTask_LED1 */
  /* Infinite loop */
  for(;;)
  {
    osDelay(1);
  }
  /* USER CODE END AppTask_LED1 */
}

/* USER CODE BEGIN Header_AppTask_LED2 */
/**
* @brief Function implementing the LED2_Task thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_AppTask_LED2 */
void AppTask_LED2(void *argument)
{
  /* USER CODE BEGIN AppTask_LED2 */
  /* Infinite loop */
  for(;;)
  {
    osDelay(1);
  }
  /* USER CODE END AppTask_LED2 */
}

        文件freertos.c中有3个函数,这3个函数都是CubeMX自动生成的。

  • 函数MX_FREERTOS_Init()是FreeRTOS对象初始化函数,用于创建定义的两个任务。自动生成的只是任务函数的框架
  • 函数AppTask_LED1()是任务Task_LED1的任务函数。
  • 函数AppTask_LED2()是任务Task_LED2的任务函数。

        在freertos.c的私有变量定义部分,定义了两个任务的句柄变量和任务属性变量。任务Task_LED1采用动态分配内存方式,任务Task_LED2采用静态分配内存方式,它们的任务属性变量的赋值不同

        函数MX_FREERTOS_Init()创建了Task_LED1和Task_LED2两个任务,创建的任务用任务句柄变量表示。操作任务时,需要使用任务句柄变量作为参数,如vTaskDelete()、vTaskSuspend()等函数,都需要传递一个任务句柄变量作为输入参数。

(3)编写任务函数代码

        CubeIDE自动生成的只是任务函数的框架,里面没有可用的应用程序,用户想执行什么样的应用,就把应用代码写进任务函数。

        对任务框架及属性稍微做些修改,编写并观察带时间片的抢占式任务调度方法的特点,及vTaskDelay()和vTaskDelayUntil()等函数的使用方法。

1)相同优先级的任务的执行

        FreeRTOS使用默认的带时间片的抢占式任务调度方法,并且将两个任务的优先级都设置为osPriorityNormal。为两个任务的任务函数编写代码,使两个LED分别以不同的周期闪烁。

/* USER CODE BEGIN Header_AppTask_LED1 */
/**
* @brief Function implementing the LED1_Task thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_AppTask_LED1 */
void AppTask_LED1(void *argument)
{
  /* USER CODE BEGIN AppTask_LED1 */
  /* Infinite loop */
  for(;;)
  {
	  HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_6);			//PA6=LED1
	  //vTaskDelayUntil(&previousWakeTime, ticks1);		//循环周期1000ms
	  //vTaskDelay(ticks1);
	  HAL_Delay(1000);									//由TIM6控制,不会使任务进入阻塞状态,而是一直处于连续运行状态
	  //osDelay(1);
  }
  /* USER CODE END AppTask_LED1 */
}

/* USER CODE BEGIN Header_AppTask_LED2 */
/**
* @brief Function implementing the LED2_Task thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_AppTask_LED2 */
void AppTask_LED2(void *argument)
{
  /* USER CODE BEGIN AppTask_LED2 */
  /* Infinite loop */
  for(;;)
  {
	  HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_4);			//PA4=LED2
	  //vTaskDelayUntil(&previousWakeTime, ticks2);		//循环周期500ms
	  //vTaskDelay(ticks2);
	  HAL_Delay(500);
	  //osDelay(1);
  }
  /* USER CODE END AppTask_LED2 */
}

        这两个任务函数的功能很简单,就是分别使LED1和LED2以不同的周期闪烁。任务函数的for循环使用了延时函数HAL_Delay(),这个延时函数不会使任务进入阻塞状态,而是一直处于连续运行状态。

        构建项目后,我们将其下载到开发板并运行,会发现LED1和LED2都能闪烁,两个任务都可以执行。

        带时间片的抢占式任务调度器会在基础时钟每次中断时,进行一次任务调度申请,在没有其他中断处理时,就会进行任务调度。在图2-9中,时间轴上的t1、t2等时间点表示基础时钟发生中断的时间点,也就是进行任务调度的时间点,默认周期是1ms。

        因为两个任务具有相同的优先级,所以调度器使两个任务轮流占用CPU。两个任务都是连续运行的,所以每个任务每次占用CPU的时间都是一个嘀嗒信号周期,不占用CPU时,就处于就绪状态。系统中还有一个空闲任务,但是因为用户的两个任务是连续执行的,且优先级高空闲任务,所以空闲任务总是无法获得CPU的使用权,总是处于就绪状态。

2)低优先级任务被“饿死”

        再次,对程序稍作修改,将任务Task_LED2的优先级修改为osPriorityBelowNormal,任务Task_LED1的优先级仍然为osPriorityNormal。可以在CubeMX里修改后重新生成代码,也可以直接修改freertos.c中为任务Task_LED2的任务属性赋值的语句。两个任务的任务函数代码无须修改,与前面的相同。

        构建项目后,我们将其下载到开发板并运行,会发现只有LED1闪烁,而LED2不闪烁。

        Task_LED1具有高优先级,且是连续运行态,它会一直占用CPU,不会进入阻塞状态,所以低优先级的任务Task_LED2和默认任务都无法获得CPU的使用权,只能一直处于就绪状态,就好像它们被“饿死”了。

3)高优先级任务主动进入阻塞状态

        再次,对前面的程序稍作修改,使Task_LED2的优先级为osPriorityBelowNormal,任务LED1的优先级仍然为osPriorityNormal。修改后两个任务的任务函数代码如下:

/* USER CODE END Header_AppTask_LED1 */
void AppTask_LED1(void *argument)
{
  /* USER CODE BEGIN AppTask_LED1 */
  /* Infinite loop */
  TickType_t ticks1 = pdMS_To_TICKS(1000);    //时间(ms)转换为节拍数(ticks)
  for(;;)
  {
	  HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_6);			//PA6=LED1
	  //vTaskDelayUntil(&previousWakeTime, ticks1);		//循环周期1000ms
	  vTaskDelay(ticks1);
	  //HAL_Delay(1000);									//由TIM6控制,不会使任务进入阻塞状态,而是一直处于连续运行状态
	  //osDelay(1);
  }
  /* USER CODE END AppTask_LED1 */
}
 
/* USER CODE END Header_AppTask_LED2 */
void AppTask_LED2(void *argument)
{
  /* USER CODE BEGIN AppTask_LED2 */
  /* Infinite loop */
  for(;;)
  {
	  HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_4);			//PA4=LED2
	  //vTaskDelayUntil(&previousWakeTime, ticks2);		//循环周期500ms
	  //vTaskDelay(ticks2);
	  HAL_Delay(500);
	  //osDelay(1);
  }
  /* USER CODE END AppTask_LED2 */
}

        对Task_LED1的任务函数代码做了修改。pdMS_TO_TICKS()宏函数的功能是将时间(单位ms)转换为基础时钟节拍数。在for无限循环中,使用了延时函数vTaskDelay()。这个函数的作用不但包括延时,而且使当前任务进入阻塞状态,以便低优先级任务可以在任务调度时获得CPU的使用权。

        不修改任务Task_LED2的任务函数代码,在for循环中,还是使用延时函数HAL_Delay(),所以任务task_LED2还是连续运行的。

        构建项目后,我们将其下载到开发板并运行,会发现LED1和LED2都能闪烁,两个任务都可以执行。

 

        时间轴上的一个周期不再是一个嘀嗒信号周期,而是时间。

  • 在for循环里,任务Task_LED1每次执行完功能代码后,就调用vTaskDelay()函数延时1000ms,并且进入阻塞状态,所以任务Task_LED1大部分时间处于阻塞状态。
  • 虽然任务Task_LED2的优先级比任务Task_LED1的低,但是在任务Task_LED1处于阻塞状态时,任务Task_LED2可以获得CPU的使用权。此外,因为Task_LED2是连续运行的,所以它占用了CPU的大部分时间。
  • 任务Task_LED1在延时结束后,因为其优先级高,可以重新抢占CPU的使用权。
  • 因为任务Task_LED2是连续运行的,不会进入阻塞状态,默认任务仍然无法获得CPU的使用权。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2333062.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

uniapp微信小程序基于wu-input二次封装TInput组件(支持点击下拉选择、支持整数、电话、小数、身份证、小数点位数控制功能)

一、 最终效果 二、实现了功能 1、支持输入正整数---设置specifyTypeinteger 2、支持输入数字(含小数点)---设置specifyTypedecimal,可设置decimalLimit来调整小数点位数 3、支持输入手机号--设置specifyTypephone 4、支持输入身份证号---设…

leetcode-419.棋盘上的战舰

leetcode-419.棋盘上的战舰 文章目录 leetcode-419.棋盘上的战舰一.题目描述二.第一次代码提交三.第二次代码提交 一.题目描述 二.第一次代码提交 class Solution { public:int countBattleships(vector<vector<char>>& board) {int m board.size(); //列数i…

使用uglifyjs对静态引入的js文件进行压缩

前言 因为有时候js文件没有npm包&#xff0c;或者需要修改&#xff0c;只能引入静态的js&#xff0c;那么这个时候就可以对js进行压缩了。我其实想通过vite、webpack等插件进行压缩的&#xff0c;可是他都不能定位到public目录下面的文件&#xff0c;所以我只能自己压缩了。编…

程序加壳脱壳原理和实现

理论 一个可运行的执行文件&#xff0c;至少会有一个代码段&#xff0c;程序的入口点指向代码段&#xff0c;程序运行的时候&#xff0c;从入口点开始执行代码段指令 为了将一个正常的程序进行加壳保护&#xff0c;至少要三部分逻辑配合 1、待加壳保护的程序 2、加壳逻辑 3…

【数据分析实战】使用 Matplotlib 绘制折线图

1、简述 在日常的数据分析、科研报告、项目可视化展示中&#xff0c;折线图是一种非常常见且直观的数据可视化方式。本文将带你快速上手 Matplotlib&#xff0c;并通过几个实际例子掌握折线图的绘制方法。 Matplotlib 是 Python 中最常用的数据可视化库之一&#xff0c;它能够…

数据仓库标准库模型架构相关概念浅讲

数据仓库与模型体系及相关概念 数据仓库与数据库的区别可参考&#xff1a;数据库与数据仓库的区别及关系_数据仓库和数据库-CSDN博客 总之&#xff0c;数据库是为捕获数据而设计&#xff0c;数据仓库是为分析数据而设计 数据仓库集成工具 在一些大厂中&#xff0c;其会有自…

亚洲区域健康人群免疫细胞marker

最近发现一篇文献&#xff0c;作者来自新加坡基因研究所&#xff0c;这篇文章大概是整理了619个亚洲人群的免疫多样性图集&#xff08;AIDA&#xff09;&#xff0c;跨越了7个国家&#xff0c;最终使用了1,265,624个免疫细胞的单细胞数据&#xff0c;并最终确定了8种主要的免疫…

三极管以及mos管

三极管与mos管的高低电平导通判断 &#xff08;1&#xff09;三极管的高低电平导通判断 三极管中有2个PN结&#xff0c;分别称为发射结和集电极结&#xff0c;按材料划分为硅材料三极管&#xff08;硅管&#xff09;&#xff0c;锗材料三极管&#xff08;锗管&#xff09;&am…

PPT模板之--个人简历

还在为制作 PPT 时毫无头绪、对着空白页面抓耳挠腮而烦恼吗&#xff1f;别担心&#xff0c;这里就是你的 PPT 灵感补给站&#xff01;在这个快节奏的信息时代&#xff0c;一份吸睛又高效的 PPT 至关重要&#xff0c;它能在商务汇报中助你赢得先机&#xff0c;在课堂展示时让你脱…

springboot--页面的国际化

今天来实现页面中的国际化 首先&#xff0c;需要创建一个新的spring boot项目&#xff0c;导入前端模板&#xff0c;在我的博客中可以找到&#xff0c;然后将HTML文件放在templates包下&#xff0c;将其他的静态资源放在statics包下&#xff0c;如下图结构 页面的国际化主要在首…

前端学习10—Ajax

1 AJAX 简介 AJAX 全称为 Asynchronous JavaScript And XML&#xff0c;就是异步的 JS 和 XML 通过 AJAX 可以在浏览器中向服务器发送异步请求&#xff0c;最大优势为&#xff1a;无刷新获取数据 AJAX 不是新的编程语言&#xff0c;而是一种将现有的标准组合在一起使用的新方…

list的常见接口使用

今天&#xff0c;我们来讲解一下C关于STL标准库中的一个容器list的常见接口。 在我们之前c语言数据结构中&#xff0c;我们已经了解过了关于链表的知识点了&#xff0c;那么对于现在理解它也是相对来说比较容易的了。 数据结构--双向循环链表-CSDN博客 1. 定义与包含头文件 …

一维差分数组

2.一维差分 - 蓝桥云课 问题描述 给定一个长度为 n 的序列 a。 再给定 m 组操作&#xff0c;每次操作给定 3 个正整数 l, r, d&#xff0c;表示对 a_{l} 到 a_{r} 中的所有数增加 d。 最终输出操作结束后的序列 a。 ​​Update​​: 由于评测机过快&#xff0c;n, m 于 20…

再次重拾jmeter之踩坑

1.添加“csv数据文件设置”&#xff0c;运行时提示 java.lang.IllegalArgumentException: Filename must not be null or empty检查多次后才发现因为我运行的是整个线程组&#xff0c;所以对应http请求下不能包括空的csv文件 2. 填写ip时不能加/&#xff0c;要在路径里加&…

4-6记录(B树)

找左边右下或者右边左下 转化成了前驱后继的删除 又分好几种情况&#xff1a; 1. 只剩25&#xff0c;小于2&#xff0c;所以把父亲拉到25旁边&#xff0c;兄弟的70顶替父亲 对于25&#xff0c;25的后继就是70&#xff0c;25后继的后继是71&#xff08;中序遍历) 2. 借左子树…

06软件测试需求分析案例-添加用户

给职业顾问部的老师添加用户密码后&#xff0c;他们才能登录使用该软件。只有admin账户具有添加用户、修改用户信息、删除用户的权利。admin是经理或团队的第一个人的账号&#xff0c;后面招一个教师就添加一个账号。 通读需求是提取信息&#xff0c;提出问题&#xff0c;输出…

Nacos服务发现和配置管理

目录 一、Nacos概述 1. Nacos 简介 2. Nacos 特性 2.1 服务发现与健康监测 2.2 动态配置管理 2.3 动态DNS服务 2.4 其他关键特性 二、 服务注册和发现 2.1 核心概念 2.2 Nacos注册中心 2.3 Nacos单机模式 2.4 案例——服务注册与发现 2.4.1 父工程 2.4.2 order-p…

操作系统 3.1-内存使用和分段

如何简单使用内存 这张幻灯片展示了计算机如何开始执行程序的基本过程&#xff0c;涉及到存储器、指令寄存器&#xff08;IR&#xff09;、运算器和控制器等计算机组件。 存储器&#xff1a;程序被加载到内存中。图中显示了一个指令 mov ax, [100]&#xff0c;它的作用是将内存…

禅道MCP Server开发实践与功能全解析

一、简介 1、MCP Server核心定义 MCP Server&#xff08;Meta Command Protocol Server&#xff09;是一种基于客户端-服务器架构的轻量级服务程序&#xff0c;采用统一的mcp协议格式&#xff0c;通过连接多样化数据源和工具为AI应用提供扩展能力。它作为中间层&#xff0c;实…

GNSS静态数据处理

1 安装数据处理软件&#xff1a;仪器之星&#xff08;InStar &#xff09;和 Trimble Business Center 做完控制点静态后&#xff0c;我们需要下载GNSS数据&#xff0c;对静态数据进行处理。在处理之前需要将相关软件在自己电脑上安装好&#xff1a; 仪器之星&#xff08;InS…