2025-10-20 11:03:27 [显示全部楼层]
28浏览
查看: 28|回复: 2

[ESP8266/ESP32] Firebeetle 2 ESP32 C5体验之:显示网络天气

[复制链接]
本帖最后由 仔爸 于 2025-10-20 11:04 编辑

最近一直想学习使用esp32等MCU微控制器来驱动显示屏(包括液晶显示屏和墨水屏),趁着DFROBOT开展的FireBeetle 2 ESP32 C5评测项目,来尝试一下。
不过由于FireBeetle 2 ESP32 C5没有官方推出的micropython固件,因此刚拿到手的时候,这块C5真的像块砖头,在我手里活不起来。得益于PY学习笔记(苦行僧)自行编译的集成了不同显示屏驱动与LVGL轻量级通用图形库,让我可以直接拿来烧录使用。这里有一篇他的文章,我看了收获很多,大家也可以参考:[尝鲜DFRobot ESP32-C5开发板,玩转LVGL Micropython](https://mc.dfrobot.com.cn/thread-396470-1-1.html)

一、硬件对比
首先我们可以看一下esp32, esp32-c6和esp32-c5的比较,通过询问豆包,给出如下结果:ESP32 与 ESP32 C5、C6 对比:https://www.doubao.com/thread/wed26643be24cbd4a),给出的结论是:初代ESP32 主打性能与兼容性,C6 强化无线能力(双频段 + 音频),C5 则是低成本简化版,按需选择即可。

二、烧录固件
在esp32 c5上烧录micropython固件也是走了一点弯路,不能用普通的方法进行烧录,比如直接用thonny软件烧录本地固件,以及使用flash download tool,对于FireBeetle 2 ESP32 C5来说都是有问题的,据DFROBOT的刘工(DF刘忠渝)说是因为如下原因(在测试的时候,确实也出现了内存错误,估计是内存小的原因):
  • ESP32-C5是revision v0.1版本的  烧录固件一定要用压缩包里的flash download软件
  • C5 RAM有限。一些micropython示例会提示内存分配失败
  • 还有3块屏幕的驱动文件没有添加进去 。硬件发出前会添加进去

这是他提供的烧录文件及适配各种屏幕的固件包(烧录工具:下载附件flash_download_tool.zip包含st7789显示驱动的固件:下载附件ESP32-C5(N4)(revision v0.1)-st7789固件.rar
使用压缩包里的工具烧录还是挺简单的,注意烧录地址是0x2000,不要搞错了。

三、项目实施
这是项目运行后的效果:
Firebeetle 2 ESP32 C5体验之:显示网络天气图6
1. 硬件连接
固件烧录成功之后,进行了一些基础的点LED之类的,都没有问题,接下来就要尝试连接屏幕了。手头还是有多块屏,而且提供的固件也比较全,我就选了一块1.54英寸,分辨率是240x240的8针屏幕(不带触摸),通过SPI方式通信。
将屏幕的通信引脚与FireBeetle 2 ESP32 C5扩展板的IO进行链接,如下表所示,注意VCC接3.3V而不要接5V(查资料别人都这么说,我也不敢接5V,万一屏幕废了呢):
Firebeetle 2 ESP32 C5体验之:显示网络天气图1

2. 初步探索
打开Thonny软件,用USB线连接C5,进入Micropython编程模式。在Thonny底部的终端窗口输入如下命令查看一下固件集成了哪些模块:
  1. help('modules')
复制代码

显示如下图所示内容,其中发现st7789屏幕驱动和LVGL库(版本9.3.0)都在。
Firebeetle 2 ESP32 C5体验之:显示网络天气图2

赶紧查看一下st7789里面有些啥,是不是有ST7789类,
Firebeetle 2 ESP32 C5体验之:显示网络天气图3
点底部一行最右边的三个小点,看一下ST7789类里面具体有哪些方法,如下所示,好像没有高级一点的绘图方法可用,这就坚定了我尝试学习和使用LVGL的决心。
  1. ['__class__', '__init__', '__module__', '__name__', '__qualname__', '__bases__', '__del__', '__dict__', 'add_event_cb', 'delete', 'delete_event', 'delete_refr_timer', 'enable_invalidation', 'get_antialiasing', 'get_color_format', 'get_dpi', 'get_event_count', 'get_event_dsc', 'get_horizontal_resolution', 'get_inactive_time', 'get_layer_bottom', 'get_layer_sys', 'get_layer_top', 'get_next', 'get_offset_x', 'get_offset_y', 'get_physical_horizontal_resolution', 'get_physical_vertical_resolution', 'get_refr_timer', 'get_rotation', 'get_screen_active', 'get_screen_prev', 'get_theme', 'get_vertical_resolution', 'init', 'is_double_buffered', 'is_invalidation_enabled', 'reset', 'send_event', 'set_antialiasing', 'set_color_format', 'set_default', 'set_dpi', 'set_offset', 'set_physical_resolution', 'set_rotation', 'set_theme', 'trigger_activity', '_INVOFF', '_INVON', '_ORIENTATION_TABLE', '_displays', '_dummy_set_memory_location', '_flush_cb', '_flush_ready_cb', '_init_bus', '_madctl', '_on_size_change', '_set_memory_location', 'get_backlight', 'get_default', 'get_displays', 'get_params', 'get_power', 'set_backlight', 'set_color_inversion', 'set_params', 'set_physical_horizontal_resolution', 'set_physical_vertical_resolution', 'set_power']
复制代码

3. 对LVGL的学习尝试
之前一直有听说过LVGL这个东东,但一直没有尝试,毕竟在代码编程中进行界面布局是一件令人抓狂的事情,这也是N年以前我使用C等语言编写界面遭遇滑铁卢而接触到Delphi软件就觉得图形化布局非常方便的感受,后来无论是Word中的控件,还是网页前端,都希望这些界面控件能拖拖拉拉放放,再修改一下属性参数就搞定。
当然我希望LVGL也是这样的,结果……居然要纯代码,郁闷中。
而且自从我烧录了带有LVGL的micropython固件,网上找了一圈,资料最多的当然是C代码,毕竟micropython中的LVGL是从C移植过来的。
这里有一个LVGL中文手册的网站(https://lvgl.100ask.net/master/index.html),大家可以一起学习,不过最新版本的LVGL很少提供micropython代码,只提供C代码,应该是8.X版本的手册内容同时提供C和mpy代码。不过我测试发现,LVGL 8.X和9.X很多代码规则和格式都发生了变化(社区太强,更新太快,学习成本大大增加啊)
一通尝试并且遇到诸多困难之后,倒是没有学到多少内容,但收集资料倒是不少, 同时在github上follow了不少大牛,留着以后学习时参考。

4. 牛刀小试
接下来,我尝试使用esp32 c5在st7789驱动的屏幕上显示当地的天气内容,参考了github上大牛的一些代码。
  • 上传到esp32 c5上的文件结构(所有文件在文末压缩包提供)
Firebeetle 2 ESP32 C5体验之:显示网络天气图4
  • 程序思路
Firebeetle 2 ESP32 C5体验之:显示网络天气图5
  • 程序代码

  1. import lcd_bus
  2. import fs_driver
  3. from micropython import const
  4. import machine
  5. import urequests
  6. import lcd_utils
  7. from display_driver import init_display
  8. import time
  9. import json
  10. import network
  11. import lvgl as lv
  12. # import math
  13. import task_handler
  14. # 初始化WiFi
  15. sta_if = network.WLAN(network.STA_IF)
  16. sta_if.active(False)
  17. SSID = "wifi名称"  # 更换为自己的wifi名
  18. PASSWORD = "wifi密码"  # 更换为wifi密码
  19. city = "ningbo"  # 设置要显示天气的城市
  20. '''
  21. weather_dict = {'0@1x.png':'晴','1@1x.png':'晴','2@1x.png':'晴',
  22.               '3@1x.png':'晴','4@1x.png':'多云','5@1x.png':'晴间多云',
  23.               '6@1x.png':'晴间多云','7@1x.png':'大部多云','8@1x.png':'大部多云',
  24.               '9@1x.png':'阴','10@1x.png':'阵雨','11@1x.png':'雷阵雨',
  25.               '12@1x.png':'雷阵雨伴有冰雹','13@1x.png':'小雨','14@1x.png':'中雨',
  26.               '15@1x.png':'大雨','16@1x.png':'暴雨','17@1x.png':'大暴雨',
  27.               '18@1x.png':'特大暴雨','19@1x.png':'冻雨','20@1x.png':'雨夹雪',
  28.               '21@1x.png':'阵雪','22@1x.png':'小雪','23@1x.png':'中雪',
  29.               '24@1x.png':'大雪','25@1x.png':'暴雪','26@1x.png':'浮尘',
  30.               '27@1x.png':'扬沙','28@1x.png':'沙尘暴','29@1x.png':'强沙尘暴',
  31.               '30@1x.png':'雾','31@1x.png':'霾','32@1x.png':'风',
  32.               '33@1x.png':'大风','34@1x.png':'飓风','35@1x.png':'热带风暴',
  33.               '36@1x.png':'龙卷风','37@1x.png':'冷','38@1x.png':'热',
  34.               '99@1x.png':'未知'}
  35. '''
  36. weather_url = f"https://api.seniverse.com/v3/weather/now.json?key=S9hoa4Wza9Hcs2uX_&location={city}&language=zh-Hans&unit=c"
  37. def fetchWeather():
  38.     try:
  39.         result = urequests.get(weather_url)
  40.         print(result.text)
  41.         return result.text
  42.     except Exception as e:
  43.         print("Weather fetch error:", e)
  44.         return None
  45. def connect_wifi():
  46.     if not sta_if.isconnected():
  47.         print("Connecting to WiFi...")
  48.         sta_if.active(True)
  49.         sta_if.connect(SSID, PASSWORD)
  50.         max_wait = 10
  51.         while max_wait > 0:
  52.             if sta_if.isconnected():
  53.                 print("WiFi Connected. IP:", sta_if.ifconfig()[0])
  54.                 return True
  55.             max_wait -= 1
  56.             time.sleep(1)
  57.         print("WiFi Connection Failed")
  58.         return False
  59.     return True
  60. # 更新天气信息并分别存入对应的变量中
  61. def update_weather(e):
  62.     if connect_wifi():
  63.         weather_data = fetchWeather()
  64.         if weather_data:
  65.             try:
  66.                 weather_json = json.loads(weather_data)
  67.                 weather = weather_json["results"][0]["now"]["text"]
  68.                 temp = weather_json["results"][0]["now"]["temperature"]
  69.                 weather_image_keys = f"{weather_json["results"][0]["now"]["code"]}@1x.png"
  70.                 incity=weather_json["results"][0]["location"]["name"]
  71.                 last_update=weather_json["results"][0]["last_update"]
  72.                
  73.                 #ui_label1.set_text(incity)               
  74.                 ui_label2.set_text(f"{temp}°C")
  75.                 ui_label3.set_text(weather)
  76.                 ui_label4.set_text(last_update)
  77.                
  78.                 try:
  79.                     with open(f'weather_incons/{weather_image_keys}', 'rb') as f:
  80.                         png_data = f.read()
  81.                     img_cogwheel = lv.image_dsc_t({'data_size': len(png_data), 'data': png_data})
  82.                     img1.set_src(img_cogwheel)
  83.                 except Exception as e:
  84.                     print(f"Could not load weather icon {weather_image_keys}: {e}")
  85.                     ui_label3.set_text("Icon Load Error")
  86.             except Exception as e:
  87.                 print(f"Weather JSON parse error: {e}")
  88.                 ui_label3.set_text("Weather Parse Error")
  89.         else:
  90.             ui_label3.set_text("Weather Fetch Failed")
  91.     else:
  92.         ui_label.set_text("WiFi Not Connected")
  93. def SetFlag( obj, flag, value):
  94.     if (value):
  95.         obj.add_flag(flag)
  96.     else:
  97.         obj.remove_flag(flag)
  98.     return
  99. # 连接wifi
  100. connect_wifi()
  101. # 初始化LVGL
  102. lv.init()
  103. # 初始化显示屏
  104. display = init_display()
  105. display.set_power(False)
  106. display.init()
  107. display.set_color_inversion(True)
  108. display.set_rotation(lv.DISPLAY_ROTATION._0)
  109. display.set_backlight(1)
  110. #th = task_handler.TaskHandler()
  111. # 初始化LVGL
  112. scr = lv.screen_active()
  113. scr.set_style_bg_color(lv.color_hex(0x000000), 0)
  114. # 读取字体(提前上传到esp32中)
  115. fs_drv = lv.fs_drv_t()
  116. fs_driver.fs_register(fs_drv, 'S')
  117. myfont = lv.binfont_create("S:myfont_18.bin")
  118. # 获取天气数据
  119. weather_data = fetchWeather()
  120. temp, weather, weather_image_keys,incity,last_update= "N/A", "Unknown", '99@1x.png',"Unknown","Unknown"
  121. if weather_data:
  122.     try:
  123.         weather_json = json.loads(weather_data)
  124.         weather = weather_json["results"][0]["now"]["text"]
  125.         temp = weather_json["results"][0]["now"]["temperature"]
  126.         weather_image_keys = f"{weather_json["results"][0]["now"]["code"]}@1x.png"
  127.         print(weather)
  128.     except Exception as e:
  129.         print(f"Initial weather JSON parse error: {e}")
  130. # 读入天气图标并显示
  131. try:
  132.     with open(f'weather_incons/{weather_image_keys}', 'rb') as f:
  133.         png_data = f.read()
  134.     img_cogwheel_argb = lv.image_dsc_t({
  135.         'data_size': len(png_data),
  136.         'data': png_data
  137.     })
  138. except:
  139.     print(f"Could not find weather_incons/{weather_image_keys}")
  140.     # Create a fallback image or handle the error appropriately
  141.     img_cogwheel_argb = None
  142. img1 = lv.image(scr)
  143. if img_cogwheel_argb:
  144.     img1.set_src(img_cogwheel_argb)
  145. img1.align(lv.ALIGN.CENTER, 0, -25)
  146. img1.set_size(58, 58)
  147. # 显示城市(拼音)
  148. ui_label1 = lv.label(scr)
  149. ui_label1.set_text(city.upper())
  150. ui_label1.set_style_text_color(lv.color_hex(0x00FF00), 0)
  151. ui_label1.align(lv.ALIGN.CENTER, 0, -80)
  152. ui_label1.set_style_text_font(myfont, 0)
  153. # 显示温度
  154. ui_label2 = lv.label(scr)
  155. ui_label2.set_text(f"{temp}°C")
  156. ui_label2.set_style_text_color(lv.color_hex(0x00FF00), 0)
  157. ui_label2.align(lv.ALIGN.CENTER, 0, 30)
  158. ui_label2.set_style_text_font(myfont, 0)
  159. # 显示天气
  160. ui_label3 = lv.label(scr)
  161. ui_label3.set_text(weather)
  162. ui_label3.set_style_text_color(lv.color_hex(0xFFFF00), 0)
  163. ui_label3.align(lv.ALIGN.CENTER, 0, 10)
  164. ui_label3.set_style_text_font(myfont, 0)
  165. # 显示最后更新时间
  166. ui_label4 = lv.label(scr)
  167. ui_label4.set_text(last_update)
  168. ui_label4.set_style_text_color(lv.color_hex(0xFFFF00), 0)
  169. ui_label4.align(lv.ALIGN.CENTER, 0, 100)
  170. ui_label4.set_style_text_font(myfont, 0)
  171. # 天气3小时更新一次
  172. weather_timer = lv.timer_create(update_weather, 10800000, None)
  173. # 更新一次信息
  174. update_weather(None)
  175.         
  176. # --- Main Loop ---
  177. while True:
  178.     lv.tick_inc(5)
  179.     #lv.task_handler()
  180.     time.sleep_ms(5)
复制代码

四、反思
对于LVGL进行编程还不是很熟悉,发现之前我是通过framebuf等方式实现绘图,速度较慢,而LVGL中又有多种不同的显示方式,如对象类型,画布类型等。我尝试了画布类型,发现问题较多,主要是没有有效的参考文档,全靠瞎找。接下来我会继续学习,期待与大家有更多的交流。
esp32-c5程序资料:下载附件esp32-c5网络天气资料.zip

PY学习笔记  中级技师

发表于 半小时前

https:wiki.dfrobot.com.cnESP32_MicroPython_LVGL_Tutorial 可以看看这个方便不少
回复

使用道具 举报

仔爸  中级技师
 楼主|

发表于 7 分钟前

PY学习笔记 发表于 2025-10-20 21:44
https:wiki.dfrobot.com.cnESP32_MicroPython_LVGL_Tutorial 可以看看这个方便不少

谢谢,这个刘工已经发给我了,我看过了。确实可以跟着学不少东西
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4 备案 沪公网安备31011502402448

© 2013-2025 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail