
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.
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.
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:
| Address | Interrupt |
|---|---|
| FB00h, FB02h, FB04h | counter/timer channels |
| FB06h | the real-time clock |
| FB08h | the keyboard |
| FB0Ah | the analogue converter |
| FB0Ch | the fire button |
| FB0Eh | free for your own use |
| FB10h | the printer |
| FB12h | the 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 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:
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.'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.
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
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.
Part Three asks where this MOS came from - and what its successor, MOS 1.21, changed.
This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.