欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  科技

基于STM32的FreeRTOS任务管理

程序员文章站 2022-04-02 08:33:57
一、FreeRTOS操作系统特点FreeRTOS是一款实时操作系统,可以设置任务的优先级。FreeRTOS用于MCU,占用iROM约4~9Kbyte,FreeRTOS支持裁剪功能(可裁剪性强)。FreeRTOS 与 ucos-II区别:在商用条件下,FreeRTOS免费开源,而ucos-II需要收费(非商用免费)。任务设计上更容易。在ucos-II中,每个任务的优先级互不相同;而在FreeRTOS中,任务的优先级的可以相同(对于相同优先级任务,采用时间片轮询方式调度)。ucos中表示任务优先级...

一、FreeRTOS操作系统特点

FreeRTOS是一款实时操作系统,可以设置任务的优先级。
FreeRTOS用于MCU,占用iROM约4~9Kbyte,FreeRTOS支持裁剪功能(可裁剪性强)。

FreeRTOS 与 ucos-II区别:

  1. 在商用条件下,FreeRTOS免费开源,而ucos-II需要收费(非商用免费)。
  2. 任务设计上更容易。在ucos-II中,每个任务的优先级互不相同;而在FreeRTOS中,任务的优先级的可以相同(对于相同优先级任务,采用时间片轮询方式调度)。
    ucos中表示任务优先级时,数字小的级别高;在FreeRTOS中表示任务优先级时,数字大的级别高。
  3. FreeRTOS是ST钦定的第三方程序。
    比如STM32CubeMX软件可以直接生成含FreeRTOS的工程。

二、任务优先级

在FreeRTOS中,可以设定任务的优先级(一般是在创建任务的时候设置优先级)

任务优先级的范围 受限于工程中FreeRTOSConfig.h的configMAX_PRIORITIES 的值,低优先级号表示任务的优先级低,优先级号 0 表示最低优先级。有效的优先级号范围从 0 (configMAX_PRIORITES – 1)。

可以多个任务共用同一个优先级,那么当他们被调度运行时,调度器会让他们轮流执行(采用时间片机制)。

三、任务调度

任务调度由调度器控制,一般优先级调度当前可运行的任务中级别最高的任务。

而查询是否需要切换任务调度需要特定操作,一般,MCU会在每个时钟节拍产生后,检测是否需要切换任务调度。

  1. 时钟节拍发生的频率
    心跳中断频率由FreeRTOSConfig.h 中的编译时配置常量 configTICK_RATE_HZ 进行配置。
    比如,若当前工程中configTICK_RATE_HZ 为100,则当前系统每隔10ms产生一次时钟节拍,检测是否需要切换任务。
  2. 时钟节拍的来源
    由STM32的SysTick(滴答定时器)提供。具体操作如下
    (1)之后设计SysTick_init() 需要考虑,溢出时间为1.0/ configTICK_RATE_HZ 秒,使用SysTick的溢出中断
    (2)重新设计SysTick的中断服务函数(包含检测是否需要切换任务)

四、任务的状态

  1. 运行状态
    当前正在运行的任务处于运行状态,同一时间只有一个任务处于运行状态。

  2. 就绪状态
    如果任务处于非运行状态,但既没有阻塞也没有挂起,则这个任务处于就绪(ready,
    准备或就绪)状态

  3. 挂起状态
    (运行vTaskSuspend()、vTaskResume())“挂起(suspended)”也是非运行状态的子状态。处于挂起状态的任务对调度器而言是不可见的。

  4. 阻塞状态
    如果一个任务正在等待某个事件,则称这个任务处于”阻塞态(blocked)”。阻塞态是非运行态的一个子状态。
    比如:void vTaskDelay( const TickType_t xTicksToDelay ); 使当前任务阻塞xTicksToDelay个节拍

五、状态的切换

基于STM32的FreeRTOS任务管理

上图反映了FreeRTOS下任务在就绪态、运行态、挂起态和阻塞态切换。结论:

  1. 只有处于就绪态的任务才能直接进入到运行态。
  2. 处于运行态的任务A,若运行期间存在优先级更高的其他任务B(任务B必须是处于就绪态或运行态),则在最近时钟节拍后,任务B取代任务A进入到运行态,原先任务A回到就绪态。
  3. 处于运行态的任务A,若运行期间调用vTaskSuspend(),则进入到挂起态。处于挂起态的任务,需要其他任务对它调用vTaskResume(),才能进入到就绪态。
  4. 处于运行态的任务A,若运行期间调用含有阻塞功能函数(比如延时阻塞),则进入到阻塞态。处于阻塞态的任务需要满足一定的条件才能进入到就绪态。

六、FreeRTOS的使用

以STM32一个已移植FreeRTOS的工程为例:

1.任务函数

任务函数是用于实现任务功能的。
任务函数要求:

  1. 必须返回 void,而且带有一个 void *指针参数。其函数原型如下
    void ATaskFunction( void *pvParameters );
  2. 作为系统中主要执行的任务函数,函数体中要求包含while(1); 即使任务不会因为运行到函数结束而结束。任务函数设计如下:
void ATaskFunction( void *pvParameters )
{
		//变量定义

		//任务主要内容放在死循环中
		while(1)
		{
			//任务主要代码
		}
		删除任务
}

2.创建任务

基于STM32的FreeRTOS任务管理
Eg:创建LED1连续闪烁任务。

  1. 设计LED1的任务函数
void LED1TaskFunction( void *pvParameters )
{
		//变量定义
   
	    //任务主要内容放在死循环中
		while(1)
		{
			//任务主要代码(包含至少一条让当前任务阻塞代码,使其他任务可以被执行)
        LED1_FZ;
        //TIM2_delayms(500);//不使用,因为该函数不会使任务进入到阻塞态
        vTaskDelay( 500 );//使当前任务在500个节拍内,处于阻塞状态
		}
		//删除任务
}

补充:void vTaskDelay( const TickType_t xTicksToDelay );
使一个任务进入阻塞状态,等待规定时钟节拍次数达到后,回到就绪态

  1. 考虑给任务分配栈区深度
    需要结合任务函数进行分析,最终分配栈区大小必须要大于在该任务函数中分配/申请/使用到的空间之和。
    比如:当前任务函数中由于未定义变量,不需要分配特别多的空间,分配100字大小即可。

  2. 调用创建任务的函数

TaskHandle_t  LED1_TaskHandle; //创建任务句柄(作为任务的ID)
BaseType_t xTaskCreate(	LED1TaskFunction,
							“LED1Task”,
						    100,
							NULL,
							1,//优先级
							& LED1_TaskHandle );

3.删除任务

void vTaskDelete( TaskHandle_t xTaskToDelete )
参数:任务的结构体(ID)

4.挂起任务与解除挂起

void vTaskSuspend( TaskHandle_t xTaskToSuspend )
挂起任务(参数填保存任务ID的变量/ 任务的句柄)
让指定任务进入挂起态。

void vTaskResume( TaskHandle_t xTaskToResume )
让任务解除挂起继续执行(参数填保存任务ID的变量/ 任务的句柄)
让指定任务从挂起态到就绪态。

5.任务调度与停止

void vTaskStartScheduler( void )
使任务开始调度
void vTaskEndScheduler( void )
停止调度

七、实战

LED1:0.3秒状态反转
LED2:0.7秒状态反转
LED3:1.1秒状态反转
UART:从‘A’开始每隔1秒发送字母

#include "main.h"

void led1_taskfunction(void *pvParameters);
void led2_taskfunction(void *pvParameters);
void led3_taskfunction(void *pvParameters);
void uart1_taskfunction(void *pvParameters);

TaskHandle_t led1TaskHandle;
TaskHandle_t led2TaskHandle;
TaskHandle_t led3TaskHandle;
TaskHandle_t uart1TaskHandle;

int main(void)
{
	//外设初始化
	LED_init();
	uart1_init(9600);
	//操作系统时钟初始化
	SysTick_Config(SystemCoreClock/configTICK_RATE_HZ);//溢出时间和操作系统中断间隔相同
	//至少创建一个任务
	xTaskCreate(led1_taskfunction,
							"led1_task",
							64,	//任务空间,单位:字
							NULL,
							1,	//优先级
							&led1TaskHandle);
	xTaskCreate(led2_taskfunction,
							"led2_task",
							64,	//任务空间,单位:字
							NULL,
							2,	//优先级
							&led2TaskHandle);
	xTaskCreate(led3_taskfunction,
							"led3_task",
							64,	//任务空间,单位:字
							NULL,
							1,	//优先级
							&led3TaskHandle);
	xTaskCreate(uart1_taskfunction,
							"uart1_task",
							64,	//任务空间,单位:字
							NULL,
							2,	//优先级
							&uart1TaskHandle);
	//开始任务的调度
	vTaskStartScheduler();
}

//先设计LED1任务
void led1_taskfunction(void *pvParameters)
{
	while(1)//任务函数中必须有:1.死循环结构 2.释放对CPU的占用
	{
		LED1_FZ;
		vTaskDelay(300);//使任务阻塞300个节拍后,回到就绪态
	}
}

//先设计LED2任务
void led2_taskfunction(void *pvParameters)
{
	while(1)//任务函数中必须有:1.死循环结构 2.释放对CPU的占用
	{
		LED2_FZ;
		vTaskDelay(700);//使任务阻塞700个节拍后,回到就绪态
	}
}

//先设计LED3任务
void led3_taskfunction(void *pvParameters)
{
	while(1)//任务函数中必须有:1.死循环结构 2.释放对CPU的占用
	{
		LED3_FZ;
		vTaskDelay(1100);//使任务阻塞1100个节拍后,回到就绪态
	}
}

//先设计uart1任务
void uart1_taskfunction(void *pvParameters)
{
	static u8 ch = 'A';
	while(1)//任务函数中必须有:1.死循环结构 2.释放对CPU的占用
	{
		printf("ch = %c\r\n",ch++);
		vTaskDelay(1000);//使任务阻塞1000个节拍后,回到就绪态
	}
}

本文地址:https://blog.csdn.net/qq_29011025/article/details/109014880