Created
July 16, 2026 20:24
-
-
Save lynt-smitka/2e05f17dd7530d7a73979298e5adcdc2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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)] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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()