From d2b33d0719a1aacc7253e500c8fb47efcfdf04bb Mon Sep 17 00:00:00 2001 From: Tanner Collin Date: Sat, 21 Mar 2026 23:08:50 +0000 Subject: [PATCH] Add Tim's display test code --- display.py | 379 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 379 insertions(+) create mode 100644 display.py diff --git a/display.py b/display.py new file mode 100644 index 0000000..12aa63b --- /dev/null +++ b/display.py @@ -0,0 +1,379 @@ +# 7 Segment display test file +# +# By Tim + +from smbus2 import SMBus +import time +import math +import random + +""" +Each segment of the 14‑segment display is represented by a single bit +in a 16‑bit integer. We use a dictionary to map segment names ("a", "b", ...) +to their corresponding bitmask. + +Bitmask example: + 1 << 0 = 0000 0000 0000 0001 + 1 << 1 = 0000 0000 0000 0010 + 1 << 2 = 0000 0000 0000 0100 +""" + +SEG = { + "a": 1 << 0, + "b": 1 << 1, + "c": 1 << 2, + "d": 1 << 3, + "e": 1 << 4, + "f": 1 << 5, + "g1": 1 << 6, + "g2": 1 << 7, + "h": 1 << 8, + "i": 1 << 9, + "j": 1 << 10, + "k": 1 << 11, + "l": 1 << 12, + "m": 1 << 13, + "dp": 1 << 14, +} + +# A convenience list used by some effects +ALL_SEGMENTS = list(SEG.values()) + +""" +This function takes any number of segment names and returns a combined +bitmask. It lets us write: + + seg("a", "b", "c") + +instead of manually OR-ing bits together. +""" + +def seg(*names: str) -> int: + value = 0 + for n in names: + value |= SEG[n] # bitwise OR + return value + +""" +FONT is a dictionary mapping characters ("A", "b", "3", etc.) +to the bitmask that lights up the correct segments. + +This is a canonical HP‑style font, designed for clarity and legibility. +""" + +FONT = { + # punctuation + " ": 0, + "-": seg("g1","g2"), + "_": seg("d"), + ".": seg("dp"), + ":": seg("i","l"), + "!": seg("i","dp"), + "?": seg("a","b","g2","k","dp"), + "/": seg("j","m"), + "\\": seg("h","k"), + "'": seg("h"), + "\"": seg("h","j"), + ",": seg("m"), + + # digits + "0": seg("a","b","c","d","e","f"), + "1": seg("b","c"), + "2": seg("a","b","g1","g2","e","d"), + "3": seg("a","b","c","d","g1","g2"), + "4": seg("f","g1","g2","b","c"), + "5": seg("a","f","g1","g2","c","d"), + "6": seg("a","f","e","d","c","g1","g2"), + "7": seg("a","b","c"), + "8": seg("a","b","c","d","e","f","g1","g2"), + "9": seg("a","b","c","d","f","g1","g2"), + + # uppercase + "A": seg("a","b","c","e","f","g1","g2"), + "B": seg("a","j","m","g1","d","e","f"), + "C": seg("a","f","e","d"), + "D": seg("a","b","c","d","i","l"), + "E": seg("a","f","e","d","g1","g2"), + "F": seg("a","f","e","g1","g2"), + "G": seg("a","f","e","d","c","g2"), + "H": seg("f","e","b","c","g1","g2"), + "I": seg("a","d","i","l"), + "J": seg("b","c","d","e"), + "K": seg("f","e","g1","j","m"), + "L": seg("f","e","d"), + "M": seg("f","e","b","c","h","j"), + "N": seg("f","e","b","c","h","m"), + "O": seg("a","b","c","d","e","f"), + "P": seg("a","b","f","e","g1","g2"), + "Q": seg("a","b","c","d","e","f","m"), + "R": seg("a","b","f","e","g1","g2","m"), + "S": seg("a","f","g1","g2","c","d"), + "T": seg("a","i","l"), + "U": seg("f","e","b","c","d"), + "V": seg("f","e","j","k"), + "W": seg("f","e","b","c","k","m"), + "X": seg("h","j","k","m"), + "Y": seg("b","f","g1","g2","l"), + "Z": seg("a","j","k","d"), + + # lowercase + "a": seg("l","g2","c","d"), + "b": seg("f","e","d","c","g1","g2"), + "c": seg("g1","g2","e","d"), + "d": seg("b","c","d","e","g1","g2"), + "e": seg("g1","g2","e","d","c"), + "f": seg("a","f","e","g1"), + "g": seg("m","c","d","g2"), + "h": seg("f","e","g1","g2","c"), + "i": seg("i","l"), + "j": seg("b","c","d"), + "k": seg("f","e","g1","j","k"), + "l": seg("f","e"), + "m": seg("e","l","c","h","j"), + "n": seg("e","g1","g2","c"), + "o": seg("g1","g2","c","d","e"), + "p": seg("a","b","f","e","g1","g2"), + "q": seg("a","b","c","d","g1","g2"), + "r": seg("e","g1"), + "s": seg("a","f","g1","g2","c","d"), + "t": seg("f","e","d","g1"), + "u": seg("e","c","d"), + "v": seg("e","c","j"), + "w": seg("e","c","k","m"), + "x": seg("h","j","k","m"), + "y": seg("h","g2","b","c","d"), + "z": seg("a","b","d","e","k"), +} + +# Helper to fetch glyphs +def glyph_for(ch: str, dot=False) -> int: + value = FONT.get(ch, 0) + if dot: + value |= SEG["dp"] + return value + +""" +This class represents the entire display system. + +A class bundles: +• data (the display buffer) +• behavior (methods like scroll_text, chase, rainbow) + +Every method that begins with "self" operates on the display instance. +""" + +class StarburstHT16K33: + + # Constructor: runs when you create the object + def __init__(self, bus=1, addr0=0x70, addr1=0x71, brightness=15): + """ + __init__ is the constructor. It sets up the I²C bus and + initializes the display chips. + """ + self.bus = SMBus(bus) + self.addr = [addr0, addr1] # two HT16K33 chips + self.buffer = [0] * 8 # 8 digits + self.brightness = max(0, min(15, brightness)) + + # Initialize each chip + for ht16k55_addr in self.addr: + self._cmd(ht16k55_addr, 0x21) # turn on oscillator + self._cmd(ht16k55_addr, 0xE0 | self.brightness) + self._cmd(ht16k55_addr, 0x81) # display on + + # Low-level I²C helpers + def _cmd(self, address, cmd): + """Send a single command byte to the chip.""" + self.bus.write_byte(address, cmd) + + def _write_digit_block(self, address, values): + """ + Write 4 digits (8 bytes) to one HT16K33 chip. + Each digit is 16 bits, so we split into two bytes. + """ + data = [] + for v in values: + data.append(v & 0xFF) # low byte + data.append((v >> 8) & 0xFF) # high byte + self.bus.write_i2c_block_data(address, 0x00, data) + + # Basic display operations + def set_brightness(self, brightness): + """Set brightness 0–15.""" + self.brightness = max(0, min(15, brightness)) + for a in self.addr: + self._cmd(a, 0xE0 | self.brightness) + + def set_char(self, pos, ch, dot=False): + """Write a single character to a position.""" + if 0 <= pos < 8: + self.buffer[pos] = glyph_for(ch, dot) + + def set_raw(self, pos, mask): + """Write a raw bitmask to a digit.""" + if 0 <= pos < 8: + self.buffer[pos] = mask + + def clear(self): + """Clear the display buffer.""" + for i in range(8): + self.buffer[i] = 0 + + def show(self): + """Send the buffer to the hardware.""" + self._write_digit_block(self.addr[0], self.buffer[0:4]) + self._write_digit_block(self.addr[1], self.buffer[4:8]) + + # Static text writer + def write_text(self, text: str, align: str = "left"): + """ + Write up to 8 characters of text. + Demonstrates: + • slicing + • alignment logic + • enumerate() + """ + text = text[:8] + self.clear() + + if align == "right": + start = max(0, 8 - len(text)) + elif align == "center": + start = max(0, (8 - len(text)) // 2) + else: + start = 0 + + for i, ch in enumerate(text): + self.set_char(start + i, ch) + + self.show() + + # Animated scrolling text with easing curves + def scroll_text(self, text, scroll_speed=8, pulse=False, loop=False, easing="linear"): + """ + Demonstrates: + • nested functions + • closures + • easing curves + • animation loops + • time-based effects + """ + + scroll_speed = max(0, min(15, scroll_speed)) + base_delay = 0.35 * (0.85 ** scroll_speed) + + # Local function: easing curve + def ease(t): + if easing == "linear": return t + if easing == "in": return t * t + if easing == "out": return 1 - (1 - t)**2 + if easing == "inout": return 0.5 * (1 - math.cos(math.pi * t)) + return t + + pad = " " * 8 + s = pad + text + pad + n = len(s) + + pulse_phase = 0.0 + pulse_step = 0.25 + (scroll_speed / 60) + base_brightness = self.brightness + + while True: + for offset in range(n - 7): + t = offset / (n - 8) if n > 8 else 0 + delay = base_delay * (0.5 + ease(t)) + + window = s[offset:offset + 8] + self.clear() + for i, ch in enumerate(window): + self.set_char(i, ch) + self.show() + + if pulse: + pulse_phase += pulse_step + wave = 0.5 * (1 - math.cos(pulse_phase)) + b = 1 + int(wave * 14) + self.set_brightness(b) + + time.sleep(delay) + + if not loop: + break + + self.set_brightness(base_brightness) + + # Marquee wrapper + def marquee(self, text, scroll_speed=8, pulse=False, cycles=1, easing="linear"): + """Just calls scroll_text() multiple times.""" + for _ in range(cycles): + self.scroll_text(text, scroll_speed, pulse, False, easing) + + # Per-segment chase effect + def chase(self, speed=8, cycles=2): + """Lights one segment at a time across all digits.""" + delay = 0.15 * (0.85 ** speed) + for _ in range(cycles): + for segmask in ALL_SEGMENTS: + for pos in range(8): + self.buffer[pos] = segmask + self.show() + time.sleep(delay) + + # Rainbow brightness sweep + def rainbow(self, cycles=2, speed=8): + """Brightness follows a sine wave.""" + steps = 60 + delay = 0.05 * (0.85 ** speed) + + for _ in range(cycles): + for i in range(steps): + phase = i / steps + b = 1 + int(14 * 0.5 * (1 - math.cos(2 * math.pi * phase))) + self.set_brightness(b) + self.show() + time.sleep(delay) + + # Sparkle effect + def sparkle(self, duration=2.0, density=0.15, speed=8): + """Random segments flicker like stars.""" + delay = 0.05 * (0.85 ** speed) + end = time.time() + duration + + while time.time() < end: + self.clear() + for pos in range(8): + if random.random() < density: + self.buffer[pos] = random.choice(ALL_SEGMENTS) + self.show() + time.sleep(delay) + +def main(): + """ + Demonstrates: + • object creation + • calling methods + • sequencing animations + """ + disp = StarburstHT16K33(brightness=10) + + disp.write_text("HELLO") + time.sleep(1) + + disp.scroll_text("Learning Python!", scroll_speed=1, easing="inout", pulse=True) + disp.marquee("HP FONT DEMO ", scroll_speed=8, cycles=2) + disp.marquee("Learning Python! ", scroll_speed=1, cycles=5) + + disp.chase(speed=8) + disp.write_text("RAINBOW") + disp.rainbow() + + disp.write_text("SPARKLE") + disp.sparkle(duration=2.0) + + disp.write_text("DONE") + time.sleep(1) + disp.clear() + disp.show() + +if __name__ == "__main__": + main()