Skip to content

Instantly share code, notes, and snippets.

@TollyH
Last active April 29, 2024 20:02
Show Gist options
  • Save TollyH/81289034d05e208bde14c8726710a311 to your computer and use it in GitHub Desktop.
Save TollyH/81289034d05e208bde14c8726710a311 to your computer and use it in GitHub Desktop.
Play videos on a 2-line character LCD display by utilising custom characters
# This script plays a series of still frames on a 2-line character LCD display.
# It requires a Raspberry Pi Pico running my uart_lcd application:
# https://github.com/TollyH/raspberry-pi-pico/tree/main/uart_lcd
# Videos must already be split into individual 20x16 images of 1-bit colour,
# tools like ImageMagick and ffmpeg can be used to do that pretty easily.
import os
import serial
import tqdm
from PIL import Image
IMAGE_FOLDER = "Put folder path here"
SERIAL_DEVICE = "Put serial port of RPi here" # e.g. "COM5" or "/dev/ttyUSB0"
BAUD_RATE = 115200 # Keep this unless you know it needs changing
print("\nLoading frame names...")
frame_names = os.listdir(IMAGE_FOLDER)
frame_names.sort()
print("\nPreloading frames...")
frames: list[list[str]] = []
for name in tqdm.tqdm(frame_names):
image = Image.open(os.path.join(IMAGE_FOLDER, name))
assert image.mode == "1"
assert image.width == 20
assert image.height == 16
# One frame is 4x2 LCD characters (characters are 5x8)
characters: list[str] = []
for cy in range(2):
for cx in range(4):
pixel_rows: list[str] = []
for y in range(cy * 8, cy * 8 + 8):
pixel_rows.append("")
for x in range(cx * 5, cx * 5 + 5):
pixel_rows[-1] += '1' if image.getpixel((x, y)) else '0'
characters.append(' '.join(pixel_rows))
frames.append(characters)
print("\nOpening serial port...")
serial_port = serial.Serial(SERIAL_DEVICE, BAUD_RATE)
print("\nInitialising display and drawing required characters...")
serial_port.write("\n".encode())
serial_port.write("#init 2 8\n".encode())
serial_port.write("#set 1 0 0\n".encode())
serial_port.write("#backlight 1\n".encode())
serial_port.write("#clear\n".encode())
serial_port.write("#write_custom 0\n".encode())
serial_port.write("#write_custom 1\n".encode())
serial_port.write("#write_custom 2\n".encode())
serial_port.write("#write_custom 3\n".encode())
serial_port.write("#newline\n".encode())
serial_port.write("#write_custom 4\n".encode())
serial_port.write("#write_custom 5\n".encode())
serial_port.write("#write_custom 6\n".encode())
serial_port.write("#write_custom 7\n".encode())
input("Ready. Press enter key to begin animation...")
for frame in tqdm.tqdm(frames):
for i, char in enumerate(frame):
serial_port.write(f"#def_custom {i} {char}\n".encode())
serial_port.close()
# This script plays a series of still frames on my character LCD simulator:
# https://github.com/TollyH/LCDSimulator
# Videos must already be split into individual 20x16 images of 1-bit colour,
# tools like ImageMagick and ffmpeg can be used to do that pretty easily.
import os
import subprocess
import time
import tqdm
from PIL import Image
IMAGE_FOLDER = "Put folder path here"
EXE_PATH = "Put path to LCDSimulator.GUI.exe here"
INSTRUCTION_LOGGING = False # Enabling this may slightly impact performance
FRAME_DELAY = 1 / 29.97 # For 29.97 FPS, adjust for your video
print("\nLoading frame names...")
frame_names = os.listdir(IMAGE_FOLDER)
frame_names.sort()
print("\nPreloading frames...")
frames: list[list[list[str]]] = []
for name in tqdm.tqdm(frame_names):
image = Image.open(os.path.join(IMAGE_FOLDER, name))
assert image.mode == "1"
assert image.width == 20
assert image.height == 16
# One frame is 4x2 LCD characters (characters are 5x8)
characters: list[list[str]] = []
for cy in range(2):
for cx in range(4):
pixel_rows: list[str] = []
for y in range(cy * 8, cy * 8 + 8):
pixel_rows.append("")
for x in range(cx * 5, cx * 5 + 5):
pixel_rows[-1] += '1' if image.getpixel((x, y)) else '0'
characters.append(pixel_rows)
frames.append(characters)
print("\nStarting process...")
args = [EXE_PATH]
if INSTRUCTION_LOGGING:
args.append("--log")
process = subprocess.Popen(
args, stdin=subprocess.PIPE, stdout=subprocess.DEVNULL,
bufsize=1, universal_newlines=True
)
assert process.stdin is not None
print("\nInitialising display and drawing required characters...")
process.stdin.write("\n")
process.stdin.write("#power 1\n")
process.stdin.write("#init 2 8\n")
process.stdin.write("#set 1 0 0\n")
process.stdin.write("#backlight 1\n")
process.stdin.write("#clear\n")
process.stdin.write("#write_custom 0\n")
process.stdin.write("#write_custom 1\n")
process.stdin.write("#write_custom 2\n")
process.stdin.write("#write_custom 3\n")
process.stdin.write("#newline\n")
process.stdin.write("#write_custom 4\n")
process.stdin.write("#write_custom 5\n")
process.stdin.write("#write_custom 6\n")
process.stdin.write("#write_custom 7\n")
process.stdin.write("#raw_tx 0 01000000\n")
input(
"Ready. Adjust contrast and screen appearance, "
+ "then press enter key to begin animation..."
)
for frame in tqdm.tqdm(frames):
frame_start = time.time()
for char in frame:
for row in char:
process.stdin.write(f"#raw_tx 1 000{row}\n")
frame_end = time.time()
# Exclude the time taken to draw the frame from the sleep duration
# to achieve accurate frame rate.
required_delay = FRAME_DELAY - (frame_end - frame_start)
if required_delay > 0:
time.sleep(required_delay)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment