ldo_trace.png
ldo_holes.png
ldo_cut.png
Describe your soldering process here. Mention the components used (e.g., LDO, capacitors, headers) and any challenges faced during the reflow or hand-soldering process.
Explain how you flashed the firmware. For the Xiao ESP32-C3 or RP2040, describe the bootloader mode and the environment used (Arduino IDE, PlatformIO, or CircuitPython).
### Controls for your KiCAT:
#- **Feed - Pad 1:** Boosts hunger level.
#- **Play - Pad 6:** Boosts boredom/happiness level by playing jump game.
#- **Peek on Status- Pad 4:** Displays the current percentage levels in a small box at the top.
import utime
import urandom
from machine import Pin, I2C, freq
from ssd1306 import SSD1306_I2C
from ws2812 import WS2812
from steptime import STEPTIME
import framebuf
# ----- Hardware Setup -----
freq(250000000)
i2c = I2C(1, scl=Pin(7), sda=Pin(6), freq=400000)
oled = SSD1306_I2C(128, 64, i2c)
power = Pin(11, Pin.OUT)
power.value(1)
led = WS2812(12, 1, 0.5, 6)
# Pad Mapping
PAD_PINS = [2, 4, 3, 1, 27, 26]
for p in PAD_PINS:
Pin(p, Pin.IN, Pin.PULL_UP)
channels = [[STEPTIME(i, pin), [1e6], [0]] for i, pin in enumerate(PAD_PINS)]
# --- Game Bitmaps ---
CAT_A = bytearray([
0,16,64,0,56,224,0,105,160,0,201,32,1,159,160,227,
255,224,243,255,224,54,255,232,119,127,240,110,111,120,111,239,
112,126,127,248,63,255,224,15,255,192,1,129,128,1,129,128
])
CAT_B = bytearray([
0,16,64,0,56,224,0,105,160,0,201,32,1,159,160,227,
255,224,243,255,224,54,255,232,119,127,240,110,111,120,111,239,
112,126,127,248,63,255,224,15,255,192,3,0,192,6,0,96
])
fb_a = framebuf.FrameBuffer(CAT_A, 24, 16, framebuf.MONO_HLSB)
fb_b = framebuf.FrameBuffer(CAT_B, 24, 16, framebuf.MONO_HLSB)
# --- Stats & State ---
STATE_PET, STATE_GAME = 0, 1
mode = STATE_PET
hunger_lvl, boredom_lvl = 100, 100
last_tick = utime.ticks_ms()
# --- Physics Constants ---
GRAVITY = 4 #
JUMP_VEL = -18 #
GROUND_Y = 40
cat_y, cat_v, obs_x, game_score = GROUND_Y, 0, 128, 0
def show_rotated(display):
rotated_buffer = bytearray(1024)
rotated_fb = framebuf.FrameBuffer(rotated_buffer, 128, 64, framebuf.MONO_VLSB)
for y in range(64):
for x in range(128):
if display.pixel(x, y):
rotated_fb.pixel(127 - x, 63 - y, 1)
display.i2c.writeto_mem(display.addr, 0x40, rotated_buffer)
def draw_full_face(expression="neutral", blink=False):
oled.fill(0)
size = 14
if expression == "critical":
for i in range(2):
oled.line(20+i, 25, 20+size+i, 25+size, 1)
oled.line(20+i, 25+size, 20+size+i, 25, 1)
oled.line(94+i, 25, 94+size+i, 25+size, 1)
oled.line(94+i, 25+size, 94+size+i, 25, 1)
elif expression == "sad":
# Extra thick Worried Eyes (/ \)
for i in range(3):
oled.line(20, 38+i, 20+size, 32+i, 1)
oled.line(94, 32+i, 94+size, 38+i, 1)
elif blink:
oled.fill_rect(20, 32, size, 2, 1)
oled.fill_rect(94, 32, size, 2, 1)
else:
oled.fill_rect(20, 25, size, size, 1)
oled.fill_rect(94, 25, size, size, 1)
m_y = 33 if expression == "happy" else 44 if expression in ["sad", "critical"] else 38
oled.fill_rect(57, m_y-13, size, size, 1)
oled.fill_rect(44, m_y, size, size, 1)
oled.fill_rect(70, m_y, size, size, 1)
while True:
now = utime.ticks_ms()
deltas = []
for ch in channels:
sm, min_val, _ = ch
sm.put(200); sm.put(20000)
res = 4294967296 - sm.get()
if res < min_val[0]: min_val[0] = res
deltas.append(res - min_val[0])
if mode == STATE_PET:
if utime.ticks_diff(now, last_tick) > 10000:
hunger_lvl, boredom_lvl = max(0, hunger_lvl - 2), max(0, boredom_lvl - 3)
last_tick = now
if deltas[0] > 15000: hunger_lvl = min(100, hunger_lvl + 15); utime.sleep_ms(200)
if deltas[5] > 15000:
mode = STATE_GAME
cat_y, cat_v, obs_x, game_score = GROUND_Y, 0, 128, 0
utime.sleep_ms(300)
mood = "critical" if (hunger_lvl <= 0 or boredom_lvl <= 0) else "sad" if (hunger_lvl < 30 or boredom_lvl < 30) else "happy"
draw_full_face(mood, blink=(now // 2000 % 3 == 0))
if deltas[3] > 15000:
oled.fill_rect(10, 0, 108, 20, 0); oled.rect(10, 0, 108, 20, 1)
oled.text("H:{}% B:{}%".format(hunger_lvl, boredom_lvl), 18, 6)
elif mode == STATE_GAME:
oled.fill(0); oled.line(0, 56, 128, 56, 1)
if deltas[5] > 10000 and cat_y >= GROUND_Y: cat_v = JUMP_VEL
cat_v += GRAVITY
cat_y += cat_v
if cat_y > GROUND_Y: cat_y = GROUND_Y; cat_v = 0
obs_x -= 8 #
if obs_x < -16: obs_x = 128; game_score += 1
# Animation:
active_fb = fb_a if (cat_y < GROUND_Y or (now // 100 % 2 == 0)) else fb_b
oled.blit(active_fb, 20, int(cat_y))
oled.fill_rect(obs_x, 48, 8, 8, 1)
oled.text("Win: {}/5".format(game_score), 35, 0)
if obs_x < 52 and obs_x > 10 and cat_y > 32:
led.pixels_fill((255, 0, 0)); led.pixels_show(); utime.sleep_ms(500); mode = STATE_PET
if game_score >= 5:
boredom_lvl = min(100, boredom_lvl + 30); mode = STATE_PET
show_rotated(oled)
utime.sleep_ms(5)