MicroPython测试 ESP32-S3 + 8MB PSRAM + ST7789 屏幕显示GIF动画
1.准备一个GIF动画文件,可以去百度图片里随便搜索一个下载,然后在电脑上把 GIF 拆成一堆.raw二进制文件上传。可以用我下面提供的这个python代码进行转换。
# convert_gif.py (在电脑上运行) from PIL import Image import os def convert_gif_to_raw(gif_path, output_folder="raw_frames"): if not os.path.exists(output_folder): os.makedirs(output_folder) print(f"Opening {gif_path}...") im = Image.open(gif_path) # 调整大小为 240x240 (根据你的屏幕) target_size = (240, 240) frame_count = 0 try: while True: # 转换为 RGB rgb_im = im.convert('RGB') rgb_im = rgb_im.resize(target_size, Image.Resampling.LANCZOS) # 转换为 RGB565 二进制数据 data = bytearray() for y in range(target_size[1]): for x in range(target_size[0]): r, g, b = rgb_im.getpixel((x, y)) # RGB888 to RGB565 color = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3) data.append((color >> 8) & 0xFF) data.append(color & 0xFF) # 保存为 raw 文件 filename = f"{output_folder}/frame_{frame_count:03d}.raw" with open(filename, 'wb') as f: f.write(data) print(f"Saved {filename} ({len(data)} bytes)") frame_count += 1 try: im.seek(im.tell() + 1) except EOFError: break except Exception as e: print(f"Error: {e}") print(f"Done! Converted {frame_count} frames.") if __name__ == "__main__": #我测试的gif文件名称:animation.gif convert_gif_to_raw("animation.gif")运行结果:
生成好的文件放在指定的目录下的:
例如我这里是放在了:raw_frames 目录下。
2.将生成好的 raw_frames 通过Thonny上传到esp32 s3 的 MicroPython 根目录,如下图所示:
3.在Thonny里的 MicroPython 设备下面新建一个文件,例如我这里创建的 GIF动画测试.py文件,复制粘贴我下面的代码,运行,观察屏幕是否有正常显示动画效果。
import machine import time import framebuf import os import gc # ================= 配置 ================= PIN_SCL = 9 PIN_SDA = 8 PIN_CS = 5 PIN_DC = 6 PIN_RST = 7 PIN_BL = 4 SCREEN_W = 240 SCREEN_H = 240 # 👇 修改这里:设置子目录名称 FRAME_DIR = "raw_frames" # 文件夹名字 FRAME_PREFIX = "frame_" FRAME_EXT = ".raw" # ======================================= # --- 屏幕驱动类 (保持不变) --- class ST7789: def __init__(self, spi, cs, dc, rst, w, h): self.spi = spi; self.cs = cs; self.dc = dc; self.rst = rst self.w = w; self.h = h for p in [cs, dc, rst]: p.init(machine.Pin.OUT, value=1) rst.value(0); time.sleep_ms(10); rst.value(1); time.sleep_ms(120) cmds = [(0x01,[]),(0x11,[]),(0x36,[0x00]),(0x3A,[0x05]),(0xB2,[0x0C,0x0C,0x00,0x33,0x33]), (0xB7,[0x35]),(0xBB,[0x19]),(0xC0,[0x2C]),(0xC2,[0x01]),(0xC3,[0x12]),(0xC4,[0x20]), (0xC6,[0x0F]),(0xD0,[0xA4,0xA1]), (0xE0,[0xD0,0x00,0x02,0x07,0x0A,0x28,0x32,0x44,0x42,0x06,0x0E,0x12,0x14,0x17]), (0xE1,[0xD0,0x00,0x02,0x07,0x0A,0x28,0x31,0x54,0x47,0x0E,0x1C,0x17,0x1B,0x1E]), (0x21,[]),(0x29,[])] for cmd, data in cmds: self._tx(cmd, False) if data: self._tx(bytes(data), True) if cmd == 0x11: time.sleep_ms(120) self.set_window(0, 0, w-1, h-1) def _tx(self, data, is_data=False): self.dc(is_data); self.cs(0) if isinstance(data, int): data = bytes([data]) self.spi.write(data); self.cs(1) def set_window(self, x0, y0, x1, y1): self._tx(0x2A, False); self._tx(bytes([x0>>8, x0&0xff, x1>>8, x1&0xff]), True) self._tx(0x2B, False); self._tx(bytes([y0>>8, y0&0xff, y1>>8, y1&0xff]), True) self._tx(0x2C, False) def write(self, buffer): self.dc(1); self.cs(0); self.spi.write(buffer); self.cs(1) # --- 主程序 --- print("Initializing...") spi = machine.SPI(1, baudrate=90000000, polarity=0, phase=0, sck=machine.Pin(PIN_SCL), mosi=machine.Pin(PIN_SDA)) lcd = ST7789(spi, machine.Pin(PIN_CS), machine.Pin(PIN_DC), machine.Pin(PIN_RST), SCREEN_W, SCREEN_H) machine.Pin(PIN_BL, machine.Pin.OUT).value(1) # 1. 分配 PSRAM 双缓冲 print("Allocating PSRAM buffers...") buf1 = bytearray(SCREEN_W * SCREEN_H * 2) buf2 = bytearray(SCREEN_W * SCREEN_H * 2) fb1 = framebuf.FrameBuffer(buf1, SCREEN_W, SCREEN_H, framebuf.RGB565) fb2 = framebuf.FrameBuffer(buf2, SCREEN_W, SCREEN_H, framebuf.RGB565) print(f"Free RAM: {gc.mem_free()/1024:.1f} KB") # 2. 👇 修改这里:扫描子目录下的文件 full_dir_path = "/" + FRAME_DIR # 构建完整路径,例如 "/frames" # 检查目录是否存在 if full_dir_path not in ["/" + d for d in os.listdir()]: # 尝试直接列出目录内容来验证,如果报错说明目录不存在 try: files_in_dir = os.listdir(full_dir_path) except OSError: raise RuntimeError(f"Directory '{FRAME_DIR}' not found! Please create it and upload files.") else: files_in_dir = os.listdir(full_dir_path) # 筛选出 .raw 文件 frame_files = sorted([f for f in files_in_dir if f.startswith(FRAME_PREFIX) and f.endswith(FRAME_EXT)]) TOTAL_FRAMES = len(frame_files) if TOTAL_FRAMES == 0: raise RuntimeError(f"No frames found in '{FRAME_DIR}'! Please upload {FRAME_PREFIX}*{FRAME_EXT} files there.") print(f"Found {TOTAL_FRAMES} frames in '{FRAME_DIR}'. Starting playback...") frame_idx = 0 fps_count = 0 start_time = time.ticks_ms() while True: t_start = time.ticks_ms() # 选择缓冲区 idx = frame_idx % 2 buf = buf1 if idx == 0 else buf2 # 3. 👇 修改这里:读取时加上目录路径 fname_relative = frame_files[frame_idx % TOTAL_FRAMES] fname_full = full_dir_path + "/" + fname_relative # 例如:"/frames/frame_000.raw" try: with open(fname_full, 'rb') as f: f.readinto(buf) except OSError as e: print(f"Error reading {fname_full}: {e}") # 可以选择 break 或者跳过 break # 4. 发送到屏幕 lcd.set_window(0, 0, SCREEN_W-1, SCREEN_H-1) lcd.write(buf) frame_idx += 1 fps_count += 1 # 计算 FPS elapsed = time.ticks_diff(time.ticks_ms(), start_time) if elapsed >= 1000: print(f"FPS: {fps_count}") fps_count = 0 start_time = time.ticks_ms()运行结果:
显示效果:
备注:由于我的gif尺寸问题,屏幕地方是空白的,这块优化下即可
