仔爸 发表于 5 天前

Firebeetle 2 ESP32 C5体验之:显示高考倒计时

上一篇写了通过网络获取指定城市的天气,采用的是FireBeetle 2 ESP32 C5 + st7789驱动的1.54英寸的显示屏。这次换了一块屏幕,使用的是2.4英寸的ili9341驱动的液晶屏,因此要重新烧录固件。
仍采用指定的flash download tool烧录包括ili9341显示驱动的固件:

项目实施:
这是项目运行后的效果:



1.硬件连接
将屏幕的通信引脚与FireBeetle 2 ESP32 C5扩展板的IO进行链接,如下表所示,注意VCC接3.3V而不要接5V。其实因为我使用屏幕不带触摸,因此这里有几个引脚是不需要连接的,比如CS2,PEN和MISO。


2. 程序编写
论坛插入代码不友好,见最后面的附件吧。
import lcd_bus
from micropython import const
from machine import RTC,SPI
import time
import network
import math
import ntptime
import fs_driver

import ili9341# NOQA
import lvgl as lv# NOQA

# 初始化RTC
rtc = RTC()

# 网络配置
WIFI_SSID = "改成自己的WIFI名称"
WIFI_PASSWORD = "WIFI密码"

# 高考时间 (2026年6月7日)
GAOKAO_YEAR = 2026
GAOKAO_MONTH = 6
GAOKAO_DAY = 7

class TimeDisplay:
    def __init__(self):
      # 显示屏尺寸
      self.display_width = 240
      self.display_height = 320      
      # 初始化显示
      self.init_display()
      # 初始化LVGL
      self.init_lvgl()
      # 创建UI
      self.create_ui()
      
    def init_display(self):
      """初始化显示屏"""      
      _WIDTH = const(240)# 显示屏宽度
      _HEIGHT = const(320)# 显示屏高度
      _BL = const(5)# 背光引脚
      _RST = const(4)# 复位引脚
      _DC = const(2)# 数据/命令控制引脚

      _MOSI = const(7)# SPI MOSI引脚
      _MISO = const(15)# SPI MISO引脚
      _SCK = const(6)# SPI SCK引脚
      _HOST = const(1)# SPI2

      _LCD_CS = const(27)# LCD芯片选择引脚
      _LCD_FREQ = const(80000000)# LCD SPI通信频率

      _TOUCH_CS = const(26)# 触摸芯片选择引脚
      _TOUCH_FREQ = const(10000000)# 触摸SPI通信频率

      _BUFFER_SIZE = const(30720)

      try:
            # SPI配置 - 根据您的实际连接调整引脚
            # 创建SPI总线对象
            spi_bus = SPI.Bus(
                host=_HOST,   # 显式转换为整数
                mosi=_MOSI,
                miso=_MISO,
                sck=_SCK
            )

            # 创建显示屏的SPI通信对象
            display_bus = lcd_bus.SPIBus(
                spi_bus=spi_bus,
                freq=_LCD_FREQ,
                dc=_DC,
                cs=_LCD_CS
            )
            
            # 创建显示屏对象
            self.display = ili9341.ILI9341(
                data_bus=display_bus,
                #frame_buffer1=fb1,
                #frame_buffer2=fb2,
                display_width=_WIDTH,
                display_height=_HEIGHT,
                reset_pin=_RST,
                reset_state=ili9341.STATE_LOW,
                backlight_pin=_BL,
                color_space=lv.COLOR_FORMAT.RGB565,
                color_byte_order=ili9341.BYTE_ORDER_BGR,
                rgb565_byte_swap=True,
            )
            
            self.display.init(2)
            # 打开屏幕背光
            self.display.set_backlight(1)
            print("显示屏初始化成功")
      except Exception as e:
            print(f"显示初始化失败: {e}")
            raise

    def init_lvgl(self):
      """初始化LVGL"""
      lv.init()
      
      # 创建显示缓冲区 - 适配LVGL 9.3
#         try:
#             # 计算缓冲区大小 (屏幕宽度 * 10行,每个像素2字节)
#             buf_size = 240 * 10 * lv.color_t.__SIZE__# 注意这里使用240x320的屏幕尺寸
#             self.buf1 = bytearray(buf_size)
#            
#             # 创建绘制缓冲区
#             self.disp_buf = lv.draw_buf_t()
#             self.disp_buf.init(self.buf1, None, len(self.buf1) // lv.color_t.__SIZE__)
#            
#             # 创建显示设备 (LVGL 9.x 新API)
#             self.disp = lv.display_create(240, 320)# 宽度、高度参数
#             self.disp.set_draw_buf(self.disp_buf)
#             self.disp.set_flush_cb(self.display_flush)
#             self.disp.set_rotation(lv.DISPLAY_ROTATION._0)# 设置旋转角度
#            
#             print("LVGL显示驱动注册成功")
#         except Exception as e:
#             print(f"LVGL显示驱动注册失败: {e}")
#             raise
      
      print("LVGL初始化成功")

    def display_flush(self, disp, area, color_p):
      """显示刷新回调函数 - 适配LVGL 9.3"""
      try:
            # 提取刷新区域坐标
            x1 = area.x1
            y1 = area.y1
            x2 = area.x2
            y2 = area.y2
            
            # 计算区域宽度和高度
            width = x2 - x1 + 1
            height = y2 - y1 + 1
            
            # 准备显示缓冲区(将LVGL的颜色数据转换为字节数组)
            buffer = bytearray(width * height * 2)# RGB565格式,每个像素2字节
            color_p.vector_copy(buffer)# LVGL 9.x获取颜色数据的方法
            
            # 配置ILI9341显示区域并发送数据
            self.display.set_window(x1, y1, x2, y2)
            self.display.write(buffer)
            
      except Exception as e:
            print(f"显示刷新错误: {e}")
      
      # 通知LVGL刷新完成 (LVGL 9.x 新API)
      disp.flush_ready()

    def connect_wifi(self):
      """连接WiFi"""
      wlan = network.WLAN(network.STA_IF)
      wlan.active(True)
      
      if not wlan.isconnected():
            print(f"正在连接WiFi: {WIFI_SSID}")
            wlan.connect(WIFI_SSID, WIFI_PASSWORD)
            
            # 等待连接
            for i in range(20):
                if wlan.isconnected():
                  break
                time.sleep(1)
                print(".", end="")
            print()
      
      if wlan.isconnected():
            print(f"WiFi连接成功! IP: {wlan.ifconfig()}")
            return True
      else:
            print("WiFi连接失败!")
            return False

    def sync_ntp_time(self):
      """通过网络同步时间"""
      if not self.connect_wifi():
            return False
            
      try:
            print("正在同步NTP时间...")
            ntptime.settime()# 从NTP服务器获取时间
            print("时间同步成功!")
            return True
      except Exception as e:
            print(f"时间同步失败: {e}")
            return False

    def get_gaokao_countdown(self):
      """计算距离高考的天数"""
      current_time = rtc.datetime()
      current_year, current_month, current_day = current_time
      
      # 计算当前日期和高考日期的天数差
      current_days = self.date_to_days(current_year, current_month, current_day)
      gaokao_days = self.date_to_days(GAOKAO_YEAR, GAOKAO_MONTH, GAOKAO_DAY)
      
      days_left = gaokao_days - current_days
      return max(0, days_left)

    def date_to_days(self, year, month, day):
      """将日期转换为天数"""
      # 简单的日期转换算法
      month_days =
      
      # 闰年判断
      if (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0):
            month_days = 29
      
      total_days = day
      for i in range(month - 1):
            total_days += month_days
      
      # 加上年份的天数
      for y in range(2024, year):
            if (y % 4 == 0 and y % 100 != 0) or (y % 400 == 0):
                total_days += 366
            else:
                total_days += 365
               
      return total_days

    def get_weekday_name(self, weekday):
      """获取星期名称"""
      weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
      return weekdays

    def create_ui(self):
      """创建用户界面"""
      # 创建主屏幕
      self.screen = lv.screen_active()      
      
      # 设置背景颜色
      self.screen.set_style_bg_color(lv.color_hex(0x000080), 0)# 深蓝色背景
      
      # 创建日期标签
      self.date_label = lv.label(self.screen)
      self.date_label.set_text("2024-01-01")
      self.date_label.set_style_text_color(lv.color_hex(0xFFFFFF), 0)
      self.date_label.set_style_text_font(lv.font_montserrat_16, 0)
      self.date_label.align(lv.ALIGN.TOP_MID, 0, 20)
      
      # 创建星期标签
      self.weekday_label = lv.label(self.screen)
      self.weekday_label.set_text("Monday")
      self.weekday_label.set_style_text_color(lv.color_hex(0xFFFF00), 0)
      self.weekday_label.set_style_text_font(lv.font_montserrat_16, 0)
      self.weekday_label.align_to(self.date_label, lv.ALIGN.OUT_BOTTOM_MID, 0, 10)
      
      # 创建时间标签(大字体显示)
      self.time_label = lv.label(self.screen)
      self.time_label.set_text("00:00:00")
      self.time_label.set_style_text_color(lv.color_hex(0x00FF00), 0)
      self.time_label.set_style_text_font(lv.font_montserrat_16, 0)
      self.time_label.align(lv.ALIGN.CENTER, 0, -20)
      
      # 创建高考倒计时标签
      self.gaokao_label = lv.label(self.screen)
      self.gaokao_label.set_text("Countdown to the 2026 NCEE 888 days")
      self.gaokao_label.set_style_text_color(lv.color_hex(0xFFA500), 0)
      self.gaokao_label.set_style_text_font(lv.font_montserrat_16, 0)
      self.gaokao_label.align(lv.ALIGN.CENTER, 0, 30)
      
      # 创建状态标签
      self.status_label = lv.label(self.screen)
      self.status_label.set_text("时间已同步")
      self.status_label.set_style_text_color(lv.color_hex(0xCCCCCC), 0)
      self.status_label.set_style_text_font(lv.font_montserrat_14, 0)
      self.status_label.align(lv.ALIGN.BOTTOM_LEFT, 10, -10)

    def update_display(self):
      """更新显示内容"""
      try:
            # 获取当前RTC时间
            current_time = rtc.datetime()
            year, month, day, weekday, hour, minute, second, microsecond = current_time
            
            # 更新日期
            date_str = f"{year:04d}-{month:02d}-{day:02d}"
            self.date_label.set_text(date_str)
            
            # 更新星期
            weekday_str = self.get_weekday_name(weekday)
            self.weekday_label.set_text(weekday_str)
            
            # 更新时间
            time_str = f"{hour:02d}:{minute:02d}:{second:02d}"
            self.time_label.set_text(time_str)
            
            # 更新高考倒计时
            days_left = self.get_gaokao_countdown()
            gaokao_str = f"{GAOKAO_YEAR} NCEE\n {days_left} days left"
            self.gaokao_label.set_text(gaokao_str)
            
            # 刷新显示
            lv.timer_handler()
            
      except Exception as e:
            print(f"更新显示错误: {e}")

    def run(self):
      """主运行循环"""
      # 尝试同步网络时间
      if self.sync_ntp_time():
            self.status_label.set_text("Time has been synchronized")
      else:
            self.status_label.set_text("Use local time")
      
      print("开始显示时间...")
      
      # 主循环
      last_second = -1
      while True:
            current_time = rtc.datetime()
            current_second = current_time# 秒
            
            # 每秒更新一次显示
            if current_second != last_second:
                self.update_display()
                last_second = current_second
            
            # 每小时尝试同步一次时间
            if current_time == 0 and current_time == 0 and current_second == 0:# 整点
                if self.sync_ntp_time():
                  self.status_label.set_text("Time has been synchronized")
                else:
                  self.status_label.set_text("Synchronization failed")
            
            time.sleep(0.1)# 短暂延迟

# 主程序
if __name__ == "__main__":
    try:
      print("启动时间显示系统...")
      time_display = TimeDisplay()
      time_display.run()
    except Exception as e:
      print(f"程序运行错误: {e}")
      # 错误恢复:简单的控制台显示
      while True:
            current = rtc.datetime()
            print(f"时间: {current}-{current:02d}-{current:02d} {current:02d}:{current:02d}:{current:02d}")
            time.sleep(1)



3. 反思与不足
此程序还未实现数字时间的刷新,因为交稿时间紧,又对LVGL不熟悉,目前还搞不定如何创建和刷新缓冲区。等接下来搞定了,重新上传代码。也希望大牛看到我的程序,帮我改进一下。




_深蓝_ 发表于 3 天前

不支持中文吗?
页: [1]
查看完整版本: Firebeetle 2 ESP32 C5体验之:显示高考倒计时