Elite's famous split-screen mode, dissected and explained in detail
Elite uses a unique split-screen mode that enables a high-resolution black-and-white space view to coexist with a lower resolution, colour ship dashboard. There are two parts to this screen mode: the custom mode, and the split-screen aspect.
Elite's screen mode is a custom mode, based on mode 4 but with fewer pixels. This mode is set up in elite-loader.asm by reprogramming the registers of the 6845 CRTC - see the section on VDU command data at location B% for more details, but the salient part is the screen size, which is 32 columns by 31 rows rather than the 40 x 32 of standard mode 4. Screen sizes are given in terms of characters, which are 8 x 8 pixels, so this means Elite's custom screen mode is 256 x 248 pixels, in monochrome.
The split-screen aspect is implemented using a timer. The timer is set when the vertical sync occurs, which happens once every screen refresh. While the screen is redrawn, the timer runs down, and it is set up to run out just as the computer starts to redraw the dashboard section. When the timer hits zero it generates an interrupt, which runs the code below to reprogram the Video ULA to switch the number of colours per pixel from 2 (black and white) to 4, so the dashboard can be shown in colour. The trick is setting up the timer so that the interrupt happens at the right place during the screen refresh.
Looking at the code, you can see the SHEILA+&44 and &45 commands in LINSCN start the 6522 System VIA T1 timer counting down from 14622 (the high byte is 57, the low byte is 30). The authors almost certainly arrived at this exact figure by getting close and then tweaking the result, as the vertical sync doesn't quite happen when you would expect, but here's how they would have got an initial figure to start working from.
First, we need to know more about the screen structure and exactly where the vertical sync occurs. Looking at the 6845 registers for screen mode 4, we get the following:
- The horizontal total register (R0) is set to 63, which means the total number of character columns is 64, the same as the default for mode 4 (the number stored in R0 is the number of columns minus 1)
- The vertical total register (R4) is set to 38, which means the total number of character rows is 39, the same as the default for mode 4 (the number stored in R4 is the number of rows minus 1)
- The vertical displayed register (R6), which gives us the number of character rows, is set to 31 in elite-loader.asm, a change from the default value of 32 for mode 4
- The vertical sync position register (R7) is 34, which again is the default for mode 4
For the countdown itself, we use the 6522 System VIA T1 timer, which ticks away at 1 MHz, or 1 million times a second. Each screen row contains 64 characters, or 64 * 8 = 512 pixels, and in mode 4 pixels are written to the screen at a rate of 1MHz, so that's 512 ticks of the timer per character row.
This means for every screen refresh, all 39 lines of it, the timer will tick down from 39 * 512 = 19968 ticks. If we can work out how many ticks there are between the vertical sync firing and the screen redraw reaching the dashboard, we can use the T1 timer to switch the colour depth at the right moment.
Register R7 determines the position of the vertical sync, and it's set to 34 for mode 4. In theory, this means that the vertical sync is fired when the screen redraw hits row 34, though in practice the sync actually fires quite a bit later, at around line 34.5.
It's probably easiest to visualise the screen layout in terms of rows, with row 1 being the top of the screen:
1 First row of space view . . ... 24 rows of space view = 192 pixel rows ... . 24 Last row of space view 25 First row of dashboard . . ... 7 rows of dashboard = 56 pixel rows ... . 31 Last row of dashboard . . ... vertical retrace period ... . 34.5 Vertical sync fires . . ... 4.5 rows between vertical sync and end of screen ... . 39 Last row of screen
So starting at the vertical sync, we have 4.5 rows before the end of the screen, and then 24 rows from the top of the screen down to the start of the dashboard, so that's a total of 28.5 rows. So given that we have 512 ticks per row, we get:
28.5 * 512 = 14592
So if we started our timer from 14592 at the vertical sync and let it tick down to zero, then it should get there just as we reach the dashboard.
However, because of the way the interrupt system works, this needs a little tweaking, which is where the low byte of the timer comes in. In the code below, the low byte is set to 30, to give a total timer count of 14622.
(Interestingly, in the loading screen in elite-loader.asm, the T1 timer for the split screen has 56 in the high byte, and 50 in the low byte, so it counts down from 14386 instead of 14622. As a result the screen does flicker quite a bit more at the top of the dashboard, and there are quite a few red and yellow pixels above the split, as it switches a bit too early. Perhaps the authors didn't think it worth spending time perfecting the loader's split screen? Who knows...)