Task: Learn to Implement Horizontal Scrolling in BASIC on Atari 8-Bit Computers
Needed: Web browser
Time: 30-60 mins
Scrolling games like Defender, Caverns of Mars, and Eastern Front are tons of fun and I always wondered how the cool scrolling effects were programmed. This was not one of the programming tricks I picked up back in the day, and it has taken some time for me to understand the concept of how this works with the Atari and how to to do the programming to implement it. I have previously posted about vertical scrolling which it turns out is much easier than horizontal scrolling. A key difference is that you can achieve a fine vertical scroll using only BASIC code while fine horizontal scrolling requires some assembly code executed during the vertical blank period using an interrupt. I will explain how to do this below.
Before proceeding, I recommend first reading my post on vertical scrolling and then my recent post on vertical blank interrupts (VBI). For a more technical piece on scrolling I highly recommend this post from the Player-Missile site. I will explain fine horizontal scrolling in five parts. As with vertical scrolling, we will start with screen memory and the BASIC program to set it up. Second, we will cover the display list and the BASIC program to set it up. Third, we will cover the necessary use of assembly language code called from BASIC to execute the coarse and fine horizontal scrolling. Fourth, we will cover the vertical blank interrupt which will call and execute our assembly code for scrolling during the vertical blank so as not to interfere with ANTIC drawing the screen.
The figure below illustrates screen memory (top) and how it is partitioned for vertical scrolling (middle) and horizontal scrolling (bottom). We are using Graphics 2 in this example which has 20 bytes or characters per displayed screen line. Recall that for vertical scrolling each 20 byte sequential chunk of memory gets mapped onto the screen as lower and lower mode lines with the start of screen memory at the top left of the screen. We simply provide the starting address of the screen memory following the first mode line of the display list and the ANTIC chip takes care of mapping the 20 byte blocks of screen memory to each subsequent mode line. For a coarse vertical scroll we simply change the start of screen memory from X to X+20 in the display list and that shifts the screen up for an upward vertical scroll.
Horizontal scrolling is organized differently because we need to scroll to the left or right. Thus, we need to provide more than one 20 byte line of data per mode line so there are characters to scroll on and off. The easiest way for me to think about this is to reserve a different chunk of memory (60 bytes in this case) for each mode line. As we will cover below, we will need to provide the memory address of where each chunk of screen data resides for the display list. Note that 60 bytes of data for Graphics 2 will get you two independent screens for seamless scrolling. As the darkest blue screen scrolls off to the left the second lighter blue screen scrolls on. The lightest blue screen would need to be identical to the first screen so the when it scrolls on the screen memory can reset to the first screen without notice.
Here is an explanation of the first part of the BASIC code for the demo which does some initializations and sets up screen memory for the scrolling.
Line 20 moves the top of memory back four pages. We will use the reserved memory beyond the new top of memory for our screen data.
20 POKE 106,PEEK(106)-4
Lines 30 to 50 simply set up an introductory screen to let the user know the program is initializing before it starts the scroll demo.
30 GRAPHICS 2+16
40 SETCOLOR 0,0,14:SETCOLOR 4,7,0
50 PRINT #6;”INITIALIZING”
Line 60 sets up page 6 (DL = 1536 = 256 * 6) as the memory location where we will store the display list. We then save the high byte (i.e. 6) and the low byte (i.e. 0) for use in the display list.
Line 70 sets up a string to store the vertical blank interrupt assembly language routine.
70 DIM VBI$(10)
Lines 80 initializes our horizontal scroll counter for the fine scroll. This is initialized to 16 and then used by the assembly routine as the counter for the fine scroll register (HSCROL). We store this in page zero memory location 203 for easy access from the assembly program. Line 90 initializes a delay counter which counts the number of vertical blank periods. This is used to control the speed of the scroll which would otherwise sail by because the assembly code for the scroll is called and executed every time the screen is drawn (60 times per second). This is stored in memory location 204.
80 POKE 203,16:REM HSCROL counter
90 POKE 204,2:REM VB delay counter
Line 110 sets up the low byte and high byte of the memory address where we will store our screen data. Recall that we reserved four pages of memory beyond the top of RAM (see line 20) for this purpose. We will use the start of this RAM to hold the data for the second Graphics 2 mode line which we intend to scroll. We define the high byte (PEEK(106)) and the low byte (0) to be used by the display list and then the overall memory address for the start of this screen memory (i.e. SCR1) which we will use to write the data. Line 120 sets up an additional 20 bytes of memory which we will reuse for each additional blank mode line on the screen. If you want different data for each mode line you would define them here one at a time using the same code with variable increments for each line.
100 REM *** SETUP SCREEN MEMORY ABOVE RAMTOP ***
Line 130 writes 276 bytes of screen data at PEEK(106) to PEEK(106)+275. Why 276? First, our Graphics 2 screen is 20 bytes wide and we need more data than that for the buffer that ANTIC adds for scrolling and any additional screens of data we want to scroll on. I decided to write 256 bytes of data for my horizontal scroll. This gets me 12 screens of data (12 * 20 = 240 bytes) plus an additional 16 bytes left over. The 12 screens are nice because as one scrolls off the other can scroll on seamlessly. The 16 bytes are less than a screen so I added an additional 20 bytes. This is important because the display list instruction we will use to scroll has a 256 byte (i.e. one page of memory) limit. When the display list hits 256 it resets back to zero (more details below on what the display list is doing). As the display list increments its screen memory location for our mode line 2 the characters in memory shift left thus creating the coarse horizontal scroll. When this hits 256 all the screen data is to the left of the screen. Thus, we need the remaining 20 bytes of the 276 total to fill the last screen. You want this to match the first screen so when it resets and draws the first screen again they match and the scroll appears seamless. The benefit of incrementing to 256, and getting the automatic reset back to 0, is that we do not need to keep track of where the coarse scroll count is in our assembly language program (details later). Note that all the other mode lines are fixed at 20 bytes with blank screen data. These are not scrolled in this demo. Line 140 writes the zeros to the next 20 bytes of memory after the first 276 (i.e. LO2, HI2, & SCR2). These 20 bytes are reused over and over for mode lines 1 and 3-12.
130 FOR I=0 TO 275:READ DAT:POKE SCR1+I,DAT:NEXT I
140 FOR I=0 TO 19:POKE SCR2+I,0:NEXT I
Here are the 276 bytes of screen data read by line 130. The numbers in the DATA statements refer to the Atari internal character set. See the Table 1 from Chapter 3 of Compute’s First Book of Atari Graphics for the number to character mapping.
1000 REM *** 276 BYTES OF SCREEN DATA FOR MODE LINE 2 ***
1010 DATA 10,10,10,10,0,28,28,28,28,28,28,28,28,28,28,0,10,10,10,10
1020 DATA 10,10,10,10,0,28,28,28,28,28,28,28,28,28,28,0,10,10,10,10
1030 DATA 10,10,10,10,0,28,28,28,28,28,28,28,28,28,28,0,10,10,10,10
1040 DATA 10,10,10,10,0,28,28,28,28,28,28,28,28,28,28,0,10,10,10,10
1050 DATA 10,10,10,10,0,28,28,28,28,28,28,28,28,28,28,0,10,10,10,10
1060 DATA 10,10,10,10,0,28,28,28,28,28,28,28,28,28,28,0,10,10,10,10
1070 DATA 10,10,10,10,0,28,28,28,28,28,28,28,28,28,28,0,10,10,10,10
1080 DATA 10,10,10,10,0,28,28,28,28,28,28,28,28,28,28,0,10,10,10,10
1090 DATA 10,10,10,10,0,28,28,28,28,28,28,28,28,28,28,0,10,10,10,10
1100 DATA 10,10,10,10,0,28,28,28,28,28,28,28,28,28,28,0,10,10,10,10
1110 DATA 10,10,10,10,0,28,28,28,28,28,28,28,28,28,28,0,10,10,10,10
1120 DATA 10,10,10,10,0,28,28,28,28,28,28,28,28,28,28,0,10,10,10,10
1130 DATA 10,10,10,10,0,28,28,28,28,28,28,28,28,28,28,0,10,10,10,10
1140 DATA 0,28,28,28,28,28,28,28,28,28,28,0,10,10,10,10
As we covered in the vertical scrolling post, the display list is key to achieving coarse horizontal scrolling. With vertical scrolling we could give the display list the start of screen memory once for the first mode line and the ANTIC chip knows to take each chunk of 20 bytes (e.g. for Graphics 2) and place it in subsequent rows down the screen. Coarse vertical scrolling is achieved by incrementing or decrementing the start of screen memory in the display list which moves the rows up or down. Coarse horizontal scrolling is a bit more complicated because we need provide the display list with the start of screen memory for each and every line. This is done with the load memory scan (LMS) instruction. Normally, the display list consists of the graphics mode number (ANTIC value) for each mode line (e.g. Graphics 2 is ANTIC ‘7’ in display list). We must add a 64 to the number specifying the graphics mode for the LMS instruction which then needs to be followed by two additional lines specifying the low byte and then the high byte of the screen memory address for that mode line. In our BASIC example, this is our new top of RAM or PEEK(106) for the high byte and 0 (start of that page of memory) for the low byte of mode line 2. The following is a description of the display list in our demo BASIC program. Recall that we are storing the display list on page 6 of memory (i.e. DL = 1536 = 6 * 256).
Lines 240 to 260 start the display list and specify the required three blank mode lines not shown on the screen. We POKE 112 for the blank lines to memory locations DL, DL+1, and DL+2 (i.e. memory locations 1536, 1537, and 1538).
200 REM *** SETUP & MODIFY DISPLAY LIST ***
210 REM *** ANTIC MODE 7 (GRAPHICS 2) ***
220 REM *** +64 FOR LOAD MEMORY SCAN (LMS) ***
230 REM *** +16 ON MODE LINE 2 FOR HSCROLL ***
240 POKE DL+0,112
250 POKE DL+1,112
260 POKE DL+2,112
Line 270 specifies the blank screen data (we wrote 0s to this memory block) initialized on BASIC line 140 for the first mode. Here, we POKE to DL+3 a 7 (for graphics 2) followed by a 64 for the LMS instruction. We follow this with a POKE to DL+4 for the low byte of the screen memory location (LO1) and DL+5 for the high byte (HI1).
270 POKE DL+3,7+64:POKE DL+4,LO2:POKE DL+5,HI2
Line 280 is for mode line 2 which we intend to scroll to the left. We POKE a 7+64+16 to DL+6 where the additional 16 enables coarse horizontal scrolling. We follow this with a POKE to DL+7 for the low byte of the screen memory location (LO2) and DL+8 for the high byte (HI2). Our assembly program (see below) will increment DL+7 (memory location 1543) to achieve the coarse scroll to the left. We would decrement this value to scroll to the right with screen data flowing in from the left.
280 POKE DL+6,7+64+16:POKE DL+7,LO1:POKE DL+8,HI1
Lines 290 to 380 are the same as 270 and specify the additional 10 blank mode lines. Line 390 signals the end of the display list and a jump to the vertical blank before drawing the next screen (we will use this period to execute our scrolling code). Line 400 and 410 provide the low and then high bytes of the memory location where the display list is stored (here it is high byte 6 for page 6 of memory and low byte of 0). Line 420 and 430 tell the Atari where the display list is so it doesn’t use the one it created when we did the Graphics 2 command back on line 30 for the intro screen. You could set this up as a loop to save line numbers, but I decided to be verbose in the demo.
290 POKE DL+9,7+64:POKE DL+10,LO2:POKE DL+11,HI2
300 POKE DL+12,7+64:POKE DL+13,LO2:POKE DL+14,HI2
310 POKE DL+15,7+64:POKE DL+16,LO2:POKE DL+17,HI2
320 POKE DL+18,7+64:POKE DL+19,LO2:POKE DL+20,HI2
330 POKE DL+21,7+64:POKE DL+22,LO2:POKE DL+23,HI2
340 POKE DL+24,7+64:POKE DL+25,LO2:POKE DL+26,HI2
350 POKE DL+27,7+64:POKE DL+28,LO2:POKE DL+29,HI2
360 POKE DL+30,7+64:POKE DL+31,LO2:POKE DL+32,HI2
370 POKE DL+33,7+64:POKE DL+34,LO2:POKE DL+35,HI2
380 POKE DL+36,7+64:POKE DL+37,LO2:POKE DL+38,HI2
390 POKE DL+39,65
400 POKE DL+40,DLLO
410 POKE DL+41,DLHI
420 POKE 560,DLLO
430 POKE 561,DLHI
Assembly Language Code for Coarse and Fine Horizontal Scrolling
We described above the method for coarse scrolling which involves incrementing or decrementing the low byte of the memory address for the screen data of a given mode line in the display list. This moves characters by shifting them one whole character to the left or right. This usually not desirable because the scrolling effect is at a macro level. Fine scrolling is controlled by the horizontal scroll register (HSCROL) and moves characters one color clock at a time (Graphics 2 characters have eight color clocks for a screen which is 160 color clocks wide). Note that each graphics mode has its own color clock resolution (e.g. Graphics 0 characters have four color clocks). I refer readers to the Player-Missile site for more details about color clocks. HSCROL is a write-only register at address 54276. A character can be scrolled right by POKEing values 0 to 7 in a loop for each of the eight color clocks making up the characters. Scrolling left can be accomplished by POKEing values 15 to 8 in a loop. When the character is in its new spot after the fine scroll has completed its coarse position is moved to match, as described above, and then the HSCROL register reset. Unfortunately, both the fine and coarse scrolling needs to be executed in assembly language during the vertical blank. This is because execution in BASIC, which is easy to do, interferes with the ANTIC chip drawing the screen creating some stutters in the scrolling. We describe the vertical blank interrupt routine below in the next section.
The assembly code for our horizontal scrolling is shown below. The leftmost column are the assembly language mnemonics. The second column are decimal values for the assembly commands which we will include in DATA statements in BASIC (see lines 2000 to 2050) and then READ into memory (Page 6 + 50) for execution by the vertical blank interrupt routine. The third column are detailed comments about what each line does. This code is only for making the BASIC DATA statements and is not meant to be compiled using an assembler.
There are five parts to the scrolling routine. The first four lines of code is a simple delay routine which will skip the scrolling routines for a certain number of vertical blank periods (in this case 2) to slow down the scrolling. Once the delay is over, the next six lines performs the fine scrolling. The key here is to execute the fine scroll once per vertical blank which means we need to keep track of the HSCROL value stored in zero page memory location 203 and skip the coarse scroll below until it is done. This is explained in the comments. Once the fine scroll is done, the next four lines execute the coarse scroll by incrementing (adding one) to the low byte of our screen memory for mode line 2 in the display list. Here, the LMS low byte instruction for mode line 2 is stored in page 6 memory location 1543. Finally, we update the HSCROL counter, reset the HSCROL register, and exit. The timing of this HSCROL reset after the coarse scroll is important.
Line 520 of the BASIC code reads in this assembly code and stores it in page 6 memory location 1536+50 to 1536+88. We point the vertical blank interrupt to this code later. Also shown are the DATA statements with the decimal values from the assembly code shown above. For example, line 2010 of data corresponds to the four lines of assembly code for the delay.
500 REM *** READ IN ASSEMBLY CODE FOR SCROLLING ***
510 REM *** STORE IN P6 MEMORY LOCATION 1536+I ***
520 FOR I=50 TO 88:READ DAT:POKE 1536+I,DAT:NEXT I
2000 REM *** CODE FOR SCROLLING MODE LINE 2 ***
2010 DATA 198,204,208,32,169,2,133,204
2020 DATA 166,203,202,142,4,212,134,203,224,7,208,16
2030 DATA 173,7,6,24,105,1,141,7,6
2040 DATA 162,15,134,203,142,4,212
2050 DATA 76,98,228
Assembly Language Code for the Vertical Blank Interrupt
The key to making this work is to execute the assembly code described above during the vertical blank so it doesn’t interfere with ANTIC drawing the screen. For this we need a vertical blank interrupt (VBI) routine. I have covered how to implement a VBI in assembly in a previous post. The assembly code for the VBI is very similar to the code presented in that post. Briefly, we specify a deferred VBI by loading the accumulator with a 7 and then specify the starting memory location of our assembly code by loading the high byte in the X register and the low byte in the Y register. Note that the high byte is the same page 6 address. However, we are using the first 41 bytes of page 6 for our display list. We will start the location of our scrolling code at low byte 50 of page 6.
The decimal values for this code can be found in lines 3000 and 3010 of the BASIC code. We read the code into our VBI$ string in BASIC lines 600 and 610. We execute the VBI code just once in line 710.
600 REM *** READ CODE FOR VERTICAL BLANK INTERRUPT ***
610 FOR I=1 TO 10:READ DAT:VBI$(I)=CHR$(DAT):NEXT I
700 REM *** SHORT & SWEET MAIN LOOP! ***
720 GOTO 720
3000 REM *** CODE FOR VERTICAL BLANK INTERRUPT ***
3010 DATA 104,169,7,162,6,160,50,76,92,228
Once the VBI has been executed there is nothing left for our BASIC program to do than loop indefinitely on line 720. Our assembly code for scrolling will be executed by the operating system in the deferred vertical blank interrupt every time ANTIC finishes drawing the screen.
Here is the ATR file and a text file with the BASIC code the horizontal scrolling demo. This is a DOS 2.0S disk image. The filename of the BASIC code is HSCROLL.BAS. Here is the assembly language file shown above.
Open Altirra and load BASIC. I used Turbo BASIC XL but any flavor of BASIC compatible with Atari BASIC will do. You can also load the built-in BASIC from Altirra from the File->Attach special cartridge menu.
Open the BASIC code in your PC text editor or web browser and copy the text to your clipboard. Click on the View tab of Altirra and choose the paste text option from the bottom of the list. Altirra will slowly paste the text into the BASIC command line. Note you can go to the System tab and select “Warp Speed” for fast pasting. It is important to keep focus on the Altirra window or the paste will stop and it will lose some characters.
Also try booting the ATR file in Altirra or on your Atari from your PC using SIO2PC. You can also load the ATR on a FujiNet or similar device and boot from there. You must have a BASIC cartridge loaded. You can load the file from BASIC using LOAD “D:HSCROLL.BAS” and then type RUN.
I think you can now appreciate the complexity of horizontal scrolling. There are many moving parts to this and each needs to be performed just so. It took me some time to understand how this works and to get the code to work.
In addition the playermissile.com website I mentioned, there are several other sources I recommend reading. Each explains scrolling differently and with different key points. Hopefully my synthesis will make these sources easier to read and understand.
The Atari Assembly Language Programmer’s Guide by Moose and Lorenz is a great resource. I learned a lot from their assembly code and examples. See page 249.
The Atari Graphics and Arcade Game Design book by Stanton and Pinal also has some good descriptions of scrolling. See Chapter 7 (page 221).
Chapter 6 of De Re Atari also has some good information about scrolling. Worth the read.