One aspect of the Atari 8 bit computers I never fully understand from a hardware perspective is display list interrupts. In the past I wrote a few custom display lists, but none that used interrupts (non maskable or NMI’s). The extent was mixing graphics modes in programs I wrote. After listening to Rob McMullen’s Player/Missle podcast episode 7 titled “April 81: Gem Drop (Homebrew)” (found here: http://playermissile.com/podcast/ep007.html) with the talk about Super IRG and display lists and interrupts, I wanted to delve further than I had.
I set out to do something simple and just change the background color in a few places on a graphics 0 (text) screen to give the appearance of having more than 1 color.
This display list is still all graphics 0 (text), but will have 3 lines, color change, 6 lines, color change, 6 lines, color change, 6 lines, color change, 3 lines. To start I set the background color to dark gray. The first color change I set to red, with the next being white, the next being blue, the last being back to dark gray.
Display List
The display list is built out of commands and is as follows:
The first three commands tell the display to display 8 blank line (scan lines, not text lines), times 3, for 24 lines total. Think of this as the overscan area of the display:
112,112,112
Next is a command to tell the display list where screen memory is located (load memory scan, value 64). The syntax is command, memory address low byte, memory address high byte. The memory address is 40000. To get the high byte 156) divide 40000 by 256. To get the low byte (64), subtract the high byte times 256 (39936) from the memory address of 40000. I used 66 instead of 64 so it would also draw one line of graphics 0 text (value 2):
66,64,156
Draw one line of graphics 0 text, then issue the command to invoke the routine to change the color (display list interrupt, value 128). I used 130 instead of 128 so it would also draw one line of graphics 0 text (value 2) before invoking the interrupt:
2,130
Draw five lines of graphics 0 text, then issue the command to invoke the routine to change the color (display list interrupt, value 128). I used 130 instead of 128 so it would also draw one line of graphics 0 text (value 2) before invoking the interrupt:
2,2,2,2,2,130
Draw five lines of graphics 0 text, then issue the command to invoke the routine to change the color (display list interrupt, value 128). I used 130 instead of 128 so it would also draw one line of graphics 0 text (value 2) before invoking the interrupt:
2,2,2,2,2,130
Draw five lines of graphics 0 text, then issue the command to invoke the routine to change the color (display list interrupt, value 128). I used 130 instead of 128 so it would also draw one line of graphics 0 text (value 2) before invoking the interrupt:
2,2,2,2,2,130
Draw 3 lines of graphics 0 text:
2,2,2
Command to exit the display list, rather jump and wait for vertical blank interrupt (VBI), value 65:
65
Complete Display List Instruction Set
112,112,112,66,64,156,2,130,2,2,2,2,2,130,2,2,2,2,2,130,2,2,2,2,2,130,2,2,2,65
Display List Interrupt (NMI)
What I learned, and likely why I never wrote a display list with interrupts, is that an interrupt can only be completed with an assembly routine, and there can be only one. So I had to write an assembly routine to set the background color based on when it was called. Learning Assembly has paid off. This also required a counter, and a list of color codes placed in memory somewhere.
The assembly routine to change colors follows.
Push the accumulator onto the stack (to save it):
0600 PHA
Transfer the X register to the accumulator:
0601 TXA
Push the accumulator onto the stack again (thus saving the X register):
0602 PHA
Load the X register with the value in memory location 209 ($D1). This is the initial color value offset to use (+1):
0603 LDX $D1
Decrement the X register. This gives us the index in the color table of the color to change to:
0605 DEX
Load the accumulator with the X offset into the color table stored in memory location 1024 ($0400):
0606 LDA #0400,X
Store the accumulator into WSYNC. This causes the 6502 to wait for horizontal sync so there is a clean seperation of color at the boundary:
0609 STA $D40A ; WSYNC
Compare the X register with value 0 to determine if we are on the last color change:
060C CPX #$00
Branch over the next instruction if the X register was not 0, otherwise process the next instruction:
060E BNE $0612
Load the accumulator with value 4 (there are 4 color changes). This occurs at the last color change:
0610 LDX #$04
Store the accumulator, which is holding the color to change to, into the background color register at memory location $D018:
0612 STA $D018 ; BGCLR
Store the current value of the X register (it was decremented above) back into the counter at memory location 209 ($D1):
0615 STX $D1
Pull the last stack value back into the accumulator (this was the previous X register value when the interrupt routine started):
0617 PLA
Transfer the accumulator to the X register, effectively restoring it to the value it had before the interrupt routine started:
0618 TAX
Pull the stack value back into the accumulator (this was the previous accumulator value when the interrupt routine started):
0619 PLA
Issue the return from interrupt:
061A RTI
Complete Interrupt Assembly Routine
0600 PHA
0601 TXA
0602 PHA
0603 LDX $D1
0605 DEX
0606 LDA #0400,X
0609 STA $D40A ; WSYNC
060C CPX #$00
060E BNE $0612
0610 LDX #$04
0612 STA $D018 ; BGCLR
0615 STX $D1
0617 PLA
0618 TAX
0619 PLA
061A RTI
Putting It Together
I chose to store the NMI assembly routine in page 6 (memory location 1536). I stored the color values in the cassette buffer, otherwise known as page 4 (memory location 1024). I stored the counter in an used byte at memory location 209.
Rather than write a test program in Assembly, I decided to do it in BASIC where I could test out minor changes immediately.
Here is the code with a breakdown following:
Code
10 REM *** INIT SCR, ANTIC OFF WHILE BUILD ***
15 GRAPHICS 0:POKE 709,12:POKE 710,2
49 REM
50 REM *** INIT VARS ***
55 DL=PEEK(560)+256*PEEK(561)
60 NMI=1536:NL=0:NH=6
70 COLRS=1024:CL=0:CH=4:POKE 209,4
99 REM
100 REM *** LOAD NMI ***
105 FOR I=0 TO 26:READ B:POKE NMI+I,B:NEXT I
115 DATA 72,138,72,166,209,202,189,0,4,141,10,212,224,0,208,02,162,4,141,24,208,134,209,104,170,104,64
199 REM
200 REM *** LOAD DLIST ***
205 FOR I=0 TO 29:READ B:POKE DL+I,B:NEXT I
215 DATA 112,112,112,66,64,156,2,130,2,2,2,2,2,130,2,2,2,2,2,130,2,2,2,2,2,130,2,2,2,65
299 REM
300 REM *** COLORS LOAD ***
305 FOR I=0 TO 3:READ B:POKE COLRS+I,B:NEXT I
315 DATA 2,146,14,50
399 REM
400 REM *** ACTIVATE NMI,DL,ANTIC ON ***
410 POKE 512,NL:POKE 513,NH:POKE 54286,192
Breakdown
- Line 15: Set screen to graphics 0. Set text color to intensity 12. Set background color to dark gray.
- Line 55: Get the address of the display list and store it in variable DL.
- Line 60: Set the variable NMI to the memory address 1536. Set NL (0) and NH (6) to the low and high byte values needed to reach memory address 1536 from the display list interrupt vector. (6*256) + 0 = 1536.
- Line 70: Set the variable COLRS to the memory address 1024. Set CL (0) and CH (4) to the low and high byte values needed to reach memory address 1024 from display list interrupt routine (NMI).
- Line 105: Read each byte of the NMI data and store it in consecutive memory addresses starting at the NMI memory location referenced by variable NMI (1536) by looping through the number of values, reading each one, and poking that value into the current loop iteration offset of the NMI base memory location (think 1536+x).
- Line 115: The decimal byte values of the instructions from the NMI assembly routine.
- Line 205: Read each byte of the display list instruction data and store it in consecutive memory addresses starting at the display list memory location referenced by variable DL by looping through the number of values, reading each one, and poking that value into the current loop iteration offset of the display list base memory location.
- Line 215: The decimal byte values of the display list instructions.
- Line 305: Read each byte of the color table data and store it in consecutive memory addresses starting at the color table memory location referenced by variable COLRS by looping through the number of values, reading each one, and poking that value into the current loop iteration offset of the color table base memory location.
- Line 315: The decimal byte values of the color table (gray, blue, white, red).
- Line 410: Activate the new display list with interrupts by setting the display list interrupt vector low byte (memory location 512) and high byte (memory location 513) to point to the memory location of the NMI (1536 or $0600). Then enable the interrupts by setting memory location 54286 to value 192.
Results
Running the program produces the following display and leaves me at the READY prompt. I deleted the READY prompt and numbered each line. The display stays this way until RESET is pressed:

Next I will attempt to duplicate this test program in Assembly.
Like this:
Like Loading...