ENSONIQ ASR-10

The moment a stream of hex bytes decoded into a name — and a dead machine spoke for the first time in software.


The Handshake

With the peripheral mocks in place and the ROM patch bypassing the DOC/DSP verification, the firmware reached code we hadn't seen before. The CPU was now at PC=0xFFF89C0A — the DUART communication routine. It was time for the 68302 to talk to the front panel.

The protocol is simple but strict. The firmware sends a handshake byte to Channel B of the 68681 DUART by writing to THRB (Transmit Holding Register B) at address FC4817. Then it waits for the front panel to respond. The waiting is brutal: an inner loop of 8,000 DBRA cycles, repeated in an outer loop of 4,096 iterations, and the whole thing retries up to 14 times. If no response comes, the firmware gives up on that byte and tries the next one.

DUART handshake protocol
; Phase 1: Send 0xE7, wait for ACK[FFF89C0A] MOVE.B #$E7, ($FC4817) ; THRB <- 0xE7 (init command)[FFF89C10] MOVE.W #$0FFF, D1 ; outer loop: 4096 attempts[FFF89C14] MOVE.W #$1F3F, D0 ; inner loop: 8000 DBRA cycles[FFF89C18] DBRA D0, * ; wait...[FFF89C1C] BTST #0, ($FC4813) ; test RXRDY in SRB[FFF89C22] BNE.S read_response ; if RXRDY: read the byte[FFF89C24] DBRA D1, inner_loop ; else: try again; ... retry up to 14 times total; Phase 2: Send 0x71, wait for ACK[FFF89C40] MOVE.B #$71, ($FC4817) ; THRB <- 0x71 (display ready?); ... same wait protocol, 9 retries

The first byte is 0xE7 — likely a "reset display" or "initialize front panel" command. The second is 0x71 — possibly "are you ready for display data?" On real hardware, the front panel microcontroller would process these and respond. In our emulator, we set the DUART mock to report RXRDY immediately and return 0x00 (ACK) for both bytes.

The handshake succeeded on the first try. The firmware moved on.

The Bytes

And then it happened. After the handshake, the firmware began writing a stream of bytes to THRB. Not commands this time — data. Our I/O logger captured every one:

DUART Channel B TX -- display data
[THRB W] 66 ; 'f' -- display command?[THRB W] 20 20 20 ; ' '[THRB W]45 4E 53 4F 4E 49 51; 'ENSONIQ'[THRB W] 20 20 ; ' '[THRB W]41 53 52 2D 31 30; 'ASR-10'[THRB W] 20 20 20 20 ; ' '

We stared at the hex dump. Then we ran it through ASCII conversion.

hex to ASCII
$ python3 -c " bytes_out = [0x66, 0x20, 0x20, 0x20, 0x45, 0x4E, 0x53, 0x4F, 0x4E, 0x49, 0x51, 0x20, 0x20, 0x41, 0x53, 0x52, 0x2D, 0x31, 0x30, 0x20, 0x20, 0x20, 0x20] print(''.join(chr(b) for b in bytes_out))" f ENSONIQ ASR-10

The leading 0x66 ('f') is not a printable character in this context — it's a display command byte, most likely a "set cursor position" instruction for the LCD controller. What follows is the payload: three spaces for centering, then the machine's name, padded with trailing spaces to fill the display width.

ENSONIQ  ASR-10
40x2 Character LCD -- Virtual Display

There it was. Twenty-three bytes, decoded from a serial stream between a 30-year-old firmware and a mock DUART, rendering the name of a machine that hadn't been manufactured since 1998. The ASR-10 was speaking.

Alive

After the display string was sent, the firmware continued executing. The CPU settled into a tight loop at PC=0xFFFB8D6E — a scan routine that polls the keyboard matrix and waits for user input. On real hardware, this is where the ASR-10 sits at idle, the cursor blinking on the LCD, ready for the musician to press a button or play a key.

The machine was alive. Not fully — the DSP was bypassed, the floppy disk wasn't loaded, the keyboard matrix was unmocked — but the core firmware had booted to its idle state and produced output. From the CPU's perspective, it had completed initialization and was waiting for the world to interact with it.

post-display execution state
PC =$FFFB8D6E; keyboard scanner loopSR = $2000 ; supervisor mode, interrupts enabledSSP = $0007FFF0 ; supervisor stack in high RAMState: idle -- waiting for keyboard inputDisplay: " ENSONIQ ASR-10 "DSP: bypassed (ROM patch)FDC: not yet mockedNext frontier: mock the floppy disk controllerto load the OS from the virtual disk image.

The Road From Here

The next step is clear: mock the floppy disk controller. The firmware's boot sequence expects to load the full operating system from disk — the 412-block OS image we extracted back in the first article. Once the FDC responds correctly and feeds sectors to the firmware's disk loader, all 577 functions of the ASR-10 OS will come to life in RAM. The display will show the version string, the effects engine will initialize, and the full user interface will become accessible.

Beyond the FDC lies the keyboard matrix, MIDI through the SCC serial channels, the ES5506 DOC for wavetable synthesis, and the ES5510 DSP for real-time effects. Each one is a layer of the machine waiting to be resurrected.


From raw EPROM dumps to a working boot sequence displaying the machine's name. From two unlabeled binary files and no documentation to a reconstructed memory map, four identified chips, and a firmware that boots to idle and writes to a virtual display.

Reverse engineering is about patience, pattern recognition, and letting the hardware tell its own story through the I/O traces. You don't need the schematic. You don't need the service manual. You listen to the firmware — every register write is a declaration, every polling loop is a question, every byte on the bus is a word in a language the machine has been speaking since 1994.

You just have to learn to read it.

This project is ongoing. The emulator continues to evolve as we mock more peripherals and work toward loading the full OS from the virtual floppy disk. Follow along as the ASR-10 comes back from the dead, one chip at a time.