【FreeRTOS实战】一章速通freertos含扩展MQTT,SD卡,

发布于:2025-06-18 ⋅ 阅读:(23) ⋅ 点赞:(0)

【FreeRTOS实战】基于STM32的温湿度监测系统开发全攻略

大家好!今天我要带大家深入了解一个经典的嵌入式系统开发项目——基于FreeRTOS的温湿度监测系统。无论你是嵌入式开发新手还是想巩固知识的老手,这篇文章都能帮你掌握FreeRTOS的核心概念和实际应用技巧。一起动手实践吧!

一、项目功能概述

在智能硬件开发中,温湿度监测是最基础也是最常见的应用场景之一。本项目基于FreeRTOS实现了一个功能完善的温湿度监测系统,主要特点如下:

  1. 按键实时查询:按下KEY0(PC5)按键时,立即在串口打印当前温湿度数据
  2. 温度超限报警:当检测到温度≥30℃时,LED1以200ms间隔闪烁进行警示
  3. 定时数据采集:利用软件定时器每1000ms自动获取一次温湿度数据
  4. 多任务协同工作:通过队列传递温度数据,使用事件标志组实现任务间通信

这个项目麻雀虽小但五脏俱全,集成了FreeRTOS的任务管理、同步机制、定时器等核心功能,是入门嵌入式操作系统的绝佳实践案例。

二、开发环境与技术要点

2.1 硬件平台

  • 主控芯片:STM32F103系列微控制器
  • 传感器:DHT11温湿度传感器(成本低且易于使用)
  • 人机交互:按键KEY0(PC5)、指示灯LED1(GPIOA_PIN_8)

2.2 FreeRTOS核心知识点详解

2.2.1 任务管理机制

FreeRTOS是一个轻量级实时操作系统,其核心是强大的多任务调度机制。与裸机编程不同,FreeRTOS允许我们将不同功能模块编写为独立任务,系统会根据优先级自动调度这些任务。

任务创建函数xTaskCreate六大参数详解

BaseType_t xTaskCreate(
    TaskFunction_t pvTaskCode,      // 任务函数指针,指向任务的实现代码
    const char * const pcName,      // 任务名称,便于调试和识别
    uint16_t usStackDepth,          // 任务堆栈大小(字)
    void *pvParameters,             // 传给任务的参数指针
    UBaseType_t uxPriority,         // 任务优先级,数值越大优先级越高
    TaskHandle_t *pxCreatedTask     // 任务句柄,用于后续操作该任务
);

🔍 知识拓展:FreeRTOS采用抢占式调度,高优先级任务随时可以抢占低优先级任务。当多个相同优先级的任务就绪时,系统会采用时间片轮转方式让它们轮流执行。

2.2.2 任务间同步机制

在多任务环境下,任务间的同步和通信至关重要。FreeRTOS提供了多种机制:

  1. 互斥信号量:保护共享资源,解决资源冲突问题

    • 只允许一个任务访问受保护的资源
    • 支持优先级继承,解决优先级反转问题
  2. 队列(Queue):实现任务间数据传递

    • 先进先出(FIFO)的数据结构
    • 支持多发送者和多接收者
    • 可设置阻塞超时时间
  3. 事件标志组(Event Group):实现任务间事件通知

    • 每一位代表一个事件
    • 可同时等待和设置多个事件
    • 支持"与"和"或"逻辑的等待模式
2.2.3 软件定时器的应用

FreeRTOS的软件定时器是基于系统节拍的,不依赖硬件定时器资源:

  • 单次定时器:触发一次后自动删除
  • 周期定时器:按设定周期重复触发
  • 定时器回调函数:定时时间到达时自动执行,不占用任务资源

💡 实用技巧:软件定时器的回调函数在定时器服务任务中执行,优先级较高,因此回调函数不应包含阻塞操作或耗时过长的代码。

三、项目实现步骤详解

3.1 系统框架设计

首先,我们需要明确系统的总体架构。本项目分为三个主要任务和一个定时器:

  1. key_task:检测按键状态,按下后通知打印任务
  2. print_task:接收通知后打印温湿度数据
  3. led_task:根据温度值控制LED状态
  4. 软件定时器:周期性采集温湿度数据

各模块的交互关系如下:

                    ┌─────────────┐
                    │ 软件定时器   │
                    │  (1000ms)   │
                    └──────┬──────┘
                           │ 采集温湿度
                           ▼
┌──────────┐      ┌─────────────────┐      ┌──────────┐
│ key_task │──────► 全局温湿度变量  │◄─────│ led_task │
└─────┬────┘      │ (互斥信号量保护) │      └─────┬────┘
      │           └─────────────────┘            │
      │ 事件通知                       队列传递温度 │
      ▼                                          ▼
┌──────────┐                              ┌──────────┐
│print_task│                              │  LED控制  │
└──────────┘                              └──────────┘

3.2 基础定义与变量声明

首先定义系统所需的宏和全局变量:

// 事件标志组第0位宏定义
#define EVENT_FLAG_BIT0  (1 << 0)  // 定义事件标志位,用于按键通知打印任务

// 各种句柄定义
TimerHandle_t timer_hdl;           // 软件定时器句柄
SemaphoreHandle_t mutex_hdl;       // 互斥信号量句柄,用于保护全局变量
QueueHandle_t queue_hdl;           // 队列句柄,用于传递温度数据
EventGroupHandle_t event_hdl;      // 事件标志组句柄,用于任务间通知
TaskHandle_t key_task_hdl, print_task_hdl, led_task_hdl;  // 任务句柄

// 温湿度全局变量
unsigned char g_temp, g_humi;      // 分别存储温度和湿度值

为什么需要这些定义?

  • 事件标志位通过位操作方便高效地表示多个事件状态
  • 句柄是操作系统资源的标识符,通过句柄可以操作相应资源
  • 全局变量用于存储共享数据,但需要互斥保护以确保数据一致性

3.3 任务函数声明

在main函数前需要声明各个任务和回调函数:

// 按键检测任务函数
void key_task(void *param);  // 负责检测按键并通知打印任务

// 数据打印任务函数
void print_task(void *param);  // 负责等待通知并打印温湿度数据

// LED控制任务函数
void led_task(void *param);  // 负责根据温度控制LED状态

// 定时器回调函数
void timer_cb(TimerHandle_t xTimer);  // 定时器触发时采集温湿度数据

3.4 系统初始化与资源创建

main函数是系统入口,完成各种资源创建和任务初始化:

int main(void) {
    // 硬件初始化代码(省略)
    
    // 创建事件标志组并检查是否成功
    event_hdl = xEventGroupCreate();  // 创建事件标志组
    if (event_hdl == NULL) {
        printf("事件标志组创建失败\n");  // 创建失败时输出错误信息
        return -1;  // 返回错误码
    }

    // 创建互斥信号量并检查是否成功
    mutex_hdl = xSemaphoreCreateMutex();  // 创建互斥信号量
    if (mutex_hdl == NULL) {
        printf("互斥信号量创建失败\n");  // 创建失败时输出错误信息
        return -1;  // 返回错误码
    }

    // 创建软件定时器
    timer_hdl = xTimerCreate(
        "timer",                 // 定时器名称
        pdMS_TO_TICKS(1000),     // 定时周期:1000ms
        pdTRUE,                  // 自动重装载:pdTRUE表示周期定时器
        (void *)1,               // 定时器ID:任意值,用于标识定时器
        timer_cb                 // 回调函数:定时器到期时执行
    );
    
    // 启动定时器,阻塞时间为永久
    xTimerStart(timer_hdl, portMAX_DELAY);  // 启动定时器,如果无法立即启动则永久等待

    // 创建队列:长度为1,每个项目大小为unsigned char
    queue_hdl = xQueueCreate(1, sizeof(unsigned char));  // 创建队列用于传递温度数据

    // 创建按键检测任务
    xTaskCreate(
        key_task,       // 任务函数
        "key_task",     // 任务名称
        128,            // 堆栈大小:128字
        NULL,           // 任务参数:无
        4,              // 优先级:4(中等)
        &key_task_hdl   // 任务句柄
    );

    // 创建数据打印任务(更高优先级)
    xTaskCreate(print_task, "print_task", 256, NULL, 5, &print_task_hdl);

    // 创建LED控制任务(最高优先级)
    xTaskCreate(led_task, "led_task", 128, NULL, 6, &led_task_hdl);

    // 启动任务调度器
    vTaskStartScheduler();  // 启动FreeRTOS调度器,此后系统开始运行
    
    // 正常情况下,永远不会执行到这里
    return 0;
}

操作流程解析

  1. 首先创建事件标志组和互斥信号量,这些是任务间通信的基础设施
  2. 然后创建软件定时器并启动,注意pdMS_TO_TICKS函数将毫秒转换为系统节拍数
  3. 创建队列,用于定时器向LED任务传递温度数据
  4. 创建三个任务,注意优先级设置:LED任务(6) > 打印任务(5) > 按键任务(4)
  5. 最后启动调度器,此后系统开始按照任务优先级运行

🔍 知识拓展:为什么LED任务优先级最高?因为它负责报警功能,必须及时响应温度变化。打印任务次之,因为它只在按键按下时工作。按键任务优先级最低,因为按键检测可以有一定延迟。

3.5 定时器回调函数实现

定时器回调函数负责采集温湿度数据,并通过队列通知LED任务:

void timer_cb(TimerHandle_t xTimer) {
    unsigned char temp, humi;  // 临时存储温湿度数据
    
    // 获取互斥信号量,保护共享资源访问
    xSemaphoreTake(mutex_hdl, portMAX_DELAY);  // 获取互斥信号量,如果被占用则永久等待
    
    // 调用DHT11驱动函数获取温湿度数据
    DHT11_Read_Data(&humi, &temp);  // 读取温湿度数据
    
    // 保存到全局变量
    g_temp = temp;  // 更新全局温度变量
    g_humi = humi;  // 更新全局湿度变量
    
    // 释放互斥信号量
    xSemaphoreGive(mutex_hdl);  // 释放互斥信号量,允许其他任务访问共享资源
    
    // 向队列发送温度数据,阻塞时间为永久
    xQueueSend(queue_hdl, &temp, portMAX_DELAY);  // 发送温度数据到队列
}

核心要点

  • 使用互斥信号量保护全局变量,防止数据读写冲突
  • 采用xSemaphoreTakexSemaphoreGive成对使用的模式
  • 通过队列将温度数据传递给LED任务,实现任务间通信

3.6 按键检测任务实现

按键任务负责检测用户输入,并通知打印任务:

void key_task(void *param) {
    while (1) {  // 任务永久循环
        // 检测按键状态:低电平表示按下
        if (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_5) == GPIO_PIN_RESET) {
            // 延时10ms进行消抖
            vTaskDelay(pdMS_TO_TICKS(10));  // 短暂延时,消除机械按键抖动
            
            // 再次检测,确认按键确实被按下
            if (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_5) == GPIO_PIN_RESET) {
                // 设置事件标志组第0位为1,通知打印任务
                xEventGroupSetBits(event_hdl, EVENT_FLAG_BIT0);  // 设置事件标志,通知打印任务
            }
        }
        
        // 任务延时1ms,让出CPU资源
        vTaskDelay(pdMS_TO_TICKS(1));  // 短暂延时,避免占用全部CPU时间
    }
}

按键消抖原理详解

  1. 机械按键按下时会产生多次电信号抖动,可能导致一次按键被误判为多次
  2. 消抖方法是:检测到按键按下→延时等待抖动结束→再次检测确认
  3. 确认按下后,通过事件标志组通知打印任务执行打印操作

💡 实用技巧:按键消抖的延时时间一般为10-20ms,具体取决于按键质量。高质量按键抖动小,可以用更短的延时。

3.7 数据打印任务实现

打印任务负责等待按键通知,并打印温湿度数据:

void print_task(void *param) {
    while (1) {  // 任务永久循环
        // 等待事件标志组第0位为1
        xEventGroupWaitBits(
            event_hdl,           // 事件标志组句柄
            EVENT_FLAG_BIT0,     // 等待的事件位
            pdTRUE,              // pdTRUE表示等待后自动清除事件位
            pdFALSE,             // pdFALSE表示任一位满足即可返回
            portMAX_DELAY        // 永久阻塞,直到事件发生
        );
        
        // 获取互斥信号量,保护全局变量访问
        xSemaphoreTake(mutex_hdl, portMAX_DELAY);  // 获取互斥信号量
        
        // 打印温湿度数据
        printf("温度:%u℃,湿度:%u%%\r\n", g_temp, g_humi);  // 打印温湿度数据
        
        // 释放互斥信号量
        xSemaphoreGive(mutex_hdl);  // 释放互斥信号量
    }
}

事件等待机制详解

  • xEventGroupWaitBits函数用于等待特定事件位被设置
  • pdTRUE参数表示等待成功后自动清除事件位,避免重复触发
  • pdFALSE参数表示只要指定的任一位为1即可返回(本例中只等待一个位)
  • portMAX_DELAY表示永久阻塞,直到事件发生

3.8 LED控制任务实现

LED任务负责根据温度数据控制LED状态,实现温度报警功能:

void led_task(void *param) {
    unsigned char temp;  // 存储从队列接收的温度数据
    
    while (1) {  // 任务永久循环
        // 从队列接收温度数据,永久阻塞直到收到数据
        if (xQueueReceive(queue_hdl, &temp, portMAX_DELAY) == pdTRUE) {
            // 判断温度是否≥30℃
            if (temp >= 30) {
                // 温度过高,LED闪烁报警
                HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_8);  // 翻转LED状态
                vTaskDelay(pdMS_TO_TICKS(200));  // 延时200ms,控制闪烁频率
            } else {
                // 温度正常,LED熄灭
                HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET);  // 熄灭LED
            }
        }
        
        // 任务延时1ms,让出CPU资源
        vTaskDelay(pdMS_TO_TICKS(1));  // 短暂延时
    }
}

LED控制逻辑

  • 通过xQueueReceive从队列获取温度数据,阻塞等待直到有数据到来
  • 根据温度值判断LED状态:
    • 温度≥30℃时:LED以200ms间隔闪烁,提示温度过高
    • 温度<30℃时:LED保持熄灭状态,表示温度正常

四、FreeRTOS核心机制深度解析

4.1 任务状态转换详解

在FreeRTOS中,任务有多种状态,理解这些状态对掌握系统行为至关重要:

在这里插入图片描述

五种任务状态

  1. 运行态(Running):正在CPU上执行的任务
  2. 就绪态(Ready):可以执行但等待CPU资源的任务
  3. 阻塞态(Blocked):等待某个事件(如定时器、信号量)的任务
  4. 挂起态(Suspended):被显式挂起的任务,不参与调度
  5. 删除态(Deleted):已被删除但资源未完全释放的任务

状态转换触发因素

  • 任务创建后进入就绪态
  • 调度器选择最高优先级的就绪任务进入运行态
  • 运行任务调用阻塞API(如vTaskDelay)进入阻塞态
  • 运行任务被更高优先级任务抢占回到就绪态
  • 阻塞条件满足(如延时结束)后任务回到就绪态

4.2 优先级反转问题与解决方案

在多任务系统中,优先级反转是一个常见但危险的问题:

优先级反转场景

  1. 低优先级任务L获取互斥资源,正在处理共享数据
  2. 高优先级任务H就绪,抢占任务L的执行
  3. 任务H需要访问同一资源,因互斥锁被占用而阻塞
  4. 中优先级任务M就绪并执行,间接阻塞了高优先级任务H
  5. 结果:中优先级任务M优先于高优先级任务H执行,优先级关系被"反转"

在这里插入图片描述

解决方案

  1. 优先级继承:低优先级任务持有互斥量时,临时继承等待该互斥量的最高优先级任务的优先级
  2. 优先级天花板:互斥量有一个预设的优先级天花板,任何任务获取该互斥量时都临时提升到这个优先级

💡 FreeRTOS实现:FreeRTOS的互斥信号量(SemaphoreMutex)自动支持优先级继承机制,而普通二值信号量不支持。因此,对共享资源的保护应优先使用互斥信号量。

4.3 任务间通信方式对比

FreeRTOS提供多种任务间通信机制,各有优缺点:

通信方式 优点 缺点 适用场景
全局变量+互斥保护 简单直接,易于实现 需额外同步机制,可能有竞争条件 简单数据共享,更新频率低
队列(Queue) 支持多生产者/消费者,自带同步 内存开销较大,容量有限 数据传递,生产者-消费者模型
事件标志组(Event Group) 可表示多个事件,节约资源 只能传递位状态,不能传递数据 事件通知,触发操作
任务通知(Task Notification) 性能最高,资源开销最小 一个任务只有一个通知值,功能有限 简单信号通知,替代二值信号量
信号量(Semaphore) 经典同步机制,使用简单 不能传递数据,只能传递信号 资源计数,任务同步

本项目中的选择:

  • 使用互斥信号量保护全局温湿度变量,保证数据一致性
  • 使用队列传递温度数据,实现定时器到LED任务的数据流
  • 使用事件标志组实现按键任务到打印任务的通知,避免轮询开销

🔍 知识拓展:在资源紧张的系统中,任务通知(Task Notification)是最轻量级的通信方式,可以替代二值信号量、计数信号量甚至队列,性能提升30-40%。

五、项目调试与常见问题解决

5.1 调试方法与技巧

调试嵌入式系统需要结合多种方法:

  1. 串口调试

    • 在关键点添加printf输出
    • 使用RTT或SWO等实时输出技术,不阻塞系统执行
  2. LED指示灯调试

    • 不同闪烁模式表示不同状态
    • 例如:快闪表示错误,慢闪表示正常
  3. RTOS调试工具

    • FreeRTOS提供vTaskList()函数打印任务状态
    • 使用任务统计信息分析CPU占用率
  4. 使用调试器

    • 设置断点观察变量
    • 单步执行分析程序流程
    • 实时观察堆栈使用情况

5.2 常见问题及解决方法

  1. 任务栈溢出
    • 症状:系统不稳定,随机重启
    • 解决:增加任务堆栈大小,减少局部变量使用
    • 调试:启用FreeRTOS的栈溢出检测功能
// 在FreeRTOSConfig.h中启用栈溢出检测
#define configCHECK_FOR_STACK_OVERFLOW    2
  1. 优先级设置不合理

    • 症状:高优先级任务无法及时响应
    • 解决:合理分配任务优先级,避免高优先级任务长时间执行
    • 原则:响应时间要求高的任务优先级高,计算密集型任务优先级低
  2. 互斥保护不当

    • 症状:数据不一致,系统死锁
    • 解决:确保互斥资源的获取和释放成对出现,避免嵌套锁
    • 技巧:尽量缩小互斥保护的代码范围,减少持有锁的时间
  3. 定时器回调函数阻塞

    • 症状:系统定时不准,其他任务响应迟缓
    • 解决:定时器回调函数中不执行耗时操作,只做简单数据处理和通知
    • 技巧:将耗时操作放到单独的任务中,通过队列或事件通知
  4. DHT11传感器读取错误

    • 症状:温湿度数据异常或无法读取
    • 解决:检查硬件连接,增加上拉电阻,确保时序正确
    • 技巧:添加多次读取平均值的逻辑,过滤异常数据

六、完整项目代码

下面是完整的项目代码,集成了所有功能模块:

#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
#include "event_groups.h"
#include "timers.h"
#include "stm32f10x.h"
#include "stdio.h"

// 事件标志组第0位宏定义
#define EVENT_FLAG_BIT0  (1 << 0)  // 定义事件标志位,用于按键通知打印任务

// 定时器句柄
TimerHandle_t timer_hdl;  // 软件定时器句柄,用于周期性获取温湿度数据

// 互斥信号量句柄
SemaphoreHandle_t mutex_hdl;  // 互斥信号量句柄,用于保护全局温湿度变量

// 队列句柄
QueueHandle_t queue_hdl;  // 队列句柄,用于传递温度数据给LED任务

// 事件标志组句柄
EventGroupHandle_t event_hdl;  // 事件标志组句柄,用于按键任务通知打印任务

// 任务句柄
TaskHandle_t key_task_hdl, print_task_hdl, led_task_hdl;  // 各任务的句柄

// 温湿度全局变量
unsigned char g_temp, g_humi;  // 存储当前温度和湿度值的全局变量

// 函数声明
void key_task(void *param);  // 按键检测任务
void print_task(void *param);  // 数据打印任务
void led_task(void *param);  // LED控制任务
void timer_cb(TimerHandle_t xTimer);  // 定时器回调函数
void DHT11_Read_Data(unsigned char *humi, unsigned char *temp);  // DHT11驱动函数

int main(void) {
    // 硬件初始化代码(省略)
    // ...

    // 创建事件标志组并检查是否成功
    event_hdl = xEventGroupCreate();  // 创建事件标志组
    if (event_hdl == NULL) {
        printf("事件标志组创建失败\n");  // 创建失败时打印错误信息
        return -1;  // 返回错误码
    }

    // 创建互斥信号量并检查是否成功
    mutex_hdl = xSemaphoreCreateMutex();  // 创建互斥信号量
    if (mutex_hdl == NULL) {
        printf("互斥信号量创建失败\n");  // 创建失败时打印错误信息
        return -1;  // 返回错误码
    }

    // 创建软件定时器
    timer_hdl = xTimerCreate(
        "timer",                 // 定时器名称
        pdMS_TO_TICKS(1000),     // 定时周期:1000ms
        pdTRUE,                  // 自动重装载:周期定时器
        (void *)1,               // 定时器ID:用于标识定时器
        timer_cb                 // 回调函数:定时到期时执行
    );
    
    // 启动定时器
    xTimerStart(timer_hdl, portMAX_DELAY);  // 启动定时器,阻塞时间为永久

    // 创建队列
    queue_hdl = xQueueCreate(1, sizeof(unsigned char));  // 创建长度为1的队列

    // 创建key_task任务
    xTaskCreate(
        key_task,       // 任务函数
        "key_task",     // 任务名称
        128,            // 堆栈大小
        NULL,           // 任务参数
        4,              // 优先级
        &key_task_hdl   // 任务句柄
    );
    
    // 创建print_task任务
    xTaskCreate(print_task, "print_task", 256, NULL, 5, &print_task_hdl);
    
    // 创建led_task任务
    xTaskCreate(led_task, "led_task", 128, NULL, 6, &led_task_hdl);

    // 启动任务调度器
    vTaskStartScheduler();  // 启动FreeRTOS调度器
    
    // 正常情况下不会执行到这里
    return 0;
}

// 定时器回调函数
void timer_cb(TimerHandle_t xTimer) {
    unsigned char temp, humi;  // 临时变量存储温湿度数据
    
    // 获取互斥信号量
    xSemaphoreTake(mutex_hdl, portMAX_DELAY);  // 获取互斥信号量,保护全局变量
    
    // 读取温湿度数据
    DHT11_Read_Data(&humi, &temp);  // 调用DHT11驱动函数读取数据
    
    // 保存到全局变量
    g_temp = temp;  // 更新全局温度变量
    g_humi = humi;  // 更新全局湿度变量
    
    // 释放互斥信号量
    xSemaphoreGive(mutex_hdl);  // 释放互斥信号量
    
    // 向队列发送温度数据
    xQueueSend(queue_hdl, &temp, portMAX_DELAY);  // 发送温度数据到队列
}

// 按键检测任务
void key_task(void *param) {
    while (1) {  // 任务永久循环
        // 按键消抖
        if (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_5) == GPIO_PIN_RESET) {  // 检测按键是否按下
            vTaskDelay(pdMS_TO_TICKS(10));  // 延时10ms进行消抖
            if (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_5) == GPIO_PIN_RESET) {  // 再次检测确认按键按下
                // 设置事件标志
                xEventGroupSetBits(event_hdl, EVENT_FLAG_BIT0);  // 设置事件标志位通知打印任务
            }
        }
        vTaskDelay(pdMS_TO_TICKS(1));  // 短暂延时,让出CPU
    }
}

// 数据打印任务
void print_task(void *param) {
    while (1) {  // 任务永久循环
        // 等待事件标志
        xEventGroupWaitBits(
            event_hdl,           // 事件标志组句柄
            EVENT_FLAG_BIT0,     // 等待的事件位
            pdTRUE,              // 等待后自动清除事件位
            pdFALSE,             // 任一位满足即可返回
            portMAX_DELAY        // 永久阻塞等待
        );
        
        // 获取互斥信号量
        
```c
        // 获取互斥信号量
        xSemaphoreTake(mutex_hdl, portMAX_DELAY);  // 获取互斥信号量,保护全局变量访问
        
        // 打印温湿度数据
        printf("温度:%u℃,湿度:%u%%\r\n", g_temp, g_humi);  // 打印当前温湿度值
        
        // 释放互斥信号量
        xSemaphoreGive(mutex_hdl);  // 释放互斥信号量
    }
}

// LED控制任务
void led_task(void *param) {
    unsigned char temp;  // 用于存储从队列接收的温度值
    
    while (1) {  // 任务永久循环
        // 从队列接收温度数据
        if (xQueueReceive(queue_hdl, &temp, portMAX_DELAY) == pdTRUE) {  // 接收温度数据
            // 判断温度并控制LED
            if (temp >= 30) {  // 温度超过或等于30℃
                // 温度过高,LED闪烁
                HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_8);  // 翻转LED状态
                vTaskDelay(pdMS_TO_TICKS(200));  // 延时200ms控制闪烁频率
            } else {  // 温度低于30℃
                // 温度正常,LED熄灭
                HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET);  // LED熄灭
            }
        }
        vTaskDelay(pdMS_TO_TICKS(1));  // 短暂延时,让出CPU
    }
}

// DHT11驱动函数(简化实现)
void DHT11_Read_Data(unsigned char *humi, unsigned char *temp) {
    // 实际项目中需要根据DHT11通信协议实现
    // 此处为简化示例,返回模拟数据
    *humi = 60;  // 湿度60%
    *temp = 25;  // 温度25℃
    
    // 实际应用中,需要按照DHT11时序要求进行通信
    // 包括:起始信号、响应信号、数据位读取和校验
}

七、项目拓展与实战技巧

7.1 数据处理优化

实际应用中,可以通过以下方式提高系统可靠性:

  1. 滑动平均滤波:减少温湿度测量波动
// 滑动平均滤波示例代码
#define FILTER_SIZE 5  // 滤波窗口大小
unsigned char temp_buffer[FILTER_SIZE] = {0};  // 温度数据缓冲区
unsigned char filter_index = 0;  // 当前索引

// 添加新数据并计算平均值
unsigned char add_temp_data(unsigned char new_temp) {
    unsigned int sum = 0;
    
    // 将新数据加入缓冲区
    temp_buffer[filter_index] = new_temp;
    filter_index = (filter_index + 1) % FILTER_SIZE;
    
    // 计算平均值
    for (int i = 0; i < FILTER_SIZE; i++) {
        sum += temp_buffer[i];
    }
    
    return (unsigned char)(sum / FILTER_SIZE);
}
  1. 阈值迟滞:防止温度在临界值附近频繁触发报警
// 温度阈值迟滞示例
#define TEMP_HIGH_THRESHOLD 30  // 高温阈值
#define TEMP_LOW_THRESHOLD  28  // 低温阈值
unsigned char alarm_state = 0;  // 报警状态

// 更新报警状态
void update_alarm(unsigned char temp) {
    if (alarm_state == 0) {  // 当前无报警
        if (temp >= TEMP_HIGH_THRESHOLD) {  // 超过高温阈值
            alarm_state = 1;  // 进入报警状态
        }
    } else {  // 当前已报警
        if (temp < TEMP_LOW_THRESHOLD) {  // 低于低温阈值
            alarm_state = 0;  // 退出报警状态
        }
    }
}

7.2 低功耗设计

嵌入式系统通常需要考虑功耗问题,特别是电池供电设备:

  1. Tickless空闲模式:在系统空闲时停止系统节拍
// 在FreeRTOSConfig.h中启用Tickless空闲模式
#define configUSE_TICKLESS_IDLE 1
  1. 任务自动休眠:让任务在无事可做时主动让出CPU
// 在所有任务循环中添加适当的延时
vTaskDelay(pdMS_TO_TICKS(10));  // 延时10ms
  1. 外设功耗管理:按需启停外设电源
// DHT11电源控制示例
void dht11_power_control(unsigned char state) {
    if (state) {
        // 开启DHT11电源
        HAL_GPIO_WritePin(DHT11_POWER_PORT, DHT11_POWER_PIN, GPIO_PIN_SET);
        // 等待DHT11稳定
        vTaskDelay(pdMS_TO_TICKS(10));
    } else {
        // 关闭DHT11电源
        HAL_GPIO_WritePin(DHT11_POWER_PORT, DHT11_POWER_PIN, GPIO_PIN_RESET);
    }
}

7.3 可靠性提升

增强系统可靠性的几种方法:

  1. 看门狗定时器:监测系统运行状态,防止死机
// 初始化看门狗
void iwdg_init(void) {
    // 设置看门狗超时时间为1秒
    IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
    IWDG_SetPrescaler(IWDG_Prescaler_32);
    IWDG_SetReload(1000);
    IWDG_ReloadCounter();
    IWDG_Enable();
}

// 在主循环中喂狗
void feed_watchdog(void *param) {
    while (1) {
        IWDG_ReloadCounter();  // 喂狗
        vTaskDelay(pdMS_TO_TICKS(500));  // 每500ms喂一次狗
    }
}
  1. 任务监控:监控任务执行情况,检测任务卡死
// 使用FreeRTOS提供的运行时统计功能
#define configGENERATE_RUN_TIME_STATS 1

// 检查任务运行时间
void monitor_tasks(void) {
    char buffer[500];
    vTaskGetRunTimeStats(buffer);
    printf("任务运行时间统计:\r\n%s\r\n", buffer);
}

7.4 系统扩展方向

实际项目中,我们可以在此基础上进行多方面扩展:

  1. 网络连接:添加WiFi/蓝牙模块,实现远程监控
// 添加MQTT客户端,发送温湿度数据到云平台
void mqtt_task(void *param) {
    while (1) {
        // 获取温湿度数据
        xSemaphoreTake(mutex_hdl, portMAX_DELAY);
        unsigned char temp = g_temp;
        unsigned char humi = g_humi;
        xSemaphoreGive(mutex_hdl);
        
        // 构建JSON数据
        char json_buffer[100];
        sprintf(json_buffer, "{\"temperature\":%u,\"humidity\":%u}", temp, humi);
        
        // 发布到MQTT主题
        mqtt_publish("device/temperature", json_buffer, strlen(json_buffer));
        
        // 每60秒发送一次数据
        vTaskDelay(pdMS_TO_TICKS(60000));
    }
}
  1. 数据存储:添加SD卡或Flash存储,记录历史数据
// 定期记录温湿度数据到Flash
void data_logger_task(void *param) {
    while (1) {
        // 获取当前时间和温湿度数据
        RTC_TimeTypeDef time;
        HAL_RTC_GetTime(&hrtc, &time, RTC_FORMAT_BIN);
        
        xSemaphoreTake(mutex_hdl, portMAX_DELAY);
        unsigned char temp = g_temp;
        unsigned char humi = g_humi;
        xSemaphoreGive(mutex_hdl);
        
        // 构建数据记录
        DataRecord record;
        record.timestamp = time.Hours * 3600 + time.Minutes * 60 + time.Seconds;
        record.temperature = temp;
        record.humidity = humi;
        
        // 写入Flash存储
        flash_write_record(&record);
        
        // 每10分钟记录一次
        vTaskDelay(pdMS_TO_TICKS(600000));
    }
}
  1. 人机交互:添加LCD显示屏,显示实时数据和历史趋势
// LCD显示任务
void lcd_display_task(void *param) {
    while (1) {
        // 获取当前温湿度
        xSemaphoreTake(mutex_hdl, portMAX_DELAY);
        unsigned char temp = g_temp;
        unsigned char humi = g_humi;
        xSemaphoreGive(mutex_hdl);
        
        // 清屏
        LCD_Clear(BLACK);
        
        // 显示时间
        RTC_TimeTypeDef time;
        HAL_RTC_GetTime(&hrtc, &time, RTC_FORMAT_BIN);
        char time_str[20];
        sprintf(time_str, "%02d:%02d:%02d", time.Hours, time.Minutes, time.Seconds);
        LCD_ShowString(10, 10, time_str, WHITE);
        
        // 显示温湿度
        char temp_str[20], humi_str[20];
        sprintf(temp_str, "温度: %u°C", temp);
        sprintf(humi_str, "湿度: %u%%", humi);
        LCD_ShowString(10, 40, temp_str, temp >= 30 ? RED : GREEN);
        LCD_ShowString(10, 70, humi_str, WHITE);
        
        // 刷新频率1Hz
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

八、总结与进阶学习路径

通过本项目,我们实现了一个基于FreeRTOS的温湿度监测系统,涵盖了以下关键技术点:

  1. FreeRTOS任务创建与调度
  2. 软件定时器的应用
  3. 任务间通信(队列、事件标志组)
  4. 资源保护(互斥信号量)
  5. 外设控制(按键、LED、温湿度传感器)

这些知识点是嵌入式系统开发的基础,掌握了这些,你就能开发更复杂的嵌入式应用。

进阶学习方向

  1. RTOS进阶:深入学习FreeRTOS内核原理、内存管理和调度算法
  2. 通信协议:掌握I2C、SPI、UART等常用通信协议
  3. 网络开发:学习TCP/IP协议栈,实现物联网应用
  4. 功耗优化:深入了解低功耗设计技术
  5. 系统安全:嵌入式系统安全防护措施

实践项目推荐

  1. 智能家居控制器:整合多种传感器和执行器
  2. 便携式数据记录仪:添加存储和显示功能
  3. 物联网终端:实现设备联网和远程控制

通过不断学习和实践,你将能够掌握嵌入式系统开发的核心技能,为未来的智能硬件开发奠定坚实基础!


希望这篇教程对你有所帮助!如有疑问,欢迎在评论区留言交流~


网站公告

今日签到

点亮在社区的每一天
去签到