Einstein 256 Primer cover: Graphics 7's 256 colours on the Einstein 256's screen
← Back to Modules
module
15

Splitting the Screen

Introduction

S14's scroll moves the whole screen. A game almost always wants something that doesn't move: the score, the lives, the fuel gauge. On the TC-01 the video chip draws every line the same way, and all your program hears from it is the frame flag, once a frame (the assembly course's S26).

The 256's chip will interrupt your program at a line you name. At that moment you can change the registers that decide what the rest of the picture looks like: which page is shown, where it is scrolled to, the backdrop colour. The top of the screen is drawn one way and the bottom another.

The Line Interrupt

Three things set it up:

R#19the line to interrupt at
R#0 bit 4 (IE1)the line interrupt on. Graphics 4 with it is R#0 = 16h
S#1 bit 0 (FH)the line flag: set when that line is reached, cleared when you read S#1

It comes through the same IM 2 vector as the frame interrupt, FFh, so one handler gets both. It tells them apart by reading S#1. If FH is set it is the line; if not, it is the frame, and frame.asm's handler takes over as usual, reading S#0.

Two rules about where the split lands:

R#19 counts lines of the scrolled picture, not lines of the screen. With R#23 = 16, R#19 = 100 interrupts at screen line 84, where picture line 100 is showing. So when the playfield scrolls, add R#23 to R#19 every frame, or the split scrolls with it.

A change made in the line handler shows from the next line. So ask for the line above the one you want. To start the panel at screen line 160, write R#19 = 159 + R#23.

lineint.asm

The handler is short, and you will want it again in S19, so it is a file of its own that you INCLUDE after frame.asm. Save it as s15-lineint.asm.

; s15-lineint.asm - adds the line interrupt to frame.asm, for any program
; that INCLUDEs both. (S15 explains every line.)
;
; Both of the video chip's interrupts come through the same IM 2 vector,
; FFh. This file puts its own entry stub on that vector. The stub reads
; S#1: if the line flag (FH, bit 0) is set, it is the line interrupt and
; it calls the program's online; if not, it is the frame interrupt, and
; frame.asm's frame does the usual work (and calls onframe).
;
; Your program must define, as well as onframe:
;   online    - its work at the line in R#19. Called from the interrupt;
;               it may use AF only (save anything else it needs), must
;               not read port 9 (R#15 is 1 while it runs), must not call
;               MOS, and should be quick: the picture is being drawn.
; and use, in order:
;   irqon, then lineon - both tables' entries in place (interrupts off)
;   set R#19, R#0 with bit 4 (IE1) on, R#1 with bit 5 on, then EI
;   on the way out: R#0 with bit 4 off, under DI, BEFORE irqoff

LENTRY: EQU 9140h       ; this stub's home: above 8000h, clear of frame.asm's

; --- lineon: copy the stub up and point vector FFh at it
lineon: LD HL, lstub
        LD DE, LENTRY
        LD BC, lstubend-lstub
        LDIR
        LD HL, LENTRY
        LD (TABLE+255), HL
        RET

; --- lstub: copied to LENTRY. As frame.asm's stub: if the interrupt came
;     during a MOS call, page the ROM out first, and back in after.
lstub:  PUSH AF
        IN A, (26h)
        BIT 1, A        ; 1 = the ROM is in
        JR Z, ls1
        OUT (24h), A    ; ROM out
        CALL vdpint
        OUT (24h), A    ; ROM back in
        POP AF
        EI
        RETI
ls1:    CALL vdpint
        POP AF
        EI
        RETI
lstubend:

; --- vdpint: which interrupt is it? S#1 bit 0 (FH) says the line.
;     The line's work goes first, before anything else: the picture does
;     not wait. R#15 is still 1 while online runs.
vdpint: LD A, 1
        OUT (9), A
        LD A, 128+15
        OUT (9), A      ; R#15 = 1
        IN A, (9)       ; S#1 - reading it clears FH
        RRCA            ; FH into carry
        JR NC, notln
        CALL online     ; the line
        XOR A
        OUT (9), A
        LD A, 128+15
        OUT (9), A      ; R#15 = 0 again
        RET
notln:  XOR A
        OUT (9), A
        LD A, 128+15
        OUT (9), A      ; R#15 = 0 again, before frame reads S#0
        JP frame        ; the frame: frame.asm reads S#0 and calls onframe

lineon replaces frame.asm's entry on vector FFh with its own. Like frame.asm's, the stub is copied above 8000h: an interrupt that arrives during a MOS call finds the ROM over the bottom 32K, where the rest of your program lives, so the stub pages it out first (port 26h bit 1 says it is in, port 24h swaps it) and back in afterwards.

Then vdpint reads S#1 with R#15 = 1. If the line flag is set it calls your online straight away, before it even puts R#15 back. The picture doesn't wait for your handler: the chip is drawing the next line while the Z80 is still working out which interrupt it has. A handler that does a few more things first makes its changes land late, and a register written second can miss the line the first one made. So online does only the visible change, writes registers directly with OUT, and returns.

Only then does vdpint set R#15 back to 0. On the frame path that has to happen before frame reads port 9, or frame reads S#1 instead of S#0, the frame flag is never cleared, and the interrupt comes straight back.

On the way out, turn IE1 off first. frame.asm's irqoff puts MOS's table back, and vector FFh in MOS's table lands in the middle of MOS. A line interrupt still enabled then throws your program back to MOS, as an enabled frame interrupt did in S3. Write R#0 without bit 4, under DI, before irqoff.

A Colour Split

The simplest split changes one colour. In Graphics 4 every dot of colour 0 shows the backdrop, R#7. So draw sky and sea in colour 0, set R#7 to cyan in the frame interrupt and to blue in the line interrupt, and the screen is cyan above the line and blue below it. Move R#19 each frame and the waterline rises and falls.

The frame handler sets the colour for the top of the next picture. The backdrop also colours the border, so the border above the picture is cyan, and so is the border below it, apart from a line or two of blue just under the picture.

The Code: A Tide

A green rock stands in the sea. The tide comes in and goes out: the waterline climbs from line 163 to line 100 and back, a line a frame.

; S15: a colour split. One picture, two backdrop colours: sky above a
; line, sea below it - and the line rises and falls, like a tide.
;
; Everything in the picture that is colour 0 shows the backdrop, R#7.
; The frame interrupt sets R#7 to cyan for the top of the picture; the
; line interrupt, at the line in R#19, sets it to blue. Moving R#19 each
; frame moves the waterline. Runs for 500 frames (10 seconds).

        ORG 256

        DI
        ; --- Graphics 4, showing the page at 10000h
        LD A, 06h
        LD B, 0
        CALL setreg
        LD A, 5Fh
        LD B, 2
        CALL setreg

        ; --- sprites out of the way (attribute table at 1FA00h: Y = 216)
        LD A, 7
        LD B, 14
        CALL setreg
        XOR A
        OUT (9), A
        LD A, 3Ah + 40h
        OUT (9), A
        LD A, 216
        OUT (8), A
        LD A, 0F7h
        LD B, 5
        CALL setreg
        LD A, 03h
        LD B, 11
        CALL setreg

        ; --- the picture: all colour 0, with an island of colour 12
        LD A, 4
        LD B, 14
        CALL setreg     ; 10000h
        XOR A
        OUT (9), A
        LD A, 40h
        OUT (9), A      ; offset 0, writing: the run carries on in G4
        LD D, 0         ; D = line
pic1:   LD E, 0         ; E = byte across
pic2:   XOR A           ; colour 0 ...
        LD C, A
        LD A, D
        CP 60           ; ... except lines 60-191, bytes 40-87
        JR C, pic3
        LD A, E
        CP 40
        JR C, pic3
        CP 88
        JR NC, pic3
        LD C, 0CCh      ; the island
pic3:   LD A, C
        OUT (8), A
        INC E
        BIT 7, E
        JR Z, pic2      ; 128 bytes a line
        INC D
        LD A, D
        CP 192
        JR NZ, pic1

        ; --- both interrupts
        CALL irqon
        CALL lineon
        LD A, 120
        LD B, 19
        CALL setreg     ; a line to start with
        LD A, 16h       ; R#0: Graphics 4 with IE1, the line interrupt
        LD B, 0
        CALL setreg
        LD A, 0E0h      ; R#1: display on, frame interrupt on
        LD B, 1
        CALL setreg
        EI

main:   CALL waitf
        LD HL, (TICKS)
        LD DE, 500
        OR A
        SBC HL, DE
        JR C, main

        ; --- put the machine back: the line interrupt off first
        DI
        LD A, 06h       ; R#0 without IE1
        LD B, 0
        CALL setreg
        LD A, 4         ; R#7: MOS's backdrop, colour 4
        LD B, 7
        CALL setreg
        LD A, 0C0h
        CALL irqoff
        CALL tomos
        RET

; --- onframe: sky for the top of the next frame; move the waterline
onframe:
        LD A, 7         ; R#7 = colour 7, cyan
        LD B, 7
        CALL setreg
        LD A, (TICKS)   ; t = frames mod 128, folded: 0-63, then back down
        AND 7Fh
        CP 64
        JR C, of1
        CPL
        AND 7Fh
of1:    ADD A, 100      ; the waterline: lines 100-163
        DEC A           ; ask for the line above: the change shows a line late
        LD B, 19
        JP setreg

; --- online: the waterline - the backdrop turns blue from here down
online: LD A, 4         ; colour 4, blue
        OUT (9), A
        LD A, 128+7
        OUT (9), A
        RET

        INCLUDE "frame.asm"
        INCLUDE "s15-lineint.asm"

Build and run

Save it as s15-colour-split.asm next to frame.asm and s15-lineint.asm. Build and run it as in S2.

What you should see

A cyan sky, a dark green rock in the middle, and blue sea below a straight waterline right across the screen. The waterline moves a line every frame, falling for a little over a second and then rising, again and again. Below the picture the border is cyan, after a line or two of blue. After ten seconds MOS's screen and the prompt.

A green rock, cyan sky above, blue sea below a straight waterline

A Panel That Stays Still

The colour split changed one register. The score panel changes two:

  • R#2, to show a different page below the split: the panel lives in the page at 18000h (R#2 = 7Fh), the playfield in the page at 10000h (5Fh).
  • R#23, so the panel starts from its own line 0. The split shows from screen line 160, and line 160 + R#23 must be 256, which wraps to 0: so R#23 = 96.

The frame interrupt puts both back for the top of the next frame (R#2 = 5Fh, R#23 = the scroll), asks for the next line interrupt at 159 + the scroll, and feeds the playfield's next line as in S14. The playfield shows only 160 lines now, but its page still loops through all 256, so nothing else in S14's scroll changes.

What it costs: the panel's 32 lines take 4K of the page at 18000h; the rest of that page is free. The handlers cost two interrupts a frame instead of one.

The panel's text comes from MOS's own font, which is still in video memory at 1800h while your program runs: 8 bytes a character, one byte a row, a set bit for each dot of ink. glyph reads a character's 8 bytes and turns each into four Graphics 4 bytes.

The Code: River and Panel

; S15: a still score panel under a scrolling playfield.
; The top 160 lines are S14's river, scrolling up a line a frame. The
; bottom 32 lines are a panel that never moves. Runs for 500 frames.
;
; The playfield is the page at 10000h (R#2 = 5Fh), scrolled by R#23.
; The panel is 32 lines at the top of the page at 18000h (R#2 = 7Fh).
; At line 159 the line interrupt switches R#2 to the panel's page and
; R#23 to 96, so that screen line 160 shows panel line 0 (160 + 96 =
; 256, which wraps to 0). The frame interrupt puts both back for the
; top of the next frame, and asks for the next line interrupt.

        ORG 256

        DI
        ; --- Graphics 4 first, so VRAM runs carry on past 16K
        LD A, 06h
        LD B, 0
        CALL setreg
        LD A, 5Fh
        LD B, 2
        CALL setreg
        XOR A
        LD B, 23
        CALL setreg

        ; --- sprites out of the way (attribute table at 1FA00h: Y = 216)
        LD A, 7
        LD B, 14
        CALL setreg
        XOR A
        OUT (9), A
        LD A, 3Ah + 40h
        OUT (9), A
        LD A, 216
        OUT (8), A
        LD A, 0F7h
        LD B, 5
        CALL setreg
        LD A, 03h
        LD B, 11
        CALL setreg

        ; --- the panel, before the river: its text comes from MOS's
        ;     font, still in VRAM at 1800h
        CALL panel

        ; --- the river: rows 0-255 into lines 0-255
        LD HL, 0
first:  LD A, L
        CALL drawrow
        INC HL
        LD A, H
        OR A
        JR Z, first

        ; --- both interrupts
        CALL irqon
        CALL lineon
        LD A, 159       ; the first split: line 159, no scroll yet
        LD B, 19
        CALL setreg
        LD A, 16h       ; R#0: Graphics 4 with IE1
        LD B, 0
        CALL setreg
        LD A, 0E0h      ; R#1: display on, frame interrupt on
        LD B, 1
        CALL setreg
        EI

main:   CALL waitf
        LD HL, (TICKS)
        LD DE, 500
        OR A
        SBC HL, DE
        JR C, main

        ; --- put the machine back: the line interrupt off first
        DI
        LD A, 06h
        LD B, 0
        CALL setreg
        LD A, 0C0h
        CALL irqoff
        CALL tomos
        RET

; --- online: line 159 - from the next line down, show the panel
online: LD A, 7Fh       ; the panel's page
        OUT (9), A
        LD A, 128+2
        OUT (9), A
        LD A, 96        ; screen line 160 = panel line 0
        OUT (9), A
        LD A, 128+23
        OUT (9), A
        RET

; --- onframe: the top of the next frame - the playfield, scrolled on
onframe:
        LD A, 5Fh
        LD B, 2
        CALL setreg     ; the playfield's page
        LD HL, (TICKS)
        LD A, L
        LD B, 23
        CALL setreg     ; R#23 = the scroll
        LD A, L
        ADD A, 159      ; R#19 counts lines after the scroll: add it
        LD B, 19
        CALL setreg
        DEC HL          ; feed the line just scrolled off, as in S14
        LD A, L
        INC H
        JP drawrow

; --- panel: lines 0-31 of the page at 18000h. Line 0 white, the rest
;     black (colour 1), with the text on lines 12-19.
panel:  LD A, 6
        LD B, 14
        CALL setreg     ; 18000h
        XOR A
        OUT (9), A
        LD A, 40h
        OUT (9), A
        LD A, 0FFh      ; line 0: white
        LD B, 128
pn1:    OUT (8), A
        DJNZ pn1
        LD DE, 31*128   ; lines 1-31: black
pn2:    LD A, 11h
        OUT (8), A
        DEC DE
        LD A, D
        OR E
        JR NZ, pn2
        LD HL, text     ; then the text, a character at a time
        LD E, 4         ; E = the character column (8 dots each)
pn3:    LD A, (HL)
        OR A
        RET Z
        CALL glyph
        INC HL
        INC E
        JR pn3

; --- glyph: character A at column E, lines 12-19 of the panel. HL, E kept.
;     Each of the font's 8 bytes is 8 dots, 1 = ink; in Graphics 4 that
;     is 4 bytes, two dots each, the left dot in the high nibble.
glyph:  PUSH HL
        PUSH DE
        LD L, A         ; the font byte address: 1800h + code * 8
        LD H, 0
        ADD HL, HL
        ADD HL, HL
        ADD HL, HL
        LD BC, 1800h
        ADD HL, BC
        LD D, 12        ; D = panel line
gl1:    XOR A
        LD B, 14
        CALL setreg     ; MOS's font is in the first 16K
        LD A, L
        OUT (9), A
        LD A, H
        OUT (9), A      ; read address (no 40h)
        IN A, (8)
        LD C, A         ; C = the glyph's row
        LD A, 6
        LD B, 14
        CALL setreg     ; the panel's 16K
        LD A, D         ; offset = line * 128 + column * 4
        RRCA
        AND 80h
        LD B, A
        LD A, E
        ADD A, A
        ADD A, A
        OR B
        OUT (9), A
        LD A, D
        SRL A
        OR 40h
        OUT (9), A
        LD B, 4         ; 4 bytes of 2 dots
gl2:    LD A, 11h       ; both dots black ...
        SLA C
        JR NC, gl3
        OR 0E0h         ; ... left dot colour 15 (1 OR E = F)
gl3:    SLA C
        JR NC, gl4
        OR 0Eh          ; right dot colour 15
gl4:    OUT (8), A
        DJNZ gl2
        INC HL
        INC D
        LD A, D
        CP 20
        JR NZ, gl1
        POP DE
        POP HL
        RET

text:   DEFB "SCORE 001230    LIVES 3", 0

; --- drawrow: river row HL on VRAM line A of the page at 10000h (S14)
drawrow:
        PUSH HL
        PUSH AF
        RLCA
        AND 1
        ADD A, 4
        LD B, 14
        CALL setreg
        POP AF
        AND 7Fh
        RRCA
        LD C, A
        AND 80h
        OUT (9), A
        LD A, C
        AND 3Fh
        OR 40h
        OUT (9), A
        POP HL
        PUSH HL
        LD A, L
        AND 7Fh
        CP 64
        JR C, dr1
        CPL
        AND 7Fh
dr1:    ADD A, 8
        LD D, A
        LD A, L
        AND 1Fh
        LD E, 44h
        JR NZ, dr2
        LD E, 0FFh
dr2:    LD C, 128
        LD A, 0CCh
        LD B, D
dr3:    OUT (8), A
        DEC C
        DJNZ dr3
        LD A, E
        LD B, 48
dr4:    OUT (8), A
        DEC C
        DJNZ dr4
        LD A, 0CCh
        LD B, C
dr5:    OUT (8), A
        DJNZ dr5
        POP HL
        RET

        INCLUDE "frame.asm"
        INCLUDE "s15-lineint.asm"

Build and run

Save it as s15-split-screen.asm next to frame.asm and s15-lineint.asm. Build and run it as in S2.

What you should see

S14's river, scrolling up a line every frame, in the top 160 lines. Below it, starting with one white line, a black panel 32 lines deep reading SCORE 001230 LIVES 3. The river scrolls; the panel doesn't move at all, and the join stays on the same line in every frame. After ten seconds, MOS's screen and the prompt.

The scrolling river above a still black panel reading SCORE 001230 LIVES 3

Change One Thing

  • In s15-split-screen.asm, change ADD A, 159 in onframe to ADD A, 160. Which line of the screen does the panel start on now, and which of its lines will you never see?
  • Take the LD A, L and ADD A, 159 out of onframe, and write LD A, 159 instead, so R#19 no longer allows for the scroll. What happens to the join?
  • In s15-colour-split.asm, take DEC A out of onframe. Where does the waterline land, compared with the line number in the comment?

When It Goes Wrong

SymptomCause
One line at the join shows the wrong thingThe line handler is too slow, so its second register write lands a line after the first. Do the visible changes first, with direct OUTs
The panel starts one line low and its top line never showsR#19 set to the join's line, not the line above it

Summary

You can interrupt your program at a line of your choice with R#19 and R#0 bit 4, tell the line interrupt from the frame interrupt by S#1's FH bit, and change the picture from the next line down: a backdrop colour, or R#2 and R#23 for a still panel under a scrolling playfield. Ask for the line above, add the scroll, keep the line handler short, and turn IE1 off before you give MOS its table back.

Next

S16, The Blitter: Fill, Line, Point: the command engine S14 borrowed for its copy, taught properly.

Get the Newsletter

New guides, disk images and community finds, roughly once a quarter. No spam, we promise, this isn't Tatung's marketing department.
Your subscription could not be saved. Please try again.
Your subscription has been successful.

Newsletter

Subscribe to our newsletter and stay updated.