Skip to content

Instantly share code, notes, and snippets.

@lynt-smitka
Created July 16, 2026 20:24
Show Gist options
  • Select an option

  • Save lynt-smitka/2e05f17dd7530d7a73979298e5adcdc2 to your computer and use it in GitHub Desktop.

Select an option

Save lynt-smitka/2e05f17dd7530d7a73979298e5adcdc2 to your computer and use it in GitHub Desktop.
# picogame_ttp229: a TTP229 (TTP226/229 family) capacitive touch keypad on the 2-wire SERIAL
# interface (SCL + SDO), as a DROP-IN for picogame_input.Buttons.
#
# import board, picogame_ttp229 as ttp
# btn = ttp.Buttons(scl=board.GP16, sdo=board.GP17, keymap={
# 6: "UP", 8: "DOWN", 5: "LEFT", 7: "RIGHT", 4: "A", 3: "B"}) # key NUMBERS from scan()
# # then use it exactly like picogame_input.Buttons: btn.poll(); btn.is_pressed(btn.A); ...
#
import digitalio
import picogame_input as _pi # reuse the button vocabulary (bits + NAMES) + pin resolver
NAMES = _pi.NAMES
_NBITS = _pi._NBITS
def read(scl, sdo, n=16, active_low=True):
"""One 2-wire read -> a bitmask, bit i set = key (i+1) touched. digitalio.value is already ~us on
CircuitPython, so a couple of no-op spins is ample settle without a us-accurate delay."""
mask = 0
for i in range(n):
scl.value = False
for _ in range(3):
pass
pressed = (not sdo.value) if active_low else sdo.value
if pressed:
mask |= (1 << i)
scl.value = True
for _ in range(3):
pass
return mask
class Buttons:
"""Same query API as picogame_input.Buttons (poll / is_pressed / just_pressed / just_released /
repeat / has / clear), sourced from a TTP229 serial keypad. self._keys stays None so the edge
logic uses the state-diff path, exactly like the digitalio polling backend."""
UP, DOWN, LEFT, RIGHT, A, B, X, Y = _pi.UP, _pi.DOWN, _pi.LEFT, _pi.RIGHT, _pi.A, _pi.B, _pi.X, _pi.Y
L1, L2, R1, R2, START, SELECT, ALL = _pi.L1, _pi.L2, _pi.R1, _pi.R2, _pi.START, _pi.SELECT, _pi.ALL
def __init__(self, scl, sdo, keymap, keys=16, active_low=True):
# scl/sdo = a Pin object or a name ("GP16" / a board attr); keymap = {key_number: BIT or "UP"/...}
scl_pin = _pi._resolve_pin(scl)
sdo_pin = _pi._resolve_pin(sdo)
if scl_pin is None or sdo_pin is None:
raise ValueError("picogame_ttp229: unresolved SCL/SDO pin")
self._scl = digitalio.DigitalInOut(scl_pin)
self._scl.switch_to_output(value=True) # SCL idles HIGH
self._sdo = digitalio.DigitalInOut(sdo_pin)
self._sdo.switch_to_input() # module drives SDO push-pull
self._n = keys
self._active_low = active_low
self._map = [0] * (keys + 1) # key number 1..keys -> game button bit
self._mapped = 0
for k, v in keymap.items():
bit = v if isinstance(v, int) else NAMES[v]
if 1 <= k <= keys:
self._map[k] = bit
self._mapped |= bit
self.state = 0
self.prev = 0
self._hold = [0] * _NBITS
def poll(self):
self.prev = self.state
raw = read(self._scl, self._sdo, self._n, self._active_low)
s = 0
m = self._map
for i in range(self._n):
if raw & (1 << i):
s |= m[i + 1]
self.state = s
h = self._hold
i = 0
b = 1
limit = 1 << _NBITS
while b < limit:
h[i] = h[i] + 1 if (s & b) else 0
i += 1
b <<= 1
return s
def is_pressed(self, mask=ALL):
return bool(self.state & mask)
def just_pressed(self, mask=ALL):
return bool((self.state & ~self.prev) & mask)
def just_released(self, mask=ALL):
return bool((~self.state & self.prev) & mask)
def has(self, mask=ALL):
return bool(self._mapped & mask)
def repeat(self, button, delay=15, interval=4):
b = button
i = 0
while b > 1:
b >>= 1
i += 1
c = self._hold[i]
if c == 1:
return True
return c > delay and (c - delay) % interval == 0
def clear(self):
self.state = self.prev = 0
for i in range(_NBITS):
self._hold[i] = 0
def scan(self):
"""Discovery: the list of currently-touched key NUMBERS (1..n). Build with keymap={}, touch
each cap, read the number, then fill keymap."""
raw = read(self._scl, self._sdo, self._n, self._active_low)
return [i + 1 for i in range(self._n) if raw & (1 << i)]
@DamianVCechov

Copy link
Copy Markdown

Fix of row 13.
Dynamically calculates the number of bits based on the highest bit value in your button dictionary:

_NBITS = max(_pi.NAMES.values()).bit_length()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment