Interrupts work, and the monitor has a Ctrl-C

/ DINO / updated 2026-10-06 / from dino-homebrew dino interrupts monitor serial 16550 clock

DINO takes interrupts now. The UART’s interrupt line goes through a 7406N on the serial card to the interrupt core, and every test image on the ladder passed at 1.024 MHz. Then I burned a new monitor that takes its keyboard input off the receive interrupt, so Ctrl-C stops a running program:

g 8100
^C
> d 8180
0x0E
>

That’s spin.asm, a RAM program that counts on the LEDs forever. Ctrl-C brought the prompt back with the LEDs frozen on 0x0E, and 0x8180 is where spin keeps its count. With the old polling monitor the only way out of that loop was RESET.

The ladder

Each image arms the 16550’s own interrupt, so nothing waits on a button and every run repeats.

RungImageWhat it checksResult
I0isa, isasoaknothing old broke0xB4 every RESET, soak 0x00
I2intmaska request with interrupts disabled does nothing0x39 and a “.”
I7inthaltEI; HALT wakes on the UART0x5A and “DINO”
I3intcount, intresumethe interrupted program carries onone I per M, count climbs
I4intaddrswthe pushed address is always an opcode0x40 0x42 0x43 0x45, no operands
I5intflagsPUSHF/POPF make a handler invisible0x5A, 10 of 10 at 1.024 MHz and 10 of 10 at 500 kHz
I8intserserial echo with no poll loopevery key echoed once, bytes exact

END is the EEPROM output that now also feeds two 74F32 inputs. Its LOW level measured -0.02 V, so the extra load is nothing to worry about.

The clock jumper was on TC

I spent a while thinking interrupts were re-firing. intresume put a dot on the terminal about every 7.5 s when the code works out to 0.77 s. I wrote intcount to print M for each pass of the main loop and I for each handler call, and it showed one I per M, just slow. The clock jumper was on U20 pin 15, which is the ‘163’s terminal count. That runs the machine at 128 kHz. It most likely went there for the “250 kHz” run during this morning’s ALU chase, which would make that 250 kHz in the earlier entry really 128 kHz.

Back on pin 14 it’s 1.024 MHz. isa, isasoak and every rung above were rerun at that speed. Nice to know it all works at 128 kHz too.

intresume’s own OB pattern was a dud on top of that. It shows each count for about 1 ms and 0xE7 for half a second, so to the eye it never leaves 0xE7. intcount reports on the terminal instead, and that’s the one I’d use again.

0x45 never showed up, and it wasn’t the hardware

intaddr interrupts a loop of LDAI, NOP, LDBI and JMP and reports the pushed return address. In more than 30 RESETs I got 0x40, 0x42 and 0x43, and never 0x45. 0x40 also came up way too often.

The oracle takes an interrupt at that boundary fine, so I looked at timing. THRE fires four characters after the first THR write, and the only slop in that is where the UART’s first start bit lands, about 1/16 of a bit. That’s around 7 CPU clocks, against a 10-clock loop. So the request only ever landed in the same 7-clock slice of the loop, and the boundary in front of the JMP was outside it.

intaddrsw reads the DIP switch and runs SW1 extra 8-T passes of DCR/JNZ before the loop starts, which slides the slice around. With that, 0x45 shows up about as often as the others. If you’re testing interrupt timing off a UART that the CPU itself starts, the arrival point isn’t as random as it looks.

The flags gate I didn’t fit

On 10-01 I saw ~FLAGS_OUT, the enable on the ‘245 that puts the flags on the bus for PUSHF, blip low for about 46 ns. I had a gate drawn up to stop it. Then I ran intflags without it, and it passed 10 of 10 at 1.024 MHz and 10 of 10 at 500 kHz, with interrupts landing every millisecond. A blip on a scope wasn’t evidence of a failure, so the gate stays on paper.

Cold boots and the FTDI

inthalt passed on every RESET but garbled the terminal on cold boots, once with 0xFF and once with 0xE7 on the LEDs. Unplugging and replugging the FTDI cleared it. Machine on first, then the FTDI, then the terminal, and every cold boot since has read 0x5A.

imon

PROG_imon is the old monitor with input moved onto the receive interrupt.

What it does
VectorINT always lands at 0x9090 in RAM. Every prompt writes JMP isr there, sets the UART’s IER to received-data only and does EI. A loaded program can put its own handler there and gets the monitor’s back when it returns.
Receiveisr reads the byte (that read is what clears the interrupt) and drops it in a 16-byte ring at 0x80A0. getc spins on the ring, so the CPU still spins.
Ctrl-Cisr resets the stack, prints ^C and jumps to the prompt. Works from a running program, from a HALT and at the prompt.
HALTA HALT in a loaded program now waits for a key. Ctrl-C returns to the prompt, and any other key resumes the program after its HALT.
CompatibilitySame commands and replies as PROG_monitor. putc, puthex, puts and crlf sit at the same ROM addresses, so life.asm still runs. The banner says DINO IMON so I can tell which ROM is in.

The stack moved down to 0x80DF to make room, because the old one had about 12 bytes and an interrupt with four saves on top of a loaded program’s own calls would have run into the monitor’s variables.

The Python oracle got two test-only features for this: script bytes can arrive at a typing pace, and the received-data interrupt is modeled as a level. The whole polling monitor’s transcript suite runs the same against imon except for the HALT change.

Still open