Reorganize drivers and utility modules into lib/ subdirectory and update server and upload scripts
This commit is contained in:
+261
@@ -0,0 +1,261 @@
|
||||
import time
|
||||
from machine import Pin, SPI
|
||||
import framebuf
|
||||
import micropython
|
||||
|
||||
class ILI9341:
|
||||
def __init__(self, spi, cs, dc, rst=None, bl=None, width=320, height=240, invert_color=True):
|
||||
self.spi = spi
|
||||
self.cs = cs
|
||||
self.dc = dc
|
||||
self.rst = rst
|
||||
self.bl = bl
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.invert_color = invert_color
|
||||
|
||||
# 1. 1-bit Canvas Buffer (Standard MONO_HLSB for drawing)
|
||||
self.hw_len = (self.width * self.height) // 8
|
||||
self.canvas_buffer = bytearray(self.hw_len)
|
||||
self.canvas = framebuf.FrameBuffer(self.canvas_buffer, self.width, self.height, framebuf.MONO_HLSB)
|
||||
|
||||
# Pre-allocate chunk buffer for conversion (16 rows: 320 * 16 * 2 = 10240 bytes)
|
||||
self.chunk_rows = 16
|
||||
self.row_buffer = bytearray(self.width * self.chunk_rows * 2)
|
||||
|
||||
# Initialize pins
|
||||
self.cs.init(self.cs.OUT, value=1)
|
||||
self.dc.init(self.dc.OUT, value=0)
|
||||
|
||||
if self.rst is not None:
|
||||
self.rst.init(self.rst.OUT, value=1)
|
||||
|
||||
if self.bl is not None:
|
||||
self.bl.init(self.bl.OUT, value=1)
|
||||
|
||||
self.reset()
|
||||
self.init_display()
|
||||
self.clear(0)
|
||||
self.show()
|
||||
|
||||
# --- DRAWING WRAPPERS ---
|
||||
def pixel(self, x, y, c): self.canvas.pixel(x, y, c)
|
||||
def line(self, x1, y1, x2, y2, c): self.canvas.line(x1, y1, x2, y2, c)
|
||||
def rect(self, x, y, w, h, c): self.canvas.rect(x, y, w, h, c)
|
||||
def fill_rect(self, x, y, w, h, c): self.canvas.fill_rect(x, y, w, h, c)
|
||||
def text(self, msg, x, y, c=1): self.canvas.text(msg, x, y, c)
|
||||
def clear(self, c=0): self.canvas.fill(c)
|
||||
|
||||
# --- SCALABLE TEXT ---
|
||||
def text_large(self, msg, x, y, scale=2, c=1):
|
||||
char_w = 8; char_h = 8
|
||||
tmp_buf = bytearray(char_w * char_h // 8)
|
||||
tmp_fb = framebuf.FrameBuffer(tmp_buf, char_w, char_h, framebuf.MONO_HLSB)
|
||||
for char in msg:
|
||||
tmp_fb.fill(0); tmp_fb.text(char, 0, 0, 1)
|
||||
for py in range(8):
|
||||
for px in range(8):
|
||||
if tmp_fb.pixel(px, py):
|
||||
self.canvas.fill_rect(x + (px * scale), y + (py * scale), scale, scale, c)
|
||||
x += (8 * scale)
|
||||
|
||||
# --- RAW BITMAPS (1:1 scale) ---
|
||||
def bitmap(self, x, y, w, h, pixel_data):
|
||||
img = framebuf.FrameBuffer(pixel_data, w, h, framebuf.MONO_HLSB)
|
||||
self.canvas.blit(img, x, y)
|
||||
|
||||
# --- PBM FILE LOADER WITH SCALING ---
|
||||
def draw_pbm(self, filename, x, y, scale=1):
|
||||
try:
|
||||
with open(filename, 'rb') as f:
|
||||
line1 = f.readline()
|
||||
if not line1.startswith(b'P4'): print("Err: Not P4 PBM"); return
|
||||
while True:
|
||||
line = f.readline()
|
||||
if not line.startswith(b'#'): break
|
||||
dims = line.split(); w = int(dims[0]); h = int(dims[1])
|
||||
data = bytearray(f.read())
|
||||
|
||||
src_fb = framebuf.FrameBuffer(data, w, h, framebuf.MONO_HLSB)
|
||||
|
||||
if scale == 1:
|
||||
self.canvas.blit(src_fb, x, y)
|
||||
else:
|
||||
for sy in range(h):
|
||||
for sx in range(w):
|
||||
if src_fb.pixel(sx, sy):
|
||||
self.canvas.fill_rect(x + (sx * scale), y + (sy * scale), scale, scale, 1)
|
||||
print(f"Loaded {filename} (scale {scale})")
|
||||
except OSError:
|
||||
print(f"Error: Could not open {filename}")
|
||||
|
||||
# --- SCREENSHOT ---
|
||||
def save_screenshot(self, filename):
|
||||
print(f"Saving screenshot to {filename}...")
|
||||
try:
|
||||
with open(filename, 'wb') as f:
|
||||
f.write(b'P4\n')
|
||||
f.write(f"{self.width} {self.height}\n".encode())
|
||||
f.write(self.canvas_buffer)
|
||||
print("Saved!")
|
||||
except Exception as e:
|
||||
print(f"Error saving screenshot: {e}")
|
||||
|
||||
# --- HARDWARE LOGIC ---
|
||||
def reset(self):
|
||||
if self.rst is not None:
|
||||
self.rst(1); time.sleep_ms(5); self.rst(0); time.sleep_ms(15); self.rst(1); time.sleep_ms(15)
|
||||
|
||||
def write_cmd(self, cmd):
|
||||
self.dc(0); self.cs(0); self.spi.write(bytearray([cmd])); self.cs(1)
|
||||
|
||||
def write_data(self, data):
|
||||
self.dc(1); self.cs(0)
|
||||
if isinstance(data, int): self.spi.write(bytearray([data]))
|
||||
elif isinstance(data, list): self.spi.write(bytearray(data))
|
||||
else: self.spi.write(data)
|
||||
self.cs(1)
|
||||
|
||||
def init_display(self):
|
||||
# ILI9341 Initialization Sequence
|
||||
self.write_cmd(0x01) # SWRESET
|
||||
time.sleep_ms(150)
|
||||
|
||||
self.write_cmd(0xCF); self.write_data(b"\x00\xC1\x30")
|
||||
self.write_cmd(0xED); self.write_data(b"\x64\x03\x12\x81")
|
||||
self.write_cmd(0xE8); self.write_data(b"\x85\x00\x78")
|
||||
self.write_cmd(0xCB); self.write_data(b"\x39\x2C\x00\x34\x02")
|
||||
self.write_cmd(0xF7); self.write_data(b"\x20")
|
||||
self.write_cmd(0xEA); self.write_data(b"\x00\x00")
|
||||
|
||||
self.write_cmd(0xC0); self.write_data(b"\x13") # Power Control 1
|
||||
self.write_cmd(0xC1); self.write_data(b"\x13") # Power Control 2
|
||||
self.write_cmd(0xC5); self.write_data(b"\x22\x35") # VCOM Control 1
|
||||
self.write_cmd(0xC7); self.write_data(b"\xBD") # VCOM Control 2
|
||||
|
||||
# Memory Access Control (MADCTL) = 0x68 (Landscape: MV=1, MX=1, MY=0, BGR color filter)
|
||||
self.write_cmd(0x36); self.write_data(b"\x68")
|
||||
|
||||
self.write_cmd(0xB6); self.write_data(b"\x0A\xA2") # Display Function Control
|
||||
self.write_cmd(0x3A); self.write_data(b"\x55") # Pixel Format (COLMOD) = 16-bit RGB565
|
||||
self.write_cmd(0xF6); self.write_data(b"\x01\x30")
|
||||
self.write_cmd(0xB1); self.write_data(b"\x00\x1B") # Frame Rate Control
|
||||
self.write_cmd(0xF2); self.write_data(b"\x00")
|
||||
self.write_cmd(0x26); self.write_data(b"\x01") # Gamma Curve
|
||||
|
||||
self.write_cmd(0xE0); self.write_data(b"\x0F\x35\x31\x0B\x0E\x06\x49\xA7\x33\x07\x0F\x03\x0C\x0A\x00") # Positive Gamma Correction
|
||||
self.write_cmd(0xE1); self.write_data(b"\x00\x0A\x0F\x04\x11\x08\x36\x58\x4D\x07\x10\x0C\x32\x34\x0F") # Negative Gamma Correction
|
||||
|
||||
if self.invert_color:
|
||||
self.write_cmd(0x21) # INVON
|
||||
else:
|
||||
self.write_cmd(0x20) # INVOFF
|
||||
|
||||
self.write_cmd(0x11) # SLPOUT (Exit sleep mode)
|
||||
time.sleep_ms(120)
|
||||
self.write_cmd(0x29) # DISPON (Display on)
|
||||
time.sleep_ms(10)
|
||||
|
||||
def invert(self, enable):
|
||||
if enable:
|
||||
self.write_cmd(0x21) # INVON
|
||||
else:
|
||||
self.write_cmd(0x20) # INVOFF
|
||||
|
||||
def set_window(self, x0, y0, x1, y1):
|
||||
# Column Address Set (CASET)
|
||||
self.write_cmd(0x2A)
|
||||
self.write_data(bytearray([x0 >> 8, x0 & 0xFF, x1 >> 8, x1 & 0xFF]))
|
||||
|
||||
# Row Address Set (RASET)
|
||||
self.write_cmd(0x2B)
|
||||
self.write_data(bytearray([y0 >> 8, y0 & 0xFF, y1 >> 8, y1 & 0xFF]))
|
||||
|
||||
# Memory Write (RAMWR)
|
||||
self.write_cmd(0x2C)
|
||||
|
||||
@micropython.native
|
||||
def _convert_rows(self, start_row, num_rows, row_buf):
|
||||
"""Converts 1-bit monochrome row segment to 16-bit RGB565 format.
|
||||
Compiles block-wise bitwise operations at native speed.
|
||||
"""
|
||||
width = self.width
|
||||
canvas_buf = self.canvas_buffer
|
||||
idx = 0
|
||||
|
||||
for y in range(start_row, start_row + num_rows):
|
||||
byte_offset = y * (width // 8)
|
||||
for x_byte_idx in range(width // 8):
|
||||
val = canvas_buf[byte_offset + x_byte_idx]
|
||||
|
||||
# Unroll 8 bits for speed
|
||||
# Bit 7
|
||||
if val & 0x80:
|
||||
row_buf[idx] = 0xFF; row_buf[idx+1] = 0xFF
|
||||
else:
|
||||
row_buf[idx] = 0x00; row_buf[idx+1] = 0x00
|
||||
idx += 2
|
||||
|
||||
# Bit 6
|
||||
if val & 0x40:
|
||||
row_buf[idx] = 0xFF; row_buf[idx+1] = 0xFF
|
||||
else:
|
||||
row_buf[idx] = 0x00; row_buf[idx+1] = 0x00
|
||||
idx += 2
|
||||
|
||||
# Bit 5
|
||||
if val & 0x20:
|
||||
row_buf[idx] = 0xFF; row_buf[idx+1] = 0xFF
|
||||
else:
|
||||
row_buf[idx] = 0x00; row_buf[idx+1] = 0x00
|
||||
idx += 2
|
||||
|
||||
# Bit 4
|
||||
if val & 0x10:
|
||||
row_buf[idx] = 0xFF; row_buf[idx+1] = 0xFF
|
||||
else:
|
||||
row_buf[idx] = 0x00; row_buf[idx+1] = 0x00
|
||||
idx += 2
|
||||
|
||||
# Bit 3
|
||||
if val & 0x08:
|
||||
row_buf[idx] = 0xFF; row_buf[idx+1] = 0xFF
|
||||
else:
|
||||
row_buf[idx] = 0x00; row_buf[idx+1] = 0x00
|
||||
idx += 2
|
||||
|
||||
# Bit 2
|
||||
if val & 0x04:
|
||||
row_buf[idx] = 0xFF; row_buf[idx+1] = 0xFF
|
||||
else:
|
||||
row_buf[idx] = 0x00; row_buf[idx+1] = 0x00
|
||||
idx += 2
|
||||
|
||||
# Bit 1
|
||||
if val & 0x02:
|
||||
row_buf[idx] = 0xFF; row_buf[idx+1] = 0xFF
|
||||
else:
|
||||
row_buf[idx] = 0x00; row_buf[idx+1] = 0x00
|
||||
idx += 2
|
||||
|
||||
# Bit 0
|
||||
if val & 0x01:
|
||||
row_buf[idx] = 0xFF; row_buf[idx+1] = 0xFF
|
||||
else:
|
||||
row_buf[idx] = 0x00; row_buf[idx+1] = 0x00
|
||||
idx += 2
|
||||
|
||||
def show(self):
|
||||
"""Refreshes the screen by writing the frame buffer segment-by-segment."""
|
||||
self.set_window(0, 0, self.width - 1, self.height - 1)
|
||||
|
||||
self.dc(1)
|
||||
self.cs(0)
|
||||
|
||||
num_chunks = self.height // self.chunk_rows
|
||||
for chunk in range(num_chunks):
|
||||
start_row = chunk * self.chunk_rows
|
||||
self._convert_rows(start_row, self.chunk_rows, self.row_buffer)
|
||||
self.spi.write(self.row_buffer)
|
||||
|
||||
self.cs(1)
|
||||
Reference in New Issue
Block a user