FreeRTOS学习(三十九)——最终总结

从最开始创建一个LED任务,到现在理解任务调度、队列、信号量、任务通知、内存管理以及FreeRTOS工程架构,这个系列已经走到了最后一篇。

刚开始学习FreeRTOS时,我们面对的可能只是:

复制代码
xTaskCreate();

然后产生一个疑问:

复制代码
为什么写几个while(1),
STM32却能让它们一起运行?

现在再回头看,FreeRTOS其实并没有那么神秘。

它做的核心事情可以概括成:

复制代码
把程序拆成多个任务
        ↓
根据优先级进行调度
        ↓
任务之间传递数据和同步
        ↓
合理管理CPU和内存资源

本节不再增加新的知识点,而是把整个系列重新串起来,形成一套完整的FreeRTOS知识框架。


一、FreeRTOS最核心的东西还是任务

我们最开始学习的是:

复制代码
xTaskCreate();

例如:

复制代码
xTaskCreate(
    LEDTask,
    "LED",
    128,
    NULL,
    2,
    &LEDTaskHandle
);

创建以后:

复制代码
LEDTask

就成为FreeRTOS管理的一个任务。

任务通常写成:

复制代码
void LEDTask(void *pvParameters)
{
    while(1)
    {
        GPIO_ToggleBits(
            GPIOF,
            GPIO_Pin_9
        );

        vTaskDelay(
            pdMS_TO_TICKS(500)
        );
    }
}

这里最重要的并不是:

复制代码
while(1)

而是:

复制代码
vTaskDelay();

它让当前任务:

复制代码
Running
   ↓
Blocked

然后CPU可以运行其他任务。

所以学习到最后应该建立一个很重要的习惯:

写任务的时候,一定要考虑这个任务什么时候阻塞。

二、任务调度其实就是CPU在不断切换

STM32F407只有一个Cortex-M4内核。

因此:

复制代码
Task1

Task2

Task3

并不是真的同时执行。

实际过程更接近:

复制代码
Task1运行
    ↓
保存Task1现场
    ↓
Task2运行
    ↓
保存Task2现场
    ↓
Task3运行

FreeRTOS根据:

复制代码
任务优先级

任务状态

系统Tick

决定:

复制代码
下一刻CPU应该交给谁。

而STM32F407上的:

复制代码
SysTick

主要提供系统时间节拍。

复制代码
PendSV

主要负责上下文切换。

复制代码
SVC

参与启动第一个任务。

这就是我们整个任务调度体系最底层的一条主线。

三、任务之间需要传数据就想到队列

例如:

复制代码
SensorTask
采集数据
    ↓
   Queue
    ↓
UartTask
发送数据

核心函数:

复制代码
xQueueSend();

和:

复制代码
xQueueReceive();

例如:

复制代码
xQueueSend(
    SensorQueue,
    &Data,
    portMAX_DELAY
);

消费者:

复制代码
xQueueReceive(
    SensorQueue,
    &Data,
    portMAX_DELAY
);

队列不仅能够:

复制代码
传递数据

还能够:

复制代码
暂时保存数据。

所以以后遇到:

复制代码
一个任务产生数据

另一个任务处理数据

首先就可以考虑:

复制代码
Queue。

四、任务之间需要通知就考虑同步机制

不是所有情况都需要传递一大块数据。

有时候只是:

复制代码
任务A完成了

↓

告诉任务B可以开始了

这种情况下可以考虑:

复制代码
二值信号量

任务通知

例如:

复制代码
ADC采集完成
        ↓
Give
        ↓
处理任务运行

如果只是一个任务通知另一个任务:

复制代码
Task Notification

往往更加轻量。

如果需要表示:

复制代码
多个事件状态

例如:

复制代码
WiFi连接完成

传感器初始化完成

服务器连接完成

则可以考虑:

复制代码
Event Group。

所以不要看到同步就只会:

复制代码
Semaphore。

而应该根据具体需求选择合适的工具。

五、共享资源就想到互斥量

如果两个任务都要使用:

复制代码
UART

SPI

I2C

例如:

复制代码
Task1
  ↘
  UART
  ↗
Task2

就需要考虑:

复制代码
资源竞争。

这时可以使用:

复制代码
xSemaphoreCreateMutex();

访问资源:

复制代码
xSemaphoreTake(
    UartMutex,
    portMAX_DELAY
);

/* 使用UART */

xSemaphoreGive(
    UartMutex
);

互斥量解决的核心问题就是:

复制代码
这个资源我正在使用

↓

其他任务先等待。

而且FreeRTOS的Mutex还有:

复制代码
优先级继承

机制,可以降低优先级翻转带来的影响。

六、中断和任务之间要分清边界

STM32项目不可能只有任务。

还有:

复制代码
UART中断

定时器中断

外部中断

DMA中断

我们学习过一个非常重要的原则:

复制代码
中断
↓
尽量快速处理

复杂工作
↓
交给任务

例如UART收到数据:

复制代码
UART IRQ
    ↓
通知任务
    ↓
退出中断
    ↓
UartTask处理数据

在ISR中使用FreeRTOS API时,还要注意:

复制代码
FromISR

版本。

例如:

复制代码
xQueueSendFromISR();

而不是直接使用:

复制代码
xQueueSend();

这也是FreeRTOS实际工程中非常重要的一条边界。

七、FreeRTOS还需要管理RAM

FreeRTOS不是:

复制代码
任务随便创建多少都可以。

每创建一个任务,都需要:

复制代码
TCB

+

任务栈

队列、信号量等对象同样需要内存。

所以我们学习了:

复制代码
heap_1

heap_2

heap_3

heap_4

heap_5

也学习了:

复制代码
xPortGetFreeHeapSize();

检查剩余Heap。

以及:

复制代码
uxTaskGetStackHighWaterMark();

检查任务栈。

以后看到:

复制代码
程序运行一段时间HardFault

任务莫名其妙异常

除了检查代码逻辑,也应该想到:

复制代码
任务栈是不是不够?

八、一个实际FreeRTOS工程怎么设计

以后拿到一个STM32项目,不要上来就创建十几个任务。

可以先把功能列出来。

例如:

复制代码
传感器采集

数据处理

电机控制

MQTT通信

状态检测

然后分析:

复制代码
哪些功能需要独立周期运行?

哪些功能需要等待事件?

哪些功能实时性比较高?

哪些功能需要传递数据?

哪些功能共享同一个外设?

最后再决定:

复制代码
创建哪些任务
        ↓
任务优先级是多少
        ↓
使用Queue还是Notification
        ↓
哪些资源需要Mutex

所以FreeRTOS工程设计的正确顺序不是:

复制代码
先创建任务

↓

再想任务干什么

而应该是:

复制代码
分析功能
    ↓
划分任务
    ↓
确定任务关系
    ↓
选择通信同步方式
    ↓
设置优先级
    ↓
运行并测试

九、整个系列最终可以归纳成一张图

学完这个系列以后,可以把FreeRTOS理解成下面这套框架:

复制代码
                FreeRTOS
                   │
        ┌──────────┴──────────┐
        │                     │
      任务管理              任务通信
        │                     │
 xTaskCreate()              Queue
 vTaskDelay()             Semaphore
 Suspend/Resume             Mutex
 Priority               Event Group
        │              Task Notification
        │                     │
        └──────────┬──────────┘
                   │
                系统资源
                   │
          Stack / Heap
                   │
          FreeRTOSConfig
                   │
                调度器
                   │
        SysTick / PendSV / SVC
                   │
              STM32F407

如果以后某个知识点忘记了,不需要把整个FreeRTOS重新学习一遍。

只需要先判断:

复制代码
这个问题属于任务?

通信?

同步?

内存?

调度?

然后再去查对应API即可。

十、最终总结

从这个系列开始到现在,我们已经依次学习了:

复制代码
任务创建与延时

优先级与时间片

任务句柄

挂起、恢复和删除

任务状态与优先级修改

Idle Task与系统Tick

队列

二值信号量与计数信号量

互斥量与优先级继承

事件组

任务通知

软件定时器

流缓冲区与消息缓冲区

中断安全API

临界区与调度器锁

heap_1到heap_5

任务栈与栈溢出检测

Hook函数与运行时间统计

Tickless Idle

FreeRTOSConfig

调试与追踪

CMSIS-RTOS

生产者消费者模型

多任务同步

系统架构设计

FreeRTOS移植与调度原理

工程规范

性能优化

真正需要掌握的并不是把这些API全部背下来。

最重要的是建立这样一种思维:

复制代码
任务什么时候运行?

什么时候应该阻塞?

数据应该怎样传递?

任务应该怎样同步?

共享资源应该怎样保护?

中断应该做多少事情?

RAM到底够不够?

任务优先级是否合理?

当你开始主动考虑这些问题的时候,就已经不再只是:

复制代码
会调用FreeRTOS函数

而是在真正按照:

复制代码
RTOS的方式设计嵌入式程序。

最后再回到我们最开始的两个LED任务:

复制代码
xTaskCreate(
    LED1Task,
    "LED1",
    128,
    NULL,
    2,
    NULL
);

xTaskCreate(
    LED2Task,
    "LED2",
    128,
    NULL,
    2,
    NULL
);

vTaskStartScheduler();

最开始看到这些代码时,我们只是知道:

复制代码
它能够创建两个任务。

现在再看,背后已经能够想到:

复制代码
任务栈
    ↓
TCB
    ↓
Ready状态
    ↓
优先级
    ↓
SysTick
    ↓
调度器
    ↓
PendSV
    ↓
上下文切换
    ↓
CPU轮流执行两个任务

这就是这个FreeRTOS系列真正需要建立起来的完整认识。

至此,FreeRTOS中文学习系列全部完成。

相关推荐
m4Rk_2 小时前
【论文阅读】Agent 记忆机制(47):Nemori——用“预测误差”判断什么经验值得被记住
论文阅读·人工智能·学习·开源·github
约克32 小时前
2024年空调除甲醛技术参数与性能评测解析
学习
kkkkkkkkkk_Z3 小时前
学嵌入式和C语言编程数据结构|学习日记Day21:内核链表与队列学习
c语言·数据结构·学习
捷瑞电子工坊4 小时前
FreeRTOS事件组:多任务同步的利器
stm32·嵌入式·freertos·实时操作系统·标准外设库·事件组·任务同步
kdxiaojie5 小时前
Linux USB驱动阅读笔记(1)
linux·笔记·学习·usb
呼噜想睡觉5 小时前
Bootstrap3完成从导航到页脚完整页面开发
学习
深蓝海拓8 小时前
基于QtPy (PySide6) 的PLC-HMI工程项目(十五)后续完善和改进:PLC端数据解析的防粘包、残包、废包、拆包
笔记·学习·plc
小黄蚁8 小时前
LVGL学习笔记(六)
笔记·学习
zyf1044168 小时前
暑期实践日志 Day33:整体复盘核查视频,完善剪辑成果
学习·计算机网络·剪辑·暑期实践·课题任务