Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
"""
================================================================================
HAL 9000: BARE-METAL SSD1306 DRIVER (128x32) & DEMO
Hardware: RP2350 (Pico 2) | I2C1: SDA=GP14, SCL=GP15 | Gamepad: GP0-GP6
================================================================================
"""
import machine
import time
# --- GAMEPAD DRIVER ---
# GP0=UP, GP1=DOWN, GP2=LEFT, GP3=RIGHT, GP4=MID, GP5=SET, GP6=RST
pad_pins = [machine.Pin(i, machine.Pin.IN, machine.Pin.PULL_UP) for i in range(7)]
def read_gamepad():
"""Returns a list of 7 integers: 1 = Pressed, 0 = Open."""
return [1 if p.value() == 0 else 0 for p in pad_pins]
# --- THE OLED DRIVER ---
class FastOLED_128x32:
def __init__(self, i2c, address=0x3c, rotation=0):
self.i2c = i2c
self.addr = address
self.rot = rotation
# Logical dimensions for bounds checking
if self.rot == 90 or self.rot == 270:
self.width, self.height = 32, 128
else:
self.width, self.height = 128, 32
# THE DOUBLE BUFFER:
# 513 bytes total. Index 0 is the I2C control byte (0x40).
# Indexes 1-512 are the raw video RAM.
self.payload = bytearray(513)
self.payload[0] = 0x40
self.buffer = memoryview(self.payload)[1:]
self._init_display()
def _init_display(self):
# Determine hardware mirroring for 0 vs 180 degrees
seg_remap = 0xA1 if self.rot != 180 else 0xA0
com_scan = 0xC8 if self.rot != 180 else 0xC0
# Standard 128x32 Boot Sequence
init_cmds = [
0xAE, # Display OFF
0xD5, 0x80, # Set clock divide ratio
0xA8, 0x1F, # Multiplex ratio for 32 lines (0x1F = 31)
0xD3, 0x00, # Display offset
0x40, # Set start line 0
0x8D, 0x14, # Enable charge pump
0x20, 0x00, # Memory addressing mode: Horizontal
seg_remap, # Hardware Segment Remap
com_scan, # Hardware COM Output Scan Direction
0xDA, 0x02, # COM pins hardware config for 128x32
0x81, 0x8F, # Contrast
0xD9, 0xF1, # Pre-charge period
0xDB, 0x40, # VCOMH deselect level
0xA4, # Entire display ON resume
0xA6, # Normal display (not inverted)
0xAF # Display ON
]
for cmd in init_cmds:
self.i2c.writeto(self.addr, bytes([0x00, cmd]))
def show(self):
"""BLASTS the entire 513-byte payload to the screen instantly."""
self.i2c.writeto(self.addr, self.payload)
def clear(self):
"""Zeroes out the 512-byte video RAM."""
for i in range(512):
self.buffer[i] = 0
def pixel(self, x, y, color=1):
"""Translates logical coordinates, handles rotation, and sets the bit."""
# Software coordinate swapping for portrait modes
if self.rot == 90:
x, y = y, 127 - x
elif self.rot == 270:
x, y = 31 - y, x
if 0 <= x < 128 and 0 <= y < 32:
idx = x + (y // 8) * 128
if color:
self.buffer[idx] |= (1 << (y % 8))
else:
self.buffer[idx] &= ~(1 << (y % 8))
# --- GEOMETRY PRIMITIVES ---
def hline(self, x, y, w, color=1):
for i in range(x, x + w): self.pixel(i, y, color)
def vline(self, x, y, h, color=1):
for i in range(y, y + h): self.pixel(x, i, color)
def line(self, x0, y0, x1, y1, color=1):
"""Bresenham's Line Algorithm"""
dx = abs(x1 - x0)
dy = -abs(y1 - y0)
sx = 1 if x0 < x1 else -1
sy = 1 if y0 < y1 else -1
err = dx + dy
while True:
self.pixel(x0, y0, color)
if x0 == x1 and y0 == y1: break
e2 = 2 * err
if e2 >= dy:
err += dy
x0 += sx
if e2 <= dx:
err += dx
y0 += sy
def rect(self, x, y, w, h, color=1, filled=False):
if filled:
for i in range(x, x + w):
self.vline(i, y, h, color)
else:
self.hline(x, y, w, color)
self.hline(x, y + h - 1, w, color)
self.vline(x, y, h, color)
self.vline(x + w - 1, y, h, color)
def circle(self, x0, y0, r, color=1, filled=False):
"""Midpoint Circle Algorithm"""
f = 1 - r
ddF_x, ddF_y = 1, -2 * r
x, y = 0, r
self.pixel(x0, y0 + r, color)
self.pixel(x0, y0 - r, color)
self.pixel(x0 + r, y0, color)
self.pixel(x0 - r, y0, color)
if filled: self.vline(x0, y0 - r, 2*r + 1, color)
while x < y:
if f >= 0:
y -= 1
ddF_y += 2
f += ddF_y
x += 1
ddF_x += 2
f += ddF_x
if filled:
self.vline(x0 + x, y0 - y, 2*y + 1, color)
self.vline(x0 - x, y0 - y, 2*y + 1, color)
self.vline(x0 + y, y0 - x, 2*x + 1, color)
self.vline(x0 - y, y0 - x, 2*x + 1, color)
else:
self.pixel(x0 + x, y0 + y, color)
self.pixel(x0 - x, y0 + y, color)
self.pixel(x0 + x, y0 - y, color)
self.pixel(x0 - x, y0 - y, color)
self.pixel(x0 + y, y0 + x, color)
self.pixel(x0 - y, y0 + x, color)
self.pixel(x0 + y, y0 - x, color)
self.pixel(x0 - y, y0 - x, color)
def blit_8x8(self, x, y, bmp_bytes, color=1):
"""Draws an 8x8 bitmap from a tuple of 8 bytes."""
for row in range(8):
b = bmp_bytes[row]
for col in range(8):
# Check each bit from left to right
if b & (1 << (7 - col)):
self.pixel(x + col, y + row, color)
# ==============================================================================
# MAIN DEMO: THE GEOMETRY ENGINE & SHIP
# ==============================================================================
def run_oled_demo():
print("HAL 9000: Initializing I2C1 and OLED Driver...")
i2c = machine.I2C(1, sda=machine.Pin(14), scl=machine.Pin(15), freq=1000000) # Pushed to 1MHz!
# Initialize display at 0 degrees rotation
oled = FastOLED_128x32(i2c, rotation=0)
# A tiny 8x8 alien spacecraft bitmap (1 in binary means draw pixel)
# 0x18 = 00011000
# 0x3C = 00111100
ship_bmp = (
0x18,
0x3C,
0x7E,
0xFF,
0xDB,
0xFF,
0x24,
0x5A
)
ship_x, ship_y = 60, 12
print("HAL 9000: Display active. Use D-pad to fly.")
try:
while True:
# 1. Read Inputs
pad = read_gamepad()
if pad[0]: ship_y -= 2 # UP
if pad[1]: ship_y += 2 # DOWN
if pad[2]: ship_x -= 2 # LEFT
if pad[3]: ship_x += 2 # RIGHT
# Wrap around screen edges
ship_x %= oled.width
ship_y %= oled.height
# 2. Clear Double Buffer
oled.clear()
# 3. Draw Static Geometry Test Elements
oled.rect(0, 0, oled.width, oled.height) # Outer Frame
oled.circle(10, 16, 8, filled=False) # Left Target
oled.circle(118, 16, 8, filled=True) # Right Moon
oled.line(20, 0, 40, 31) # Cross line 1
oled.line(40, 0, 20, 31) # Cross line 2
# 4. Draw Player Ship
oled.blit_8x8(ship_x, ship_y, ship_bmp)
# 5. Blast to Hardware (Zero flicker)
oled.show()
# Tiny delay to keep it playable
time.sleep(0.01)
except KeyboardInterrupt:
oled.clear()
oled.show()
print("HAL 9000: Demo Terminated. Goodnight.")
run_oled_demo()9dd42891cThis file is pinned to the selected release. Download the original to open it in your design tool.