FreeRTOS(1):任务

目录

一、FreeRTOS 介绍

什么是 FreeRTOS ?

为什么选择 FreeRTOS ?

FreeRTOS 资料与源码下载

FreeRTOS 实现多任务的原理

二、移植 FreeRTOS 到STM32

手动移植

使用CubeMX快速移植

快速移植流程

 一些常见问题

 三、任务的创建与删除

1. 什么是任务?

2. 任务创建与删除相关函数

HAL库:创建和删除

 函数原型:

3. 实操

 四、任务调度

什么是任务调度?

FreeRTOS的任务调度规则是怎样的?

抢占式调度运行过程

时间片调度运行过程

五、任务的状态

HAL库 的挂起和继续函数原型:

六、任务综合小实验

实验需求

cubeMX配置 

代码实现 

用到的函数: 

完整代码:


一、FreeRTOS 介绍

什么是 FreeRTOS ?

Free即免费的,RTOS的全称是Real time operating system,中文就是实时操作系统。

注意:RTOS不是指某一个确定的系统,而是指一类操作系统。比如:uc/OS,FreeRTOS,RTX,RT-Thread等这些都是RTOS类操作系统。

FreeRTOS是一个迷你的实时操作系统内核。作为一个轻量级的操作系统,功能包括:任务管理、时间管理、信号量、消息队列、内存管理、记录功能、软件定时器、协程等,可基本满足较小系统的需要。

由于RTOS需占用一定的系统资源(尤其是RAM资源),只有μC/OS-II、embOS、salvo、FreeRTOS等少数实时操作系统能在小RAM单片机上运行。相对μC/OS-II、embOS等商业操作系统,FreeRTOS操作系统是完全免费的操作系统,具有源码公开、可移植、可裁减、调度策略灵活的特点,可以方便地移植到各种单片机上运行,其最新版本为10.4.4版。

(以上来自百度百科)

为什么选择 FreeRTOS ?

1.FreeRTOS 是免费的;

2.很多半导体厂商产品的SDK(Software Development Kit)软件开发工具包,就使用FreeRTOS作为其操作系统,尤其是WIFI、蓝牙这些带有协议栈的芯片或模块。

3.简单,因为FreeRTOS的文件数量很少。

FreeRTOS 资料与源码下载

最好的资料就是官网提供的资料

FreeRTOS - Market leading RTOS (Real Time Operating System) for embedded systems with Internet of Things extensions

FreeRTOS 实现多任务的原理

严格来说 FreeRTOS 并不是实时操作系统,因为它是分时复用的。

系统将时间分割成很多时间片,然后轮流执行各个任务。

每个任务都是独立运行的,互不影响,由于切换的频率很快,就感觉像是同时运行的一样。

FreeRTOS(1):任务_第1张图片

二、移植 FreeRTOS 到STM32

手动移植

过程复杂且繁琐,对新手不友好。如有需要手动移植,可参照以下文章:

(61条消息) FreeRTOS移植到STM32_不秃也很强的博客-CSDN博客

使用CubeMX快速移植

快速移植流程

1. 在 SYS 选项里,将 Debug 设为 Serial Wire ,并且将 Timebase Source 设为 TIM2 (其它定时器也行)。为何要如此配置?下文解说。

FreeRTOS(1):任务_第2张图片

2. 将 RCC 里的 HSE 设置为 Crystal/Ceramic Resonator 。

3. 时钟配置

4. 选择 FREERTOS 选项,并将 Interface 改为 CMSIS_V1 。V1 和 V2 有啥区别?下文解释。

FreeRTOS(1):任务_第3张图片

 5. 配置项目信息,并导出代码。 

 一些常见问题

1. Timebase Source 为什么不能设置为 SysTick ?

裸机的时钟源默认是 SysTick,但是开启 FreeRTOS 后,FreeRTOS会占用 SysTick (用来生成1ms定时,用于任务调度),所以需要需要为其他总线提供另外的时钟源。

2. FreeRTOS 版本问题

V2 的内核版本更高,功能更多,在大多数情况下 V1 版本的内核完全够用。

3. FreeRTOS 各配置选项卡的解释

Events:事件相关的创建

Task and Queues: 任务与队列的创建

Timers and Semaphores: 定时器和信号量的创建

Mutexes: 互斥量的创建

FreeRTOS Heap Usage: 用于查看堆使用情况

config parameters: 内核参数设置,用户根据自己的实际应用来裁剪定制 FreeRTOS 内核

Include parameters: FreeRTOS 部分函数的使能

User Constants: 相关宏的定义,可以自建一些常量在工程中使用

Advanced settings:高级设置

4. 内核配置、函数使能的一些翻译

内核参数的理解内容非常多,可以参考以下文章:

(61条消息) FreeRTOS系列第6篇---FreeRTOS内核配置说明_vassertcalled_研究是为了理解的博客-CSDN博客

 三、任务的创建与删除

1. 什么是任务?

任务可以理解为进程/线程,创建一个任务,就会在内存开辟一个空间。

比如:

Windows 系统中的 MarkText 、谷歌浏览器、记事本,都是任务。

任务通常都含有 while(1) 死循环。

2. 任务创建与删除相关函数

任务创建与删除相关函数有如下三个:

函数名称 函数作用
xTaskCreate() 动态方式创建任务
xTaskCreateStatic() 静态方式创建任务
vTaskDelete() 删除任务

任务动态创建与静态创建的区别:

动态创建任务的堆栈由系统分配,而静态创建任务的堆栈由用户自己传递。

通常情况下使用动态方式创建任务。

xTaskCreate 函数原型

FreeRTOS(1):任务_第4张图片1. pvTaskCode:指向任务函数的指针,任务必须实现为永不返回(即连续循环);

2. pcName:任务的名字,主要是用来调试,默认情况下最大长度是16;

3. pvParameters:指定的任务栈的大小;

4. uxPriority:任务优先级,数值越大,优先级越大

5. pxCreatedTask:用于返回已创建任务的句柄可以被引用。

返回值 描述
pdPASS 任务创建成功
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY 任务创建失败

HAL库:创建和删除

    //创建
  /* Create the thread(s) */
  /* definition and creation of LED1 */
  osThreadDef(LED1, Start_LED1, osPriorityNormal, 0, 128);
  LED1Handle = osThreadCreate(osThread(LED1), NULL);

  /* definition and creation of LED2 */
  osThreadDef(LED2, Start_LED2, osPriorityNormal, 0, 128);
  LED2Handle = osThreadCreate(osThread(LED2), NULL);

    //删除
  osThreadTerminate(LED1Handle);
  osThreadTerminate(LED2Handle);


 函数原型:

/*********************** Thread Management *****************************/
/**
* @brief  Create a thread and add it to Active Threads and set it to state READY.
* @param  thread_def    thread definition referenced with \ref osThread.
* @param  argument      pointer that is passed to the thread function as start argument.
* @retval thread ID for reference by other functions or NULL in case of error.
* @note   MUST REMAIN UNCHANGED: \b osThreadCreate shall be consistent in every CMSIS-RTOS.
*/
osThreadId osThreadCreate (const osThreadDef_t *thread_def, void *argument)
{
  TaskHandle_t handle;
  
#if( configSUPPORT_STATIC_ALLOCATION == 1 ) &&  ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
  if((thread_def->buffer != NULL) && (thread_def->controlblock != NULL)) {
    handle = xTaskCreateStatic((TaskFunction_t)thread_def->pthread,(const portCHAR *)thread_def->name,
              thread_def->stacksize, argument, makeFreeRtosPriority(thread_def->tpriority),
              thread_def->buffer, thread_def->controlblock);
  }
  else {
    if (xTaskCreate((TaskFunction_t)thread_def->pthread,(const portCHAR *)thread_def->name,
              thread_def->stacksize, argument, makeFreeRtosPriority(thread_def->tpriority),
              &handle) != pdPASS)  {
      return NULL;
    } 
  }
#elif( configSUPPORT_STATIC_ALLOCATION == 1 )

    handle = xTaskCreateStatic((TaskFunction_t)thread_def->pthread,(const portCHAR *)thread_def->name,
              thread_def->stacksize, argument, makeFreeRtosPriority(thread_def->tpriority),
              thread_def->buffer, thread_def->controlblock);
#else
  if (xTaskCreate((TaskFunction_t)thread_def->pthread,(const portCHAR *)thread_def->name,
                   thread_def->stacksize, argument, makeFreeRtosPriority(thread_def->tpriority),
                   &handle) != pdPASS)  {
    return NULL;
  }     
#endif
  
  return handle;
}
/**
* @brief  Terminate execution of a thread and remove it from Active Threads.
* @param   thread_id   thread ID obtained by \ref osThreadCreate or \ref osThreadGetId.
* @retval  status code that indicates the execution status of the function.
* @note   MUST REMAIN UNCHANGED: \b osThreadTerminate shall be consistent in every CMSIS-RTOS.
*/
osStatus osThreadTerminate (osThreadId thread_id)
{
#if (INCLUDE_vTaskDelete == 1)
  vTaskDelete(thread_id);
  return osOK;
#else
  return osErrorOS;
#endif
}

官方案例:

/* Task to be created. */
void vTaskCode( void * pvParameters )
{
	/* The parameter value is expected to be 1 as 1 is passed in the
	pvParameters value in the call to xTaskCreate() below.*/
	configASSERT( ( ( uint32_t ) pvParameters ) == 1 );
	for( ;; )
	{
		/* Task code goes here. */
	}
}
/* Function that creates a task. */
void vOtherFunction( void )
{
	BaseType_t xReturned;
	TaskHandle_t xHandle = NULL;
	
	/* Create the task, storing the handle. */
	xReturned = xTaskCreate(
			vTaskCode, /* Function that implements the task. */
			"NAME", /* Text name for the task. */
			STACK_SIZE, /* Stack size in words, not bytes. */
			( void * ) 1, /* Parameter passed into the task. */
			tskIDLE_PRIORITY,/* Priority at which the task is created. */
			&xHandle ); /* Used to pass out the created task's handle. */
	if( xReturned == pdPASS )
	{
		/* The task was created. Use the task's handle to delete the task. */
		vTaskDelete( xHandle );
	}
}

vTaskDelete 函数原型

void vTaskDelete(TaskHandle_t xTaskToDelete);

只需将待删除的任务句柄传入该函数,即可将该任务删除。

当传入的参数为NULL,则代表删除任务自身(当前正在运行的任务)。

3. 实操

两LED分别闪烁。

FreeRTOS(1):任务_第5张图片

/* USER CODE END Header_Start_LED1 */
void Start_LED1(void const * argument)
{
  /* USER CODE BEGIN Start_LED1 */
  /* Infinite loop */
  for(;;)
  {
    osDelay(500);
		
		HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8);
  }
  /* USER CODE END Start_LED1 */
}

/* USER CODE BEGIN Header_Start_LED2 */
/**
* @brief Function implementing the LED2 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Start_LED2 */
void Start_LED2(void const * argument)
{
  /* USER CODE BEGIN Start_LED2 */
  /* Infinite loop */
  for(;;)
  {
    osDelay(1000);
		
		HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9);
  }
  /* USER CODE END Start_LED2 */
}

 四、任务调度

什么是任务调度?

调度器就是使用相关的调度算法来决定当前需要执行的哪个任务。

FreeRTOS中开启任务调度的函数是 vTaskStartScheduler() ,但在 CubeMX 中被封装为osKernelStart() 。

FreeRTOS的任务调度规则是怎样的?

FreeRTOS 是一个实时操作系统,它所奉行的调度规则:

        1. 高优先级抢占低优先级任务,系统永远执行最高优先级的任务(即抢占式调度

        2. 同等优先级的任务轮转调度(即时间片调度

还有一种调度规则是协程式调度,但官方已明确表示不更新,主要是用在小容量的芯片上,用得

也不多。

抢占式调度运行过程

FreeRTOS(1):任务_第6张图片
总结:

        1. 高优先级任务,优先执行;

        2. 高优先级任务不停止,低优先级任务无法执行;

        3. 被抢占的任务将会进入就绪态

时间片调度运行过程

 FreeRTOS(1):任务_第7张图片

 总结:

        1. 同等优先级任务,轮流执行,时间片流转;

        2. 一个时间片大小,取决为滴答定时器中断周期;

        3. 注意没有用完的时间片不会再使用,下次任务 Task3 得到执行,

            还是按照一个时间片的时钟节拍运行。

五、任务的状态

FreeRTOS中任务共存在4种状态:

        Running 运行态

当任务处于实际运行状态称之为运行态,即CPU的使用权被这个任务占用(同一时间仅一个任务

处于运行态)。

        Ready 就绪态

处于就绪态的任务是指那些能够运行(没有被阻塞和挂起),但是当前没有运行的任务,因为同

优先级或更高优先级的任务正在运行。

        Blocked 阻塞态

如果一个任务因延时,或等待信号量、消息队列、事件标志组等而处于的状态被称之为阻塞态。

        Suspended 挂起态

类似暂停,通过调用函数 vTaskSuspend() 对指定任务进行挂起,挂起后这个任务将不被执行,

只有调用函数 xTaskResume() 才可以将这个任务从挂起态恢复。

总结:

        1. 仅就绪态可转变成运行态

        2. 其他状态的任务想运行,必须先转变成就绪态

HAL库 的挂起和继续函数原型:

#endif /* INCLUDE_eTaskGetState */
/**
* @brief  Suspend execution of a thread.
* @param   thread_id   thread ID obtained by \ref osThreadCreate or \ref osThreadGetId.
* @retval  status code that indicates the execution status of the function.
*/
osStatus osThreadSuspend (osThreadId thread_id)
{
#if (INCLUDE_vTaskSuspend == 1)
    vTaskSuspend(thread_id);
  
  return osOK;
#else
  return osErrorResource;
#endif
}

/**
* @brief  Resume execution of a suspended thread.
* @param   thread_id   thread ID obtained by \ref osThreadCreate or \ref osThreadGetId.
* @retval  status code that indicates the execution status of the function.
*/
osStatus osThreadResume (osThreadId thread_id)
{
#if (INCLUDE_vTaskSuspend == 1)  
  if(inHandlerMode())
  {
    if (xTaskResumeFromISR(thread_id) == pdTRUE)
    {
      portYIELD_FROM_ISR(pdTRUE);
    }
  }
  else
  {
    vTaskResume(thread_id);
  }
  return osOK;
#else
  return osErrorResource;
#endif
}

六、任务综合小实验

FreeRTOS(1):任务_第8张图片

实验需求

创建 4 个任务:taskLED1,taskLED2,taskKEY1,taskKEY2,任务要求如下:

taskLED1:间隔 500ms 闪烁 LED1;

taskLED2:间隔 1000ms 闪烁 LED2;

taskKEY1:如果 taskLED1 存在,则按下 KEY1 后删除 taskLED1 ,否则创建 taskLED1 ;

taskKEY2:如果 taskLED2 正常运行,则按下 KEY2 后挂起 taskLED2 ,否则恢复 taskLED2

cubeMX配置 

FreeRTOS(1):任务_第9张图片

代码实现 

用到的函数: 

osThreadDef(LED1, Start_LED1, osPriorityNormal, 0, 128);
 LED1Handle = osThreadCreate(osThread(LED1), NULL);

osThreadTerminate(LED1Handle);

osThreadSuspend(LED2Handle);

osThreadResume(LED2Handle);

完整代码:

usart.c        串口1输出文字

/* USER CODE BEGIN 0 */
#include "stdio.h"
//覆写printf,usart1输出
int fputc(int ch, FILE *f)
{      
    unsigned char temp[1]={ch};
    HAL_UART_Transmit(&huart1,temp,1,0xffff);  
    return ch;
}
/* USER CODE END 0 */

  freertos.c

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
/* USER CODE END Includes */


/* USER CODE BEGIN Header_Start_LED1 */
/**
  * @brief  Function implementing the LED1 thread.
  * @param  argument: Not used
  * @retval None
  */
/* USER CODE END Header_Start_LED1 */
void Start_LED1(void const * argument)
{
  /* USER CODE BEGIN Start_LED1 */
  /* Infinite loop */
  for(;;)
  {
    osDelay(500);
		
		HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8);
  }
  /* USER CODE END Start_LED1 */
}

/* USER CODE BEGIN Header_Start_LED2 */
/**
* @brief Function implementing the LED2 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Start_LED2 */
void Start_LED2(void const * argument)
{
  /* USER CODE BEGIN Start_LED2 */
  /* Infinite loop */
  for(;;)
  {
    osDelay(1000);
		
		HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9);
  }
  /* USER CODE END Start_LED2 */
}

/* USER CODE BEGIN Header_Start_KEY1 */
/**
* @brief Function implementing the KEY1 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Start_KEY1 */
void Start_KEY1(void const * argument)
{
  /* USER CODE BEGIN Start_KEY1 */
  /* Infinite loop */
  for(;;)
  {
		if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET)
		{
			osDelay(10);//防抖
			if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET)
			{
				printf("按键1按下\r\n");
				if(LED1Handle == NULL)
				{
					printf("任务1未创建,开始创建任务1\r\n");
					
					osThreadDef(LED1, Start_LED1, osPriorityNormal, 0, 128);
					LED1Handle = osThreadCreate(osThread(LED1), NULL);
					
					if(LED1Handle != NULL)
						printf("任务1创建成功");
				}
				else
				{
					printf("任务1已存在,准备删除\r\n");
					osThreadTerminate(LED1Handle);
					LED1Handle = NULL;//手动置null
				}
			}
			while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET);
		}
    osDelay(10);
  }
  /* USER CODE END Start_KEY1 */
}

/* USER CODE BEGIN Header_Start_KEY2 */
/**
* @brief Function implementing the KEY2 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_Start_KEY2 */
void Start_KEY2(void const * argument)
{
  /* USER CODE BEGIN Start_KEY2 */
	static uint8_t flag = 0;
  /* Infinite loop */
  for(;;)
  {
		if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET)
		{
			osDelay(10);//防抖
			if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET)
			{
				printf("按键2按下\r\n");
				if (flag == 0)
				{
					osThreadSuspend(LED2Handle);
					printf("任务2已暂停\r\n");
					flag = 1;
				}
				else
				{
					osThreadResume(LED2Handle);
					printf("任务2已恢复\r\n");
					flag = 0;
				}
			}
			while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET);
		}
    osDelay(10);
  }
  /* USER CODE END Start_KEY2 */
}

你可能感兴趣的:(STM32,RTOS,stm32,单片机)