Free RTOS

简单易用的实时操作系统

管理硬件和驱动 为应用程序提供服务

单片机 单核 一次只能执行一个任务

操作系统让单核 同一时刻处理多项任务

原理

多个任务快速切换

优点

提高资源利用率(硬件资源)

硬件速度太快,人的速度太慢

CPU可以在一个任务等待延时或者某些外部事件时,切换到另一个任务,从而避免CPU做无意义的等待。

不会卡住 不会丢数据(UART串口通信 如果是前一个任务执行完再执行这个任务 可能过了取数据时间 导致丢失数据)

一个任务执行完,再去执行其他任务,其他任务的数据可能丢失

缺点

浪费性能(切换任务的时间)

如何切换任务:

调度器

调度策略

时间片轮询:相同优先级,每个任务执行相同的时间,轮流执行

优先级调度:抢占优先级,数字越大优先级越高

ABC 停A才能执行B 停B才能执行C

调度原理:

优先级 当前状态

数据结构

任务状态:

就绪态才能转换为运行态 运行态转换为就绪态(被优先级高的打断 或者 任务的时间片用完)

运行中 就绪(在排队)

阻塞(时间 n个阶段一个任务 走完一个阶段) free RTOS 专用的delay 只对当前任务有效

挂起 (事件 排到了 不满足条件 满足条件后 继续排队 (就绪态)) 事件:vTaskSuspend xTaskResume

状态转换逻辑:

任务队列(怎么写代码)

时间片轮询:队列性质(先进先出)

优先级:就绪队列

就绪队列:按照优先级分成n个队列

每个队列优先级不同,数字越大,优先级越高

队列里面的任务优先级相同按时间片轮询(先进先出)

就绪队列数据结构: (二维数组)

阻塞队列(有期徒刑)

时间到了,到就绪队列

时间短的排前面(按照优先级号,排到就绪队列)

挂起队列(无期徒刑)

外部调用,才能到就绪队列

如何实现任务切换:

Systick中断

上下文切换

依赖3个中断

SysTick(优先级最高)

一般设置1ms

决定时间片的时间

维护系统时钟,并判断是否需要执行上下文切换,如果需要,则触发PendSV中断

PendSV

执行实际的上下文切换操作

SVC

用于将整个系统的第一个任务的上下文加载到CPU

FreeRTOS移植

STM32F103ZET6

不使用pfrintf调试 打印的时候 RTOS可能在执行上下文切换

用LED灯调试 PA0 PA1 PA8

SysTick(优先级最高)

demo

定制功能

编译器适配:

portable:接口

与编译器对接(Keil)

堆内存管理

heap_5多个flash不连号(一般不使用)

移植开始====================

新建文件夹FreeRTOS

FreeRTOS->include

FreeRTOS->portable

定制文件放到FreeRTOS

==========================

实现上下文切换功能(3个中断)

不用实现SVC_Handler和PendSV_Handler(源码是汇编):定制里面 用宏定义实现

只实现SysTick_Handler

/* USER CODE BEGIN Includes */
#include "FreeRTOS.h"
#include "task.h"
/* USER CODE END Includes */
/* USER CODE BEGIN PFP */
extern void xPortSysTickHandler(void);
/* USER CODE END PFP */
void SysTick_Handler(void)
{
    /* USER CODE BEGIN SysTick_IRQn 0 */
    if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) {
        xPortSysTickHandler();
    }
    /* USER CODE END SysTick_IRQn 0 */
    /* USER CODE BEGIN SysTick_IRQn 1 */
    /* USER CODE END SysTick_IRQn 1 */
}

定制:

在FreeRTOSConfig.h中增加如下三个宏

#define    xPortPendSVHandler    PendSV_Handler //xPortPendSVHandler是由FreeRTOS提供的PendSV中断服务程序的实现
#define    vPortSVCHandler    SVC_Handler       //vPortSVCHandler是由FreeRTOS提供的SVC中断服务程序的实现
#define    INCLUDE_xTaskGetSchedulerState    1  //使能xTaskGetSchedulerState函数

错误:每次HAL都重新生成PendSV SVC_Handler

方案1:

每次用HAL生成代码都需要注释掉

方案2

FreeRTOS命令惯例

https://www.freertos.org/zh-cn-cmn-s/Documentation/02-Kernel/06-Coding-guidelines/02-FreeRTOS-Coding-Standard-and-Style-Guide#%E5%91%BD%E5%90%8D%E6%83%AF%E4%BE%8B

空闲任务

https://www.freertos.org/zh-cn-cmn-s/Documentation/02-Kernel/02-Kernel-features/01-Tasks-and-co-routines/15-Idle-task

软件架构

可以单独写个启动任务,启动任务优先级需要最高

需要删除自身

创建任务

案例1 动态分配内存

xTaskCreate

使用动态内存分配 令LED1闪烁

相关参数

configMAX_PRIORITIES 任务最高优先级

https://www.freertos.org/zh-cn-cmn-s/Documentation/02-Kernel/03-Supported-devices/02-Customization#configmax_priorities

configMINIMAL_STACK_SIZE 任务最小堆栈大小,注意单位

https://www.freertos.org/zh-cn-cmn-s/Documentation/02-Kernel/03-Supported-devices/02-Customization#configminimal_stack_size

使用===========================

RTOS

RTOS运行的任务

常规代码 while代码永远执行不了

新建App文件夹

新建文件:

实现方法:

启动FreeRTOS

1 创建n个任务

2 启动任务调度器 启动FreeRTOS操作系统

2下面的代码不会执行,2执行完找上面1创建的n个任务

系统的空闲任务 占用0优先级

静态数组空间 不是堆栈空间

声明结构体不要声明指针, 声明指针不会自动创建结构体

延迟函数必须使用freeRTOS的延迟函数

vTaskDelay(100) 任务会阻塞100次中断 1次中断1ms(SysTick) 总共阻塞100ms

测试1

优先级一样

优先级不一样 vTaskDelay不要

优先级不一样 vTaskDelay开启

优先级0 系统空闲任务

如果高优先级任务不阻塞自身 调度器永远调度高优先级任务 别的任务永远执行不了

代码

#include "FreeRTOS_Task.h"

void taskCode1(void *args);
#define task1_stack_size 128
#define tsk1_priority 1
TaskHandle_t task1_handle;

void taskCode2(void *args);
#define task2_stack_size 128
#define tsk2_priority 2
TaskHandle_t task2_handle;

void taskCode3(void *args);
#define task3_stack_size 128
#define tsk3_priority 3
TaskHandle_t task3_handle;

void App_FreeRTOS_Start(void)
{
    xTaskCreate(
        taskCode1,        /* Function that implements the task. */
        "vTaskCode1",     /* Text name for the task. */
        task1_stack_size, /* Stack size in words, not bytes. */
        NULL,             /* Parameter passed into the task. */
        tsk1_priority,    /* Priority at which the task is created. */
        &task1_handle);
    xTaskCreate(
        taskCode2,        /* Function that implements the task. */
        "vTaskCode2",     /* Text name for the task. */
        task2_stack_size, /* Stack size in words, not bytes. */
        NULL,             /* Parameter passed into the task. */
        tsk2_priority,    /* Priority at which the task is created. */
        &task2_handle);
    xTaskCreate(
        taskCode3,        /* Function that implements the task. */
        "vTaskCode3",     /* Text name for the task. */
        task3_stack_size, /* Stack size in words, not bytes. */
        NULL,             /* Parameter passed into the task. */
        tsk3_priority,    /* Priority at which the task is created. */
        &task3_handle);
    vTaskStartScheduler();
}

void taskCode1(void *args)
{
    while (1)
    {
        HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin);
        vTaskDelay(500);
    }
}

void taskCode2(void *args)
{
    while (1)
    {
        HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
        vTaskDelay(500);
    }
}

void taskCode3(void *args)
{
    while (1)
    {
        HAL_GPIO_TogglePin(LED3_GPIO_Port, LED3_Pin);
        vTaskDelay(500);
    }
}

优先级上限

https://www.freertos.org/zh-cn-cmn-s/Documentation/02-Kernel/04-API-references/01-Task-creation/01-xTaskCreate

==============================================

案例2

裸机开发与FreeRTOS开发区别

需求说明

令LED1和LED2以不同的频率闪烁

要求

分别用FreeRTOS和裸机开发的方式实现

体会

在FreeRTOS中,每个Task都可以将自己看作是唯一的Task

裸机开发Task之间会相互影响

Idle作用 防止有任务有延迟

vTaskDelay阻塞当前任务

阻塞时间完 任务进入就绪队列

其他任务可以执行(主动释放资源)

案例3

一直运行的延迟

其他任务无法执行 (一直占用CPU)

现象

只有task3控制的灯一直亮

#include "FreeRTOS_demo.h"

void delay_ms(uint32_t ms)
{
    uint32_t count = ms * 72000 / 9;
    while (count--)
    {
        __NOP();
    }
}

// TASK1任务参数
// 特定类型的函数指针
void task1(void *pvParam);
// 设置任务栈大小
#define TASK1_STACK_SIZE 128
// 设置优先级 => 值越大优先级越高 越先调度
#define TASK1_PRIORITY 1
TaskHandle_t task1_handle;

// TASK2任务参数
void task2(void *pvParam);
#define TASK2_STACK_SIZE 128
#define TASK2_PRIORITY 2
TaskHandle_t task2_handle;

// TASK3任务参数
void task3(void *pvParam);
#define TASK3_STACK_SIZE 128
#define TASK3_PRIORITY 3
TaskHandle_t task3_handle;

void FreeRTOS_Start(void)
{

    // 创建任务
    xTaskCreate(task1, "TASK1", TASK1_STACK_SIZE, NULL, TASK1_PRIORITY, &task1_handle);
    xTaskCreate(task2, "TASK2", TASK2_STACK_SIZE, NULL, TASK2_PRIORITY, &task2_handle);
    xTaskCreate(task3, "TASK3", TASK3_STACK_SIZE, NULL, TASK3_PRIORITY, &task3_handle);
    // 最后一行代码一定是启动调度器
    // 调度器会启动一个空闲任务  没事干就去空闲任务
    vTaskStartScheduler();
}

// task1函数实现
void task1(void *pvParam)
{
    while (1)
    {
        Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
        // 阻塞当前任务
        vTaskDelay(400);
    }
}

void task2(void *pvParam)
{
    while (1)
    {
        Int_LED_Toggle(LED2_GPIO_Port, LED2_Pin);
        // 阻塞当前任务500ms => 之后进入就绪态排队
        vTaskDelay(500);
    }
}

void task3(void *pvParam)
{
    while (1)
    {
        Int_LED_Toggle(LED3_GPIO_Port, LED3_Pin);
        // 阻塞当前任务
        // vTaskDelay(600);

        // 一直运行的延迟 => 一直占用CPU
        delay_ms(600);
        // vTaskDelay(100);
    }
}

task3 延迟delay_ms下面加上vTaskDelay(100)

现象:

task1亮完 task2 task3灯同时亮

void task3(void *pvParam)
{
    while (1)
    {
        Int_LED_Toggle(LED3_GPIO_Port, LED3_Pin);
        // 阻塞当前任务
        // vTaskDelay(600);

        // 一直运行的延迟 => 一直占用CPU
        delay_ms(600);
        vTaskDelay(100);
    }
}

案例4

Systick中断频率

configtick_rate_hz

https://www.freertos.org/zh-cn-cmn-s/Documentation/02-Kernel/03-Supported-devices/02-Customization#configtick_rate_hz

案例5 静态分配内存

xTaskCreateStatic

https://www.freertos.org/zh-cn-cmn-s/Documentation/02-Kernel/04-API-references/01-Task-creation/02-xTaskCreateStatic

configSUPPORT_STATIC_ALLOCATION必须在FreeRTOSConfig.h中设置为 1

需求说明

令LED1闪烁

要求

静态分配内存

掌握xTaskCreateStatic用法 (不常用 了解)

静态

每个任务有单独的RAM保存状态

动态创建栈 空间大小 操作系统创建指定空间大小的数组

静态创建栈 空间大小 自己创建指定空间大小的数组

创建任务

栈信息

标识信息 -

报错

自定义:

#include "FreeRTOS_demo.h"

void delay_ms(uint32_t ms)
{
    uint32_t count = ms * 72000 / 9;
    while (count--)
    {
        __NOP();
    }
}

// 任务函数
void task1(void *args);
// 栈空间大小
#define TASK1_STK_SIZE 128
#define TASK1_PRIO 1
StackType_t task1_stk[TASK1_STK_SIZE] = {0};
StaticTask_t task1_tcb;
TaskHandle_t task1_handle;

// 栈空间大小
#define IDLE_TASK_STK_SIZE 128
StackType_t IDLE_task_stk[IDLE_TASK_STK_SIZE] = {0};
StaticTask_t IDLE_task_tcb;

// 静态创建一旦打开  需要手动给IDLE task分配空间
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer,
                                   StackType_t **ppxIdleTaskStackBuffer,
                                   uint32_t *pulIdleTaskStackSize)
{
    //注意这里的IDLE_task_tcb IDLE_task_stk 必须重新定义不能与xTaskCreateStatic的参数一致
    *ppxIdleTaskTCBBuffer = &IDLE_task_tcb;
    *ppxIdleTaskStackBuffer = IDLE_task_stk;
    *pulIdleTaskStackSize = IDLE_TASK_STK_SIZE;
}

void FreeRTOS_Start(void)
{

    // 创建静态任务
    task1_handle = xTaskCreateStatic(task1, "task1", TASK1_STK_SIZE, NULL, TASK1_PRIO, task1_stk, &task1_tcb);

    // 最后一行代码一定是启动调度器
    // 调度器会启动一个空闲任务  没事干就去空闲任务
    vTaskStartScheduler();
}

void task1(void *args)
{
    while (1)
    {
        Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
        vTaskDelay(1000);
    }
}

===================================

删除任务

vTaskDelete

https://www.freertos.org/zh-cn-cmn-s/Documentation/02-Kernel/04-API-references/01-Task-creation/03-vTaskDelete

1 宏定义INCLUDE_vTaskDelete必须定义为 1

2 从 RTOS 内核管理中移除任务。要删除的任务将从所有就绪、 阻塞、挂起和事件列表中移除

注意:

1 空闲任务负责释放由 RTOS 内核分配给已删除任务的 内存。因此,如果应用程序调用了 vTaskDelete(),请务必确保空闲任务获得足够的微控制器处理时间。

2 任务代码分配的内存不会自动释放, 应在任务删除之前手动释放。

deepseek:
在FreeRTOS中,使用vTaskDelete删除任务时,任务控制块(TCB)和栈的内存会自动释放(若为动态分配),
但任务代码中手动分配的内存(如通过pvPortMalloc)需开发者自行释放。
手动释放任务内存的步骤
​明确内存分配来源:
  TCB和栈:若通过xTaskCreate动态创建任务,TCB和栈由FreeRTOS自动释放,无需干预。
  用户分配的内存:若任务中调用了pvPortMalloc、malloc或类似函数,需手动释放。
​在任务结束前释放内存:
  1 自删除任务:若任务调用vTaskDelete(NULL)自行删除,需在调用前释放内存。
  void vMyTask(void *pvParams) {
        char *buffer = pvPortMalloc(100); // 动态分配内存
        // 使用buffer...

        // 删除任务前释放内存
        vPortFree(buffer);
        vTaskDelete(NULL); // 删除自身
  }
 2 被外部删除:若其他任务删除该任务,需通过同步机制(如队列、信号量或任务通知)通知目标任务自行清理。
    // 任务A通知任务B删除自身
    void vTaskA(void *pvParams) {
        xTaskNotify(taskB_handle, CLEANUP_REQUEST, eSetBits);
        // 等待任务B确认清理完成
        vTaskDelete(taskB_handle);
    }

    // 任务B响应清理请求
    void vTaskB(void *pvParams) {
        char *buffer = pvPortMalloc(100);
        while (1) {
            uint32_t notification;
            if (xTaskNotifyWait(0, CLEANUP_REQUEST, &notification, 0) == pdTRUE) {
                vPortFree(buffer); // 收到通知后释放内存
                break; // 退出循环,任务结束
            }
            // 正常任务逻辑...
        }
    }
​删除其他内核对象:
 若任务中创建了队列、信号量等,需调用vQueueDelete()、vSemaphoreDelete()等函数释放资源。

资源不会立即回收

空闲任务负责回收内存

留点时间给空闲任务,空闲任务优先级最低

高优先级任务删除低优先级任务

高优先级主动释放CPU 阻塞自身 否则低优先级任务无法执行 vTaskDelay(100)

案例1

案例2

#include "FreeRTOS_demo.h"

void delay_ms(uint32_t ms)
{
    uint32_t count = ms * 72000 / 9;
    while (count--)
    {
        __NOP();
    }
}

// TASK1任务参数
// 特定类型的函数指针
void task1(void *pvParam);
// 设置任务栈大小
#define TASK1_STACK_SIZE 128
// 设置优先级 => 值越大优先级越高 越先调度
#define TASK1_PRIORITY 1
TaskHandle_t task1_handle;

// TASK2任务参数
void task2(void *pvParam);
#define TASK2_STACK_SIZE 128
#define TASK2_PRIORITY 1
TaskHandle_t task2_handle;

// TASK3任务参数
void task3(void *pvParam);
#define TASK3_STACK_SIZE 128
#define TASK3_PRIORITY 1
TaskHandle_t task3_handle;

// KEY_TASK任务参数
void key_task(void *pvParam);
#define KEY_TASK_STACK_SIZE 128
#define KEY_TASK_PRIORITY 3
TaskHandle_t key_task_handle;

// START_TASK任务参数
void start_task(void *pvParam);
#define START_TASK_STACK_SIZE 128
#define START_TASK_PRIORITY 4
TaskHandle_t start_task_handle;

void FreeRTOS_Start(void)
{
    // 创建任务
    xTaskCreate(start_task, "START_TASK", START_TASK_STACK_SIZE, NULL, START_TASK_PRIORITY, &start_task_handle);
    // 调度器会启动一个空闲任务  没事干就去空闲任务
    vTaskStartScheduler();
}

void start_task(void *pvParam)
{
    xTaskCreate(task1, "TASK1", TASK1_STACK_SIZE, NULL, TASK1_PRIORITY, &task1_handle);
    xTaskCreate(task2, "TASK2", TASK2_STACK_SIZE, NULL, TASK2_PRIORITY, &task2_handle);
    xTaskCreate(task3, "TASK3", TASK3_STACK_SIZE, NULL, TASK3_PRIORITY, &task3_handle);
    xTaskCreate(key_task, "KEY_TASK", KEY_TASK_STACK_SIZE, NULL, KEY_TASK_PRIORITY, &key_task_handle);
    // vTaskDelete(start_task_handle);
    vTaskDelete(NULL); // 删除自身任务
}
// task1函数实现
void task1(void *pvParam)
{
    while (1)
    {
        Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
        // 阻塞当前任务
        vTaskDelay(400);
        // delay_ms(600);
    }
}

void task2(void *pvParam)
{
    while (1)
    {
        Int_LED_Toggle(LED2_GPIO_Port, LED2_Pin);
        // 阻塞当前任务500ms => 之后进入就绪态排队
        vTaskDelay(500);
        // delay_ms(600);
    }
}

void task3(void *pvParam)
{
    while (1)
    {
        Int_LED_Toggle(LED3_GPIO_Port, LED3_Pin);
        // 阻塞当前任务
        // vTaskDelay(600);

        // 一直运行的延迟 => 一直占用CPU
        vTaskDelay(100);
    }
}

void key_task(void *pvParam)
{
    while (1)
    {
        Key_Type key_task = Int_Key_Scan();
        if (key_task == KEY1)
        {
            Int_LED_Off(LED1_GPIO_Port, LED1_Pin);
            vTaskDelete(task1_handle);
        }
        else if (key_task == KEY2)
        {
            Int_LED_Off(LED2_GPIO_Port, LED2_Pin);
            vTaskDelete(task2_handle);
        }
        else if (key_task == KEY3)
        {
            Int_LED_Off(LED3_GPIO_Port, LED3_Pin);
            vTaskDelete(task3_handle);
        }
        vTaskDelay(100);
    }
}

=================================

任务挂起和恢复

vTaskSuspend

vTaskResume

https://www.freertos.org/zh-cn-cmn-s/Documentation/02-Kernel/04-API-references/02-Task-control/06-vTaskSuspend

https://www.freertos.org/zh-cn-cmn-s/Documentation/02-Kernel/04-API-references/02-Task-control/07-vTaskResume

只能写在task里面

专用的函数才能写在中断函数里面(fromISR)

按键1 挂起task1 按键2 恢复task1

需要阻塞自己

后面一般使用阻塞 不使用挂起

#include "FreeRTOS_demo.h"

void delay_ms(uint32_t ms)
{
    uint32_t count = ms * 72000 / 9;
    while (count--)
    {
        __NOP();
    }
}

// TASK1任务参数
// 特定类型的函数指针
void task1(void *pvParam);
// 设置任务栈大小
#define TASK1_STACK_SIZE 128
// 设置优先级 => 值越大优先级越高 越先调度
#define TASK1_PRIORITY 1
TaskHandle_t task1_handle;

// TASK2任务参数
void task2(void *pvParam);
#define TASK2_STACK_SIZE 128
#define TASK2_PRIORITY 1
TaskHandle_t task2_handle;

// KEY_TASK任务参数
void key_task(void *pvParam);
#define KEY_TASK_STACK_SIZE 128
#define KEY_TASK_PRIORITY 3
TaskHandle_t key_task_handle;

// START_TASK任务参数
void start_task(void *pvParam);
#define START_TASK_STACK_SIZE 128
#define START_TASK_PRIORITY 4
TaskHandle_t start_task_handle;

void FreeRTOS_Start(void)
{
    // 创建任务
    xTaskCreate(start_task, "START_TASK", START_TASK_STACK_SIZE, NULL, START_TASK_PRIORITY, &start_task_handle);
    // 调度器会启动一个空闲任务  没事干就去空闲任务
    vTaskStartScheduler();
}

void start_task(void *pvParam)
{
    xTaskCreate(task1, "TASK1", TASK1_STACK_SIZE, NULL, TASK1_PRIORITY, &task1_handle);
    xTaskCreate(task2, "TASK2", TASK2_STACK_SIZE, NULL, TASK2_PRIORITY, &task2_handle);
    xTaskCreate(key_task, "KEY_TASK", KEY_TASK_STACK_SIZE, NULL, KEY_TASK_PRIORITY, &key_task_handle);
    // vTaskDelete(start_task_handle);
    vTaskDelete(NULL); // 删除自身任务
}
// task1函数实现
void task1(void *pvParam)
{
    while (1)
    {
        Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
        // 阻塞当前任务
        vTaskDelay(400);
        // delay_ms(600);
    }
}

void task2(void *pvParam)
{
    while (1)
    {
        Int_LED_Toggle(LED2_GPIO_Port, LED2_Pin);
        // 阻塞当前任务500ms => 之后进入就绪态排队
        vTaskDelay(500);
        // delay_ms(600);
    }
}

void key_task(void *pvParam)
{
    while (1)
    {
        Key_Type key_task = Int_Key_Scan();
        if (key_task == KEY1)
        {
            Int_LED_Off(LED1_GPIO_Port, LED1_Pin);
            vTaskSuspend(task1_handle);
        }
        else if (key_task == KEY2)
        {
            vTaskResume(task1_handle);
        }
        else if (key_task == KEY3)
        {
            Int_LED_Off(LED2_GPIO_Port, LED2_Pin);
            vTaskSuspend(task2_handle);
        }
        else if (key_task == KEY4)
        {
            vTaskResume(task2_handle);
        }
        vTaskDelay(100);
    }
}

===========================

进/出临界区

taskENTER_CRITICAL

taskEXIT_CRITICAL

https://www.freertos.org/zh-cn-cmn-s/Documentation/02-Kernel/04-API-references/04-RTOS-kernel-control/01-taskENTER_CRITICAL_taskEXIT_CRITICAL

1 configMAX_SYSCALL_INTERRUPT_PRIORITY 设置的优先级的中断, 并启用所有高于此优先级的中断,(启用>11的优先级中断)。

2 抢占式上下文切换仅在中断内发生, 在中断被禁用时不会发生。 因此,调用 taskENTER_CRITICAL() 的任务一定会保持在运行状态, 直到退出临界区,除非任务明确试图阻塞或让出 (任务不应在临界区内部进行该操作)。

3 taskENTER_CRITICAL() 和 taskEXIT_CRITICAL() 的调用采用嵌套结构。因此,只有在每次调用 taskENTER_CRITICAL() 后执行相应的 taskEXIT_CRITICAL() 调用时, 才会退出临界区。

4 临界区必须尽量简短,否则会对中断响应时间产生不利影响。 每次调用 taskENTER_CRITICAL() 时,都必须有对应的 taskEXIT_CRITICAL() 调用。

5 不得从临界区调用 FreeRTOS API 函数。

6 不得从中断服务程序 (ISR) 调用 taskENTER_CRITICAL() 和 taskEXIT_CRITICAL()

使用sysTick中断时 不能使用HAL_delay会冲突,使用自定义的delay

程序---操作系统---硬件

操作系统控制硬件

程序直接控制硬件 IIC SPI IO

使用场景:

原子性

时序不能被打断

中间执行的操作 尽量少

临界区:操作系统内核

优先级小的可以进入临界区

优先级大的不能打断

多次进入临界区 所有任务退出临界区 FreeRTOS才能继续运行

案例

线程安全问题

count++的操作不是原子操作,需要执行4步汇编

解决

进入临界区可以确保是原子操作

方案1

方案2(推荐)

进入临界区代码应该尽量少

count才是公用变量,task1 task2都修改count

临界区实现原理

进入临界区

禁用中断

进入临界区次数

第一次进入临界区:

退出临界区

FreeRTOS能够管理的中断

191左移4位11(中断优先级)

中断代码

操作系统代码

操作系统之外的代码

代码优先级

挂起/恢复调度器

vTaskSuspendAll

xTaskResumeAll

https://www.freertos.org/zh-cn-cmn-s/Documentation/02-Kernel/04-API-references/04-RTOS-kernel-control/05-vTaskSuspendAll

https://www.freertos.org/zh-cn-cmn-s/Documentation/02-Kernel/04-API-references/04-RTOS-kernel-control/06-xTaskResumeAll

挂起调度器(一般使用临界区)

阻止上下文切换 (切换任务)

中断没有关闭,可以执行中断,还是没法保证原子性

临界区禁用了中断

===============

任务相关的实用API

获取任务优先级

链接

uxTaskPriorityGet()

设置任务优先级

链接

vTaskPrioritySet()

获取系统中任务的数量

链接

uxTaskGetNumberOfTasks()

获取当前任务的任务句柄

链接

xTaskGetCurrentTaskHandle()

根据任务名获取该任务的任务句柄

链接

xTaskGetHandle()

获取任务状态

链接

eTaskGetState()

需要在FreeRTOSConfig.h中打开宏定义

eDeleted

删除了任务 没有被回收资源

获取任务栈历史最小剩余空间

链接

uxTaskGetStackHighWaterMark()

获取指定单个的任务信息

链接

vTaskGetInfo()

获取所有任务状态信息

链接

uxTaskGetSystemState()

任务管理器

获取任务的运行时间

vTaskGetRunTimeStats()

链接

配置

1 configGENERATE_RUN_TIME_STATS configUSE_STATS_FORMATTING_FUNCTIONS 和 configSUPPORT_DYNAMIC_ALLOCATION 必须定义为 1

2 配置外设 定时器/计数器

应用程序必须提供 portCONFIGURE_TIMER_FOR_RUN_TIME_STATS():

3 返回定时器的当前计数值

portGET_RUN_TIME_COUNTER_VALUE():

4 计数器的频率应该至少是 滴答计数的 10 倍

滴答定时器周期为1ms 这里将TIM6定时器频率设置为滴答计数器的100倍 即周期为10us

5 计数值使用自己定义的tick_count,在中断函数里面实现计数,不使用定时器的count(最大65535)

一轮计数完成的中断

计数

显示内容

1 任务名称
2 Abs 时间(绝对时间)

指实际执行任务所耗费的总时间,即任务处于 “正在运行”状态的总时间。由用户为其应用程序选择合适的时间基数。

3 % 时间(时间百分比)

实质上,这里显示的是相同的信息,只不过是以占总处理时间的百分比形式显示, 而不是绝对时间。

以“表格”形式获取所有任务的信息

vTaskList()

链接

显示内容

1 任务名称
2 任务状态

在 ASCII 表中,以下字母用于表示任务的状态:

'B' - 已阻塞
'R' - 准备就绪
'D' - 已删除(等待清理)
'S' - 已挂起或已阻塞,没有超时
3 优先级

( unsigned int ) pxTaskStatusArray[ x ].uxCurrentPriority,

4 任务栈历史最小剩余空间

( unsigned int ) pxTaskStatusArray[ x ].usStackHighWaterMark,

5 任务号

( unsigned int ) pxTaskStatusArray[ x ].xTaskNumber );

延时函数

基准时间

vTaskDelay:当前时间作为基准时间

可以保证所有优先级任务都能执行

缺点:最大的优先级 能执行多长时间 会越来越慢

xTaskDelayUntil:自定义基准时间

计算时,不能丢失精度

vTaskDelay

https://www.freertos.org/zh-cn-cmn-s/Documentation/02-Kernel/04-API-references/02-Task-control/01-vTaskDelay

指定延迟时间

xTaskDelayUntil

https://www.freertos.org/zh-cn-cmn-s/Documentation/02-Kernel/04-API-references/02-Task-control/02-vTaskDelayUntil

也可以使用vTaskDelayUntil

将任务延迟到指定时间。此函数可以由周期性任务使用, 来确保恒定的执行频率。

稳定的时间周期,

基准时间+指定延迟时间

每次调用完成都会自动更新基准时间

自定义时间作为基准 延迟500ms

中间只延时了500-50=450

进阶篇

任务之间的交互

1 消息队列

传输数据(无格式要求)

特点

支持阻塞操作 不占用CPU

需要引入queue.h

xQueueCreate()

链接

xQueueSend()

链接

xQueueReceive()

链接

只有一个任务写,多个任务读 ,写优先级 大于 读优先级

发送数据 接收数据 源码分析

#include "FreeRTOS_demo.h"

void delay_ms(uint32_t ms)
{
    uint32_t count = ms * 72000 / 9;
    while (count--)
    {
        __NOP();
    }
}

// 写队列任务
void send_task(void *args);
#define SEND_TASK_STACK_SIZE 128
#define SEND_TASK_PRIO 3
TaskHandle_t send_task_handle;

// 读队列任务
void recv_task(void *args);
#define RECV_TASK_STACK_SIZE 128
#define RECV_TASK_PRIO 3
TaskHandle_t recv_task_handle;

// 打印占用时间任务
void print_task(void *args);
#define PRINT_TASK_STACK_SIZE 128
#define PRINT_TASK_PRIO 3
TaskHandle_t print_task_handle;

// 声明队列
uint8_t queue_buffer[1] = {0};
uint8_t flag = 0;

QueueHandle_t queue;
QueueHandle_t key_queue;
void FreeRTOS_Start(void)
{

    // 创建队列
    queue = xQueueCreate(1, sizeof(uint8_t));

    key_queue = xQueueCreate(1, sizeof(Key_Type));

    xTaskCreate(send_task, "send_task", SEND_TASK_STACK_SIZE, NULL, SEND_TASK_PRIO, &send_task_handle);
    xTaskCreate(recv_task, "recv_task", RECV_TASK_STACK_SIZE, NULL, RECV_TASK_PRIO, &recv_task_handle);
    xTaskCreate(print_task, "print_task", PRINT_TASK_STACK_SIZE, NULL, PRINT_TASK_PRIO, &print_task_handle);
    // 最后一行代码一定是启动调度器
    // 调度器会启动一个空闲任务  没事干就去空闲任务
    vTaskStartScheduler();
}

void send_task(void *args)
{
    while (1)
    {
        Key_Type key = Int_Key_Scan();
        if (key == KEY1)
        {
            //  手动实现消息队列的线程安全 => 只需要进入临界区即可
            // taskENTER_CRITICAL();
            // // 将数据写入到队列中
            // queue_buffer[0] = 1;
            // flag = 1;
            // taskEXIT_CRITICAL();
            // 通过队列发送数据
            uint8_t item = 1;
            xQueueSend(queue, &item, portMAX_DELAY);
        }

        if (key != KEY_NONE)
        {
            // 有按键按下
            // 通过队列发送数据
            xQueueSend(key_queue, &key, portMAX_DELAY);
        }

        vTaskDelay(10);
    }
}

void recv_task(void *args)
{
    while (1)
    {
        // if (flag == 1)
        // {
        //     // 有数据需要读
        //     // 手动实现消息队列的线程安全 => 只需要进入临界区即可
        //     taskENTER_CRITICAL();
        //     // 从队列中读取数据
        //     printf("key1 is pressed\r\n");
        //     flag = 0;
        //     taskEXIT_CRITICAL();
        // }
        // 减少CPU占用  减少扫描的次数 => 但是不及时
        // vTaskDelay(10);

        // 使用队列的接收方法
        Key_Type data;
        xQueueReceive(key_queue, &data, portMAX_DELAY);
        if (data == KEY1)
        {
            printf("key1 is pressed\r\n");
        }
        else if (data == KEY2)
        {
            printf("key2 is pressed\r\n");
        }
        else if (data == KEY3)
        {
            printf("key3 is pressed\r\n");
        }
        else if (data == KEY4)
        {
            printf("key4 is pressed\r\n");
        }
    }
}

uint8_t time_buff[128];
void print_task(void *args)
{
    while (1)
    {
        vTaskGetRunTimeStats((char *)time_buff);
        printf("\n%s\r\n", time_buff);
        vTaskDelay(1000);
    }
}

2 信号量

give释放不会阻塞 0->1

take获取会阻塞 1->0

信号量在队列基础上实现

2.1 二值信号量

需要引入semphr.h
xSemaphoreCreateBinary()

链接

xSemaphoreTake()

链接

相当于调用suspend(null);

xSemaphoreGive()

链接

相当于调用resume(receive_task);

xSemaphoreTakeFromISR()

链接

xSemaphoreGiveFromISR()

链接

案例1

HAL配置

开启串口中断 中断优先级大于11(12)

注意:唤醒接收任务的阻塞 => 不能直接写在中断处理函数中 => 任意中断都会触发

不用信号量接收数据

REC_TASK CPU占用率高

使用信号量实现

带空闲的中断函数

REC_TASK CPU占用率低

代码

注意

xSemaphoreGiveFromISR

#include "FreeRTOS_demo.h"

void delay_ms(uint32_t ms)
{
    uint32_t count = ms * 72000 / 9;
    while (count--)
    {
        __NOP();
    }
}

// 最小CPU占用来实现串口接收数据
void receive_task(void *args);
#define RECEIVE_TASK_STACK_SIZE 128
#define RECEIVE_TASK_PRIORITY 2
TaskHandle_t receive_task_handle;

// 打印任务
void print_task(void *args);
#define PRINT_TASK_STACK_SIZE 128
#define PRINT_TASK_PRIORITY 3
TaskHandle_t print_task_handle;

xSemaphoreHandle sem;
void FreeRTOS_Start(void)
{

    // 创建二值信号量
    sem = xSemaphoreCreateBinary();

    xTaskCreate(receive_task, "receive_task", RECEIVE_TASK_STACK_SIZE, NULL, RECEIVE_TASK_PRIORITY, &receive_task_handle);
    xTaskCreate(print_task, "print_task", PRINT_TASK_STACK_SIZE, NULL, PRINT_TASK_PRIORITY, &print_task_handle);
    // 最后一行代码一定是启动调度器
    // 调度器会启动一个空闲任务  没事干就去空闲任务
    vTaskStartScheduler();
}

uint8_t receive_buff[128];
uint16_t receive_len;
// void receive_task(void *args)
// {
//     while (1)
//     {
//         HAL_UARTEx_ReceiveToIdle(&huart1, receive_buff, 128, &receive_len, 1000);
//         if (receive_len > 0)
//         {
//             // 判断接收到数据
//             // 打印接收到的数据
//             printf("receive data:%s\r\n", receive_buff);
//             receive_len = 0;
//             memset(receive_buff, 0, 128);
//         }
//         // 轮询的方式添加阻塞 会降低CPU占用 但是会造成延迟
//         vTaskDelay(10);
//     }
// }

void receive_task(void *args)
{

    while (1)
    {
        // 开启串口的中断
        HAL_UARTEx_ReceiveToIdle_IT(&huart1, receive_buff, 128);
        // 什么时候能获取到信号量  表示什么时候接收到了数据
        xSemaphoreTake(sem, portMAX_DELAY); // 相当于调用suspend(null);
        // 接收到数据
        printf("%s", receive_buff);
        // 清空缓存
        memset(receive_buff, 0, 128);
    }
}

uint8_t time_buff[128];
void print_task(void *args)
{
    while (1)
    {
        vTaskGetRunTimeStats((char *)time_buff);
        printf("\n%s\r\n", time_buff);
        vTaskDelay(1000);
    }
}

// 中断回调函数
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    if (huart->Instance == USART1)
    {
        receive_len = Size;
        // 释放信号量 => resume(receive_task);
        xSemaphoreGiveFromISR(sem, NULL);
    }
}

案例2

由于HAL进行了优化

USART在接收数据过程中,不会被打断,测试效果不明显,因此需要使用寄存器方式实现

使用寄存器方式实现

int fputc(int ch, FILE *f)
{
    /* 1. 等待发送寄存器为空 */
    while ((USART1->SR & USART_SR_TXE) == 0);
    /* 2. 数据写出到数据寄存器 */
    USART1->DR = ch;
    return ch;
}

使用信号量实现

问题

没打印数据

原因:

信号量是在“空”状态下创建的,这意味着必须先用 xSemaphoreGive() 函数给出信号量, 然后才能使用 xSemaphoreTake() 函数来获取(获得)该信号量。

默认值是0 如果使用二值信号量表示资源 => 需要先释放一次

代码
#include "FreeRTOS_demo.h"

void delay_ms(uint32_t ms)
{
    uint32_t count = ms * 72000 / 9;
    while (count--)
    {
        __NOP();
    }
}

// 打印任务1
void printf1_task(void *pvParameters);
#define PRINTF1_TASK_STACK_SIZE 128
#define PRINTF1_TASK_PRIORITY 2
TaskHandle_t printf1_task_handle;

// 打印任务2
void printf2_task(void *pvParameters);
#define PRINTF2_TASK_STACK_SIZE 128
#define PRINTF2_TASK_PRIORITY 2
TaskHandle_t printf2_task_handle;

SemaphoreHandle_t binary_sem;

void FreeRTOS_Start(void)
{
    // 使用二值信号量实现互斥效果  避免资源争抢
    binary_sem = xSemaphoreCreateBinary();

    // 默认值是0 如果使用二值信号量表示资源 => 需要先释放一次
    xSemaphoreGive(binary_sem);

    // 创建任务
    xTaskCreate(printf1_task, "printf1_task", PRINTF1_TASK_STACK_SIZE, NULL, PRINTF1_TASK_PRIORITY, &printf1_task_handle);
    xTaskCreate(printf2_task, "printf2_task", PRINTF2_TASK_STACK_SIZE, NULL, PRINTF2_TASK_PRIORITY, &printf2_task_handle);

    // 最后一行代码一定是启动调度器
    // 调度器会启动一个空闲任务  没事干就去空闲任务
    vTaskStartScheduler();
}

void printf1_task(void *pvParameters)
{
    while (1)
    {
        // 需要使用资源的时候 先调用获取信号量
        xSemaphoreTake(binary_sem, portMAX_DELAY);
        printf("printf1_task===============\r\n");
        // 使用完信号量 释放信号量
        xSemaphoreGive(binary_sem);
    }
}

void printf2_task(void *pvParameters)
{
    while (1)
    {
        // 需要使用资源的时候 先调用获取信号量
        xSemaphoreTake(binary_sem, portMAX_DELAY);
        printf("printf2_task---------------\r\n");
        // 使用完信号量 释放信号量
        xSemaphoreGive(binary_sem);
    }
}

2.2 互斥信号量

解决优先级翻转

优先级继承机制

优先级翻转

厂长 厂长儿子 主管

优先级 高 低 中

厂长 厂长儿子访问同一资源:如串口

主管访问另一个资源:如LED

厂长儿子被主管打断,由于厂长在等待厂长儿子释放资源, 相当于打断了厂长

优先级继承机制:厂长儿子继承了厂长的优先级,主管就不能打断厂长儿子的任务执行

xSemaphoreCreateMutex()

链接

xSemaphoreTake()

链接

xSemaphoreGive()

链接

代码
使用二值信号量

优先级翻转

LED任务2会打断打印任务1

间接打断了打印任务3

执行顺序

打印任务1->被LED任务2打断->执行LED任务2->打印任务1->打印任务3

#include "FreeRTOS_demo.h"

void delay_ms(uint32_t ms)
{
    uint32_t count = ms * 72000 / 9;
    while (count--)
    {
        __NOP();
    }
}

// 打印任务1
void printf_task1(void *pvParameters);
#define PRINTF_TASK1_STK_SIZE 128
#define PRINTF_TASK1_PRIO 1
TaskHandle_t printf_task1_handle;

// LED任务2
void led_task2(void *pvParameters);
#define LED_TASK2_STK_SIZE 128
#define LED_TASK2_PRIO 2
TaskHandle_t led_task2_handle;

// 打印任务3
void printf_task3(void *pvParameters);
#define PRINTF_TASK3_STK_SIZE 128
#define PRINTF_TASK3_PRIO 3
TaskHandle_t printf_task3_handle;

SemaphoreHandle_t binary_sem;
// SemaphoreHandle_t mux_sem;
void FreeRTOS_Start(void)
{
    // 使用二值信号量实现互斥效果  避免资源争抢
    binary_sem = xSemaphoreCreateBinary();

    // 解决优先级翻转的问题 => 互斥信号量 => 本质还是二值信号量 具有优先级继承的功能
    // mux_sem = xSemaphoreCreateMutex();

    // 默认值是0 如果使用二值信号量表示资源 => 需要先释放一次
    xSemaphoreGive(binary_sem);
    // xSemaphoreGive(mux_sem);

    // 创建任务
    xTaskCreate(printf_task1, "printf_task1", PRINTF_TASK1_STK_SIZE, NULL, PRINTF_TASK1_PRIO, &printf_task1_handle);
    xTaskCreate(led_task2, "led_task2", LED_TASK2_STK_SIZE, NULL, LED_TASK2_PRIO, &led_task2_handle);
    xTaskCreate(printf_task3, "printf_task3", PRINTF_TASK3_STK_SIZE, NULL, PRINTF_TASK3_PRIO, &printf_task3_handle);

    // 最后一行代码一定是启动调度器
    // 调度器会启动一个空闲任务  没事干就去空闲任务
    vTaskStartScheduler();
}

void printf_task1(void *pvParameters)
{
    vTaskDelay(10);
    // 获取信号量
    xSemaphoreTake(binary_sem, portMAX_DELAY);
    // xSemaphoreTake(mux_sem, portMAX_DELAY);
    // 使用串口打印任务
    for (uint8_t i = 0; i < 10; i++)
    {
        printf("printf_task1=========================\n");
    }
    // 释放信号量
    xSemaphoreGive(binary_sem);
    // xSemaphoreGive(mux_sem);
    // 删除任务
    vTaskDelete(NULL);
}

void led_task2(void *pvParameters)
{
    vTaskDelay(20);
    for (uint8_t i = 0; i < 10; i++)
    {
        Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
        delay_ms(500);
    }
    // 删除任务
    vTaskDelete(NULL);
}

void printf_task3(void *pvParameters)
{

    vTaskDelay(50);

    // 获取信号量
    xSemaphoreTake(binary_sem, portMAX_DELAY);
    // xSemaphoreTake(mux_sem, portMAX_DELAY);
    // 使用串口打印任务
    for (uint8_t i = 0; i < 10; i++)
    {
        printf("printf_task3+++++++++++++++++++++++\n");
    }
    // 释放信号量
    xSemaphoreGive(binary_sem);
    // xSemaphoreGive(mux_sem);
    // 删除任务
    vTaskDelete(NULL);
}
使用互斥信号量
binary_sem = xSemaphoreCreateBinary();
改为
binary_sem = xSemaphoreCreateMutex();

优先级继承机制

打印任务1继承了打印任务3的优先级

LED任务2无法打断打印任务1

执行顺序

打印任务1->打印任务3->LED任务2

2.3 计数型信号量

值可以释放多次

take -- 不能<0 会阻塞

give ++

xSemaphoreCreateCounting()

链接

xSemaphoreTake()

链接

xSemaphoreGive()

链接

uxSemaphoreGetCount()

链接

实验:

按钮释放计数型信号量,当计数型信号量的值是5时,点亮LED1

代码
#include "FreeRTOS_demo.h"

void delay_ms(uint32_t ms)
{
    uint32_t count = ms * 72000 / 9;
    while (count--)
    {
        __NOP();
    }
}

// 按键任务 => 触发计数信号量释放
void task1(void *pvParameters);
#define TASK1_STK_SIZE 128
#define TASK1_PRIO 1
TaskHandle_t task1_handle;

// 等待计数的值到5 => 触发计数信号量释放
void task2(void *pvParameters);
#define TASK2_STK_SIZE 128
#define TASK2_PRIO 2
TaskHandle_t task2_handle;

SemaphoreHandle_t count_sem;

void FreeRTOS_Start(void)
{
    // 创建计数型信号量
    count_sem = xSemaphoreCreateCounting(6, 0);

    // 启动任务
    xTaskCreate(task1, "task1", TASK1_STK_SIZE, NULL, TASK1_PRIO, &task1_handle);
    xTaskCreate(task2, "task2", TASK2_STK_SIZE, NULL, TASK2_PRIO, &task2_handle);
    // 最后一行代码一定是启动调度器
    // 调度器会启动一个空闲任务  没事干就去空闲任务
    vTaskStartScheduler();
}

// 实现任务1
void task1(void *pvParameters)
{
    while (1)
    {
        Key_Type key = Int_Key_Scan();
        if (key == KEY1)
        {
            // 信号量释放 count++
            xSemaphoreGive(count_sem);
        }
    }
}

void task2(void *pvParameters)
{
    while (1)
    {
        // 获取计数型信号量的值
        uint8_t count = uxSemaphoreGetCount(count_sem);
        printf("count = %d\r\n", count);

        if (count == 5)
        {
            Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
            for (uint8_t i = 0; i < 5; i++)
            {
                // 获取信号量  count--
                xSemaphoreTake(count_sem, portMAX_DELAY);
            }
        }
        vTaskDelay(100);
    }
}

3 队列集

同时处理多个队列

xQueueCreateSet()

链接

xQueueAddToSet()

链接

xQueueSelectFromSet()

链接

代码

字节数组和字符串指针

xQueue2 = xQueueCreate(3, sizeof(uint8_t *));
uint8_t uart_buf[100];
//串口接收数据
HAL_UARTEx_ReceiveToIdle_IT(&huart1, (uint8_t *)uart_buf, 1000);
//队列发送消息
uint8_t *uart_data = uart_buf;
xQueueSend(xQueue2, &uart_data, portMAX_DELAY);


// 获取队列信息
uint8_t *uart_data;
xQueueReceive(xQueue2, &uart_data, portMAX_DELAY);

不使用队列集
#include "FreeRTOS_demo.h"

void delay_ms(uint32_t ms)
{
    uint32_t count = ms * 72000 / 9;
    while (count--)
    {
        __NOP();
    }
}

// 任务1: 接收按键信息 => 翻转LED灯
void task1(void *pvParameters);
#define TASK1_STACK_SIZE 128
#define TASK1_PRIORITY 2
TaskHandle_t task1_handle;

// 任务2: 接收串口的消息 => 根据接收的消息翻转LED灯
void task2(void *pvParameters);
#define TASK2_STACK_SIZE 128
#define TASK2_PRIORITY 2
TaskHandle_t task2_handle;

// 不使用队列集
// 两个单独的接收任务
void key_receive(void *pvParameters);
#define KEY_STACK_SIZE 128
#define KEY_PRIORITY 2
TaskHandle_t key_handle;

void uart_receive(void *pvParameters);
#define UART_STACK_SIZE 128
#define UART_PRIORITY 2
TaskHandle_t uart_handle;

// 队列保存信息
// 写入的按键信息 KeyType
QueueHandle_t xQueue1;
// 读取的串口信息 uint8_t *
QueueHandle_t xQueue2;

// 二值信号量实现串口最小资源占用
SemaphoreHandle_t xSemaphore;
void FreeRTOS_Start(void)
{
    // 创建二值信号量
    xSemaphore = xSemaphoreCreateBinary();

    // 创建两个队列
    xQueue1 = xQueueCreate(3, sizeof(Key_Type));
    xQueue2 = xQueueCreate(3, sizeof(uint8_t *));

    // 创建任务
    xTaskCreate(task1, "task1", TASK1_STACK_SIZE, NULL, TASK1_PRIORITY, &task1_handle);
    xTaskCreate(task2, "task2", TASK2_STACK_SIZE, NULL, TASK2_PRIORITY, &task2_handle);

    xTaskCreate(key_receive, "key", KEY_STACK_SIZE, NULL, KEY_PRIORITY, &key_handle);
    xTaskCreate(uart_receive, "uart", UART_STACK_SIZE, NULL, UART_PRIORITY, &uart_handle);

    // 最后一行代码一定是启动调度器
    // 调度器会启动一个空闲任务  没事干就去空闲任务
    vTaskStartScheduler();
}

// 任务1: 接收按键信息 => 翻转LED灯
void task1(void *pvParameters)
{
    while (1)
    {
        Key_Type key;
        key = Int_Key_Scan();
        if (key != KEY_NONE)
        {
            xQueueSend(xQueue1, &key, portMAX_DELAY);
        }
    }
}

uint8_t uart_buf[100];

// 任务2: 接收串口的消息 => 根据接收的消息翻转LED灯
void task2(void *pvParameters)
{
    while (1)
    {
        HAL_UARTEx_ReceiveToIdle_IT(&huart1, (uint8_t *)uart_buf, 1000);
        // task任务等待信号量释放表示串口接收到数据
        xSemaphoreTake(xSemaphore, portMAX_DELAY);

        // 已经获取到数据
        // 串口接收到的数据
        uint8_t *uart_data = uart_buf;
        // 串口接收到的数据长度
        xQueueSend(xQueue2, &uart_data, portMAX_DELAY);
    }
}

// 任务3: 接收按键信息 => 翻转LED灯
void key_receive(void *pvParameters)
{
    while (1)
    {
        // 获取队列信息
        Key_Type key;
        xQueueReceive(xQueue1, &key, portMAX_DELAY);
        if (key == KEY1)
        {
            // 翻转LED灯
            Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
        }
        else if (key == KEY2)
        {
            // 翻转LED灯
            Int_LED_Toggle(LED2_GPIO_Port, LED2_Pin);
        }
        else if (key == KEY3)
        {
            // 翻转LED灯
            Int_LED_Toggle(LED3_GPIO_Port, LED3_Pin);
        }
        else if (key == KEY4)
        {
            // 翻转LED灯
            Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
            Int_LED_Toggle(LED2_GPIO_Port, LED2_Pin);
            Int_LED_Toggle(LED3_GPIO_Port, LED3_Pin);
        }
    }
}

// 任务4: 读取串口队列的消息 => 根据接收的消息翻转LED灯
void uart_receive(void *pvParameters)
{
    while (1)
    {
        // 获取队列信息
        uint8_t *uart_data;
        xQueueReceive(xQueue2, &uart_data, portMAX_DELAY);

        // 根据字符串内容  翻转LED灯
        if (strstr((char *)uart_data, "LED1") != NULL)
        {
            Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
        }
        else if (strstr((char *)uart_data, "LED2") != NULL)
        {
            Int_LED_Toggle(LED2_GPIO_Port, LED2_Pin);
        }
        else if (strstr((char *)uart_data, "LED3") != NULL)
        {
            Int_LED_Toggle(LED3_GPIO_Port, LED3_Pin);
        }
        else if (strstr((char *)uart_data, "LEDALL") != NULL)
        {
            Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
            Int_LED_Toggle(LED2_GPIO_Port, LED2_Pin);
            Int_LED_Toggle(LED3_GPIO_Port, LED3_Pin);
        }
    }
}




// 中断处理函数释放二值信号量
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    if (huart->Instance == USART1)
    {
        // 释放信号量
        xSemaphoreGiveFromISR(xSemaphore, NULL);
    }
}

使用队列集

相当于queue_set_receive方法合并了key_receive和uart_receive方法

#include "FreeRTOS_demo.h"

void delay_ms(uint32_t ms)
{
    uint32_t count = ms * 72000 / 9;
    while (count--)
    {
        __NOP();
    }
}

// 任务1: 接收按键信息 => 翻转LED灯
void task1(void *pvParameters);
#define TASK1_STACK_SIZE 128
#define TASK1_PRIORITY 2
TaskHandle_t task1_handle;

// 任务2: 接收串口的消息 => 根据接收的消息翻转LED灯
void task2(void *pvParameters);
#define TASK2_STACK_SIZE 128
#define TASK2_PRIORITY 2
TaskHandle_t task2_handle;

// 不使用队列集
// 两个单独的接收任务
void key_receive(void *pvParameters);
#define KEY_STACK_SIZE 128
#define KEY_PRIORITY 2
TaskHandle_t key_handle;

void uart_receive(void *pvParameters);
#define UART_STACK_SIZE 128
#define UART_PRIORITY 2
TaskHandle_t uart_handle;

// 队列集接收任务
void queue_set_receive(void *pvParameters);
#define QUEUE_SET_STACK_SIZE 128
#define QUEUE_SET_PRIORITY 2
TaskHandle_t queue_set_handle;

// 打印任务
void print_task(void *pvParameters);
#define PRINT_STACK_SIZE 128
#define PRINT_PRIORITY 2
TaskHandle_t print_handle;

// 队列保存信息
// 写入的按键信息 KeyType
QueueHandle_t xQueue1;
// 读取的串口信息 uint8_t *
QueueHandle_t xQueue2;
// 队列集句柄
QueueSetHandle_t xQueueSet;
// 二值信号量实现串口最小资源占用
SemaphoreHandle_t xSemaphore;
void FreeRTOS_Start(void)
{
    // 创建二值信号量
    xSemaphore = xSemaphoreCreateBinary();

    // 创建两个队列
    xQueue1 = xQueueCreate(3, sizeof(Key_Type));
    xQueue2 = xQueueCreate(3, sizeof(uint8_t *));

    // 创建队列集
    xQueueSet = xQueueCreateSet(2);

    // 将队列添加到队列集中
    xQueueAddToSet(xQueue1, xQueueSet);
    xQueueAddToSet(xQueue2, xQueueSet);

    // 创建任务
    xTaskCreate(task1, "task1", TASK1_STACK_SIZE, NULL, TASK1_PRIORITY, &task1_handle);
    xTaskCreate(task2, "task2", TASK2_STACK_SIZE, NULL, TASK2_PRIORITY, &task2_handle);

    // xTaskCreate(key_receive, "key", KEY_STACK_SIZE, NULL, KEY_PRIORITY, &key_handle);
    // xTaskCreate(uart_receive, "uart", UART_STACK_SIZE, NULL, UART_PRIORITY, &uart_handle);

    // 创建队列集接收任务
    xTaskCreate(queue_set_receive, "queue_set", QUEUE_SET_STACK_SIZE, NULL, QUEUE_SET_PRIORITY, &queue_set_handle);

    // 创建打印任务
    xTaskCreate(print_task, "print", PRINT_STACK_SIZE, NULL, PRINT_PRIORITY, &print_handle);
    // 最后一行代码一定是启动调度器
    // 调度器会启动一个空闲任务  没事干就去空闲任务
    vTaskStartScheduler();
}

// 任务1: 接收按键信息 => 翻转LED灯
void task1(void *pvParameters)
{
    while (1)
    {
        Key_Type key;
        key = Int_Key_Scan();
        if (key != KEY_NONE)
        {
            xQueueSend(xQueue1, &key, portMAX_DELAY);
        }
    }
}

uint8_t uart_buf[100];

// 任务2: 接收串口的消息 => 根据接收的消息翻转LED灯
void task2(void *pvParameters)
{
    while (1)
    {
        HAL_UARTEx_ReceiveToIdle_IT(&huart1, (uint8_t *)uart_buf, 1000);
        // task任务等待信号量释放表示串口接收到数据
        xSemaphoreTake(xSemaphore, portMAX_DELAY);

        // 已经获取到数据
        // 串口接收到的数据
        uint8_t *uart_data = uart_buf;
        // 串口接收到的数据长度
        xQueueSend(xQueue2, &uart_data, portMAX_DELAY);
    }
}

// 任务3: 接收按键信息 => 翻转LED灯
void key_receive(void *pvParameters)
{
    while (1)
    {
        // 获取队列信息
        Key_Type key;
        xQueueReceive(xQueue1, &key, portMAX_DELAY);
        if (key == KEY1)
        {
            // 翻转LED灯
            Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
        }
        else if (key == KEY2)
        {
            // 翻转LED灯
            Int_LED_Toggle(LED2_GPIO_Port, LED2_Pin);
        }
        else if (key == KEY3)
        {
            // 翻转LED灯
            Int_LED_Toggle(LED3_GPIO_Port, LED3_Pin);
        }
        else if (key == KEY4)
        {
            // 翻转LED灯
            Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
            Int_LED_Toggle(LED2_GPIO_Port, LED2_Pin);
            Int_LED_Toggle(LED3_GPIO_Port, LED3_Pin);
        }
    }
}

// 任务4: 读取串口队列的消息 => 根据接收的消息翻转LED灯
void uart_receive(void *pvParameters)
{
    while (1)
    {
        // 获取队列信息
        uint8_t *uart_data;
        xQueueReceive(xQueue2, &uart_data, portMAX_DELAY);

        // 根据字符串内容  翻转LED灯
        if (strstr((char *)uart_data, "LED1") != NULL)
        {
            Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
        }
        else if (strstr((char *)uart_data, "LED2") != NULL)
        {
            Int_LED_Toggle(LED2_GPIO_Port, LED2_Pin);
        }
        else if (strstr((char *)uart_data, "LED3") != NULL)
        {
            Int_LED_Toggle(LED3_GPIO_Port, LED3_Pin);
        }
        else if (strstr((char *)uart_data, "LEDALL") != NULL)
        {
            Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
            Int_LED_Toggle(LED2_GPIO_Port, LED2_Pin);
            Int_LED_Toggle(LED3_GPIO_Port, LED3_Pin);
        }
    }
}

void queue_set_receive(void *pvParameters)
{
    while (1)
    {
        // 获取队列集接收信息
        QueueSetMemberHandle_t mem_handle = xQueueSelectFromSet(xQueueSet, portMAX_DELAY);
        if (mem_handle == xQueue1)
        {
            // 按键任务接收到消息
            // 获取队列信息
            Key_Type key;
            xQueueReceive(xQueue1, &key, portMAX_DELAY);
            if (key == KEY1)
            {
                // 翻转LED灯
                Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
            }
            else if (key == KEY2)
            {
                // 翻转LED灯
                Int_LED_Toggle(LED2_GPIO_Port, LED2_Pin);
            }
            else if (key == KEY3)
            {
                // 翻转LED灯
                Int_LED_Toggle(LED3_GPIO_Port, LED3_Pin);
            }
            else if (key == KEY4)
            {
                // 翻转LED灯
                Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
                Int_LED_Toggle(LED2_GPIO_Port, LED2_Pin);
                Int_LED_Toggle(LED3_GPIO_Port, LED3_Pin);
            }
        }
        else if (mem_handle == xQueue2)
        {
            // 获取队列信息
            uint8_t *uart_data;
            xQueueReceive(xQueue2, &uart_data, portMAX_DELAY);

            // 根据字符串内容  翻转LED灯
            if (strstr((char *)uart_data, "LED1") != NULL)
            {
                Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
            }
            else if (strstr((char *)uart_data, "LED2") != NULL)
            {
                Int_LED_Toggle(LED2_GPIO_Port, LED2_Pin);
            }
            else if (strstr((char *)uart_data, "LED3") != NULL)
            {
                Int_LED_Toggle(LED3_GPIO_Port, LED3_Pin);
            }
            else if (strstr((char *)uart_data, "LEDALL") != NULL)
            {
                Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
                Int_LED_Toggle(LED2_GPIO_Port, LED2_Pin);
                Int_LED_Toggle(LED3_GPIO_Port, LED3_Pin);
            }
        }
    }
}

uint8_t runtime_buff[128];
// 打印任务
void print_task(void *pvParameters)
{
    while (1)
    {
        vTaskGetRunTimeStats((char *)runtime_buff);
        printf("%s\r\n", runtime_buff);
        vTaskDelay(1000);
    }
    
}

// 中断处理函数释放二值信号量
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    if (huart->Instance == USART1)
    {
        // 释放信号量
        xSemaphoreGiveFromISR(xSemaphore, NULL);
    }
}



4 事件标志组

管理多个条件 最多24个

多个事件的组合(多个单独的信号量)

事件之间没有先后顺序

引入event_group.h

xEventGroupCreate()

链接

xEventGroupSetBits()

链接

xEventGroupWaitBits()

链接

xEventGroupClearBits()

链接

代码

#include "FreeRTOS_demo.h"

void delay_ms(uint32_t ms)
{
    uint32_t count = ms * 72000 / 9;
    while (count--)
    {
        __NOP();
    }
}

// 按键任务
void key_task(void *pvParameters);
#define KEY_TASK_STACK_SIZE 128
#define KEY_TASK_PRIORITY 2
TaskHandle_t key_task_handle;

// led闪烁任务
void led_task(void *pvParameters);
#define LED_TASK_STACK_SIZE 128
#define LED_TASK_PRIORITY 3
TaskHandle_t led_task_handle;

EventGroupHandle_t event_group;

void FreeRTOS_Start(void)
{

    // 创建事件标志组
    event_group = xEventGroupCreate();

    // 创建任务
    xTaskCreate(key_task, "key_task", KEY_TASK_STACK_SIZE, NULL, KEY_TASK_PRIORITY, &key_task_handle);
    xTaskCreate(led_task, "led_task", LED_TASK_STACK_SIZE, NULL, LED_TASK_PRIORITY, &led_task_handle);
    // 最后一行代码一定是启动调度器
    // 调度器会启动一个空闲任务  没事干就去空闲任务
    vTaskStartScheduler();
}
uint8_t key_value = 0;
void key_task(void *pvParameters)
{
    // 按下任意一个按键都不会触发LED闪烁 => 必须4个按键全部按一遍才行
    while (1)
    {
        // Key_Type key = Int_Key_Scan();
        // if (key == KEY1)
        // {
        //     key_value |= 1;
        // }
        // else if (key == KEY2)
        // {
        //     key_value |= 2;
        // }
        // else if (key == KEY3)
        // {
        //     key_value |= 4;
        // }
        // else if (key == KEY4)
        // {
        //     key_value |= 8;
        // }

        Key_Type key = Int_Key_Scan();

        // 第二个参数表示需要 或的值
        xEventGroupSetBits(event_group, 0x01 << ((uint8_t)key));
    }
}

void led_task(void *pvParameters)
{
    while (1)
    {
        // if (key_value == 15)
        // {
        //     // 满足最终的条件
        //     for (uint8_t i = 0; i < 4; i++)
        //     {
        //         Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
        //         delay_ms(500);
        //     }
        //     key_value = 0;
        // }

        // 等待事件标志组完成条件
        xEventGroupWaitBits(event_group,15,pdTRUE,pdTRUE,portMAX_DELAY);

        for (uint8_t i = 0; i < 4; i++)
        {
            Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
            // 延时500ms => 当前任务运行500ms => 两个任务的时间片轮询 => 1s
            delay_ms(500);
        }

        // 如果需要手动清0 => clear_bits
        xEventGroupClearBits(event_group,15);
    }
}

高级篇

直接任务通知

4种任务同步方式整合

state 二值信号量

value 事件标志组

队列集(带Indexed) 不能完全代替队列集,数组中任务单独判断,不会影响数组中其他任务

一次传4个字节(地址),可以传地址(没法实现线程安全)

xTaskNotifyGive()

链接

ulTaskNotifyTake()

链接

xTaskNotify()

链接

xTaskNotifyWait()

链接

代码

案例1
#include "FreeRTOS_demo.h"

void delay_ms(uint32_t ms)
{
    uint32_t count = ms * 72000 / 9;
    while (count--)
    {
        __NOP();
    }
}

// 最小CPU占用来实现串口接收数据
void receive_task(void *args);
#define RECEIVE_TASK_STACK_SIZE 128
#define RECEIVE_TASK_PRIORITY 2
TaskHandle_t receive_task_handle;

// 打印任务
void print_task(void *args);
#define PRINT_TASK_STACK_SIZE 128
#define PRINT_TASK_PRIORITY 3
TaskHandle_t print_task_handle;


void FreeRTOS_Start(void)
{

    xTaskCreate(receive_task, "receive_task", RECEIVE_TASK_STACK_SIZE, NULL, RECEIVE_TASK_PRIORITY, &receive_task_handle);
    xTaskCreate(print_task, "print_task", PRINT_TASK_STACK_SIZE, NULL, PRINT_TASK_PRIORITY, &print_task_handle);
    // 最后一行代码一定是启动调度器
    // 调度器会启动一个空闲任务  没事干就去空闲任务
    vTaskStartScheduler();
}

uint8_t receive_buff[128];
uint16_t receive_len;

void receive_task(void *args)
{

    while (1)
    {
        // 开启串口的中断
        HAL_UARTEx_ReceiveToIdle_IT(&huart1, receive_buff, 128);
        
        // 等待任务通知 => 永远是使用阻塞任务的通知组
        // 1. true => 表示调用之后清0
        // 2. false => 表示调用之后减1
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);

        // 接收到数据
        printf("%s", receive_buff);
        // 清空缓存
        memset(receive_buff, 0, 128);
    }
}

uint8_t time_buff[128];
void print_task(void *args)
{
    while (1)
    {
        vTaskGetRunTimeStats((char *)time_buff);
        printf("\n%s\r\n", time_buff);
        vTaskDelay(1000);
    }
}

// 中断回调函数
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    if (huart->Instance == USART1)
    {
        receive_len = Size;
        // 发送任务通知receive_task
        vTaskNotifyGiveFromISR(receive_task_handle, NULL);
    }
}

案例2
#include "FreeRTOS_demo.h"

void delay_ms(uint32_t ms)
{
    uint32_t count = ms * 72000 / 9;
    while (count--)
    {
        __NOP();
    }
}

// 按键任务
void key_task(void *pvParameters);
#define KEY_TASK_STACK_SIZE 128
#define KEY_TASK_PRIORITY 2
TaskHandle_t key_task_handle;

// led闪烁任务
void led_task(void *pvParameters);
#define LED_TASK_STACK_SIZE 128
#define LED_TASK_PRIORITY 2
TaskHandle_t led_task_handle;

void FreeRTOS_Start(void)
{

    // 创建任务
    xTaskCreate(key_task, "key_task", KEY_TASK_STACK_SIZE, NULL, KEY_TASK_PRIORITY, &key_task_handle);
    xTaskCreate(led_task, "led_task", LED_TASK_STACK_SIZE, NULL, LED_TASK_PRIORITY, &led_task_handle);

    // 最后一行代码一定是启动调度器
    // 调度器会启动一个空闲任务  没事干就去空闲任务
    vTaskStartScheduler();
}

void key_task(void *pvParameters)
{

    // 按下任意一个按键都不会触发LED闪烁 => 必须4个按键全部按一遍才行
    while (1)
    {
        // 根据按下的按键不同  =>  发送不同位的按键通知
        Key_Type key = Int_Key_Scan();
        // 通常使用setBits设置对应位表示事件标志组  或者 eSetValueWithOverwrite可以传递一个32位数据
        if (key != KEY_NONE)
        {
            xTaskNotify(led_task_handle, 1 << ((uint8_t)key), eSetBits);
        }
    }
}

void led_task(void *pvParameters)
{
    uint32_t notify_value;
    ulTaskNotifyValueClear(led_task_handle, 0xffff);
    while (1)
    {
        // 每次接收到通知  都会退出阻塞
        xTaskNotifyWait(0, 0, &notify_value, portMAX_DELAY);
        printf("notify_value = %d\r\n", notify_value);
        if (notify_value == 15)
        {
            // 满足条件
            for (uint8_t i = 0; i < 4; i++)
            {
                Int_LED_Toggle(LED1_GPIO_Port, LED1_Pin);
                delay_ms(500);
            }
            ulTaskNotifyValueClear(led_task_handle, 15);
        }
    }
}

软件定时器

依赖系统滴答定时器SysTick

xTimerCreate()

链接

xTimerStart()

链接

xTimerStop()

链接

xTimerChangePeriod()

链接

低功耗模式

configUSE_TICKLESS_IDLE=1

低功耗使能

钩子函数:

在系统进入低功耗模式前的函数 configPRE_SLEEP_PROCESSING(x)

系统退出低功耗模式后执行的函数 configPOST_SLEEP_PROCESSING(x)

最多睡眠233ms 2^24/72000

内存管理

链接

xPortGetFreeHeapSize()

当前剩余的heap资源

xPortGetMinimumEverFreeHeapSize()

历史剩余的最小heap资源值

heap_5

外接flash内存

最大堆空间 17*1024=17,408

tcb 120字节

总结

sysTick

pendsv

svc

上下文 保存在TCB块

创建任务

进/出临界区

执行时间尽量短

SysTick中断被禁用

挂起/恢复调度器

SysTick中断正常使用

延时函数

vTaskDelay

xTaskDelayUntil 优先级高

代码执行时间大于 xTaskDelayUntil 时间,会影响xTaskDelayUntil 时间

printf费时

调度 时间

任务之间交互

消息队列

直接任务通知

state信号量

value队列传递数据

软件定时器(硬件定时器不够用)

低功耗模式

空闲

空闲时间>2ms

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐