STM32F103C8 + FreeRTOS + ESP32 学习记录(二):ESP32连接WiFi热点获取时间数据
基于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 | 程序烧录与调试 |
| 连接线 | 杜邦线(公对公、公对母) | 若干 | 硬件连接 |
各模块选型理由:
-
STM32F103C8T6(Blue Pill):
- Cortex-M3内核,72MHz主频,性能足够运行FreeRTOS
- 64KB Flash,20KB RAM,满足本项目需求
- 丰富的GPIO和外设(USART、SPI、I2C等)
- 成本低廉,社区资源丰富
-
ESP32(ESP-32S):
- 双核处理器,支持Wi-Fi和蓝牙
- 内置TCP/IP协议栈,支持AT指令控制
- 与STM32通过UART通信,接口简单
- 功耗较低,适合嵌入式应用
-
MPU6050:
- 集成三轴加速度计和三轴陀螺仪
- I2C接口,占用GPIO少
- 内置DMP(数字运动处理器),减轻主控负担
-
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引脚连接到电源地总线
注意事项:
- 电平匹配:STM32和ESP32均为3.3V电平,可直接连接
- 上拉电阻:I2C总线需要4.7kΩ上拉电阻至3.3V
- 电源滤波:每个模块的VCC引脚附近应加0.1μF去耦电容
- 布线规范:信号线尽量短,避免平行走线,减少干扰
二、软件环境搭建
2.1 STM32开发环境配置
-
安装STM32CubeIDE:从ST官网下载安装
-
安装STM32F1 HAL库:
- 在STM32CubeIDE中,通过"Help"→"Manage Embedded Software Packages"安装STM32F1系列HAL库
- 或使用STM32CubeMX生成初始化代码
-
安装FreeRTOS:
// 通过STM32CubeMX添加FreeRTOS中间件 // 或手动下载FreeRTOS源码:https://www.freertos.org/ -
串口驱动安装:
- Windows:安装CH340/CH341串口驱动
- Linux:通常自动识别,无需额外安装
2.2 ESP32-C3 AT固件烧录(针对ESP32-C3-MINI)
-
下载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和蓝牙功能
-
硬件连接准备:
- ESP32-C3-MINI开发板通常自带USB转串口芯片,可直接通过USB线连接电脑
- 如需手动进入下载模式:
- 按住BOOT按钮(或IO9引脚拉低)
- 按一下RESET按钮
- 松开BOOT按钮
- 确认电脑识别到串口设备(Windows:设备管理器查看COM端口)
-
使用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.binWindows用户:也可以使用Flash Download Tool,但需要选择正确的芯片类型为"ESP32-C3"
-
ESP32-C3特殊配置:
- ESP32-C3使用RISC-V架构,与ESP32的Xtensa架构不同
- AT固件默认波特率:115200
- 默认Wi-Fi模式:Station模式
- 支持蓝牙5.0(需启用相应AT命令)
-
验证AT固件:
- 使用串口调试工具(如Putty、SecureCRT、Arduino串口监视器)
- 设置波特率:115200,数据位:8,停止位:1,无校验
- 发送"AT"命令,应收到"OK"响应
- 发送"AT+GMR"查看固件版本和编译信息
- 发送"AT+CWMODE?"查看当前Wi-Fi模式
-
常见问题:
- 无法进入下载模式:检查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; // 数据互斥锁
任务调度流程图:
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"
);
}
配置要点总结:
- 堆栈分配:根据任务复杂度合理分配堆栈大小
- 优先级设置:时间同步(3) > 天气获取(2) > LCD显示(1)
- 内存管理:使用静态分配提高系统可靠性
- 中断配置:正确配置SysTick、PendSV和SVC中断
- 错误处理:包含调度器启动失败的恢复机制
- 硬件初始化:确保所有外设在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 任务调度流程图
任务调度关键点:
- 优先级策略:时间同步(3) > 天气获取(2) > LCD显示(1)
- 同步机制:使用互斥锁保护共享数据,避免竞态条件
- 周期控制:
vTaskDelayUntil确保精确的任务执行周期 - 资源管理:Wi-Fi连接完成后及时释放,避免资源占用
- 错误处理:每个任务都有完善的错误检测和恢复机制
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)