The screen, keyboard and mouse through ports

The Screen is not memory here, it is a group of ports: colours, coordinates and one command per write. Drawing, a colour byte with three bits of red, double buffering, the keys polled by code and the pointer read three ways.

A framebuffer is a run of memory in which each element is one pixel, and it is how the MIPS and RISC-V simulators here reach their screens. This Z80 has none, and the reason is arithmetic: 256 by 192 pixels at one byte each is 48 KB, three quarters of the whole address space, spent on a picture. So the Screen, the Keyboard and the Mouse are more ports, decoded next to the console ports of the previous lecture (ADR 0011).

The Screen ports

Drawing is always the same two steps: write the colours and the coordinates to their ports, then write one command to the command port, which runs one operation on whatever is currently set.

portwhat it holds
0x10pen colour: lines, outlines, single pixels and text
0x11fill colour: the inside of shapes, the flood fill and the clear
0x12pen width in pixels, at least 1
0x13X, the first coordinate
0x14Y
0x15X2, the second coordinate: the end of a line, the far corner
0x16Y2
0x17the command port: one write runs one drawing operation
0x18reading it gives the colour of the pixel at (X, Y)
0x19the text cursor's column, in 8 by 8 character cells
0x1Athe text cursor's row

Every one of those is a byte, which is why the Screen is at most 256 by 256 pixels, and it is 256 by 192 until a program resizes it. The origin is the top left, x grows right and y grows down, and drawing outside the Screen is quietly ignored.

The commands are numbers written to 0x17:

commandwhat it draws
0one pixel at (X, Y), in the pen colour
1a line from (X, Y) to (X2, Y2)
2a line from the drawing position to (X, Y)
3move the drawing position to (X, Y) without drawing
4a rectangle from (X, Y) to (X2, Y2), filled and outlined
5the same rectangle, outline only
6the ellipse inscribed in that rectangle, filled and outlined
7the same ellipse, outline only
8flood fill outwards from (X, Y)
9fill the whole Screen with the fill colour
10resize the Screen to X by Y and clear it; a 0 means 256
11double buffering on
12double buffering off
13present: show the off-screen image

A rectangle excludes its right and bottom edges, the way EASy68K's does, so a box whose corners meet draws nothing.

The colour byte

A colour is one byte in a 3-3-2 layout: three bits of red in bits 7 to 5, three of green in bits 4 to 2 and two of blue in bits 1 and 0. Two bits of blue is what is left over, and it is why the greys are not exactly neutral.

colourbytecolourbyte
black0x00red0xE0
blue0x03magenta0xE3
green0x1Cyellow0xFC
cyan0x1Fwhite0xFF
grey0x92orange0xF0

Press Run on this one and watch the Screen panel next to it.

Try changing ld a, 220 on the ellipse to ld a, 180: the box stops being wide and the ellipse becomes a circle.

The last loop is the character port from the previous lecture, unchanged. Text and graphics share one image here, the way EASy68K's single output window did, so anything printed on the console ports also lands on the Screen at the text cursor, in 8 by 8 cells: 32 columns by 24 rows on the default Screen. Command 9 wipes both.

Double buffering and the frame

Drawing a moving picture straight onto the visible Screen shows every half finished frame. Command 11 sends the drawing to an off-screen copy instead and command 13 shows it, so the reader only ever sees whole frames.

Pacing is port 0x41: reading it waits for the next animation frame and gives back 0. One read per frame is how an animation runs at the display's pace instead of as fast as the host can go.

This one runs until you press Stop.

cp LIMITX catches both edges with one unsigned comparison: a step that would take x below zero wraps it round past 250, which is above the limit as well.

Try changing ld a, C_BUF_ON to ld a, 12, which turns double buffering off. The ball still moves and now it flickers, because you are watching the clear and the draw happen.

Port 0x40 is the other way to pace a program: put a number of hundredths of a second in b and read it, and the program waits that long. Port 0x42 reads one byte of the hundredths since the run started, with b choosing which byte. All three of these suspend the program without freezing the editor, so Stop still answers and the Screen still repaints.

The keyboard

Four ports, and they are all reads:

portreading gives
0x201 when a typed character is waiting on the character port, else 0
0x211 while the key whose code is in b is held down, else 0
0x22the code of the last key pressed, 0 before the first press
0x23the code of the last key released

Port 0x21 is what a game reads: it consumes nothing, so a key held down answers 1 every time round the loop, and it needs the in r,(c) form because the key code travels in b.

The key codes are EASy68K's, the same table every language in this editor uses. A letter is the ASCII code of its capital, so A is 0x41 whether or not Shift is held; a digit is its ASCII code; and the arrows are left 0x25, up 0x26, right 0x27, down 0x28.

Click the Screen panel before you press a key: the Screen only gets the keyboard when it has the focus, and a ring around it says so while it does.

Click the Screen, hold an arrow key and the square moves. The four reads all use the same c, since the port never changes, and only b is reloaded between them.

The typed characters and the key state are two different things. Port 0x20 and the character port 0x00 are the ones that answer "what did they type", one keystroke at a time; port 0x21 answers "is this key down now" and consumes nothing, which is what a game wants.

The mouse

Four ports again, and b selects which view the read answers with: 0 is where the pointer is and which buttons are down now, 1 is the state at the last button release, 2 at the last button press. The two snapshots persist until the next one, so a program that polls slowly still sees every click.

portreading gives
0x30the pointer's X in the selected view, in Screen pixels
0x31its Y
0x32the buttons and modifiers of that view
0x33the mouse event count, a byte that wraps

The buttons byte is one bit each: bit 0 left, bit 1 right, bit 2 middle, bit 3 the double-click flag (only in the last-press view), bit 4 Shift, bit 5 Alt, bit 6 Ctrl.

Click the Screen, then drag with the left button held: a cyan disc follows the pointer, Shift makes it red and the right button clears. bit 0, e and bit 1, e are the bit test from the arithmetic lecture, reading two bits of the one byte the port answered with.

Port 0x33 is how a program tells a new click from one it has already handled: read the count, compare it with the count you saw last time, and act only when it has changed. Polling the buttons alone cannot do that, since a button held for half a second reads as down every frame.

Your turn

Fill a red rectangle over the box from (10, 10) to (100, 100), then read the colour of the pixel at (50, 50) back and leave it in a. Red is 0xE0, so a comes out at E0. The pen and the fill both have to be red, or the pixel you read might be on the outline.

Show solution

The second one uses the text cursor. Put it at column 5 and row 3, print HI there, and then read the two cursor ports back into b and c. The cursor moves as it prints, so b comes out at 7, two cells further right, and c at 3.

Show solution