Rebuilding Coffee Machine Firmware from a Hex Dump — The Clock Wasn’t 16MHz
I usually write about web and cloud. Today it’s a completely different layer: rebuilding the control-board firmware for the coffee machines that go into our unmanned cafés.
There is no source
We use a commercial coffee-machine board (M500/M400), and the manufacturer’s source code doesn’t exist for us. All we had was a hex file pulled off the board and an Excel document describing the serial protocol. To change behavior or support new hardware, there was exactly one path: disassemble the hex, recover the behavior, and rewrite it from scratch.
So I started a clean rewrite targeting the ATmega2560.
The clock wasn’t 16MHz
This is the moment I remember most from the project. It’s an Arduino Mega–class board, so naturally I assumed 16MHz — and the brew sequence timing came out subtly wrong. About 8% fast.
Going back to the original firmware’s disassembly and working backwards from Timer1’s preload value and prescaler gave the answer: F_CPU = 14.7456MHz. They’d used a crystal that divides cleanly for serial communication (38400 baud) with zero error. Assume 16MHz and your 100ms sequence tick runs 8% fast, which throws off every extraction time. I learned in my hands that with hardware you verify what is, not what ought to be.
Rewriting everything non-blocking
The structural problem with the original firmware was blocking. Protocol responses lagged while writing EEPROM; sequences stalled while reading the ADC. Rewriting it, I set one rule: the main loop waits for nothing.
- ADC sensor reads: a non-blocking state machine
- EEPROM writes: asynchronous, one byte per tick
- Brew sequence: a state machine on 100ms ticks
- Protocol (serial link to the kiosk): highest priority, with bootloader entry guaranteed even after a watchdog reset
The M400 board’s brew unit has no position sensor, so I added a state machine that infers position from motor current and detects overload.
The tooling is part of the firmware
Writing the firmware isn’t the whole job. I built tools alongside it in Python: a simulator for testing the protocol without a board, a serial monitor for the real board, a firmware uploader, and an automated tester. I also added host-side unit tests — compiling the embedded code on a PC to verify the logic alone. It was ultimately validated on real hardware and went into machines in actual stores.
Why this was fun
My 18-year career started with MFC, an embedded virtual keyboard, and telecom servers. Coming full circle back down to the low level, I could see what had accumulated in between. Work that used to mean an oscilloscope and intuition now means throwing the disassembly at Claude Code and working out together “given this timer configuration, what’s the actual frequency?”, then building a Python simulator in half a day to verify it. The tools changed; the essence didn’t. Digging all the way down into how the machine actually behaves.
Embedded feels distant to web developers, but a non-blocking loop is an event loop, and a serial protocol is ultimately parsing and a state machine. You already know these things. If you get the chance, come down a layer. It’s fun.