Inside the Einstein: The MOS, a TatungBytes course: the MOS 1.2 start up screen on a rainbow sunburst
← Back to Modules
module
12

Interrupts and the Clock

Introduction

So far the MOS has done things when asked: start up, print, read a key. But the Einstein also keeps the time, and nobody asks it to. Once a second, whatever the machine is doing - waiting at the prompt, running your program - it stops for a moment, adds one to the clock and carries on. That is an interrupt, and this section follows one.

Interrupts

An interrupt is a signal from a chip to the processor: stop what you are doing and deal with me. The Z80 finishes its current instruction, pushes the address of the next one, and runs a handler. When the handler returns, the interrupted program carries on exactly where it left off, unaware - provided the handler put back every register it used.

The question is how the Z80 knows which handler to run, when there are ten sources of interrupts.

Mode 2: a table of handlers

The Z80 has three interrupt modes, and the MOS chooses the most flexible. At start-up:

        ld a,0fbh               ;0080
        ld i,a                  ;0082
...
        im 2                    ;0090

In interrupt mode 2, the interrupting chip supplies one byte, and the I register supplies another. Together they make an address - here FBxxh - and the Z80 reads a handler's address from that address. So FB00h onwards is a table of handlers, one entry per source of interrupts:

AddressInterrupt
FB00h, FB02h, FB04hcounter/timer channels
FB06hthe real-time clock
FB08hthe keyboard
FB0Ahthe analogue converter
FB0Chthe fire button
FB0Ehfree for your own use
FB10hthe printer
FB12hthe user port

The table is in RAM because start-up copied it there - it is the first 20 bytes of SCINIT:

SCINIT:
        defb 04eh,0fch,04eh,0fch,04eh,0fch,047h,0fch    ;0ff0  N.N.N.G.
        defb 04eh,0fch,04eh,0fch,04eh,0fch,04eh,0fch    ;0ff8  N.N.N.N.
        defb 084h,0fch,04eh,0fch                        ;1000  ..N.

Read as addresses: FC4Eh, FC4Eh, FC4Eh, FC47h, FC4Eh, FC4Eh, FC4Eh, FC4Eh, FC84h, FC4Eh. Almost every source points at FC4Eh, which is just EI / RETI - acknowledge and return. Only two do work: FB06h, the clock, goes to FC47h; FB10h, the printer, to FC84h.

The clock

The Einstein's counter/timer chip divides its clock down until it produces one interrupt a second, through FB06h, to this code - copied to FC47h at start-up:

ZTMINT:
        push hl                 ;107f (runs at fc47h)
        push af                 ;1080 (runs at fc48h)
        call 0fc51h             ;1081 (runs at fc49h)
        pop af                  ;1084 (runs at fc4ch)
        pop hl                  ;1085 (runs at fc4dh)
        ei                      ;1086 (runs at fc4eh)
        reti                    ;1087 (runs at fc4fh)
        ld hl,0fb91h            ;1089 (runs at fc51h)
        ld a,039h               ;108c (runs at fc54h)
        call 0fc7dh             ;108e (runs at fc56h)
        ret nc                  ;1091 (runs at fc59h)
        ld a,035h               ;1092 (runs at fc5ah)
        call 0fc7dh             ;1094 (runs at fc5ch)
        ret nc                  ;1097 (runs at fc5fh)
        ...
        inc (hl)                ;10b5 (runs at fc7dh)
        cp (hl)                 ;10b6 (runs at fc7eh)
        ret nc                  ;10b7 (runs at fc7fh)
        ld (hl),030h            ;10b8 (runs at fc80h)
        dec hl                  ;10ba (runs at fc82h)
        ret                     ;10bb (runs at fc83h)

The time is kept as six ASCII digits at FB8Ch-FB91h: hours, minutes, seconds. Start-up set them all to '0' (S6). Each tick:

  • FB91h, the last seconds digit, is incremented by the little routine at FC7Dh: INC (HL), then compare with the digit's limit in A - '9'. If the digit has not passed its limit, RET NC - and the caller's own RET NC ends the tick.
  • If it has passed, the routine sets it back to '0', steps HL to the digit to its left, and returns with carry set. The caller loads the next limit - '5' for the tens of seconds - and goes again.

Seconds roll into minutes, and the code carries on the same way, digit by digit, for the hours. And notice what that buys: the time is already in characters. Printing it needs no conversion at all.

Notice also what the handler does not do: it never touches port 24h. It uses only RAM - the digits, and its own code in high RAM - so it works whichever memory is switched in at the bottom, at the moment of the interrupt. That is S4's rule once more, and why it matters that this code runs at FC47h, in high RAM, rather than in the ROM.

The code

        ORG 256

        LD HL,0FB8Ch
        LD B,6
NEXT:   LD A,(HL)
        RST 8
        DEFB 158        ; print one digit of the time
        INC HL
        DJNZ NEXT
        RET

Build and run

Press Run.

What you should see

Six digits after the prompt: the time since the Einstein started, in hours, minutes and seconds. Run a few seconds after switching on and you get something like:

>000006

Wait fifteen seconds and run it again, and it shows fifteen more:

>000021

Summary

An interrupt makes the Z80 run a handler between two instructions of whatever else it is doing. In mode 2, I = FBh and the device's byte select an entry in the table at FB00h, copied there at start-up. Most entries point at a do-nothing EI / RETI; FB06h points at the clock handler at FC47h, which counts six ASCII digits at FB8Ch once a second. It lives in high RAM and never switches banks, so it works whatever is running.

Next

Part Three asks where this MOS came from - and what its successor, MOS 1.21, changed.

Get the Newsletter

New guides, disk images and community finds, roughly once a quarter. No spam, we promise, this isn't Tatung's marketing department.
Your subscription could not be saved. Please try again.
Your subscription has been successful.

Newsletter

Subscribe to our newsletter and stay updated.

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.