bit-banged the calculator's link port to run GPT on a TI-84 Plus
a TI-84 Plus that talks to a large language model via a custom cloud relay proxy server.
[ TI-84 Plus ] -- 2.5mm link cable --> [ Pico W ] --(WSS)--> [ relay ] --> [ Ollama / OpenAI / ... ]
^ |
+---- arrow-key pager (TI-BASIC) <-- silent-link push <-------- paginated reply
What's in here
src/: MicroPython sources for the Pico W. Bit-bang DBUS, packet layer, variable transfers, WebSocket-over-TLS client, and the bridge supervisortools/: host-side utilities. Relay server (echo / LLM / passthrough modes), TI-BASIC source-to-.8Xptokenizer,.8Xpextractor, ad-hoc push/listen scriptsprograms/: on-calculator code.asm_chat/CHAT.z80(the dumb pipe) andbasic_deck/DECK.basic(the user-facing pager) are the two that matter for the chat UX. The other directories are bring-up artifacts kept around as worked examplestests/: host-side tests for the framing layers (packet, variable headers, BASIC tokenizer, .8Xp extractor, bridge pairing logic).deploy/: example systemd units for running the relay as a service on a Linux host
Hardware
- TI-84 Plus (any 84+ family calculator with a 2.5mm link port) Tested on plain 84+ (not Silver Edition, not CSE/CE)
- Raspberry Pi Pico W. Wifi is required: the bridge connects out to a TCP or WSS endpoint
- Cable: a 2.5mm TRS link cable wired to two Pico GPIOs and ground.
Default pin mapping in
src/wire.py: TIP -> GP6, RING -> GP7. Both lines have internal pullups enabled
On the wire
the pico talks DBUS (TI's silent-link protocol over the 2.5mm port) to
the calculator. the wire layer is a software bit-bang: idle is both
lines high, sender pulls one line low to encode a bit, receiver
acknowledges by pulling the other low. bytes are LSB-first. packets are
[machine_id][cmd][len_lo][len_hi][data...][cs_lo][cs_hi].
The chat path uses two transfer directions:
- Calc -> Pico: Z80 program (
CHAT.z80) calls_SendVarCmdfor Str1 (text) and Str2 (math). The Pico's listen loop receives them via the standard RTS/CTS/DATA flow - Pico -> Calc: PC-master push of Str3..Str9, Str0 (reply pages), followed by real var N (page count). The calc must be at the home screen for the OS's idle silent-link receive to accept these. The asm exits cleanly so the deck is parked there before the push starts
there is a settle delay (SETTLE_MS in src/bridge.py, default 600ms)
between pushes: the OS needs wallclock to rearm the idle silent-link
receive after each redraw.
web console below tails every WebSocket frame the relay handles, which made debugging the calc-to-LLM round trip vastly easier than guessing from one side of the wire at a time.
Development
just # list recipes
just test # host-side unit tests (pytest)
just sync # push src/*.py to the Pico
just repl # mpremote REPL on the Pico
just relay # local TCP relay on :9999 (passthrough)
just relay-echo # local TCP relay, auto-reply with "echo: <text>"
just relay-llm # local LLM relay, reads .env for OLLAMA_API_KEY
License
MIT