Fork me on GitHub

On Scrollers

2002-06-14 (last edition of the initial revision)

My grandfather taught me the energy of life goes in a circle, in a perfect flow; balanced. He said, until I find my centre, my circle will never be whole

— The One

Huh, so finally, as promised; the tutorial on scrollers. BTW, all my "huh" sounds aren’t like American huh, as in a question or as in a "oh yeah?", but rather phew, like a sigh. Have you been waiting for this one? I hope you have, because it was a damn pain in the ass to write the scroller program, even though it’s simple. It began with me reaching too high, also, forgetting about the bitplane layout of the graphics memory. When I put a little lower ambition level, for the sake of keeping it simple, things went smoother. Now, Luca Turilli playing in Winamp, the mood is set, time to write. The people who already know how to do scrollers will probably laugh their ass off at this clumsy scroller, which really is bad in every way except learning the basic stuff, I’ll probably do a more advanced one later on; I’ve heard that building on knowledge is good.

A few happy news first. I’ve gotten mail from three different people, excluding Maarten Martens. Thanks guys, you know who you are! One mail from FrEd highlighted a few misses I made, concerning the compatibility with Devpac 2. My initlib had a little bug. It works fine in Devpac 3, but not in 2. Two lines had d0-7 in them, it really should read d0-d7, but it’s fixed now. I know some other things may also be difficult with other assemblers than Devpac 3, so if the code doesn’t work for you, just use Devpac 3. I have tested every piece of code with that on an original Atari ST(e), so there should be no problem. Thanks go out to FrEd for pointing this out, and also to mOdmate of Checkpoint for telling me a little about the workings of $fffc02.

Yep, a scroller. I’m a bit unsure of where to start, but I guess I’ll just work from the top down. What does a scroller do? Letters go from the right of the screen, to the left of the screen (usually). New letters are brought in from the right, "outside" of the screen. How can this be achieved? The screen memory needs to be moved "to the left", and then we need information to bring in the new characters from the right. OK, this seems to build on an idea to have letters stored as graphics. Hum, yes, we have a font collection in a degas file. In that way, we’ll have letters in graphics format, we can take the information from the font file and put it on the screen. Then, we move the screen memory to the left. Easy? No, damn hard for a first timer at least.

Included in this tutorial should be a file called font.pi1, this is the font file, I stole it from James Ingram’s demo tutorials, so I wouldn’t have to make my own. Immediately load this up and look at it, either using Degas, the program from Chapter 6 or any other method. Lucky lucky, lots of characters to choose from. Each character is 32 × 32 pixels big, resulting in 10 characters per line. This is all well and fine, the next step is to actually know how to point to the beginning of, for example, letter C. If we know where this letter begins, we can put it on our screen simply by moving the data into the screen memory. Just as we did when displaying a whole picture.

tutorial 07 font
Figure 6. font.pi1

The font picture is aligned with the ASCII table, meaning that it looks like the ASCII table does. In the ASCII Table, by Stephen McNabb, you’ll find an ASCII table, in which you can look up the number for each character. As you can see, space (the first character in the font), begins at $20, then comes ! at $21 and so on. This means, that if we take the ASCII value for a character, and subtract by $20, we’ll have the corresponding number in the font. Hum, a test perhaps. C is at $43 in ASCII, subtract $20 makes $23, which is 35 (decimal). There are 10 characters per line, so we skip to the fourth line, begin counting; 0 (>), 1(?), 2(@), 3(A), 4(B), 5©, yay, right on! (remember to start counting from 0).

Now we need to know what address this is at. The way to do this is to put the beginning of the font picture address in an address register, and increment by a number. Think of the font as a coordinate system, then C would be at 3,5. We need to increment the pointer by a certain value for each coordinate, this shouldn’t be to hard.

Each line is 160 bytes, and each character is 32 lines. This means that for every Y coordinate, we need to increment the pointer by 32 × 160 bytes, right? Think about it, if we want * which is on the second line (1,0), we need to point to the font address + 32 lines down. Each character is 32 pixels wide, 16 pixels are 4 words, taking up 8 bytes, we need twice this. So for each X coordinate, we need to increment the pointer by 16 bytes.

Does this seem right? Let’s try. We want letter C, at 3,5. Thus we should increment by 3×32×160 + 5×16 = 15440 bytes. C is about the middle of the screen and 15440 is about half of 32000, so it seems safe to assume that the formula above is working. Question is, how do we get the X and Y coordinates? We had a value for C, right, that was 35. The first digit seems to be the Y coordinate, and the second the X coordinate. If we divide 35 by 10 we get 3.5. 3 is the quotient and 5 the remainder. The instruction divu (DIVide Unsigned) puts the quotient in the lower word of a data register, and the remainder in the higher word. swap is an instruction that swaps the low and high word in a data register. Great! We now have what we need. The code looks like this:

                move.l  #character, a0           ; points to character
                move.l  #font+34, a1             ; points to pixel start

                move.b  (a0), d0                 ; put letter ascii value in d0

                add.b   #-$20, d0                ; align asciin with font number
                divu    #10, d0                  ; 10 letters per row

                move.w  d0, d1                   ; d1 contains y value
                swap    d0
                move.w  d0, d2                   ; d2 contains x value

                mulu    #16, d2                  ; 16 bytes for each letter
                mulu    #32, d1                  ; 32 lines per row
                mulu    #160, d1                 ; 160 bytes per row

                move.l  #font+34, a0             ; put font screen start in a0

                add.l   d2, d1                   ; add x and y value together
                add.l   d1, a0                   ; a0 points to correct letter

                section data
font            incbin  font.pi1
character       dc.b    "C"

Since each character is an ASCII value, we only use a byte to represent it. If we put things inside "", that means we want the ASCII value. So the message dc.b "C", means that message is a byte containing the ASCII value for C. We could just as well have written message dc.b $43, but this is more difficult to understand. Hopefully, the code will speak for itself with the comments and the theory given above. This is not a complete program, but just a code snippet to show the font part. More will follow.

We know how to point to the font, now we need to know how to shift the screen memory, in order to achieve the scrolling effect. One would think that all it took was a big loop moving bytes. Like so (a0 and a1 contain the address of the screen memory)

                add.l   #1, a1                   ; put a1 8 pixels ahead of a0
                move.l  #159, d0                 ; scroll a line
loop
                move.b  (a1)+, (a0)+

For each loop we take the byte one byte ahead, and move it one byte to the left. This should move 8 pixels each loop, right? Wrong! Totally wrong! The screen is made of 16 pixel clusters, each cluster being 8 bytes long. So when you just barge in and move single bytes like that, you’ll misalign the whole shit. Not only will the colours be misaligned, the pixels will be as well. Consider this memory configuration.

First byte Second byte

%11000000

%00000000

first word

%11000000

%00000101

second word

%01000000

%00000110

third word

%01100000

%00000000

fourth word

%00000000

…​

fifth word

$3F800000

$00000642

pixels

If we use the move loop from above, the first byte will drop out, the second byte will be moved into the first byte, the first byte of the second word will go into the second byte of the first word and so on, in the end, we get this.

First byte Second byte

%00000000

%11000000

first word

%00000101

%01000000

second word

%00000110

%01100000

third word

%00000000

%00000000

fourth word

$00000542

$17400000

pixels

Not really, the pixels we had before. So, in order to overcome this in an easy way we move 16 pixels each time. This will produce a very fast scroller, but an easy one to code for. If we move 16 pixels, we won’t have to worry about getting misaligned bitplanes, since the 16 pixel clusters will never be broken up, like they were above. a0 and a1 contains the screen address, while a2 points to the character in the font.

                add.l   #8, a1                   ; put a1 16 pixels ahead of a0
                move.l  #31, d1                  ; 32 lines to scroll
                move.l  #18, d0                  ; 19 16 pixel clusters + font part
scroll
                move.w  (a1)+, (a0)+
                move.w  (a1)+, (a0)+
                move.w  (a1)+, (a0)+
                move.w  (a1)+, (a0)+             ; 16 pixels moved
                dbf     d0, scroll               ; keep moving 16 pixel clusters
                move.l  #18, d0                  ; reset loop counter
                move.w  (a2), (a0)+
                move.w  2(a2), (a0)+
                move.w  4(a2), (a0)+             ; 16 pixels of the font
                move.w  6(a2), (a0)+             ; character moved in
                add.l   #8, a1                   ; increment screen pointer, align with
                                                 ; a0
                add.l   #160, a2                 ; next line of font
                dbf     d1, scroll               ; do another line

This is all just a bunch of move words, and some adds to keep everything aligned. The first move section will move 4 words from a1, which points one 16 bit cluster ahead of a0, to a0. This is repeated 19 times. After this loop, a0 points to the beginning of the last 16 pixel cluster, and a0 points to the beginning of the second line. For the last 16 pixel cluster, we want information from the font, not from the screen. So here we move information from a2 into a0. Instead of post incrementing a2, I use indexes. After the font data is moved onto the screen, I add 8 to a1, so that it will again be 16 pixels ahead of a0. Since a0 was incremented during the font move part, and a1 was not. 160 is added to a2, so that the font pointer will now point to the next line in the font. Repeat for 32 lines.

Now the two most important techniques have been covered, how to know where the character is in the font, and how to scroll. Now we mix and match. In order to synchronize the entire scroller to the VBL, I put a wait VBL trap in the beginning of the main loop. Then I do my stuff, and in the end of the main loop, I check if the space bar is pressed, if it is, just drop out of the loop. If space bar is not pressed, then the main loop will begin again, with a VBL wait, making sure that the main loop is looped through at 50 times a second. You’ll probably be wondering exactly how I determine whether the space bar is pressed.

This little piece will do the trick: cmp.b #$39, $fffc02. Uh, says you, looking at the ASCII table (hopefully) and wondering how $39 can be space, when it should be $20. The $fffc02 part can be easily guessed, this is probably where the last key press end up, but why $39? ASCII deals with characters, and special characters like line feed and so. There’s also something called scan codes. Every key on the keyboard has its value, its scan code, so you’ll be able to determine what key was pressed. Look at the picture below:

scancode
Figure 7. Keyboard Scan Codes

While we’re still on the topic, I might as well give you the full detail. You can also check when a key is released, not just pressed. When the key is released, the high bit of $fffc02 is set, meaning you get a whole different value. Consider this.

%00111001 $39 space pressed
%10111001 $b9 space released

So, if you cmp.b #$b9, then you check if space is released. This can be used in many fun ways, like changing the background to red when space is pressed, then checking to see when space is released and then restore background. Or accelerate a car in a car game until the button is released, at which time you begin deceleration. I don’t know how often this is updated or how fast you can really press keys. Say for example that you check $fffc02 every VBL to see what key is pressed and released, suppose this dude is like Flash, and manage to press a button, then release it and press another within 1/50 of a second, then you’d loose the check for the release of the key, but I doubt you’ll have to worry about this. Back to reality, here’s the scroller.

                jsr     initialise
               
                movem.l font+2, d0-d7
                movem.l d0-d7, $ff8240
                
                move.w  #2, -(a7)                ; get physbase
                trap    #14
                addq.l  #2, a7
                move.l  d0, screen               ; store screen memory
 
main
                move.w  #37, -(sp)               ; wait vbl
                trap    #14
                addq.l  #2, sp
               
                cmp     #0, font_counter         ; check if new character in message
                bne     has_character            ; if not, skip get new character
                
                move.w  #2, font_counter         ; reset font_counter
; we need to point to a new characetr in the font

                move.l  message_pointer, a0      ; pointer into the message
                clr.l   d0                       ; clear, just to be sure
                move.b  (a0), d0                 ; put letter ascii value in d0

                cmp     #0, d0                   ; end of message?
                bne     not_end                  ; if not, branch
                
                move.l  #message, message_pointer; reset message_pointer
                move.l  message_pointer, a0
                clr.l   d0                       ; clear, just to be sure
                move.b  (a0), d0                 ; put letter ascii value in d0
 
not_end
; now we have a character in d0 for sure
               add.l    #1, message_pointer      ; point to next character
              
               add.b    #-$20, d0                ; align ascii with font number
               divu     #10, d0                  ; 10 letters per row
               
               move.w   d0, d1                   ; d1 contains y value
               swap     d0
               move.w   d0, d2                   ; d2 contains x value
              
               mulu     #16, d2                  ; 16 bytes for each letter
               mulu     #32, d1                  ; 32 lines per row
               mulu     #160, d1                 ; 160 bytes per row
              
               move.l   #font+34, a0             ; put font screen start in a0
              
               add.l    d2, d1                   ; add x and y value together
               add.l    d1, a0                   ; a0 points to correct letter
              
               move.l   a0, font_address         ; store calculated pointer
  
has_character
               add.w    #-1, font_counter 
              
               move.l   screen, a0
               move.l   screen, a1
               move.l   font_address, a2
               add.l    #8, a1                   ; put a1 16 pixels ahead of a0
              
               move.l   #31, d1                  ; 32 lines to scroll
               move.l   #18, d0                  ; 19 16 pixel clusters + font part
scroll
               move.w   (a1)+, (a0)+ 
               move.w   (a1)+, (a0)+ 
               move.w   (a1)+, (a0)+
               move.w   (a1)+, (a0)+             ; 16 pixels moved
               dbf      d0, scroll               ; keep moving 16 pixel clusters
               
               move.l   #18, d0                  ; reset loop counter
               
               move.w   (a2), (a0)+
               move.w   2(a2), (a0)+
               move.w   4(a2), (a0)+             ; 16 pixels of the font 
               move.w   6(a2), (a0)+             ; character moved in 
               add.l    #8, a1                   ; increment screen pointer, align with a0
               add.l    #160, a2                 ; next line of font
               
               dbf      d1, scroll               ; do another line
               
               add.l    #8, font_address         ; move 16 pixels forward in font
               
               cmp.b    #$39, $fffc02            ; space pressed?
               bne      main                     ; if not, repeat main
               
               jsr      restore
 
               clr.l    -(a7)
               trap     #1

               include initlib.s

               section data

font           incbin  font.pi1
screen         dc.l    0
font_address   dc.l    0
font_counter   dc.w    0

message        dc.b    "A  COOL  SCROLLER!    BUT  A  BIT  FAST, "
               dc.b    "  SCROLLING  16  PIXELS  EACH  VBL."
               dc.b    "    THAT'S 2.5 SCREENS EACH SECOND!"
               dc.b    "             ", 0

message_pointer dc.l   message
tutorial 07 screenshot
Figure 8. A scroller

There are really only two more small things that are new; the font counter and the message pointer. Also take note how I put in the scrolling message, by just using lots of dc.b, and a 0 as an end control character. By changing the text here, you can obviously change the scroller message. Perhaps to the well known "Hello World!", which I most deliberately avoided.

So what’s the font counter and message address? Well, the font counter keeps track of when it’s time to calculate a new address for a new character. This is set to 2, because every second loop, a whole character has been moved to the screen and the address for the next character in the message will have to be calculated. Had we scrolled 8 pixels each VBL, the font counter would have been set to 4 instead.

The message pointer is an index into the message. In order to know which character to get next time, we must have some pointer into the message. The message pointer begins by pointing to the message, which is good, since that’s where the first character is. The first time through the main loop, the font counter will announce that an address for a new character will have to be calculated. That address is calculated and stored. The message pointer will then point to the next character in the scroller message, which is space, and so on. When the whole message has been scrolled through, the value 0 (not character 0) will be moved from the message. This is a signal that the message is at an end, and the message pointer will be reset, once again pointing to the start of the message. I hope it’s understandable, the one tricky part is all the tests and branches, just walk through them a couple of times, slowly. You can use pen and paper for this, or the MonST.

We’ve begun to get somewhere. If you’ve paid attention so far, you’ll have acquired quite some programming skills. There are still some basic things to cover, in order to be really self sufficient (mainly timers, double buffering, sprites and bit manipulation) but you’re on a good way. Now might be the time to look at alternative sources and learn something from there. For example, you could begin to look at James Ingram’s demo coding tutorials. That was where I began, I found them quite hard but now we’ve gotten more or less to the level where he begins his stuff. That is that from me right now. Upon request from mOdmate of Checkpoint, the next tutorial will probably be on timers, I think. This means we’ll be able to remove the bottom and top borders, cool stuff!

Funnily enough James Ingram reached out in April 2022 and says the following:

So I wanted an old skool Atari font to do something,and google threw me this: https://nguillaumin.github.io/perihelion-m68k-tutorials/_on_scrollers.html

Haha! So now I am going to use my own font, which I created myself years ago :)

I had a look at your code and I thought you might like a couple of tips, if you’re still interested in 68000.

If you look at the instruction timings, it is always faster to rotate than to multiple. So instead of

   mulu #8

I would always

   lsl #3

Adding "q" to the end of some instructions makes them faster to execute.:

  addq #8

executes faster than

  add #8

That’s because the addq command includes the number "8" in the same word as the instruction, whereas the "add" instruction is followed by a word which contains the number to add.

The addq only works with numbers up to 16 (I think).

Your move 2(a2)+ instructions can be replaced with a single movem. And since you want to do the whole row, you don’t need to worry about limiting your movems to the width of a letter. Just

	movem.l	(a5)+,d0-7/a0-4
	movem.l	d0-7/a0-4,coun(a6)

as many times as you need.

Finally, I would pre-process the font and the scrolltext. The font would be re-arranged in memory so that I could grab a character with a set of movem`s and then `movep` it onto the screen.

That’s all in my tutorial, if you want the details :D

Finally, if you fancy a challenge, I found a way to scroll the entire screen 8 bits per frame. It’s also in my tutorial, which you already have :)

Cheers, and thanks again!

James Ingram