Infinite Scrolling on the ZX81 with Machine Code Magic
Taking a different approach to scrolling by combining ZX81 BASIc with machine code.
Finding inspiration again from my programming groups, the idea of an infinitely scrolling screen of text came up. There were a couple of different solutions posted, some quite ingenious. I had done something similar in the past, but this time I used a bit of machine code magic to create a more flexible solution. The result is Print Scroll.
# Let’s talk about scrolling.
Unlike almost every other computer on the market at the time, the ZX81 just stops when your printing hits the bottom of the screen. You can use CONT
to continue your program, but that clears the screen, and any wrapped text is lost. You can use SCROLL
, but you still have to be aware of wrapping text, or the program will halt. Thus, screen management is the primary concern when writing a text program.
There are, of course, ways around this. You can use PRINT AT
to always print at specific locations on screen, mostly avoiding the whole mess. You can plan your prints to always be less than 32 characters. In that case, you use a semicolon to avoid printing a line return and use SCROLL
instead. If you have text that could go over 32 characters, you can do some string magic to manage anything that wraps.
All of these solutions are good workarounds to the problem. But, I wanted something different. What if I could scroll the screen when a wrap occurred? In this way, I could print basically forever without having to fuss with all the ideas above. It turns out, I can.
Print Scrolling Running and Listing, ZX81 Movie, 2024 by Steven Reid
# Sprinkle in some machine code.
The ZX81 can easily call machine code using the USR
function. It usually returns whatever the contents were in the BC register as a positive integer. Since most of the time that is just discarded, most programs will load the value into, say, RAND
or assign it to a variable. But what if there was another way?
Searching around the ZX81 forums, I discovered there is. You can actually abort the BASIC line from within machine code with a call to the error routine:
; all done
rst $08 ; abort basic line
db $ff ; will continue to next line
The value is thus discarded, and the program keeps running as if nothing happened. In the same post, it also showed how you can then read through the lines afterward. In this case, I wanted to interpret the rest of the PRINT
statement and display that.
To do that, I use a few more ROM routines as follows:
rst $20 ; skip semicolon
call scanning ; deal with parameters
call stkfetch ; pop details off calc stack
The advantage of all this is that the parameters don’t have to be static. Within BASIC, you can use variables and functions to dynamically pass arguments to the machine code. Now, I didn’t figure out all the nuances of these functions, but in testing, it does a decent job of managing most code.
What all this ultimately does is put a string onto the calculator stack. I can then walk through that string, just like I usually would, and print each character to the display. To do that, I wrote a modified print routine that checks if we’ve printed at the bottom right of the display. If so, it scrolls the display and resets the print location to the bottom left of the line. Here is the primary function call:
; Print Scroll
; this prints a string at DE, stopping when BC is zero
; scrolls on display overflow
print_scroll:
ld a,(de) ; load character
rst $10 ; print it!
call check_bottom ; at bottom of screen?
inc de ; increment de
dec bc ; decrement length
ld a,b
or c ; check if bc is zero
jr nz,print_scroll ; loop if not zero
ret ; else we are done
; ---
I played around with a couple of different ways of checking the bottom and scrolling the screen. I was trying to decide if there was a better way to check and manage the position. In the end, I stuck with what worked.
# Making the program useful.
With the machine code worked out, I now needed to make a program from it. For my initial testing, I built the program as usual and let it error out. I could then edit the BASIC to test it out. I followed what the poster had originally used, which was to bring “HELLO “ forever.
Although that worked, it was hard to test and debug. Lately, I’ve been compiling my programs using “z80asm +zx81,” which creates a .P file that I can run from any emulator. The upside is it deals with all the ZX81 system stuff for me. The downside is that I can’t adjust the BASIC code within the program.
I had thought about dropping the default linker before, but the templates I found were for different compilers and didn’t quite meet my needs. To my surprise, I found an example for z80asm that I could use and was well documented. Incorporating that into my code was pretty easy and worked like a charm. I now had an easy way to include whatever BASIC I wanted into the program. The only problem was, I needed a way to compile the BASIC code.
Print Scrolling, Listing, ZX81 Screenshot, 2024 by Steven Reid
# Making the ZX81 write code for me.
Now, I could have written a script that would read in a ZX81 program and dump out the code. Although that would work, it would require some complex workflow. Discarding that idea, I took to programming the code on a ZX81 emulator instead. I then wrote a routine that would print out the code as hex.
To my surprise, the program was straightforward to write and more efficient than I expected. I was able to modularize the code, allowing me to easily adjust the BASIC and run the program to dump each line out. It exits when it reaches STOP
. I’ll share more on this program in a future article.
With that in place, I could enter some example lines of code that I could use with the routine. I could then transcribe that into my program and compile it. This made testing a lot faster, not having to retype the BASIC code each time. If I did need to change something, I would adjust the BASIC, then reenter the new lines in my machine language program. It’s nice being able to use the ZX81 again to aid in programming it.
# A working routine. Let’s have some fun.
I had a lot of fun with the routine. Since it acts like an extension to the PRINT
command, I tried a number of different routines to see what it could do. Since I could use variables with it, I decided to create a string array and let it randomly print one of three phrases. This resulted in a more dynamic display than the original version.
The one problem I did have is that the scanning routine stopped when it reached a semicolon. To get around that problem, I first added the space to the string. But, I quickly figured out that I could just add a space like so:
60 PRINT USR 16541;S$(INT (RND*3)+1)+" "
This worked well for the demo program. It does have the downside of being a bit slow running in BASIC, but you can do some pretty fun things with this routine. While working on this article, I worked through all the ways I could and couldn’t get the routine to work.
Print Scrolling, ZX81 Screenshot, 2024 by Steven Reid
If you use anything other than a string, the routine will stop with a report code. You can put them after the string, but since I abort after the first scanning routine, they are ignored. I’m sure if I spent more time in the ROM routines, I could do more. As is, it is quite a fun routine to use.
Fortunately, the ZX81 does a pretty good job of checking syntax on what can be displayed. Since the code is through the calculator routine, you can get pretty creative. The demo code does a good job of showing the use of functions and string manipulation. I also tested boolean routines and STR$
functions. So far, all works as expected. It’s kind of nice that you can call a machine language routine and use BASIC code as you would expect.
# What more could I do?
Although it’s a great program for infinite scrolling, it has one drawback. As designed, it assumes that each print continues on the same line. That is, it assumes you have a semicolon after every print. Although handy for the intent I was going for, it isn’t always practical.
An easy fix is to use SCROLL
to add a newline. Although that works, it negates the cleaner scroll that I used here. I can think of a couple of ways around this. One is to have two routines: one that adds a newline and one that doesn’t. You use the one based on your desired output. Or, I could find some way to see if a semicolon is at the end of the BASIC line and have the routine act similar to how a regular PRINT
command would.
Given that the program works as intended, this ended up being a thought experiment. I could go down a rabbit hole of recreating functionality and decided to stop while I was ahead. The fun here was playing around a bit with passing parameters to machine code. I doubt I’d do much with this code in the future. But, should the need arise, I have some code to start from.