> snackdriven.com

I Spent a Week Staring at Bands

Okay, so. Last time I cracked the little screen on my keyboard and put a 12-hour clock and a spinning pink skull on it, and I called that the end of the story. It was the tutorial level. What I actually wanted crept in afterward: the whole keyboard made mine, not just pixels on a screen. Recolor the factory rainbow to my own colors, keep the screen while I did it, and put something worth looking at on it, like whatever’s playing. That took a week. This is the messy part I skipped over.

The first thing I learned is that building the workbench is its own project. Claude and I built a little browser app, al80-studio, that drives the screen from a tab and cracked the lighting protocol, so I could bend the factory rainbow into a palette I chose. Same arrangement as last time: Claude writes confident code fast, and I find out on the actual keyboard whether it was right. Set a value, read it back, and the keyboard repeats it with a confirmation byte on top. That part, at least, never lied to me.

Then I got greedy.

I wanted the recolor to live on the keyboard, not in a browser tab I have to remember to open. So we pulled the vendor’s firmware apart and compiled our own. Keys, per-key lighting, a full editable keymap, all built clean and working. One problem, and it’s the whole reason I bought this keyboard: the custom firmware can’t light the screen. One physical pin pulls double duty as both an LED enable and a screen control line, and they can’t both have it. Untangling which is which is a logic-analyzer job I haven’t sat down for. So I have a lovely custom firmware that does everything except the one thing I care about.

Then two dead ends, both the same shape of wrong, and both Claude’s. It spent an evening hunting the firmware for a separate data wire to the side light bar, certain it was its own addressable strip. It isn’t. The bar is three more LEDs on the same chip as the keys, and the real fix was three lines in a table. Same with the radio: it was sure a wireless keyboard had a hidden control channel we could drive everything through. It doesn’t, it just carries keystrokes. Both detours, except the radio dig coughed up the exact math the keyboard uses to read its own battery, which none of us went looking for and I am absolutely going to use.

And then the banding, which I’ll be telling people about for a while. Photos came out striped, red and blue horizontal bands like a broken broadcast. Claude found a cousin keyboard online, same chip family, whose docs said it stored pixels column-first instead of in rows. Tidy. The banding was obviously that, it decided, and it built the fix and shipped it. I put it on the keyboard and the photo came out sideways, which proved the exact opposite: this screen stores pixels in rows like anything else, and column order was never the bug.

The real bug was dumber, and I’m fond of it. We were shoving the image at the screen faster than it could swallow, and it was quietly dropping bytes on the floor. Send a block, wait for the screen’s one-byte acknowledgment, send the next. Flow control, the least glamorous thing in computing. And the twist: Claude got extra careful and added pacing delays between the blocks, and every time I tested it, it got worse. The screen doesn’t want gaps, it wants the natural back-and-forth. We took the delays out and it cleaned up.

Which brings me to the wall that beat us longest. The now-playing card is built, album art and track and a progress bar rendered down to the 96 by 160 panel. (I called it 112 by 137 last post. Wrong about that too, turns out.) The frame writes perfectly: all 549 blocks acknowledged, in about a second. And the screen shows the old picture. It confirms every block and displays nothing new. There’s a “now actually show it” step we’re either not sending or sending a beat too early.

A beat too early was the right instinct, but the thing that actually cracked it was me at the keyboard, pressing the physical button that’s supposed to show a picture. It showed one. Last week’s, not the frame we’d just written. So the write was landing somewhere the screen wasn’t looking, and it was never about the pixels. Timing again, same as the flow control.

The setup packet we’d been treating as a header is the command that shows the picture. It tells the screen to commit the buffer to the glass, and it needs a moment to do it: a pause after the announce, a shorter one after the setup, and the commit takes. The native code fired the two back to back with no gap, so the picture sat in a scratch buffer, acknowledged and never committed, while the screen kept showing the last one it finished. The browser had shown a fresh frame earlier only because that path happened to pace the two packets. The rewrite didn’t. We’d had the fix and thrown it out, then spent days looking for it somewhere else.

Then the last twist, and it fits this post a little too well. The final command in the sequence, the one we’d added to switch to the picture view and show the card, doesn’t mean show the card. It means advance to the next stored picture. So every time the frame committed and appeared, that line flipped straight past it to an older slot. That was the half-second flash I kept catching out of the corner of my eye, the picture arriving and leaving on the same breath. The last step, quietly undoing the thing it was there to finish. We deleted the line and it held.

By the end the pattern was the same one from last time. Claude was confident and wrong, over and over, and my whole job was putting each theory on the real keyboard to watch it fall apart. Column-first, the phantom light strip, the secret radio channel, the safety delays: every one sounded certain, and every one lost to the hardware. The thing I keep relearning is that the confident version and the correct version aren’t the same version, and no amount of the machine sounding sure moves which one the screen agrees with.

So, whew. For a week the keyboard kept showing me last week’s picture. Now it shows me this one, cover art and all, after Claude burned three logins guessing at Spotify’s auth flow and I made it go read the actual docs. The whole fix, after all of that, was two pauses and one deleted line. Anyway. I spent a week staring at bands so I could stare at bands.

← back to the log