Beamhacking Intro
Despite what Wikipedia might say, the TRS-80 Model 3 has always had a vertical resolution of 192 lines. After all, the the raster beam goes through a full 192 lines scanning out 12 for each of the 16 character rows. It's not that their description is wrong, just incomplete. Yes, there are 64 character columns. Yes, there are 16 character rows. Yes, each character cell may have a text character or a graphics character. Yes, those 64 graphics characters have all combinations of 2 x 3 pixel blocks. Yes, that gives you an effective 128 x 48 resolution.What did they miss?
Time.
You might get the impression that each screen is drawn instantaneously. Or maybe each character is redrawn when it changes. You might even think that the character rows are drawn one at a time. But it turns out that there's just a single beam used to draw the entire screen. It scans left to right starting from the top. It moves quickly, to be sure, covering the entire screen in about a 1/60th of a second. Yet at any one time it is drawing a single pixel.
There's another detail they missed.
Memory.
As you might expect, the circuitry driving the beam doesn't remember the entire screen contents and then guide the beam. It doesn't even read an entire row of 64 characters at a time. In fact, all it ever remembers at any one time is a 1/12th of a character. One little slice. Obviously it has to read each character on the screen, but it turns out it reads each character 12 times — once for every one of the 12 lines to takes up to make a single row of characters.
Because the beam takes some time to draw and because it doesn't remember much at once we have an opportunity to get more lines of resolution. All we need do is change the character after the beam has done one pass but before the next.
In other words, it looks like this. If we change the screen once and a while and don't try hard we can get blocky pixels like this:
















































This is great news! We know exactly what a program needs to do. The only thing to work out is when to write to the screen. It'd be great if there were a "raster interrupt" that will tell the processor "Hey! I'm about to start a line". We'll just set it up to go off at line 3, change a character, set it to go off at line 4 and change it again.
Sorry, there isn't.
Well, how about some kind of status bit we can read that says when a line starts. That's more tedious to program and costly in CPU but we can count lines and make the changes at the right time.
Nope, don't have that.
Surely there's an interrupt that happens once per frame? That's pretty standard equipment. Now, we'll have to figure out how long each line is and use a timing loop to wait until the right line is scanning, do the changes and wait until the next line. Tougher, but can do.
Well, almost.
Huh?
There's a interrupt that happens every second frame.
Oh. Oh dear. So we wait for the interrupt, then carefully time our code until some line, do our thing. But if we wait for the next interrupt there'll be an awful glitch where the character cell wasn't changed. So instead we'll need to time our code some more until that line comes around a second time and do the change. Now we can wait for the interrupt.
Why wait for the interrupt?
Hmmm, I see your point. May as well just keep on tracking time ourselves so that we stay in sync with the beam. But will that even work?
Yes, yes it will. The video and processor run off the same base clock. Thanks to the rigid magic of digital timing they'll stay in sync.
Is that going to be easy?
Not so much.
Clearly getting a full 192 lines of resolution is possible. Catching the 1/30th second interrupt is relatively easy but there are subtleties. Keeping track of every cycle in your program? Our processor, the Z-80 is at least predictable. We can tell how long a program will run, in principle. In practice there's a lot of extra work to do. Bad enough that we need the program to operate correctly. Having it run in some exact amount of time will make the job a lot tougher. Next time we'll get into some of the details and work out how to tackle these problems. For now let's just keep in mind that it can be done.
George Phillips, July 20, 2009, george -at- 48k.ca