目录
一、FreeRTOS 介绍
什么是 FreeRTOS ?
为什么选择 FreeRTOS ?
FreeRTOS 资料与源码下载
FreeRTOS 实现多任务的原理
二、移植 FreeRTOS 到STM32
手动移植
使用CubeMX快速移植
快速移植流程
一些常见问题
三、任务的创建与删除
1. 什么是任务?
2. 任务创建与删除相关函数
HAL库:创建和删除
函数原型:
3. 实操
四、任务调度
什么是任务调度?
FreeRTOS的任务调度规则是怎样的?
抢占式调度运行过程
时间片调度运行过程
五、任务的状态
HAL库 的挂起和继续函数原型:
六、任务综合小实验
实验需求
cubeMX配置
代码实现
用到的函数:
完整代码:
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版。
(以上来自百度百科)
1.FreeRTOS 是免费的;
2.很多半导体厂商产品的SDK(Software Development Kit)软件开发工具包,就使用FreeRTOS作为其操作系统,尤其是WIFI、蓝牙这些带有协议栈的芯片或模块。
3.简单,因为FreeRTOS的文件数量很少。
最好的资料就是官网提供的资料
FreeRTOS - Market leading RTOS (Real Time Operating System) for embedded systems with Internet of Things extensions
严格来说 FreeRTOS 并不是实时操作系统,因为它是分时复用的。
系统将时间分割成很多时间片,然后轮流执行各个任务。
每个任务都是独立运行的,互不影响,由于切换的频率很快,就感觉像是同时运行的一样。
过程复杂且繁琐,对新手不友好。如有需要手动移植,可参照以下文章:
(61条消息) FreeRTOS移植到STM32_不秃也很强的博客-CSDN博客
1. 在 SYS 选项里,将 Debug 设为 Serial Wire ,并且将 Timebase Source 设为 TIM2 (其它定时器也行)。为何要如此配置?下文解说。
2. 将 RCC 里的 HSE 设置为 Crystal/Ceramic Resonator 。
3. 时钟配置
4. 选择 FREERTOS 选项,并将 Interface 改为 CMSIS_V1 。V1 和 V2 有啥区别?下文解释。
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博客
任务可以理解为进程/线程,创建一个任务,就会在内存开辟一个空间。
比如:
Windows 系统中的 MarkText 、谷歌浏览器、记事本,都是任务。
任务通常都含有 while(1) 死循环。
任务创建与删除相关函数有如下三个:
函数名称 | 函数作用 |
xTaskCreate() | 动态方式创建任务 |
xTaskCreateStatic() | 静态方式创建任务 |
vTaskDelete() | 删除任务 |
任务动态创建与静态创建的区别:
动态创建任务的堆栈由系统分配,而静态创建任务的堆栈由用户自己传递。
通常情况下使用动态方式创建任务。
xTaskCreate 函数原型
1. pvTaskCode:指向任务函数的指针,任务必须实现为永不返回(即连续循环);
2. pcName:任务的名字,主要是用来调试,默认情况下最大长度是16;
3. pvParameters:指定的任务栈的大小;
4. uxPriority:任务优先级,数值越大,优先级越大;
5. pxCreatedTask:用于返回已创建任务的句柄可以被引用。
返回值 | 描述 |
pdPASS | 任务创建成功 |
errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY | 任务创建失败 |
//创建
/* 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,则代表删除任务自身(当前正在运行的任务)。
两LED分别闪烁。
/* 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 是一个实时操作系统,它所奉行的调度规则:
1. 高优先级抢占低优先级任务,系统永远执行最高优先级的任务(即抢占式调度)
2. 同等优先级的任务轮转调度(即时间片调度)
还有一种调度规则是协程式调度,但官方已明确表示不更新,主要是用在小容量的芯片上,用得
也不多。
1. 高优先级任务,优先执行;
2. 高优先级任务不停止,低优先级任务无法执行;
3. 被抢占的任务将会进入就绪态
总结:
1. 同等优先级任务,轮流执行,时间片流转;
2. 一个时间片大小,取决为滴答定时器中断周期;
3. 注意没有用完的时间片不会再使用,下次任务 Task3 得到执行,
还是按照一个时间片的时钟节拍运行。
FreeRTOS中任务共存在4种状态:
Running 运行态
当任务处于实际运行状态称之为运行态,即CPU的使用权被这个任务占用(同一时间仅一个任务
处于运行态)。
Ready 就绪态
处于就绪态的任务是指那些能够运行(没有被阻塞和挂起),但是当前没有运行的任务,因为同
优先级或更高优先级的任务正在运行。
Blocked 阻塞态
如果一个任务因延时,或等待信号量、消息队列、事件标志组等而处于的状态被称之为阻塞态。
Suspended 挂起态
类似暂停,通过调用函数 vTaskSuspend() 对指定任务进行挂起,挂起后这个任务将不被执行,
只有调用函数 xTaskResume() 才可以将这个任务从挂起态恢复。
总结:
1. 仅就绪态可转变成运行态
2. 其他状态的任务想运行,必须先转变成就绪态
#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
}
创建 4 个任务:taskLED1,taskLED2,taskKEY1,taskKEY2,任务要求如下:
taskLED1:间隔 500ms 闪烁 LED1;
taskLED2:间隔 1000ms 闪烁 LED2;
taskKEY1:如果 taskLED1 存在,则按下 KEY1 后删除 taskLED1 ,否则创建 taskLED1 ;
taskKEY2:如果 taskLED2 正常运行,则按下 KEY2 后挂起 taskLED2 ,否则恢复 taskLED2
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 */
}