|
| 1 | +#!/usr/bin/env python3 |
| 2 | +"""Framebuffer fill script - writes color patterns via fpb_cli mem-write""" |
| 3 | + |
| 4 | +import subprocess |
| 5 | +import sys |
| 6 | +import os |
| 7 | +import time |
| 8 | +import argparse |
| 9 | + |
| 10 | +# 默认参数 |
| 11 | +DEFAULT_PORT = "/dev/ttyACM1" |
| 12 | +DEFAULT_BAUDRATE = 921600 |
| 13 | +DEFAULT_FB_ADDR = 0x2005E000 |
| 14 | +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) |
| 15 | +CLI_PATH = os.path.join(SCRIPT_DIR, "cli", "fpb_cli.py") |
| 16 | + |
| 17 | +# 常用颜色 (RGB565 big-endian) |
| 18 | +COLORS = { |
| 19 | + "red": "F800", |
| 20 | + "green": "07E0", |
| 21 | + "blue": "001F", |
| 22 | + "white": "FFFF", |
| 23 | + "black": "0000", |
| 24 | + "yellow": "FFE0", |
| 25 | + "cyan": "07FF", |
| 26 | + "magenta": "F81F", |
| 27 | + "orange": "FD20", |
| 28 | +} |
| 29 | + |
| 30 | + |
| 31 | +def mem_write(port, baudrate, addr, data_hex): |
| 32 | + """调用 CLI 写内存""" |
| 33 | + cmd = [ |
| 34 | + sys.executable, CLI_PATH, |
| 35 | + "--port", port, |
| 36 | + "--baudrate", str(baudrate), |
| 37 | + "mem-write", hex(addr), data_hex, |
| 38 | + ] |
| 39 | + env = os.environ.copy() |
| 40 | + env["PYTHONPATH"] = SCRIPT_DIR + os.pathsep + env.get("PYTHONPATH", "") |
| 41 | + result = subprocess.run(cmd, capture_output=True, text=True, timeout=10, |
| 42 | + cwd=SCRIPT_DIR, env=env) |
| 43 | + if result.returncode != 0: |
| 44 | + print(f" FAIL @ {hex(addr)}: {result.stderr.strip()}") |
| 45 | + return False |
| 46 | + return True |
| 47 | + |
| 48 | + |
| 49 | +def fill_solid(port, baudrate, fb_addr, width, height, color_hex): |
| 50 | + """纯色填充""" |
| 51 | + # 每次写 128 像素 = 256 字节 = 512 hex chars |
| 52 | + chunk_pixels = 128 |
| 53 | + chunk_hex = color_hex * chunk_pixels |
| 54 | + total_pixels = width * height |
| 55 | + addr = fb_addr |
| 56 | + |
| 57 | + print(f"Filling {width}x{height} with #{color_hex} ({total_pixels} pixels)...") |
| 58 | + written = 0 |
| 59 | + while written < total_pixels: |
| 60 | + n = min(chunk_pixels, total_pixels - written) |
| 61 | + data = color_hex * n |
| 62 | + if not mem_write(port, baudrate, addr, data): |
| 63 | + return |
| 64 | + addr += n * 2 # 2 bytes per pixel |
| 65 | + written += n |
| 66 | + pct = written * 100 // total_pixels |
| 67 | + print(f"\r Progress: {pct}% ({written}/{total_pixels})", end="", flush=True) |
| 68 | + print("\n Done!") |
| 69 | + |
| 70 | + |
| 71 | +def fill_stripes_h(port, baudrate, fb_addr, width, height, colors, stripe_h=16): |
| 72 | + """水平条纹""" |
| 73 | + chunk_pixels = 128 |
| 74 | + total_pixels = width * height |
| 75 | + addr = fb_addr |
| 76 | + |
| 77 | + print(f"Filling {width}x{height} horizontal stripes (h={stripe_h})...") |
| 78 | + written = 0 |
| 79 | + for y in range(height): |
| 80 | + color_idx = (y // stripe_h) % len(colors) |
| 81 | + color = colors[color_idx] |
| 82 | + row_written = 0 |
| 83 | + while row_written < width: |
| 84 | + n = min(chunk_pixels, width - row_written) |
| 85 | + data = color * n |
| 86 | + if not mem_write(port, baudrate, addr, data): |
| 87 | + return |
| 88 | + addr += n * 2 |
| 89 | + row_written += n |
| 90 | + written += width |
| 91 | + if y % 8 == 0: |
| 92 | + pct = written * 100 // total_pixels |
| 93 | + print(f"\r Progress: {pct}% (row {y}/{height})", end="", flush=True) |
| 94 | + print("\n Done!") |
| 95 | + |
| 96 | + |
| 97 | +def fill_stripes_v(port, baudrate, fb_addr, width, height, colors, stripe_w=32): |
| 98 | + """垂直条纹""" |
| 99 | + chunk_pixels = 128 |
| 100 | + total_pixels = width * height |
| 101 | + addr = fb_addr |
| 102 | + |
| 103 | + print(f"Filling {width}x{height} vertical stripes (w={stripe_w})...") |
| 104 | + written = 0 |
| 105 | + for y in range(height): |
| 106 | + # 构建一行像素 |
| 107 | + row = "" |
| 108 | + for x in range(width): |
| 109 | + color_idx = (x // stripe_w) % len(colors) |
| 110 | + row += colors[color_idx] |
| 111 | + # 分块写入 |
| 112 | + offset = 0 |
| 113 | + while offset < len(row): |
| 114 | + chunk = row[offset:offset + chunk_pixels * 4] # 4 hex chars per pixel |
| 115 | + byte_len = len(chunk) // 2 |
| 116 | + if not mem_write(port, baudrate, addr, chunk): |
| 117 | + return |
| 118 | + addr += byte_len |
| 119 | + offset += len(chunk) |
| 120 | + written += width |
| 121 | + if y % 8 == 0: |
| 122 | + pct = written * 100 // total_pixels |
| 123 | + print(f"\r Progress: {pct}% (row {y}/{height})", end="", flush=True) |
| 124 | + print("\n Done!") |
| 125 | + |
| 126 | + |
| 127 | +def fill_gradient(port, baudrate, fb_addr, width, height): |
| 128 | + """红绿渐变""" |
| 129 | + chunk_pixels = 128 |
| 130 | + total_pixels = width * height |
| 131 | + addr = fb_addr |
| 132 | + |
| 133 | + print(f"Filling {width}x{height} gradient...") |
| 134 | + written = 0 |
| 135 | + for y in range(height): |
| 136 | + row = "" |
| 137 | + for x in range(width): |
| 138 | + r = (x * 31) // width # 0-31 |
| 139 | + g = (y * 63) // height # 0-63 |
| 140 | + b = 0 |
| 141 | + rgb565 = (r << 11) | (g << 5) | b |
| 142 | + row += f"{rgb565:04X}" |
| 143 | + offset = 0 |
| 144 | + while offset < len(row): |
| 145 | + chunk = row[offset:offset + chunk_pixels * 4] |
| 146 | + byte_len = len(chunk) // 2 |
| 147 | + if not mem_write(port, baudrate, addr, chunk): |
| 148 | + return |
| 149 | + addr += byte_len |
| 150 | + offset += len(chunk) |
| 151 | + written += width |
| 152 | + if y % 8 == 0: |
| 153 | + pct = written * 100 // total_pixels |
| 154 | + print(f"\r Progress: {pct}% (row {y}/{height})", end="", flush=True) |
| 155 | + print("\n Done!") |
| 156 | + |
| 157 | + |
| 158 | +def fill_checkerboard(port, baudrate, fb_addr, width, height, c1="F800", c2="001F", block=16): |
| 159 | + """棋盘格""" |
| 160 | + chunk_pixels = 128 |
| 161 | + total_pixels = width * height |
| 162 | + addr = fb_addr |
| 163 | + |
| 164 | + print(f"Filling {width}x{height} checkerboard (block={block})...") |
| 165 | + written = 0 |
| 166 | + for y in range(height): |
| 167 | + row = "" |
| 168 | + for x in range(width): |
| 169 | + if ((x // block) + (y // block)) % 2 == 0: |
| 170 | + row += c1 |
| 171 | + else: |
| 172 | + row += c2 |
| 173 | + offset = 0 |
| 174 | + while offset < len(row): |
| 175 | + chunk = row[offset:offset + chunk_pixels * 4] |
| 176 | + byte_len = len(chunk) // 2 |
| 177 | + if not mem_write(port, baudrate, addr, chunk): |
| 178 | + return |
| 179 | + addr += byte_len |
| 180 | + offset += len(chunk) |
| 181 | + written += width |
| 182 | + if y % 8 == 0: |
| 183 | + pct = written * 100 // total_pixels |
| 184 | + print(f"\r Progress: {pct}% (row {y}/{height})", end="", flush=True) |
| 185 | + print("\n Done!") |
| 186 | + |
| 187 | + |
| 188 | +PATTERNS = { |
| 189 | + "solid": "纯色填充", |
| 190 | + "hstripe": "水平条纹", |
| 191 | + "vstripe": "垂直条纹", |
| 192 | + "gradient": "红绿渐变", |
| 193 | + "checker": "棋盘格", |
| 194 | + "all": "依次执行所有图案", |
| 195 | +} |
| 196 | + |
| 197 | + |
| 198 | +def main(): |
| 199 | + parser = argparse.ArgumentParser(description="Framebuffer fill via FPB CLI") |
| 200 | + parser.add_argument("--port", default=DEFAULT_PORT) |
| 201 | + parser.add_argument("--baudrate", type=int, default=DEFAULT_BAUDRATE) |
| 202 | + parser.add_argument("--addr", default=hex(DEFAULT_FB_ADDR), |
| 203 | + help=f"帧缓冲区地址 (default: {hex(DEFAULT_FB_ADDR)})") |
| 204 | + parser.add_argument("--width", type=int, default=466, help="屏幕宽度 (pixels)") |
| 205 | + parser.add_argument("--height", type=int, default=466, help="屏幕高度 (pixels)") |
| 206 | + parser.add_argument("--color", default="red", choices=list(COLORS.keys()), |
| 207 | + help="纯色填充的颜色") |
| 208 | + parser.add_argument("pattern", choices=list(PATTERNS.keys()), |
| 209 | + help="填充图案: " + ", ".join(f"{k}={v}" for k, v in PATTERNS.items())) |
| 210 | + |
| 211 | + args = parser.parse_args() |
| 212 | + fb_addr = int(args.addr, 16) |
| 213 | + |
| 214 | + print(f"Port: {args.port} @ {args.baudrate}") |
| 215 | + print(f"FB addr: {hex(fb_addr)}, size: {args.width}x{args.height}") |
| 216 | + print() |
| 217 | + |
| 218 | + patterns_to_run = [] |
| 219 | + if args.pattern == "all": |
| 220 | + patterns_to_run = ["solid", "hstripe", "vstripe", "gradient", "checker"] |
| 221 | + else: |
| 222 | + patterns_to_run = [args.pattern] |
| 223 | + |
| 224 | + for pat in patterns_to_run: |
| 225 | + t0 = time.time() |
| 226 | + if pat == "solid": |
| 227 | + fill_solid(args.port, args.baudrate, fb_addr, |
| 228 | + args.width, args.height, COLORS[args.color]) |
| 229 | + elif pat == "hstripe": |
| 230 | + fill_stripes_h(args.port, args.baudrate, fb_addr, |
| 231 | + args.width, args.height, |
| 232 | + ["F800", "07E0", "001F", "FFFF"], stripe_h=16) |
| 233 | + elif pat == "vstripe": |
| 234 | + fill_stripes_v(args.port, args.baudrate, fb_addr, |
| 235 | + args.width, args.height, |
| 236 | + ["F800", "07E0", "001F", "FFE0"], stripe_w=32) |
| 237 | + elif pat == "gradient": |
| 238 | + fill_gradient(args.port, args.baudrate, fb_addr, |
| 239 | + args.width, args.height) |
| 240 | + elif pat == "checker": |
| 241 | + fill_checkerboard(args.port, args.baudrate, fb_addr, |
| 242 | + args.width, args.height) |
| 243 | + elapsed = time.time() - t0 |
| 244 | + print(f" Time: {elapsed:.1f}s\n") |
| 245 | + |
| 246 | + if len(patterns_to_run) > 1: |
| 247 | + time.sleep(2) |
| 248 | + |
| 249 | + |
| 250 | +if __name__ == "__main__": |
| 251 | + main() |
0 commit comments