Computus Interruptus, Part 7

Authors

Wes Brzozowski

Publication

Publication Details

Volume: 4 Issue: 5

Date

July - August 1986

Pages

7-10
See all articles from SINCUS v4 n5

Now we’ll cover a couple of tricks we can do because the maskable interrupt is synchronized with the computer’s video output.

Since we’re looking at interrupts and video, we should first take a quick look at how a video image is formed. A monochrome television or monitor can only produce a single tiny spot on the screen, and control its brightness, but it can also aim that spot in different places on the screen. By sweeping the spot quickly from left to right, we get the impression of a horizontal line. Then, by drawing lots of horizontal lines, it builds up a picture.

The video standard used in North America is called NTSC. This stands for National Television Standards Committee, although some swear it means Never Twice the Same color. The NTSC system uses 525 lines every 1/30th of a second to make a single picture or “video frame.” If we count up the number lines the T/S 2068 gives us, that is 24 character lines x 8 pixel lines per character, we get only 192 lines. Something is terribly wrong: where are the rest?

Actually, the single frame drawn every 1/30th of a second consists of two “interlaced half frames,” each drawn one sixtieth of a second. An interlaced half frame contains only every other line in the picture. The next half-frame fills in the spaces. The reason this is done has to do with the way our eyes continue to “see” something for a short time after it has gone. The interlacing works along with this property of our eyes to prevent us from noticing any flickering in the picture.

Your computer changes the video timing just a bit, so that each half-frame is drawn on top of the previous one. This essentially changes the frame rate from 525 lines every 1/30th of a second to 262 1/2 lines every 1/60th of a second. We still don’t notice any flicker because the coardser frames are being displayed twice as fast.

A quick subtraction tells us there’s still 70 1/2 lines not accounted for. These lines include the border above and below the character area we have control over, as well as some special lines reserved for “electronic housekeeping.” Whenever your TV picture rolls, you’ll notice an odd black bar between the top and bottom of the “real picture.” That’s where the special lines are. While this may appear to just interesting trivia, it can be very useful to us. Here’s how.

When you produce a program that “moves” an object around the screen by erasing the old picture and drawing a new one, you’ll notice the object has a lot of annoying flicker. This is because the computer sometimes sends that portion of the picture to the screen between the time you erased the object and drew the next. As such, there are some frames when the object is not in the screen at all.

Now, if you’l run the interrupt driven sprite program, you may be surprised to see absolutely no hint of flicker! Since the sprite can run concurrnetly with your own programs, you can run both together and see a very noticeable difference.

The reason is that the maskable interrupt that produces the sprite always occurs when the “black bar” is being drawn off-screen. Since the sprite is put into the display file right after the maskable interrupt, it’s always there by the time the computer reads it! Thus, there can be no flicker.

Some programs “buy more time” by putting static garbage at the top of the display, like scores, remaining lives, and such. Since the moving obkects then never get to that height, there’s more time to play with the display before any flickering can be seen. Another clever method to gain time is to put crude animation (that can be done quickly) all around the screen, but to keep the detailed animation always near the bottom, when there’s plenty of time to put it in pace before the video hardware tries to “pick it up.” Prime examples are the incredibly detailed walking men in TIR-NA-NOG and DUN-DARACH.

Interestingly enough, while many programs use the interrupt to remove flicker, they usually don’t use anything as fancy as the IM2 interrupt handler we’ve seen before. Instead, they uses the little understood but very powerful machine code instruction, HALT. When this instruction is run, the microprocessor stops and waits. It’ll wait forever, unless you shit it off, send a RESET signal, or an interrupt occurs. When the interrupt is received, the T/S 2068 does its normal keyboard reading and then resumes at the instruction after the HALT.

In our sample programs, we’ll use this new method, even though the old IM2 handler might be better. This will allow us to see how HALT works in a few simple cases. You may wish to rewrite them yourself to work using the IM2 handler.

Type in the listing below, leaving out the REM statement and run it with GOTO 1000. Note that typing RUN won’t work. The program is written in its odd order for speed purposes. A GOTO statement always works fastest when going to the first line of a program and gets slower the further in the line actually is.

1 REM Produces a flickering orange border
10 RANDOMIZE USR 23728: BORDER 2
20 RANDOMIZE USR 23728: BORDER 6
30 GO TO 10
1000 POKE 23728,118
1010 POKE 23729,201
1020 GO TO 10

Lines 1000 and 1010 produce a very short machine code program consisting of just a HALT and RET. When we call this from BASIC, we will be synchronized to the video. In lines 10 and 20 the, we synchronize to the video and alternately switch from a red to a yellow border. Now, for the first time, your border is orange. Unfortunately, we get a lot of flicker. The alternate changes cause a 30 hertz rate, which our eyes can see. While you may not have a use for flickering borders, you may wish to alternately print some red and yellow on the screen for a nice flickering flame effect. It’s also possible to do slight changes at the pixel level. Jet Set Willy uses this effect to produce a more subtle shimmering in one of its rooms. As you might expect, these would be the most useful in machine code; we use mostly BASIC here, to make our examples easier to follow.

By the way, lest anyone suspect that the orange flickering is due to BASIC’s slowness, the listing below does the same thing, but in machine code. There’s no way out of the program, so you’ll need to power cycle you computer.

10 DATA 118,62,2,211,254,118,62,6,211,254,24,244
20 CLEAR 49999
30 FOR j=50000 TO 50011
40 READ k: POKE j,k
50 NEXT j
60 RANDOMIZE USR 50000

We’ve been playing with the border rather than the screen to get you thinking about how we might use the border for some fancy tricks. Consider that if our program is synchronized to the video, we should be able to wait until the computer is in the middle of a picture before switching the border. If we wait the same amount each time, we can produce a “horizon” that extends right through the border area!

Still you disbelieve? Type in the listing below and start it with GOTO 300. Lines 1 and 2 run some machine code we’ll talk about in a minute, and allow the BREAK key to work. Line 300 is the machine code. Line 350 POKEs the two byte number that determines where the horizon will be in the border (feel free to change the values). In line 400, we change the border only because this also sets the color of the bottom two lines of the screen. Lines 410-430 load in the machine code. Lines 450-500 set the horizon inside the normal screen area.

1 RANDOMIZE USR 50000
2 GO TO 1
300 DATA 42,172,92,118,62,2,211,254,43,124,181,32,251,62,6,211,254,201
350 POKE 23728,112: POKE 23729,5
400 BORDER 6
410 CLEAR 49999
420 FOR j=50000 to 50017
430 READ k: POKE j,k
440 NEXT j
450 PAPER 6
460 PRINT AT 16,0;
470 FOR j=16 TO 21
480 PRINT " ";
490 NEXT j
500 GO TO 1
600 REM Full screen horizon
700 REM Start with GO TO 300

The Machine code is listed below. The first instruction gets the two POKES we made in line 350 of the BASIC program. The we HALT, to wait for the interrupt. We resume in the next 2 instructions by making a red border. The next four instructions just loop around, just giving us a time delay that depends on the size of the POKEs we did earlier. The we make the border yellow and return.

C350 2AB05C LD HL,(5CB0)
C353 76 HALT
C354 3E02 LD A, 02
C356 D3FE OUT (FE), A
C358 2B DEC HL
C359 7C LD A,H
C35A B5 OR L
C35B 20FB JR NZ, C358
C35D 3E06 LD A, 06
C35F D3FE OUT (FE), A
C361 C9 RET

In a real program, we probably wouldn’t waste so much time in a wait loop. We’d divide our code into tasks that always take the same amount of time to execute and the parts that take variable amounts of time. The parts that are always the same should be substituted for part of the wait loop. The rest won’t be done until after the border color is changed.

The Spectrum program Aquaplane uses this method to produce a full screen horizon. Although I haven’t seen it, I suspect that the border part of the horizon will be in the wrong place when run with a T/S 2068 and an American TV set, because the British TV standard has an additional 100 lines, hence a different time delay.

Before anyone gets any great ideas about a super duper flight simulator, don’t forget that banking an airplane causes the horizon to tilt, while the full screen horizon must always be level. As such, the trick is suited for a horizon when we are presumed to be on land or water and don’t tilt around. As before, the demonstrator program is written partly in BASIC, to make a complex subject easier to follow. If you really want to use it, you’ll want to do some work in machine code.

Let’s return to the NMI screen copy routine. As I said, we broke some rules for the sake of simplicity. Because of this, the routine was likely to go bonkers after doing the copy. The basic problems were that we didn’t save all the necessary registers and the COPY routine we call disables and enables the maskable interrupt, something which normally can’t be done in an NMI handler. We’ll look at the contortions we must endure to make it all work.

Substitute this program for the NMI screen scopy, following the exact same procedure before loading it. This time, you should be able to produce as many screen copies as you want.

1 REM Complete NMI Screen COPY
10 DATA 245,229,197,33,9,60,229,237,69,237,95,245,243,6,192,205,175,14,243,241,226,24,60,251,193,225,241,201
20 CLEAR 49151: OUT 244,192
30 POKE 49152+102,195
40 POKE 49152+103,0
50 POKE 49152+104,60
60 FOR j=15360 TO 15387
70 READ k: POKE j+49152,k
80 NEXT j
90 OUT 244,0

The listing below is the new NMI handler and it’s incredibly whacky. Let’s check it out. The PUSH AF and PUSH HL are standard for NMI, and we’ve added another to do the same to the BASIC registers, since the ROM COPY routine will change them. This isn’t bad so far, but readers who tried to redo the routine to copy the screen without messing the interrupts will have lots of trouble. We’re going to “sneak around” the problem.

100 ORG #3C00
110
120 ;THE NORMAL NMI PUSHES
130 PUSH AF
140 PUSH HL
150
160 ; SAVE AN ADDITIONAL REGISTER
170 PUSH BC
180
190 ;GET BACK OLD IN STATUS
200
210 LD HL,NEXT
220 PUSH HALT
230 RETN
240
250 ;The RETN CAUSES US TO CONTINUE HERE
260
270 NEXT LD A,R
280 PUSH AF
290
300 DI
310 LD B, C0
320 CALL 0A05
330 DI
340
350 ; RESTORE PROPER INT STATUS
360 POP AF
370 JP PO, DONE
380 EI
390
400 DONE POP BC
410 POP HL
420 POP AF
430
440 ; INT STATUS HAS ALREADY BEEN RESTORED
450 ; SO WE DON'T USE RETN
460
470 RET

Recall that the reason we didn’t want to fiddle with the maskable interrupts is that their status (which is saved when an NMI occurs) would then be corrupted, and it might not be restored properly when we execute our RETN. The trick, then, is to find out what the status was, save it where we can get at it, and then restore it ourselves before we return. This requires tricks that the “learning machine code” books just don’t cover. Still, once we’ve done them, they work fine, and we can EI and DI to our heart’s content.

The trick begins with the LD HL, NEXT; PUSH HL and RETN lines. Note that “NEXT” is the address of the instruction following this group, so executing it simply causes us to continue into the next instruction. However, it also restores the original interrupt status.

The instructions LD A,R and PUSH AF appear entirely useless. But if we look carefully at the flags that are affected when we use the R register, we see that the interrupt status is put in to the P/V flag. Now we have the interrupt status and we preserve it with a little “PUSH.”

The next 3 instruction call the COPY routine in the ROM, like last time. After the CALL, we do a DI; POP AF; JP PO, DONE and EI. These restore the interrupt status. Note that we popped the flags that contained the interrupt status, and so the jump instruction has nothing to do with parity. The “parity flag” in this case is telling us whether the maskable interrupt should be enabled or disabled and we jump accordingly.

We then restore the registers we’ve changed and return to the code where it was interrupted. Note that we used a RET and not a RETN. This is because a RETN would restore a corrupted interrupt status. Since we’ve already restored it properly, we must instead use RET, which then leaves it alone.

Be aware that if your NMI switch is too bouncy for the capacitor to handle, it could cause two NMI pulses to occur. If this happens, the second one can corrupt the maskable interrupt status irreparably. Also note that this routine (and the ROM COPY routine) put a lot of entries on the stack. If you put too many things on the stack you may overwrite some machine code or variables.

Scroll to Top