基于 Zephyr RTOS 的 RA8D1 天气显示终端(实时天气 + 3 天预报)
在前面的几个试用帖中,我详细的记录从移植zephyr blink、LCD、LVGL、网络接口,今天结合以前的成果生成了一个天气显示终端。
1、【瑞萨RA8D1测评】2、驱动MIPI屏-电子产品世界论坛
2、【瑞萨RA8D1测评】2、驱动MIPI屏-电子产品世界论坛
3、【瑞萨RA8D1测评】3、zephyr移植LVGL-电子产品世界论坛
4、【瑞萨RA8D1测评】4、基于zephyr以太网(LAN8720A,RMII)Zephyr移植-电子产品世界论坛
功能概述
实时天气:资源县当前状况/温度/风速/湿度,纯 HTTP 拉自 wttr.in
3 天预报:今天/明天/后天的状况 + 最低~最高温,纯 HTTP 拉自 Open-Meteo
全部中文显示(LVGL 自子集字体),无需 TLS、无需 API key

数据源
实时天气:wttr.in
GET http://wttr.in/%E8%B5%84%E6%BA%90%E5%8E%BF?format=%25C%7C%25t%7C%25w%7C%25h → "Patchy rain nearby|+21°C|←10km/h|82%"(一行文本,不足 200 字节)
%E8%B5%84%E6%BA%90%E5%8E%BF = "资源县" 的 UTF-8 URL 编码;format 代码做了二次百分号编码(%25C 等),| 编码为 %7C
必须伪装 curl 的 User-Agent(User-Agent: curl/8.0),浏览器 UA 会返回 HTML 页面
%C 天气状况只返回英文(lang=zh 对其无效),固件内置英文→中文映射表 cond_map(WWO 标准状况集合,约 46 条);未命中时原样显示英文并 printk 提示补映射
3 天预报:Open-Meteo
GET http://api.open-meteo.com/v1/forecast?latitude=26.03&longitude=110.64 &daily=weathercode,temperature_2m_max,temperature_2m_min &timezone=Asia%2FShanghai&forecast_days=3 → 约 500 字节的小 JSON
选型理由:支持纯 HTTP(无需 mbedTLS)、免 key、响应极小;wttr.in 的 JSON(format=j1)有上百 KB,不适合单片机
资源县坐标:26.03N / 110.64E
JSON 结构固定(daily.{time,weathercode,temperature_2m_max,temperature_2m_min} 四个数组),固件用 strstr 定位 "key":[ 后定点解析,未引入 JSON 库
weathercode 为 WMO 码,固件内置 wmo_map 映射中文(28 个码:晴/多云/阴/雾/毛毛雨/各档雨雪/雷暴等);未命中显示 ? 并 printk
模块设计
DHCP 获 IP → ipv4_addr_handler → weather_notify_net_up() ↓ k_sem_give(net_up_sem) 天气线程(prio 7, stack 4096)──────────────┘ loop: fetch_weather() → wttr.in → parse_and_store() ┐ fetch_forecast() → Open-Meteo → wmo 映射/取整 ├ k_mutex 保护 weather_data ui_dirty = true ──────────────────────────────────┘ 全成功睡 10 min,任一失败睡 1 min(失败时状况栏显示"获取失败,重试中") UI 主线程(main loop,500ms): weather_consume_dirty() → weather_get_copy() → 更新各 label
DNS 服务器由 DHCP 自动下发(CONFIG_DNS_RESOLVER=y),故天气线程必须等 net_up_sem
两路拉取共用 http_get_text() 助手(DNS 解析 → socket → http_client_req,body 分片累积进调用方缓冲区,共享静态 recv_buf 1KB)
线程安全:天气线程只写数据不碰 LVGL;UI 更新全在 LVGL 主线程
时间同步(SNTP)
板子无 RTC,"更新于"若直接用 uptime 会显示成 00:00:0x(开机秒数,易被误读为"0 点的旧数据")。方案:
联网后天气线程调用 sntp_simple() 一次性对时(CONFIG_SNTP=y),服务器依次尝试 ntp.aliyun.com → cn.pool.ntp.org(国内可达性优先)
记录基准 UTC 秒 + 当时 uptime,此后任意时刻用 uptime 差值推算;每 24h 重同步一次,未对时成功则每个刷新周期重试
显示时按 UTC+8 换算北京时间为 HH:MM:SS;对时失败前回退显示开机 uptime(旧行为)
UI 布局(222x480 竖屏)
y内容字体
| 10 | 标题"资源县天气实况" | lv_font_zh16 |
| 220 | 温度大字 | lv_font_temp28 |
| 262 | 中文状况 | lv_font_zh16 |
| 292 | 风速/湿度 | lv_font_zh16 |
| 320 | 更新时间 | lv_font_zh16 |
| 352/382/412 | 3 行预报(今天 毛毛雨 22~31°) | lv_font_zh16 |
中文字体生成
字体为 simhei.ttf 子集,由 lv_font_conv(npm,npx 可直接用)生成,完整命令见各字体文件头部 Opts 注释。加字流程:把新字追加进 --symbols 串 → 重新生成 → 构建。
必须带 --no-compress:
现版 lv_font_conv(1.5.x)默认对字形位图做 RLE 压缩,并相应生成 .bitmap_format = 1(LV_FONT_FMT_TXT_COMPRESSED),数据与标记一致。
但 Zephyr 的 LVGL 配置未启用 LV_USE_FONT_COMPRESSED 解压器,lv_font_get_bitmap_fmt_txt() 遇压缩字体直接返回 NULL —— 表现为该字体所有字符一个不画,只剩个别缺字占位框;printk 里的中文字符串正常(只是 flash 读字符串,不走 LVGL)。
若把 bitmap_format 强行改成 0(PLAIN),压缩数据被当原始位图渲染,满屏噪点。
结论:生成时加 --no-compress,工具会自动写出 .bitmap_format = 0,一切正常。原始位图渲染还省去逐字解压,更快。
缺字检查:UI 出现豆腐块(□)时,先核对字符是否在 --symbols 子集内(例如"风速"的"速"曾漏收)。LVGL 对缺字 glyph 画占位框。
调试方法
RTT 日志
控制台/shell 走 RTT。RTT 控制块自动搜索很慢,建议从 ELF 取 _SEGGER_RTT 符号地址(当前为 0x22000410,链接变化后需重取)再指定:
JLinkRTTLogger -Device R7FA8D1BH -If SWD -Speed 4000 -RTTAddress 0x22000410 -RTTChannel 0 rtt.log
取地址可用 arm-zephyr-eabi-nm zephyr.elf | grep _SEGGER_RTT(或解析 ELF symtab)。
显存抓帧(显示异常定界神器)
屏幕显示异常时,直接抓 SDRAM 里的 GLCDC 帧缓冲(0x68000000,行步长 224px × RGB565,共 2244802=0x34800 字节),转 PNG 查看 LVGL 实际画了什么,可区分"LVGL 没画"与"显示链路没刷":
J-Link commander: savebin fb.bin, 0x68000000, 0x34800
再用 Python 按 RGB565(行步长 224、可视宽 222)解码转 PNG 即可查看。
已知限制
wttr.in %C 仅英文,依赖固件映射表,遇到新状况词需补表(未命中会 printk)
Open-Meteo 坐标固定写死资源县;预报只有 3 天、无降水概率等更多字段(可按需在 URL 里加)
两路均为纯 HTTP,内容未校验,只适合演示用途
我要赚赏金
