Interrupts

The 8253 chip inside the MZ-80A. This is what we'll be controlling through software a lot of the time for our graphics effects

The 8253 chip inside the MZ-80A. This is what we’ll be controlling through software a lot of the time for our graphics effects

 

Unlike many other 8-bit computers of the era, the MZ-80A is only equipped with one single source of interrupt. Under normal conditions of use this interrupt occurs as part of the Real Time Clock. After 12 hours have elapsed an interrupt happens which takes care of the switch-over from AM to PM. The trigger for this interrupt is the Intel 8253 chip within the MZ-80A and, more specifically, one of the 8253’s counters.

The 8253 chip has three countdown counters. The first counter is used for producing sound (and is a square wave generator, more on this in the ‘Sound’ section of this site). The second and third counters are linked together in hardware and this cannot be undone. The reason for this is that Sharp intend for them to be used just for the RTC. The input clock to the second counter is an unchangeable 31.5Khz. This is divided down to 1Hz by this counter (which is set as a rate generator). The output from this counter is hard-wired to the input for the third, and final, counter. This counter is usually set to a value of 43500 to begin with (which is the number of seconds in 12 hours). As the second counter is effectively giving a 1Hz input to this third counter, we now achieve an interrupt once 12 hours have elapsed. The interrupt occurs because the third counter’s output is gated with the MZ-80A’s interrupt mask and it is also set up to be an “interrupt on terminal count” type of counter.

However, if you are prepared to abandon your need for an RTC (and I’m sure this is not a problem for most people writing games), we can instead commandeer the 8253 to give us the kind of interrupts we might need to cause interesting effects on the CRT screen whilst not interfering with the smooth running of our games.

The only issue with doing this on the MZ-80A is that of synchronisation. Some computers of the era are able to specify an interrupt point during screen frame display, others (like the Amstrad CPC) have several known points during screen draw when interrupts are guaranteed. As the single interrupt we are using on the MZ has nothing to do with the screen, we must anchor it to the start of screen drawing with our only available means. This is the wait for vsync. The only way to achieve this is, as you will see, to ensure that your very first interrupt handler always waits for vsync to occur. This is the only way we can guarantee the kind of timings and positionings on the screen that we need to create stable, guaranteed effects with no loss of position and also no flickering / tearing.

One last point is that the 8253’s counters are very tricky to code for with respect to the screen. They always take one single clock cycle (and this is not the Z80 clock we are talking about, it’s the input clock to the counter) to change their value to a new value. You must take this into account when coding a screen effect with interrupts. Even at the 8253’s fastest possible countdown (with the 2nd counter inputting as quickly as it is possible to the 3rd counter) we are still only able to create a new effect once four scanlines have passed. We cannot do anything earlier than this. You could, of course, switch an effect on for one scanline and off for another in the same interrupt handler but you would still have to wait four scanlines before another interrupt could occur. Unfortunately, there is no easy rule to screen draw-related interrupts on the MZ-80A, it is mostly trial and error. You just have to ensure that your very last interrupt handler happens no later than the last 4 scanlines of display, otherwise when you jump back to your first interrupt handler it will wait for vsync after vsync has already occurred naturally and you will simply see a lot of flickering or other bad visual effects.

Screen draw-related interrupts on the MZ-80A are no simple topic to cover, so apologies for the wall of text here but if you are serious about coding them then this is all very important information. One last point, the vector address for the interrupt is hard-coded to address 0038 with the standard Z80 interrupt mode which the MZ cold starts in. This is a ROM address and you will note that it jumps straight to address 1038 which luckily is in RAM. You will need to modify the address held at 1039 – 103A to go to your own first interrupt handler. Lastly, of course, the Monitor ROM can be switched out with a read of memory mapped I/O address E00C which will plant the Monitor at C000 (which then becomes the ROM area). You can then have free access to address 0038 if you so wish. With no further delay now, here are my example screen display altering interrupts…

 

Inverse Split Using Interrupts

This routine uses the MZ-80A’s interrupt (triggered by the 8253 chip) to create a single bar (8 pixels high) of inverse at the top of the screen every single frame. The interrupt handlers are very short routines that take very little processing time away from any main program code. The 8253 chip is programmed by sending a relevant control word to memory mapped I/O address E007 which will tell each counter how it should behave. Then counter #2 is loaded with a value (at address E006) and counter #1 is also loaded with a value (at address E005). The interrupt occurs when counter #2 has reached zero. Again, all code shown is in the format acceptable to Avalon’s Zen Assembler and I realise that this program could be written in a far more condensed way but I have made it this way to show exactly what is going on step by step.


DI                           ; Disable all interrupts
LD SP,4000H                  ; set stack pointer to an arbitrary location
                             ; in memory. Modify this to your own
                             ; requirements

LD A,(1039H)                 ; Preserve Zen's breakpoint
LD (3200H),A                 ; vector at an arbitrary address in memory
LD A,(103AH)
LD (3201H),A

LD HL,1039H                  ; Set up our own vector
LD DE,MYINT                  ; to our first interrupt handler
LD (HL),E
INC HL
LD (HL),D

LD HL,0E007H                 ; Set 8253 counters to
LD (HL),74H                  ; counter #1 is rate generator
LD (HL),0B0H                 ; counter #2 is interrupt on terminal count
DEC HL                       ; Initialise counter #1 to 0002 and counter #2
LD (HL),01H                  ; to 0001 and enable interrupts
LD (HL),00H
DEC HL
LD (HL),02H
LD (HL),00H
EI

LOOP: LD A,0F4H              ; Test to see if
LD (0E000H),A                ; Spacebar has been
NOP                          ; pressed. If not then
LD A,(0E001H)                ; jump back to LOOP
AND 1
JR NZ,LOOP

DI                           ; Disable interrupts and
LD A,(3200H)                 ; restore Zen's vector back to its
LD (1039H),A                 ; original location. Finally ensure
LD A,(3201H)                 ; routine finishes with inverse off
LD (103AH),A
LD A,(0E014H)
RET

MYINT: PUSH AF               ; Preserve all registers. You may
PUSH BC                      ; not need to store all of them.
PUSH DE                      ; But this must be done if integrating
PUSH HL                      ; interrupts into BASIC code.
PUSH IX                      ; Wait for Vsync to occur so
PUSH IY                      ; we know we are synchronised with
LD HL,0E002H                 ; display. Switch on inverse and set
LOOP2: LD A,(HL)             ; up 8253 counters to wait a large enough
RLA                          ; delay before triggering next
JR C,LOOP2                   ; interrupt so that we have gone
LD A,(0E015H)                ; past vsync, through top of screen
LD HL,1039H                  ; and up to end of first 8 scanlines
LD DE,MYINT2                 ; with inverse on.
LD (HL),E
INC HL
LD (HL),D
LD HL,0E006H
LD (HL),01H
LD (HL),00H
DEC HL
LD (HL),3FH
LD (HL),00H
POP IY
POP IX
POP HL
POP DE
POP BC
POP AF
EI
RET

MYINT2: PUSH AF               ; Preserve all registers.
PUSH BC                       ; Wait a very tiny 8-bit counter delay.
PUSH DE                       ; This is for fine tuning of inverse effect
PUSH HL                       ; on->off otherwise we'll see flickering
PUSH IX                       ; and tearing. Then we switch inverse
PUSH IY                       ; off and set up the 8253 counters for
LD B,16H                      ; a much larger delay to help us get
LOOP3: NOP                    ; through most of the scanlines on the
DJNZ LOOP3                    ; screen before ending up near the end
LD A,(0E014H)                 ; of the screen so we can begin to
LD HL,1039H                   ; plan for triggering the first interrupt
LD DE,MYINT3                  ; handler again. This takes some planning
LD (HL),E                     ; as the 8253 counters do not change their
INC HL                        ; values immediately when written to.
LD (HL),D
LD HL,0E006H
LD (HL),01H
LD (HL),00H
DEC HL
LD (HL),65H
LD (HL),00H
POP IY
POP IX
POP HL
POP DE
POP BC
POP AF
EI
RET

MYINT3: PUSH AF                ; Preserve all registers.
PUSH BC                        ; Inverse is already off from the last
PUSH DE                        ; interrupt handler. All we need to do now
PUSH HL                        ; is plan ahead for the wait for vsync
PUSH IX                        ; which happens in the first interrupt
PUSH IY                        ; handler. We have to be as close as possible
LD HL,1039H                    ; to the bottom of the screen for this to
LD DE,MYINT4                   ; happen. But at this point, even though
LD (HL),E                      ; we have now set the counters to their
INC HL                         ; fastest values, we won't see them change
LD (HL),D                      ; for another clock cycle. So we still have
LD HL,0E006H                   ; some distance to cover on the screen
LD (HL),01H                    ; and this will happen before the 8253
LD (HL),00H                    ; acknowledges the new values in this handler.
DEC HL
LD (HL),02H
LD (HL),00H
POP IY
POP IX
POP HL
POP DE
POP BC
POP AF
EI
RET

MYINT4: PUSH AF                ; Preserve all registers.
PUSH BC                        ; We still have a few scanlines left to
PUSH DE                        ; burn at the end of the screen before
PUSH HL                        ; we re-trigger our first interrupt
PUSH IX                        ; handler (that waits for vsync). If we
PUSH IY                        ; wait slightly too long then we'll cause
LD HL,1039H                    ; flickering of the whole effect
LD DE,MYINT5                   ; as we wait for vsync after vsync has
LD (HL),E                      ; already occurred. This interrupt has the
INC HL                         ; 8253 set at its fastest rate which
LD (HL),D                      ; gives us 4 scanlines before the next
LD HL,0E006H                   ; interrupt occurs.
LD (HL),01H
LD (HL),00H
DEC HL
LD (HL),02H
LD (HL),00H
POP IY
POP IX
POP HL
POP DE
POP BC
POP AF
EI
RET

MYINT5: PUSH AF                 ; Preserve all registers.
PUSH BC                         ; We now burn our final 4 scanlines
PUSH DE                         ; with this interrupt. After the 4
PUSH HL                         ; scanlines are up we go back to our
PUSH IX                         ; first interrupt handler which carries
PUSH IY                         ; out the sync with the display.
LD HL,1039H                     ; We have now completed our effect
LD DE,MYINT                     ; and taken the least amount of
LD (HL),E                       ; time away from the main program
INC HL                          ; code as we can per frame.
LD (HL),D
LD HL,0E006H
LD (HL),01H
LD (HL),00H
POP IY
POP IX
POP HL
POP DE
POP BC
POP AF
EI
RET

And here is the same routine in BASIC, naturally it strips out a lot of stuff that is unnecessary once you are in the BASIC environment such as setting up a stack (we don’t want to mess with BASIC’s stack) and preserving Zen’s vector at 1039 – 103A as Zen isn’t loaded and, obviously, also the loop that waited for the Spacebar to be pressed before exiting as we want to immediately return to BASIC once the interrupt has been set in motion. Please also note, although there is an impact on the speed of BASIC it is not very noticeable.


5 LIMIT $CF12
10 FOR A=53011 TO 53247
20 READ B
30 POKE A,B
40 NEXT A
50 END
60 DATA 243,33,57,16,17,48,207,115,35
70 DATA 114,33,7,224,54,116,54,176
80 DATA 43,54,1,54,0,43,54,2
90 DATA 54,0,251,201,245,197,213,229
100 DATA 221,229,253,229,33,2,224,126
110 DATA 23,56,252,58,21,224,33,57
120 DATA 16,17,97,207,115,35,114,33
130 DATA 6,224,54,1,54,0,43,54
140 DATA 63,54,0,253,225,221,225,225
150 DATA 209,193,241,251,201,245,197,213
160 DATA 229,221,229,253,229,6,22,0
170 DATA 16,253,58,20,224,33,57,16
180 DATA 17,144,207,115,35,114,33,6
190 DATA 224,54,1,54,0,43,54,101
200 DATA 54,0,253,225,221,225,225,209
210 DATA 193,241,251,201,245,197,213,229
220 DATA 221,229,253,229,33,57,16,17
230 DATA 183,207,115,35,114,33,6,224
240 DATA 54,1,54,0,43,54,2,54
250 DATA 0,253,225,221,225,225,209,193
260 DATA 241,251,201,245,197,213,229,221
270 DATA 229,253,229,33,57,16,17,222
280 DATA 207,115,35,114,33,6,224,54
290 DATA 1,54,0,43,54,2,54,0
300 DATA 253,225,221,225,225,209,193,241
310 DATA 251,201,245,197,213,229,221,229
320 DATA 253,229,33,57,16,17,48,207
330 DATA 115,35,114,33,6,224,54,1
340 DATA 54,0,253,225,221,225,225,209
350 DATA 193,241,251,201

If you’d like a copy of this BASIC program to run on your MZ-80A then please find it in this folder here. Please note it will NOT work in an emulator as I have yet to find an emulator that can handle the kind of display effects we are creating here. As shown in the video above, the file is called BASIC-INT.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s