FreeRTOS学习(三十五)——系统架构综合项目

前面已经学习了任务、队列、信号量、互斥量、任务通知等内容,也完成了生产者消费者和多任务同步实验。

但是实际STM32项目中,很少只有两个任务。

例如一个采集系统可能同时需要:

复制代码
采集传感器

处理数据

串口通信

状态指示

故障检测

如果所有功能都塞进一个while(1)中,程序会越来越难维护。

所以这一节不再学习新的FreeRTOS API,而是把之前的知识组合起来,看看一个简单的STM32 FreeRTOS工程应该怎样进行任务划分和系统架构设计

本节学习目标:学会把一个完整功能拆分成多个任务,并根据任务之间的关系选择队列、任务通知和互斥量。


一、先确定项目功能

假设现在使用STM32F407制作一个简单的数据采集系统。

系统需要完成:

复制代码
每500ms采集一次传感器

采集完成以后:

复制代码
处理传感器数据

然后:

复制代码
通过串口发送数据

同时LED:

复制代码
每1000ms闪烁一次

用于表示系统正在正常运行。

如果使用裸机程序,可能直接写成:

复制代码
while(1)
{
    ReadSensor();

    ProcessData();

    SendData();

    LED_Toggle();
}

程序小时没有问题。

但是以后再增加:

复制代码
按键

报警

网络通信

故障检测

这个while(1)就会越来越复杂。

二、使用FreeRTOS划分任务

我们可以把系统拆成:

复制代码
SensorTask
    ↓
采集数据

ProcessTask
    ↓
处理数据

UartTask
    ↓
发送数据

LedTask
    ↓
状态指示

整个结构:

复制代码
SensorTask
    ↓
   Queue
    ↓
ProcessTask
    ↓
   Queue
    ↓
UartTask

LedTask独立周期运行

这样每个任务只负责:

复制代码
一类比较明确的工作。

以后传感器代码出问题:

复制代码
检查SensorTask

串口发送出问题:

复制代码
检查UartTask

程序结构会清楚很多。

三、定义数据结构

实际传感器通常不只有一个数据。

例如:

复制代码
typedef struct
{
    float temperature;
    float humidity;
}SensorData_t;

表示一次采集得到:

复制代码
温度

湿度

然后创建两个队列:

复制代码
QueueHandle_t SensorQueue;
QueueHandle_t UartQueue;

第一个:

复制代码
SensorQueue

SensorTask → ProcessTask

第二个:

复制代码
UartQueue

ProcessTask → UartTask

四、创建队列

初始化:

复制代码
SensorQueue = xQueueCreate(
                  5,
                  sizeof(SensorData_t)
              );

UartQueue = xQueueCreate(
                5,
                sizeof(SensorData_t)
            );

每个队列最多保存:

复制代码
5组SensorData_t数据。

然后判断:

复制代码
if((SensorQueue == NULL) ||
   (UartQueue == NULL))
{
    printf("Queue Create Failed\r\n");
}

队列创建失败通常需要检查:

复制代码
FreeRTOS Heap是否足够。

五、SensorTask负责采集

SensorTask:

复制代码
void SensorTask(void *pvParameters)
{
    SensorData_t Data;

    while(1)
    {
        Data.temperature =
            ReadTemperature();

        Data.humidity =
            ReadHumidity();

        xQueueSend(
            SensorQueue,
            &Data,
            portMAX_DELAY
        );

        vTaskDelay(
            pdMS_TO_TICKS(500)
        );
    }
}

这个任务只负责:

复制代码
读取传感器

↓

把数据发送出去

至于:

复制代码
数据怎么处理

怎么通过串口发送

SensorTask都不关心。

这就是任务划分以后非常重要的思想:

一个任务尽量只负责自己的主要功能。

六、ProcessTask负责处理

ProcessTask:

复制代码
void ProcessTask(void *pvParameters)
{
    SensorData_t Data;

    while(1)
    {
        if(xQueueReceive(
               SensorQueue,
               &Data,
               portMAX_DELAY
           ) == pdPASS)
        {
            Data.temperature += 0.5f;

            xQueueSend(
                UartQueue,
                &Data,
                portMAX_DELAY
            );
        }
    }
}

如果没有传感器数据:

复制代码
ProcessTask

↓

阻塞

SensorTask发送数据以后:

复制代码
ProcessTask被唤醒

↓

处理数据

↓

发送给UartQueue

这里的:

复制代码
Data.temperature += 0.5f;

只是模拟数据处理。

实际工程中可以替换成:

复制代码
滤波

校准

单位转换

数据计算

等操作。

七、UartTask负责发送

UartTask:

复制代码
void UartTask(void *pvParameters)
{
    SensorData_t Data;

    while(1)
    {
        if(xQueueReceive(
               UartQueue,
               &Data,
               portMAX_DELAY
           ) == pdPASS)
        {
            printf(
                "Temp = %.2f, Hum = %.2f\r\n",
                Data.temperature,
                Data.humidity
            );
        }
    }
}

如果没有数据:

复制代码
UartTask阻塞。

只有ProcessTask处理完成:

复制代码
ProcessTask
    ↓
UartQueue
    ↓
UartTask

UartTask才开始发送。

因此CPU不会浪费时间不断查询:

复制代码
有没有新数据?

八、LedTask独立运行

LED状态任务:

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

        vTaskDelay(
            pdMS_TO_TICKS(1000)
        );
    }
}

这个任务与:

复制代码
传感器采集

数据处理

串口发送

没有直接关系。

所以它可以:

复制代码
独立周期运行。

如果系统正常调度:

复制代码
LED持续闪烁。

如果程序严重异常:

复制代码
LED停止闪烁

还可以作为一个简单的系统状态观察手段。

九、任务优先级怎么分配

例如:

复制代码
xTaskCreate(
    SensorTask,
    "Sensor",
    256,
    NULL,
    3,
    NULL
);

xTaskCreate(
    ProcessTask,
    "Process",
    256,
    NULL,
    2,
    NULL
);

xTaskCreate(
    UartTask,
    "Uart",
    256,
    NULL,
    2,
    NULL
);

xTaskCreate(
    LedTask,
    "LED",
    128,
    NULL,
    1,
    NULL
);

这里给SensorTask稍高优先级:

复制代码
SensorTask    3

ProcessTask   2

UartTask      2

LedTask       1

因为我们希望:

复制代码
到达采样时间以后

优先完成数据采集。

LED只是状态指示:

复制代码
晚几毫秒闪烁通常没有影响

所以优先级最低。

但是要注意:

优先级不是越高越好。

任务优先级应该根据:

复制代码
实时性要求

重要程度

任务之间的依赖关系

合理设置。

十、总结

这一节没有学习新的FreeRTOS函数,而是把前面的知识组合成了一个简单的系统架构。

整个项目:

复制代码
SensorTask
采集数据
    ↓
SensorQueue
    ↓
ProcessTask
处理数据
    ↓
UartQueue
    ↓
UartTask
发送数据

同时:

复制代码
LedTask
    ↓
独立运行
    ↓
显示系统状态

这里最重要的并不是代码本身,而是:

复制代码
先根据功能划分任务

↓

再分析任务之间需要传递什么数据

↓

选择队列、信号量、通知或互斥量

↓

最后确定任务优先级

不要看到FreeRTOS支持多任务,就简单地:

复制代码
一个功能随便创建一个任务

所有任务全部while(1)

真正合理的FreeRTOS工程应该让每个任务:

复制代码
职责明确

关系清楚

该阻塞时阻塞

需要数据时再运行

这样随着项目功能增加,程序仍然能够保持比较清晰的结构。

相关推荐
传奇开心果编程5 小时前
【xilem0.4基础语法学与练】第13课:Xilem 0.4 最简短代码体现“一切皆设计图“
学习·rust·前端框架
小雪崩5 小时前
嵌入式学习 day45:51单片机基础
学习·51单片机
彧azz7 小时前
Java学习语法篇:变量
java·学习
Yanjun2i7 小时前
Agent学习记录五:Pydantic验证
人工智能·python·学习
xqqxqxxq9 小时前
AI Agent学习:Agent协作工具技术笔记(李博杰《深入理解 AI Agent》4.6观后总结)
笔记·学习
白猫不黑9 小时前
大学网安方向学习路线:零基础与有基础的学习顺序整理
学习·web安全·计算机·网络安全·信息安全·编程·src漏洞
工业HMI实战笔记10 小时前
玩具制造HMI:注塑成型的快速换模与质量监控界面
人工智能·学习·交互·制造
你在我身后11 小时前
发展路线建议
学习
彧azz11 小时前
数据结构:关于图的学习
c语言·数据结构·笔记·学习·算法
苦猿的大模型日记11 小时前
Day57|从0学习 Claude Code(七):我没把说明书全塞给它,用到哪本才翻哪本
学习