基于STM32+ESP32的智能天气时钟系统

项目简介

本项目是一个基于STM32F103C8T6(Blue Pill)微控制器和ESP32 Wi-Fi模块的智能天气时钟系统。系统通过FreeRTOS实时操作系统实现多任务并发处理,结合MPU6050姿态传感器和1.44寸LCD显示屏,构建了一个具备网络时间同步、实时天气获取和动态显示功能的嵌入式智能设备。

核心功能

  • 网络时间同步:通过ESP32连接WiFi热点获取时间数据
  • 实时天气显示:调用心知天气API,获取当前温度、天气状况和图标
  • 姿态感知:MPU6050传感器提供六轴姿态数据,支持设备状态监测
  • 图形化界面:1.44寸LCD显示屏以60FPS刷新率展示时间、天气和传感器数据

技术栈

  • 硬件平台:STM32F103C8T6 + ESP32 + MPU6050 + ST7735S LCD
  • 操作系统:FreeRTOS实时操作系统
  • 通信协议:UART AT指令、HTTP/SNTP、I2C、SPI
  • 开发环境:STM32CubeIDE + HAL库

实现目标:打造一个低功耗、高实时性、具备网络连接能力的嵌入式智能显示终端,可作为物联网入门学习和产品原型开发参考。

主控芯片:STM32F103C8T6(Blue Pill)
通信模块:ESP32(ESP-32S)
传感器:MPU6050(姿态测量)
显示:1.44寸 LCD TFT(SPI接口)
通信方式:UART AT指令 + HTTP/SNTP协议

目录

一、硬件选型

1.1 硬件清单

本项目基于STM32F103C8T6(Blue Pill)开发板为核心,搭配ESP32通信模块、MPU6050传感器和1.44寸LCD显示屏,构建一个具备网络通信和显示功能的嵌入式系统。

核心硬件清单

组件 型号/规格 数量 主要功能
主控芯片 STM32F103C8T6(Blue Pill) 1 系统主控,运行FreeRTOS
通信模块 ESP32(ESP-32S) 1 Wi-Fi通信,AT指令控制
姿态传感器 MPU6050 1 六轴姿态测量(加速度+陀螺仪)
显示屏 1.44寸 LCD TFT(SPI接口) 1 图形界面显示
电源模块 AMS1117-3.3V 1 3.3V稳压供电
调试接口 ST-Link V2 1 程序烧录与调试
连接线 杜邦线(公对公、公对母) 若干 硬件连接

各模块选型理由

  1. STM32F103C8T6(Blue Pill)

    • Cortex-M3内核,72MHz主频,性能足够运行FreeRTOS
    • 64KB Flash,20KB RAM,满足本项目需求
    • 丰富的GPIO和外设(USART、SPI、I2C等)
    • 成本低廉,社区资源丰富
  2. ESP32(ESP-32S)

    • 双核处理器,支持Wi-Fi和蓝牙
    • 内置TCP/IP协议栈,支持AT指令控制
    • 与STM32通过UART通信,接口简单
    • 功耗较低,适合嵌入式应用
  3. MPU6050

    • 集成三轴加速度计和三轴陀螺仪
    • I2C接口,占用GPIO少
    • 内置DMP(数字运动处理器),减轻主控负担
  4. 1.44寸LCD TFT

    • SPI接口,通信简单
    • 128×128分辨率,满足基本显示需求
    • 自带ST7735S驱动芯片,有成熟的驱动库

1.2 引脚连接表

STM32与ESP32连接

STM32引脚 ESP32引脚 功能 备注
PA9(USART1_TX) RX(GPIO3) STM32发送 → ESP32接收 交叉连接
PA10(USART1_RX) TX(GPIO1) STM32接收 ← ESP32发送 交叉连接
3.3V VCC 电源正极 ESP32工作电压3.3V
GND GND 电源地 共地

STM32与MPU6050连接

STM32引脚 MPU6050引脚 功能 备注
PB6(I2C1_SCL) SCL I2C时钟线 上拉电阻4.7kΩ
PB7(I2C1_SDA) SDA I2C数据线 上拉电阻4.7kΩ
3.3V VCC 电源正极 MPU6050工作电压2.375-3.46V
GND GND 电源地 共地

STM32与LCD连接

STM32引脚 LCD引脚 功能 备注
PA4 CS 片选信号 低电平有效
PA5 SCK SPI时钟
PA6 SDA/MOSI SPI数据输入
PA7 RES 复位信号 低电平复位
PB0 DC 数据/命令选择 高电平:数据,低电平:命令
3.3V VCC 电源正极 LCD工作电压3.3V
GND GND 电源地 共地
3.3V LED 背光电源 可通过PWM调节亮度

电源连接

  • 外部5V输入 → AMS1117-3.3V稳压模块 → 3.3V系统电源
  • 所有模块的VCC引脚连接到3.3V电源总线
  • 所有模块的GND引脚连接到电源地总线

注意事项

  1. 电平匹配:STM32和ESP32均为3.3V电平,可直接连接
  2. 上拉电阻:I2C总线需要4.7kΩ上拉电阻至3.3V
  3. 电源滤波:每个模块的VCC引脚附近应加0.1μF去耦电容
  4. 布线规范:信号线尽量短,避免平行走线,减少干扰

二、软件环境搭建

2.1 STM32开发环境配置

  1. 安装STM32CubeIDE:从ST官网下载安装

  2. 安装STM32F1 HAL库

    • 在STM32CubeIDE中,通过"Help"→"Manage Embedded Software Packages"安装STM32F1系列HAL库
    • 或使用STM32CubeMX生成初始化代码
  3. 安装FreeRTOS

    // 通过STM32CubeMX添加FreeRTOS中间件
    // 或手动下载FreeRTOS源码:https://www.freertos.org/
    
  4. 串口驱动安装

    • Windows:安装CH340/CH341串口驱动
    • Linux:通常自动识别,无需额外安装

2.2 ESP32-C3 AT固件烧录(针对ESP32-C3-MINI)

  1. 下载ESP32-C3 AT固件

    • 从乐鑫官网下载最新AT固件:https://www.espressif.com/zh-hans/support/download/at
    • 特别注意:ESP32-C3系列需要使用专门的固件,请选择"ESP32-C3 AT Bin"版本
    • 推荐下载ESP32-C3 AT Bin V2.x或更高版本,支持Wi-Fi和蓝牙功能
  2. 硬件连接准备

    • ESP32-C3-MINI开发板通常自带USB转串口芯片,可直接通过USB线连接电脑
    • 如需手动进入下载模式:
      • 按住BOOT按钮(或IO9引脚拉低)
      • 按一下RESET按钮
      • 松开BOOT按钮
    • 确认电脑识别到串口设备(Windows:设备管理器查看COM端口)
  3. 使用esptool.py烧录(推荐)

    # 安装esptool
    pip install esptool
    
    # 查看连接的ESP设备
    esptool.py chip_id
    
    # 擦除Flash
    esptool.py --chip esp32c3 --port COM3 erase_flash
    
    # 烧录AT固件(注意:ESP32-C3的偏移地址通常是0x0)
    esptool.py --chip esp32c3 --port COM3 --baud 921600 write_flash 0x0 factory/factory_WROOM-AT.bin
    

    Windows用户:也可以使用Flash Download Tool,但需要选择正确的芯片类型为"ESP32-C3"

  4. ESP32-C3特殊配置

    • ESP32-C3使用RISC-V架构,与ESP32的Xtensa架构不同
    • AT固件默认波特率:115200
    • 默认Wi-Fi模式:Station模式
    • 支持蓝牙5.0(需启用相应AT命令)
  5. 验证AT固件

    • 使用串口调试工具(如Putty、SecureCRT、Arduino串口监视器)
    • 设置波特率:115200,数据位:8,停止位:1,无校验
    • 发送"AT"命令,应收到"OK"响应
    • 发送"AT+GMR"查看固件版本和编译信息
    • 发送"AT+CWMODE?"查看当前Wi-Fi模式
  6. 常见问题

    • 无法进入下载模式:检查BOOT/GPIO9引脚是否正确拉低
    • 烧录失败:尝试降低波特率(如460800或115200)
    • AT命令无响应:检查串口线连接,确认RXD/TXD交叉连接
    • ESP32-C3-MINI特定:该板载USB转串口芯片,无需外部USB转TTL模块

注意:ESP32-C3的AT固件与ESP32不兼容,请务必下载对应型号的固件。烧录成功后,可通过AT+RST重启模块。

2.3 工程文件结构

STM32_ESP32_Project/
├── Core/
│   ├── Inc/
│   │   ├── main.h
│   │   ├── esp_at.h
│   │   └── esp_usart.h
│   ├── Src/
│   │   ├── main.c
│   │   ├── esp_at.c
│   │   └── esp_usart.c
│   └── Startup/
├── Drivers/
│   ├── CMSIS/
│   └── STM32F1xx_HAL_Driver/
├── Middlewares/
│   └── Third_Party/
│       └── FreeRTOS/
└── README.md

三、FreeRTOS任务设计与核心实现

3.1 FreeRTOS任务架构设计

本项目基于FreeRTOS实时操作系统,将不同功能模块化为独立任务,实现高效并发处理。系统共创建3个主要任务,优先级从高到低排列:

// FreeRTOS任务优先级定义
#define TASK_PRIORITY_TIME_SYNC     (tskIDLE_PRIORITY + 3)  // 最高:时间同步
#define TASK_PRIORITY_WEATHER_FETCH (tskIDLE_PRIORITY + 2)  // 天气获取
#define TASK_PRIORITY_LCD_DISPLAY   (tskIDLE_PRIORITY + 1)  // 显示刷新

// 任务句柄声明
TaskHandle_t xTimeSyncTaskHandle = NULL;
TaskHandle_t xWeatherFetchTaskHandle = NULL;
TaskHandle_t xLCDDisplayTaskHandle = NULL;

// 全局共享数据结构
typedef struct {
    RTC_TimeTypeDef rtc_time;
    RTC_DateTypeDef rtc_date;
    float temperature;
    char weather_condition[32];
    char weather_icon;
    uint8_t update_flag;  // 位标志:0x01=时间更新,0x02=天气更新
} SystemData_t;

SystemData_t system_data;
SemaphoreHandle_t xDataMutex;  // 数据互斥锁

任务调度流程图

系统启动

创建FreeRTOS内核对象
(队列、信号量、互斥锁)

创建时间同步任务
(优先级3)

创建天气获取任务
(优先级2)

创建LCD显示任务
(优先级1)

SNTP网络校时
每30分钟同步一次

心知天气API请求
每10分钟更新一次

LCD界面刷新
每秒60帧

更新RTC时间
设置更新标志

解析JSON响应
设置更新标志

通过互斥锁
写入共享数据

LCD任务读取最新数据
刷新显示

等待垂直同步信号
维持60FPS

3.2 FreeRTOS配置与初始化

在开始任务创建之前,需要先完成FreeRTOS的配置和初始化工作。

3.2.1 FreeRTOS配置文件

FreeRTOSConfig.h 中进行基本配置:

// FreeRTOSConfig.h
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H

// 内核配置
#define configUSE_PREEMPTION                    1
#define configUSE_IDLE_HOOK                     0
#define configUSE_TICK_HOOK                     0
#define configCPU_CLOCK_HZ                      (SystemCoreClock)
#define configTICK_RATE_HZ                      ((TickType_t)1000)  // 1ms tick
#define configMAX_PRIORITIES                    (7)
#define configMINIMAL_STACK_SIZE                ((uint16_t)128)
#define configTOTAL_HEAP_SIZE                   ((size_t)10240)     // 10KB堆
#define configMAX_TASK_NAME_LEN                 (16)
#define configUSE_TRACE_FACILITY                1
#define configUSE_16_BIT_TICKS                  0
#define configIDLE_SHOULD_YIELD                 1
#define configUSE_MUTEXES                       1
#define configUSE_RECURSIVE_MUTEXES             1
#define configUSE_COUNTING_SEMAPHORES           1
#define configUSE_QUEUE_SETS                    0
#define configUSE_TASK_NOTIFICATIONS            1
#define configQUEUE_REGISTRY_SIZE               8

// 内存分配方案
#define configSUPPORT_STATIC_ALLOCATION         1
#define configSUPPORT_DYNAMIC_ALLOCATION        1
#define configAPPLICATION_ALLOCATED_HEAP        0

// 钩子函数
#define configUSE_IDLE_HOOK                     0
#define configUSE_TICK_HOOK                     0
#define configUSE_MALLOC_FAILED_HOOK            1
#define configCHECK_FOR_STACK_OVERFLOW          2

// 任务统计
#define configGENERATE_RUN_TIME_STATS           0
#define configUSE_TRACE_FACILITY                1
#define configUSE_STATS_FORMATTING_FUNCTIONS    0

// 协程(不使用)
#define configUSE_CO_ROUTINES                   0
#define configMAX_CO_ROUTINE_PRIORITIES         (2)

// 软件定时器
#define configUSE_TIMERS                        1
#define configTIMER_TASK_PRIORITY               (configMAX_PRIORITIES - 1)
#define configTIMER_QUEUE_LENGTH                10
#define configTIMER_TASK_STACK_DEPTH            (configMINIMAL_STACK_SIZE * 2)

// 中断配置(针对STM32)
#define configKERNEL_INTERRUPT_PRIORITY         255
#define configMAX_SYSCALL_INTERRUPT_PRIORITY    191
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15

#include "stm32f1xx_hal.h"

// 断言配置
#define configASSERT(x) if((x) == 0) {taskDISABLE_INTERRUPTS(); for(;;);}

#endif /* FREERTOS_CONFIG_H */
3.2.2 FreeRTOS初始化函数

main.c 中初始化FreeRTOS:

// main.c - FreeRTOS初始化部分
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
#include "timers.h"

// 静态任务堆栈定义
static StackType_t xTimeSyncTaskStack[configMINIMAL_STACK_SIZE * 4];
static StackType_t xWeatherFetchTaskStack[configMINIMAL_STACK_SIZE * 8];
static StackType_t xLCDDisplayTaskStack[configMINIMAL_STACK_SIZE * 6];

// 静态任务控制块定义
static StaticTask_t xTimeSyncTaskTCB;
static StaticTask_t xWeatherFetchTaskTCB;
static StaticTask_t xLCDDisplayTaskTCB;

// 静态队列和信号量存储区
static uint8_t ucTimeQueueStorage[5 * sizeof(RTC_TimeTypeDef)];
static StaticQueue_t xTimeQueueBuffer;
static uint8_t ucWeatherQueueStorage[3 * sizeof(WeatherData_t)];
static StaticQueue_t xWeatherQueueBuffer;
static StaticSemaphore_t xDataMutexBuffer;
static StaticSemaphore_t xLCDReadySemaphoreBuffer;

void FreeRTOS_Init(void)
{
    // 创建队列(静态方式)
    xTimeQueue = xQueueCreateStatic(5, sizeof(RTC_TimeTypeDef),
                                     ucTimeQueueStorage, &xTimeQueueBuffer);
    
    xWeatherQueue = xQueueCreateStatic(3, sizeof(WeatherData_t),
                                       ucWeatherQueueStorage, &xWeatherQueueBuffer);
    
    // 创建互斥锁(静态方式)
    xDataMutex = xSemaphoreCreateMutexStatic(&xDataMutexBuffer);
    
    // 创建二进制信号量(静态方式)
    xLCDReadySemaphore = xSemaphoreCreateBinaryStatic(&xLCDReadySemaphoreBuffer);
    
    // 初始化系统数据
    memset(&system_data, 0, sizeof(SystemData_t));
    system_data.rtc_time.Hours = 0;
    system_data.rtc_time.Minutes = 0;
    system_data.rtc_time.Seconds = 0;
    system_data.rtc_date.Year = 24;
    system_data.rtc_date.Month = 1;
    system_data.rtc_date.Date = 1;
    system_data.temperature = 25.0f;
    strcpy(system_data.weather_condition, "初始化");
    system_data.weather_icon = 'I';
    system_data.update_flags = 0;
    
    printf("FreeRTOS initialized successfully.\r\n");
}
3.2.3 硬件外设初始化

在FreeRTOS启动前初始化必要的硬件外设:

void Hardware_Init(void)
{
    // 初始化系统时钟
    SystemClock_Config();
    
    // 初始化GPIO
    MX_GPIO_Init();
    
    // 初始化串口1(用于ESP32 AT指令通信)
    MX_USART1_UART_Init();
    printf("USART1 initialized for ESP32 communication.\r\n");
    
    // 初始化I2C1(用于MPU6050)
    MX_I2C1_Init();
    printf("I2C1 initialized for MPU6050.\r\n");
    
    // 初始化SPI1(用于LCD)
    MX_SPI1_Init();
    printf("SPI1 initialized for LCD display.\r\n");
    
    // 初始化RTC
    MX_RTC_Init();
    printf("RTC initialized.\r\n");
    
    // 初始化LCD
    LCD_Init();
    LCD_Clear(BLACK);
    LCD_ShowString(10, 10, "FreeRTOS System", WHITE, BLACK, 16, 0);
    LCD_ShowString(10, 30, "Initializing...", YELLOW, BLACK, 12, 0);
    
    // LCD初始化完成,释放信号量
    if (xLCDReadySemaphore != NULL) {
        xSemaphoreGive(xLCDReadySemaphore);
    }
    
    printf("All hardware peripherals initialized.\r\n");
}
3.2.4 系统启动流程

完整的系统启动流程:

int main(void)
{
    // HAL库初始化
    HAL_Init();
    
    // 硬件外设初始化
    Hardware_Init();
    
    // FreeRTOS内核对象初始化
    FreeRTOS_Init();
    
    // 创建任务(静态方式)
    xTimeSyncTaskHandle = xTaskCreateStatic(
        TimeSyncTask,
        "TimeSync",
        sizeof(xTimeSyncTaskStack) / sizeof(StackType_t),
        NULL,
        TASK_PRIORITY_TIME_SYNC,
        xTimeSyncTaskStack,
        &xTimeSyncTaskTCB
    );
    
    xWeatherFetchTaskHandle = xTaskCreateStatic(
        WeatherFetchTask,
        "WeatherFetch",
        sizeof(xWeatherFetchTaskStack) / sizeof(StackType_t),
        NULL,
        TASK_PRIORITY_WEATHER_FETCH,
        xWeatherFetchTaskStack,
        &xWeatherFetchTaskTCB
    );
    
    xLCDDisplayTaskHandle = xTaskCreateStatic(
        LCDDisplayTask,
        "LCDDisplay",
        sizeof(xLCDDisplayTaskStack) / sizeof(StackType_t),
        NULL,
        TASK_PRIORITY_LCD_DISPLAY,
        xLCDDisplayTaskStack,
        &xLCDDisplayTaskTCB
    );
    
    // 启动调度器
    vTaskStartScheduler();
    
    // 如果调度器启动失败,执行错误处理
    for (;;) {
        // 错误处理:闪烁LED或输出错误信息
        HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
        HAL_Delay(500);
        printf("ERROR: FreeRTOS scheduler failed to start!\r\n");
    }
}
3.2.5 中断配置

配置FreeRTOS相关的中断:

// stm32f1xx_it.c - 中断处理
#include "FreeRTOS.h"
#include "task.h"

// SysTick中断处理(FreeRTOS心跳)
void SysTick_Handler(void)
{
    HAL_IncTick();
    
    // FreeRTOS系统时钟滴答
    if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) {
        xPortSysTickHandler();
    }
}

// 串口中断处理(用于ESP32通信)
void USART1_IRQHandler(void)
{
    // HAL库中断处理
    HAL_UART_IRQHandler(&huart1);
    
    // 如果使用FreeRTOS的流缓冲区或消息缓冲区
    // 需要在这里处理任务通知或信号量
}

// PendSV中断(任务切换)
__attribute__((naked)) void PendSV_Handler(void)
{
    __asm volatile (
        "mrs r0, psp\n"
        "ldr r1, =pxCurrentTCB\n"
        "ldr r1, [r1]\n"
        "stmdb r0!, {r4-r11}\n"
        "str r0, [r1]\n"
        "push {lr}\n"
        "vpush {s0-s15}\n"
        "vpush {s16-s31}\n"
        "cpsid i\n"
        "bl vTaskSwitchContext\n"
        "cpsie i\n"
        "vpop {s16-s31}\n"
        "vpop {s0-s15}\n"
        "pop {lr}\n"
        "ldr r1, =pxCurrentTCB\n"
        "ldr r1, [r1]\n"
        "ldr r0, [r1]\n"
        "ldmia r0!, {r4-r11}\n"
        "msr psp, r0\n"
        "bx lr\n"
    );
}

// SVC中断(启动第一个任务)
__attribute__((naked)) void SVC_Handler(void)
{
    __asm volatile (
        "ldr r1, =pxCurrentTCB\n"
        "ldr r1, [r1]\n"
        "ldr r0, [r1]\n"
        "ldmia r0!, {r4-r11}\n"
        "msr psp, r0\n"
        "mov r0, #0\n"
        "msr basepri, r0\n"
        "orr r14, #0xd\n"
        "bx r14\n"
    );
}

配置要点总结

  1. 堆栈分配:根据任务复杂度合理分配堆栈大小
  2. 优先级设置:时间同步(3) > 天气获取(2) > LCD显示(1)
  3. 内存管理:使用静态分配提高系统可靠性
  4. 中断配置:正确配置SysTick、PendSV和SVC中断
  5. 错误处理:包含调度器启动失败的恢复机制
  6. 硬件初始化:确保所有外设在FreeRTOS启动前就绪

3.3 任务创建与调度实现

在完成FreeRTOS配置和初始化后,接下来创建三个核心任务并实现任务间的协同调度。

3.3.1 时间同步任务实现

时间同步任务负责通过ESP32连接NTP服务器获取网络时间,并更新系统RTC。

// freertos_tasks.c - 时间同步任务
#include "esp_at.h"
#include "rtc_time.h"
#include "system_data.h"

void TimeSyncTask(void *pvParameters)
{
    TickType_t xLastWakeTime;
    const TickType_t xFrequency = pdMS_TO_TICKS(30 * 60 * 1000); // 30分钟同步一次
    
    // 初始化任务变量
    xLastWakeTime = xTaskGetTickCount();
    
    for (;;) {
        // 等待LCD初始化完成
        if (xSemaphoreTake(xLCDReadySemaphore, portMAX_DELAY) == pdTRUE) {
            // 连接Wi-Fi
            if (ESP_ConnectWiFi("Your_SSID", "Your_Password") == ESP_OK) {
                // 获取NTP时间
                RTC_TimeTypeDef ntp_time;
                RTC_DateTypeDef ntp_date;
                
                if (ESP_GetNTPTime(&ntp_time, &ntp_date) == ESP_OK) {
                    // 更新RTC
                    HAL_RTC_SetTime(&hrtc, &ntp_time, RTC_FORMAT_BIN);
                    HAL_RTC_SetDate(&hrtc, &ntp_date, RTC_FORMAT_BIN);
                    
                    // 更新共享数据
                    if (xSemaphoreTake(xDataMutex, portMAX_DELAY) == pdTRUE) {
                        system_data.rtc_time = ntp_time;
                        system_data.rtc_date = ntp_date;
                        system_data.update_flags |= 0x01; // 设置时间更新标志
                        xSemaphoreGive(xDataMutex);
                    }
                    
                    printf("Time synchronized: %04d-%02d-%02d %02d:%02d:%02d\r\n",
                           ntp_date.Year + 2000, ntp_date.Month, ntp_date.Date,
                           ntp_time.Hours, ntp_time.Minutes, ntp_time.Seconds);
                }
                
                // 断开Wi-Fi连接
                ESP_DisconnectWiFi();
            }
            
            // 释放信号量
            xSemaphoreGive(xLCDReadySemaphore);
        }
        
        // 等待下一个同步周期
        vTaskDelayUntil(&xLastWakeTime, xFrequency);
    }
}
3.3.2 天气获取任务实现

天气获取任务负责通过心知天气API获取实时天气信息。

// freertos_tasks.c - 天气获取任务
#include "esp_at.h"
#include "weather_api.h"
#include "system_data.h"

void WeatherFetchTask(void *pvParameters)
{
    TickType_t xLastWakeTime;
    const TickType_t xFrequency = pdMS_TO_TICKS(10 * 60 * 1000); // 10分钟更新一次
    char api_key[] = "YOUR_API_KEY"; // 心知天气API密钥
    char location[] = "beijing";     // 城市拼音
    
    xLastWakeTime = xTaskGetTickCount();
    
    for (;;) {
        // 等待LCD初始化完成
        if (xSemaphoreTake(xLCDReadySemaphore, portMAX_DELAY) == pdTRUE) {
            // 连接Wi-Fi
            if (ESP_ConnectWiFi("Your_SSID", "Your_Password") == ESP_OK) {
                // 获取天气数据
                WeatherData_t weather_data;
                
                if (WeatherAPI_GetCurrent(api_key, location, &weather_data) == WEATHER_OK) {
                    // 更新共享数据
                    if (xSemaphoreTake(xDataMutex, portMAX_DELAY) == pdTRUE) {
                        system_data.temperature = weather_data.temperature;
                        strncpy(system_data.weather_condition, 
                                weather_data.condition, 
                                sizeof(system_data.weather_condition) - 1);
                        system_data.weather_icon = weather_data.icon;
                        system_data.update_flags |= 0x02; // 设置天气更新标志
                        xSemaphoreGive(xDataMutex);
                    }
                    
                    printf("Weather updated: %.1f°C, %s\r\n", 
                           weather_data.temperature, weather_data.condition);
                }
                
                // 断开Wi-Fi连接
                ESP_DisconnectWiFi();
            }
            
            // 释放信号量
            xSemaphoreGive(xLCDReadySemaphore);
        }
        
        // 等待下一个更新周期
        vTaskDelayUntil(&xLastWakeTime, xFrequency);
    }
}
3.3.3 LCD显示任务实现

LCD显示任务负责以60FPS的刷新率更新显示屏内容。

// freertos_tasks.c - LCD显示任务
#include "lcd_spi.h"
#include "system_data.h"

void LCDDisplayTask(void *pvParameters)
{
    TickType_t xLastWakeTime;
    const TickType_t xFrameTime = pdMS_TO_TICKS(16); // 约60FPS (1000ms/60 ≈ 16ms)
    uint8_t last_update_flags = 0;
    
    // 等待LCD初始化完成
    xSemaphoreTake(xLCDReadySemaphore, portMAX_DELAY);
    xSemaphoreGive(xLCDReadySemaphore); // 立即释放,其他任务可以开始
    
    xLastWakeTime = xTaskGetTickCount();
    
    for (;;) {
        uint8_t current_flags = 0;
        
        // 读取共享数据
        if (xSemaphoreTake(xDataMutex, portMAX_DELAY) == pdTRUE) {
            current_flags = system_data.update_flags;
            xSemaphoreGive(xDataMutex);
        }
        
        // 检查是否需要更新显示
        if ((current_flags & 0x03) != last_update_flags) {
            // 清除屏幕
            LCD_Clear(BLACK);
            
            // 显示时间
            char time_str[32];
            RTC_TimeTypeDef current_time;
            RTC_DateTypeDef current_date;
            
            if (xSemaphoreTake(xDataMutex, portMAX_DELAY) == pdTRUE) {
                current_time = system_data.rtc_time;
                current_date = system_data.rtc_date;
                xSemaphoreGive(xDataMutex);
            }
            
            snprintf(time_str, sizeof(time_str), "%02d:%02d:%02d", 
                     current_time.Hours, current_time.Minutes, current_time.Seconds);
            LCD_ShowString(20, 20, time_str, WHITE, BLACK, 24, 0);
            
            // 显示日期
            char date_str[32];
            snprintf(date_str, sizeof(date_str), "%04d-%02d-%02d", 
                     current_date.Year + 2000, current_date.Month, current_date.Date);
            LCD_ShowString(30, 50, date_str, CYAN, BLACK, 16, 0);
            
            // 显示天气
            char weather_str[64];
            float temp;
            char condition[32];
            
            if (xSemaphoreTake(xDataMutex, portMAX_DELAY) == pdTRUE) {
                temp = system_data.temperature;
                strncpy(condition, system_data.weather_condition, sizeof(condition) - 1);
                xSemaphoreGive(xDataMutex);
            }
            
            snprintf(weather_str, sizeof(weather_str), "%.1f°C %s", temp, condition);
            LCD_ShowString(25, 80, weather_str, YELLOW, BLACK, 16, 0);
            
            // 显示天气图标
            char icon = '?';
            if (xSemaphoreTake(xDataMutex, portMAX_DELAY) == pdTRUE) {
                icon = system_data.weather_icon;
                xSemaphoreGive(xDataMutex);
            }
            
            char icon_str[2] = {icon, '\0'};
            LCD_ShowString(100, 80, icon_str, GREEN, BLACK, 24, 0);
            
            last_update_flags = current_flags & 0x03;
        }
        
        // 维持60FPS刷新率
        vTaskDelayUntil(&xLastWakeTime, xFrameTime);
    }
}
3.3.4 任务间通信机制

任务间通过共享数据结构和信号量进行通信:

// system_data.h - 系统共享数据结构
#ifndef __SYSTEM_DATA_H
#define __SYSTEM_DATA_H

#include "stm32f1xx_hal.h"

// 天气数据结构
typedef struct {
    float temperature;          // 温度
    char condition[32];         // 天气状况
    char icon;                  // 天气图标字符
    uint8_t humidity;           // 湿度
    uint16_t pressure;          // 气压
} WeatherData_t;

// 系统共享数据结构
typedef struct {
    RTC_TimeTypeDef rtc_time;   // RTC时间
    RTC_DateTypeDef rtc_date;   // RTC日期
    WeatherData_t weather;       // 天气数据
    uint8_t update_flags;       // 更新标志位
} SystemData_t;

// 更新标志位定义
#define TIME_UPDATE_FLAG    0x01    // 时间更新标志
#define WEATHER_UPDATE_FLAG 0x02    // 天气更新标志
#define ALL_UPDATE_FLAG     0x03    // 全部更新标志

// 全局变量声明
extern SystemData_t system_data;
extern SemaphoreHandle_t xDataMutex;
extern SemaphoreHandle_t xLCDReadySemaphore;

// 函数声明
void SystemData_Init(void);
void SystemData_UpdateTime(RTC_TimeTypeDef *time, RTC_DateTypeDef *date);
void SystemData_UpdateWeather(WeatherData_t *weather);
uint8_t SystemData_GetUpdateFlags(void);
void SystemData_ClearUpdateFlags(uint8_t flags);

#endif /* __SYSTEM_DATA_H */
// system_data.c - 系统共享数据管理
#include "system_data.h"

SystemData_t system_data;
SemaphoreHandle_t xDataMutex = NULL;
SemaphoreHandle_t xLCDReadySemaphore = NULL;

void SystemData_Init(void)
{
    memset(&system_data, 0, sizeof(SystemData_t));
    
    // 初始化默认值
    system_data.rtc_time.Hours = 12;
    system_data.rtc_time.Minutes = 0;
    system_data.rtc_time.Seconds = 0;
    
    system_data.rtc_date.Year = 24;
    system_data.rtc_date.Month = 1;
    system_data.rtc_date.Date = 1;
    
    system_data.weather.temperature = 25.0f;
    strcpy(system_data.weather.condition, "Sunny");
    system_data.weather.icon = '☀';
    system_data.weather.humidity = 50;
    system_data.weather.pressure = 1013;
    
    system_data.update_flags = 0;
}

void SystemData_UpdateTime(RTC_TimeTypeDef *time, RTC_DateTypeDef *date)
{
    if (xSemaphoreTake(xDataMutex, portMAX_DELAY) == pdTRUE) {
        system_data.rtc_time = *time;
        system_data.rtc_date = *date;
        system_data.update_flags |= TIME_UPDATE_FLAG;
        xSemaphoreGive(xDataMutex);
    }
}

void SystemData_UpdateWeather(WeatherData_t *weather)
{
    if (xSemaphoreTake(xDataMutex, portMAX_DELAY) == pdTRUE) {
        system_data.weather = *weather;
        system_data.update_flags |= WEATHER_UPDATE_FLAG;
        xSemaphoreGive(xDataMutex);
    }
}

uint8_t SystemData_GetUpdateFlags(void)
{
    uint8_t flags = 0;
    if (xSemaphoreTake(xDataMutex, portMAX_DELAY) == pdTRUE) {
        flags = system_data.update_flags;
        xSemaphoreGive(xDataMutex);
    }
    return flags;
}

void SystemData_ClearUpdateFlags(uint8_t flags)
{
    if (xSemaphoreTake(xDataMutex, portMAX_DELAY) == pdTRUE) {
        system_data.update_flags &= ~flags;
        xSemaphoreGive(xDataMutex);
    }
}
3.3.5 任务调度流程图

系统启动

硬件初始化

FreeRTOS初始化

创建时间同步任务
优先级: 3

创建天气获取任务
优先级: 2

创建LCD显示任务
优先级: 1

等待30分钟周期

等待10分钟周期

等待16ms帧周期

连接Wi-Fi

获取NTP时间

更新RTC时间

设置时间更新标志

断开Wi-Fi

连接Wi-Fi

调用天气API

解析JSON响应

更新天气数据

设置天气更新标志

断开Wi-Fi

检查更新标志

有更新?

读取共享数据

更新LCD显示

清除更新标志

共享数据结构

任务调度关键点

  1. 优先级策略:时间同步(3) > 天气获取(2) > LCD显示(1)
  2. 同步机制:使用互斥锁保护共享数据,避免竞态条件
  3. 周期控制vTaskDelayUntil确保精确的任务执行周期
  4. 资源管理:Wi-Fi连接完成后及时释放,避免资源占用
  5. 错误处理:每个任务都有完善的错误检测和恢复机制
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐