基于 STM32F407、FreeRTOS 与 ESP32-C3的嵌入式天气时钟。
Weather Clock 是一套运行在 STM32F407VET6 上的嵌入式天气时钟系统。
项目以 FreeRTOS 为运行基础,通过 ESP32-C3 AT 固件接入 Wi-Fi,使用 SNTP 同步网络时间,通过 HTTP 请求获取心知天气数据,同时采集 AHT20 温湿度传感器数据,最终在 240 × 320 分辨率的 ST7789 LCD 上集中显示时间、日期、室内温湿度、室外温度、天气图标及网络状态。
项目重点不只是完成多个外设功能,而是围绕以下问题进行设计:
- 如何划分系统任务并控制不同业务的刷新周期;
- 如何避免网络请求、传感器采集阻塞 FreeRTOS 软件定时器;
- 如何解决多个业务模块同时访问 LCD 时的资源竞争;
- 如何利用 DMA、中断和信号量降低 CPU 轮询开销;
- 如何通过分层日志、超时退出和错误页面定位软硬件问题;
- 如何在资源有限的 MCU 上完成图片、中文字体和天气数据解析。
- 基于 FreeRTOS 实现多任务调度、软件定时器、消息队列和信号量同步;
- 通过 ESP32-C3 AT 指令接入 Wi-Fi;
- 获取并监测 SSID、BSSID、信道及 RSSI 等网络状态;
- 通过 SNTP 获取网络时间,并同步至 STM32 内部 RTC;
- 使用 LSE 作为 RTC 时钟源,实现本地时间持续运行;
- 通过 HTTP 请求获取心知天气实时天气数据;
- 解析城市、天气现象、天气代码和室外温度;
- 根据天气代码映射晴、阴、多云、雨、雪、雷阵雨等图标;
- 通过 AHT20 采集室内温度和湿度;
- 使用 ST7789 显示时间、日期、室内环境及室外天气;
- 支持中文点阵字库和 RGB565 图片资源;
- 通过 USART 调试串口输出系统运行过程和错误信息;
- 对 Wi-Fi、SNTP、HTTP、I2C 和内存异常设置分阶段诊断信息。
| 欢迎页面 | Wi-Fi 连接页面 |
|---|---|
![]() |
![]() |
| 天气主页面 | Wi-Fi 错误页面 |
|---|---|
![]() |
![]() |
当前仓库中展示的是 UI 设计稿。后续将补充实物运行照片、上电演示视频和串口调试截图。
| 类型 | 技术 |
|---|---|
| MCU | STM32F407VET6,ARM Cortex-M4F |
| 编程语言 | C |
| RTOS | FreeRTOS |
| 固件库 | STM32F4 Standard Peripheral Library |
| 开发环境 | Keil MDK5、ARM Compiler 5 |
| 调试工具 | ST-Link、串口助手、逻辑分析仪 |
| 网络模块 | ESP32-C3,AT 固件 |
| 显示模块 | ST7789,240 × 320,RGB565 |
| 环境传感器 | AHT20 |
| 网络协议 | Wi-Fi、HTTP、SNTP |
| 数据接口 | USART、SPI、I2C |
| 系统机制 | Task、Queue、Software Timer、Semaphore、DMA、Interrupt |
| 天气服务 | 心知天气 API |
项目按照应用层、系统服务层、驱动层和硬件层进行组织。
flowchart TB
subgraph APP["应用层"]
INIT["启动流程<br/>main_init"]
CLOCK["时间与日期更新"]
INDOOR["室内温湿度采集"]
WEATHER["室外天气更新"]
WIFI["Wi-Fi 状态监测"]
PAGE["页面与局部刷新"]
end
subgraph SERVICE["FreeRTOS 系统服务层"]
TIMER["软件定时器"]
WORKQ["工作队列<br/>Queue + Worker Task"]
UIQ["UI 消息队列"]
SYNC["二值信号量"]
end
subgraph DRIVER["驱动层"]
ESPAT["ESP32-C3 AT 驱动"]
LCD["ST7789 驱动"]
SENSOR["AHT20 驱动"]
RTC["RTC 驱动"]
CONSOLE["Console 调试串口"]
DELAY["TIM6 微秒时基"]
end
subgraph HARDWARE["硬件层"]
ESP32-C3["ESP32-C3"]
ST7789["ST7789 LCD"]
AHT20["AHT20"]
LSE["LSE + 内部 RTC"]
UART["USB-TTL / 串口工具"]
end
INIT --> TIMER
TIMER --> CLOCK
TIMER --> WORKQ
WORKQ --> WIFI
WORKQ --> INDOOR
WORKQ --> WEATHER
CLOCK --> PAGE
WIFI --> PAGE
INDOOR --> PAGE
WEATHER --> PAGE
PAGE --> UIQ
UIQ --> LCD
WIFI --> ESPAT
WEATHER --> ESPAT
CLOCK --> RTC
INDOOR --> SENSOR
ESPAT --> SYNC
LCD --> SYNC
CONSOLE --> SYNC
ESPAT --> ESP32-C3
LCD --> ST7789
SENSOR --> AHT20
RTC --> LSE
CONSOLE --> UART
系统启动后按照以下顺序完成初始化:
flowchart LR
RESET["系统复位"] --> LOW["底层时钟初始化"]
LOW --> WQ["创建 Workqueue"]
WQ --> INIT["创建 Init Task"]
INIT --> BOARD["RTC / AHT20 / Console 初始化"]
BOARD --> UI["创建 UI Task"]
UI --> WELCOME["显示欢迎页面"]
WELCOME --> AT["初始化 ESP32-C3AT 驱动"]
AT --> WIFI["连接 Wi-Fi"]
WIFI --> MAIN["显示天气主页面"]
MAIN --> APP["创建周期任务与软件定时器"]
APP --> DELETE["删除 Init Task"]
初始化任务优先级较高,用于确保系统按照确定顺序完成硬件、显示和网络初始化。初始化完成后主动调用 vTaskDelete(NULL) 删除自身,释放任务资源。
项目主要创建三个应用任务,并使用 FreeRTOS 内部 Timer Service Task 处理软件定时器。
| 任务 | 优先级 | 主要职责 |
|---|---|---|
| Init Task | 9 | 完成板级初始化、页面初始化、ESP32-C3初始化和 Wi-Fi 连接 |
| UI Task | 8 | 串行处理文字、图片和区域填充请求 |
| Workqueue Task | 5 | 执行网络、传感器和状态检查等周期工作 |
| Timer Service Task | 9 | 管理软件定时器并触发回调 |
FreeRTOS 关键配置:
- 抢占式调度;
- 系统节拍频率为 1000 Hz;
- 最大优先级数量为 10;
- 动态内存管理采用
heap_4; - FreeRTOS 堆空间设置为 92 KB;
- 启用软件定时器;
- 启用队列、互斥量和信号量;
- 启用栈溢出检测;
- 启用动态内存申请失败钩子;
- 使用
configASSERT捕获非法状态。
不同业务具有不同实时性要求,因此使用独立软件定时器控制刷新周期。
| 业务 | 更新周期 | 执行方式 |
|---|---|---|
| 时间显示 | 1 秒 | 软件定时器回调直接读取 RTC |
| 室内温湿度 | 3 秒 | 定时器触发后投递至 Workqueue |
| Wi-Fi 状态 | 5 秒 | 定时器触发后投递至 Workqueue |
| 室外天气 | 1 分钟 | 定时器触发后投递至 Workqueue |
| SNTP 校时 | 正常 1 小时 | 定时器触发后投递至 Workqueue |
| SNTP 失败重试 | 1 秒 | 动态修改下一次定时周期 |
软件定时器只负责产生调度事件。HTTP 请求、ESP32-C3 AT 交互和 AHT20 采集等可能发生等待的操作,被转移到 Workqueue Task 中执行。
这样可以避免耗时操作长时间占用 Timer Service Task,降低其他软件定时器被连带延迟的风险。
项目实现了一个轻量级工作队列:
Software Timer
│
│ 生成 work_message_t
▼
FreeRTOS Queue(长度 16)
│
▼
Workqueue Task
│
├── time_sync
├── wifi_update
├── inner_update
└── outdoor_update
工作项由函数指针和参数组成:
typedef void (*work_t)(void *param);
typedef struct
{
work_t work;
void *param;
} work_message_t;Workqueue 的作用:
- 将定时触发和具体业务执行解耦;
- 避免在软件定时器回调中执行阻塞操作;
- 对网络访问和传感器访问进行串行化;
- 统一记录工作项开始执行的调试日志;
- 减少为每个周期业务单独创建任务带来的栈空间开销。
当前设计使用单工作线程,因此结构简单、内存开销较低,但某个 HTTP 请求耗时较长时,会延迟队列中的其他工作。后续可以将网络任务和传感器任务拆分为不同工作队列。
所有 LCD 操作统一由 UI Task 执行。其他任务不能直接并发操作 ST7789,而是向 UI Queue 投递消息。
UI 支持三类操作:
UI_ACTION_FILL_COLOR
UI_ACTION_WRITE_STRING
UI_ACTION_DRAW_IMAGE数据流如下:
业务任务
│
│ ui_fill_color / ui_write_string / ui_draw_image
▼
UI Queue(长度 16)
│
▼
UI Task
│
▼
ST7789 Driver
│
▼
SPI2 + DMA
采用单独 UI Task 的原因:
- 防止多个任务同时访问 SPI 和 LCD;
- 避免显示命令与像素数据交叉;
- 将业务逻辑与具体屏幕驱动解耦;
- 便于后续扩展动画、页面切换和刷新合并;
- 保证界面绘制顺序与消息入队顺序一致。
对于字符串消息,调用方使用 pvPortMalloc 创建字符串副本,UI Task 完成显示后再调用 vPortFree 释放,避免调用方局部变量失效造成悬空指针。
室外天气更新链路如下:
sequenceDiagram
participant T as Outdoor Timer
participant W as Workqueue
participant E as ESP AT Driver
participant S as 心知天气 API
participant P as Weather Parser
participant U as UI Queue
participant L as ST7789
T->>W: 投递 outdoor_update
W->>E: esp_at_http_get(url)
E->>S: AT+HTTPCLIENT / HTTPS 请求
S-->>E: HTTP 响应与 JSON 数据
E-->>W: 返回响应缓冲区
W->>P: parse_seniverse_response()
P-->>W: 城市、天气、代码、温度
W->>W: 与上一次天气数据比较
W->>U: 温度和图标刷新消息
U->>L: SPI2 + DMA 刷新指定区域
天气解析没有引入完整 JSON 库,而是通过字符串搜索和受长度限制的 sscanf 提取所需字段,包括:
- 城市名称;
- 城市路径;
- 天气现象;
- 天气代码;
- 室外温度。
这种方式依赖少、代码量小,适合当前固定格式的响应,但对服务器字段格式变化的兼容性有限。后续可替换为轻量级 JSON 解析器。
ESP32-C3通过 USART2 与 STM32 通信:
- 波特率:115200;
- 数据格式:8N1;
- 发送:USART2 TX + DMA1 Stream6;
- 接收:USART2 RXNE 中断;
- 同步:FreeRTOS 二值信号量;
- 接收缓冲区:1024 字节。
驱动支持的主要 AT 功能:
| 功能 | AT 指令 |
|---|---|
| 模块在线检测 | AT |
| 恢复默认配置 | AT+RESTORE |
| 设置 Station 模式 | AT+CWMODE=1 |
| 连接 Wi-Fi | AT+CWJAP |
| 查询连接状态 | AT+CWSTATE? |
| 查询 AP 信息 | AT+CWJAP? |
| 配置 SNTP | AT+CIPSNTPCFG |
| 获取网络时间 | AT+CIPSNTPTIME? |
| 发起 HTTP 请求 | AT+HTTPCLIENT |
接收中断按行累积响应,并识别以下关键应答:
OK
ERROR
busy
ready
中断识别到完整应答后,通过 xSemaphoreGiveFromISR 唤醒等待任务,并调用 portYIELD_FROM_ISR 请求必要的任务切换。
每条 AT 命令均设置独立超时时间,避免 ESP32-C3异常或串口断线后任务永久阻塞。
系统通过 ESP32-C3获取 SNTP 时间,再同步到 STM32 内部 RTC。
时间链路:
ESP32-C3SNTP
│
▼
解析星期、月份、日期和时间
│
▼
写入 STM32 RTC
│
▼
RTC 使用 LSE 持续计时
│
▼
每秒刷新 LCD 时间和日期
RTC 驱动采用以下一致性处理:
- 写入 RTC 后重新读取并验证秒字段;
- 读取 RTC 时连续读取两次;
- 两次结果一致后才返回数据;
- 避免日期和时间寄存器跨秒更新时读取到不一致数据。
SNTP 获取失败或时间数据非法时,系统将下一次校时间隔缩短为 1 秒;成功后恢复为 1 小时。
AHT20 使用 I2C2 与 STM32 通信:
- I2C 频率:100 kHz;
- SCL:PB10;
- SDA:PB11;
- 7 位设备地址:
0x38; - STM32 标准库发送地址参数:
0x70。
驱动流程:
发送初始化/校准命令
│
▼
检查设备状态
│
▼
发送测量命令 0xAC 0x33 0x00
│
▼
轮询 Busy 状态
│
▼
读取 6 字节原始数据
│
▼
提取 20 位温度和湿度数据
│
▼
转换为实际温度和相对湿度
转换公式:
Humidity = RawHumidity × 100 / 2²⁰
Temperature = RawTemperature × 200 / 2²⁰ - 50
I2C 的 START、地址发送、字节发送和字节接收均设置微秒级超时。总线异常或设备无应答时,函数返回错误,而不是无限等待。
ST7789 使用 SPI2 与 STM32 通信:
| 信号 | STM32 引脚 |
|---|---|
| SCK | PB13 |
| MOSI | PC3 |
| MISO | PC2 |
| CS | PE2 |
| RESET | PE3 |
| DC | PE4 |
| BL | PE5 |
显示参数:
- 分辨率:240 × 320;
- 像素格式:RGB565;
- SPI 模式:Mode 0;
- 发送顺序:MSB First;
- SPI 分频:4;
- 像素数据传输:DMA1 Stream4 Channel 0。
寄存器命令使用 8 位 SPI 发送,像素数据切换为 16 位 SPI 发送。
大块像素数据通过 DMA 传输。DMA 完成中断释放二值信号量,等待任务被唤醒后继续下一段数据发送。
单次 DMA 传输长度受 NDTR 寄存器限制,因此驱动会将大块图像拆分为不超过 65535 个半字的数据块。
对于纯色填充,关闭 DMA 内存地址递增,使 DMA 重复读取同一个 RGB565 颜色值,减少额外颜色缓冲区占用。
系统只在数据发生变化时刷新对应区域。
例如:
- Wi-Fi 连接状态与上一次状态一致时不刷新;
- RTC 时间与上次读取结果一致时不刷新;
- 室内温湿度未变化时不刷新;
- 室外天气结构体与上一次结果一致时不刷新。
这样可以减少:
- SPI 总线传输量;
- DMA 传输次数;
- LCD 重复绘制;
- 界面闪烁;
- 无意义的 CPU 和内存访问。
天气代码会被映射为晴天、夜晚、阴天、多云、雨、雷阵雨、雪等图标。
项目通过“分阶段日志 + 超时退出 + 硬件信号验证”的方式定位问题。
AT 驱动可打印发送命令和完整响应:
[DEBUG] Send: AT
[DEBUG] Response:
OK
Wi-Fi 初始化被拆分为多个阶段:
[AT] init failed
[WIFI] init failed
[SNTP] init failed
[WIFI] Connection Timeout
通过日志可以判断问题位于:
- ESP32-C3未启动;
- USART 波特率或接线错误;
- AT 固件没有正确响应;
- Station 模式设置失败;
- Wi-Fi 名称或密码错误;
- AP 无法连接;
- SNTP 初始化失败。
硬件侧可使用串口工具或逻辑分析仪检查 PA2、PA3 上的 115200 波特率数据,确认 STM32 是否发出 AT 指令,以及 ESP32-C3是否返回响应。
温湿度采集分为三个阶段:
[AHT20] start measurement failed
[AHT20] wait for measurement failed
[AHT20] read measurement failed
定位顺序:
- 检查 PB10/PB11 是否存在上拉;
- 检查 START 和设备地址;
- 检查 AHT20 是否返回 ACK;
- 检查 Busy 位是否按预期清除;
- 检查是否读取到完整 6 字节数据;
- 检查原始值拼接和转换公式。
I2C 事件检查包含超时机制,因此传感器掉线不会使整个系统永久卡死。
当屏幕不显示或刷新异常时,可按以下顺序定位:
- 检查 RESET、DC、CS 和背光引脚电平;
- 检查 SPI2 的 SCK 和 MOSI 波形;
- 检查 SPI 数据位宽是否在寄存器和像素数据之间正确切换;
- 检查 DMA1 Stream4 Channel 0 配置;
- 检查 DMA 完成中断是否进入;
- 检查二值信号量是否正确释放;
- 检查 UI Queue 中的消息类型和坐标范围。
所有屏幕操作由 UI Task 串行执行,降低并发写屏导致花屏和命令错位的可能性。
系统分别输出:
[SNTP] get time failed
[SNTP] invalid date formate
[SNTP] sync time: ...
[RTC] ...
如果网络时间获取成功但屏幕时间异常,可以继续检查:
- 月份英文缩写转换;
- 星期转换;
- RTC 年份偏移;
- LSE 是否起振;
- RTC 写入后读取验证;
- 日期与时间是否跨秒读取。
天气更新区分两类错误:
[WEATHER] http error
[WEATHER] parse failed
http error 表示 AT 请求或网络通信失败;parse failed 表示收到了响应,但数据格式不符合解析器预期。
这种区分可以避免把所有问题都归因于“网络错误”。
FreeRTOS 配置了以下开发期保护:
Assert Called
Stack Overflowed
Malloc Failed
对应机制:
configASSERT:捕获队列、信号量、任务参数等非法状态;vApplicationStackOverflowHook:检测任务栈溢出;vApplicationMallocFailedHook:检测动态内存申请失败。
这些异常会通过调试串口打印文件、行号或任务名称,便于进一步定位。
| 模块 | 外设 | 引脚/配置 |
|---|---|---|
| ESP32-C3 TX/RX | USART2 | PA2 / PA3,115200 8N1 |
| 调试串口 | USART1 | PA9 / PA10,115200 8N1 |
| AHT20 | I2C2 | PB10 / PB11,100 kHz |
| ST7789 SCK | SPI2 | PB13 |
| ST7789 MOSI | SPI2 | PC3 |
| ST7789 MISO | SPI2 | PC2 |
| ST7789 CS | GPIO | PE2 |
| ST7789 RESET | GPIO | PE3 |
| ST7789 DC | GPIO | PE4 |
| ST7789 BL | GPIO | PE5 |
| RTC | STM32 内部 RTC | LSE 外部低速晶振 |
| 微秒时基 | TIM6 | 1 MHz 计数时基 |
实际接线前请确认显示模块和 ESP32-C3模块的供电要求,并确保模块与 STM32 共地。
Weather-Clock/
├── app/
│ ├── main.c # 程序入口与启动流程
│ ├── board.c # 板级时钟及基础外设初始化
│ ├── app.c # 周期业务与软件定时器
│ ├── workqueue.c # 工作队列
│ ├── ui.c # UI 消息队列与 UI Task
│ ├── wifi.c # Wi-Fi 初始化与连接流程
│ ├── weather.c # 天气响应解析
│ ├── font/ # 中文和数字点阵字库
│ ├── image/ # LCD 图片及天气图标
│ └── page/ # 欢迎、连接、错误和主页面
├── driver/
│ ├── aht20/ # AHT20 I2C 驱动
│ ├── console/ # USART 调试串口驱动
│ ├── esp_at/ # ESP32-C3 AT 驱动
│ ├── rtc/ # STM32 RTC 驱动
│ ├── st7789/ # ST7789 SPI/DMA 驱动
│ └── tim_delay/ # TIM6 微秒时基
├── firmware/
│ ├── cmsis/ # CMSIS 与启动文件
│ └── driver/ # STM32F4 标准外设库
├── third_lib/
│ └── freertos/ # FreeRTOS 内核与移植层
├── resources/
│ ├── design/ # UI 设计图
│ └── images/ # 原始图片资源
├── tools/
│ └── convert_chinese_font.py # 中文点阵字库转换工具
└── mdk/
└── stm32f407.uvprojx # Keil MDK 工程文件
git clone https://github.com/MrW1027/Weather-Clock.git
cd Weather-Clock打开:
app/wifi.h
将占位符替换为测试网络信息:
#define WIFI_SSID "YOUR_WIFI_SSID"
#define WIFI_PASSWD "YOUR_WIFI_PASSWORD"在心知天气平台注册并获取 API Key,然后在:
app/app.c
中找到天气请求地址,将:
YOUR_SENIVERSE_API_KEY
替换为自己的 API Key,并根据需要修改 location 参数。
请勿将真实 Wi-Fi 密码、API Key、Token 或其他敏感信息提交到公开仓库。
使用 Keil MDK5 打开:
mdk/stm32f407.uvprojx
推荐环境:
- Keil MDK5;
- STM32F4 Device Family Pack;
- ARM Compiler 5;
- ST-Link 下载器。
当前工程使用 ARM Compiler 5 配置。若使用 ARM Compiler 6,需要检查汇编启动文件、编译选项和标准库兼容性。
在 Keil 中执行:
Build → Download
下载完成后复位开发板。
使用 USB-TTL 或板载调试串口连接 USART1:
Baud Rate : 115200
Data Bits : 8
Stop Bits : 1
Parity : None
Flow Ctrl : None
正常启动时可以观察到类似日志:
[SYS] Build Date: ...
[DEBUG] Workqueue task started
[AT] inited
[WIFI] inited
[SNTP] inited
[WIFI] connecting
[WIFI] Connected
[SNTP] sync time: ...
[AHT20] Temperature: ...
[WEATHER] ...
室内采集、天气请求、Wi-Fi 检查和 SNTP 同步都属于低频业务。如果分别创建任务,需要为每个任务分配独立栈空间。
使用统一 Workqueue 可以减少任务数量和 RAM 占用,同时将耗时操作从软件定时器回调中移出。
ST7789 驱动及其 DMA 传输不是可重入资源。由多个任务直接调用可能导致寄存器命令、坐标数据和像素数据交叉。
使用 UI Queue 将所有显示请求串行化,可以从架构上规避并发写屏问题。
LCD 图片、颜色填充和串口输出包含较多连续数据。DMA 可以减少 CPU 逐字节搬运的开销,中断与信号量用于在传输完成后唤醒任务,使任务不需要持续轮询 DMA 状态。
当前天气接口只需要少量固定字段,字符串搜索方式占用空间较小,也便于理解数据解析过程。
代价是对响应格式变化的适应能力有限,因此后续计划替换为轻量级 JSON 解析器。
- Wi-Fi 和天气服务配置仍通过源码中的编译期宏或字符串设置;
- Wi-Fi 启动连接失败后会显示错误页面并停止初始化流程;
- 运行过程中可以检测 Wi-Fi 断开,但尚未实现完整自动重连状态机;
- ESP32-C3 AT 层使用固定大小的响应缓冲区;
- AT 驱动当前采用单请求串行处理,不支持多个并发请求;
- 天气响应解析依赖固定字段格式;
- 城市参数在源码中配置,暂不支持屏幕端动态选择;
- Workqueue 为单工作线程,长时间网络请求可能延迟其他工作;
- 当前缺少主机端单元测试和硬件在环自动化测试;
- 仓库暂未提供预编译固件。
- 将 Wi-Fi、API Key 和城市信息移入独立配置层;
- 实现 Wi-Fi 自动重连状态机和退避重试;
- 拆分网络工作队列与传感器工作队列;
- 分离 ESP32-C3 TX/RX 缓冲区并增强异常响应解析;
- 引入轻量级 JSON 解析器;
- 增加天气接口失败缓存和离线显示;
- 增加按键配置及参数持久化;
- 增加任务栈余量和运行状态统计;
- 增加实物照片、运行视频和逻辑分析仪波形;
- 增加天气解析器的主机端单元测试;
- 提供可直接烧录的 Release 固件。
本项目完成了从板级外设驱动、FreeRTOS 任务设计、网络通信、传感器采集到图形界面显示的完整嵌入式应用链路。
相比简单地将各个外设功能依次组合,项目重点处理了周期业务调度、阻塞操作隔离、LCD 并发访问、DMA 完成同步、网络异常检测和分阶段问题定位等工程问题。
通过该项目,实践了以下能力:
- STM32 外设寄存器和标准外设库开发;
- FreeRTOS 任务、队列、软件定时器和信号量;
- USART、SPI、I2C、DMA、中断和 RTC;
- ESP32-C3 AT 指令交互与网络状态解析;
- HTTP 天气数据获取和轻量级数据解析;
- 嵌入式 GUI 资源转换与局部刷新;
- 串口日志、超时机制和软硬件联合调试;
- 嵌入式系统模块划分与资源权衡。
无滞
GitHub:@MrW1027
项目地址:Weather-Clock



