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工程应该让每个任务:

复制代码
职责明确

关系清楚

该阻塞时阻塞

需要数据时再运行

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

相关推荐
YM52e1 小时前
语言地区选择器-鸿蒙ArkTS国际化页面设计
学习·华为·harmonyos
不是光头 强2 小时前
Java 后端 AI 技术选型与学习路线
java·人工智能·学习
cloudification2 小时前
virtual
学习
MartinYeung52 小时前
[论文学习]δ-STEAL:基于本地差分隐私的大语言模型窃取攻击
网络·学习·语言模型
paopaokaka_luck2 小时前
基于springboot3+vue3的文山民族文化资源展示与推广平台(协同过滤算法、Echarts 图形化分析)
java·前端·spring boot·学习·echarts
wuhuhuan4 小时前
【JMeter 学习打卡 Day 4】多用户登录 + 动态 token 关联
学习·jmeter
Code额5 小时前
Python asyncio 异步编程全套学习文档(零基础完整版)
python·学习·oracle·async·异步·asyncio
在线考试系统推荐6 小时前
2026年7月最新实测:三大考试软件导入试题能力对比
人工智能·学习·系统架构
小弥儿6 小时前
GitHub今日热榜 | 2026-08-20:Agent 上下文数据库接棒
数据库·学习·开源·github