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.
The overview of this topic is in Assembly basics. The same topic in MIPS, RISC-V.
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.
| port | what it holds |
|---|---|
0x10 | pen colour: lines, outlines, single pixels and text |
0x11 | fill colour: the inside of shapes, the flood fill and the clear |
0x12 | pen width in pixels, at least 1 |
0x13 | X, the first coordinate |
0x14 | Y |
0x15 | X2, the second coordinate: the end of a line, the far corner |
0x16 | Y2 |
0x17 | the command port: one write runs one drawing operation |
0x18 | reading it gives the colour of the pixel at (X, Y) |
0x19 | the text cursor's column, in 8 by 8 character cells |
0x1A | the 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:
| command | what it draws |
|---|---|
| 0 | one pixel at (X, Y), in the pen colour |
| 1 | a line from (X, Y) to (X2, Y2) |
| 2 | a line from the drawing position to (X, Y) |
| 3 | move the drawing position to (X, Y) without drawing |
| 4 | a rectangle from (X, Y) to (X2, Y2), filled and outlined |
| 5 | the same rectangle, outline only |
| 6 | the ellipse inscribed in that rectangle, filled and outlined |
| 7 | the same ellipse, outline only |
| 8 | flood fill outwards from (X, Y) |
| 9 | fill the whole Screen with the fill colour |
| 10 | resize the Screen to X by Y and clear it; a 0 means 256 |
| 11 | double buffering on |
| 12 | double buffering off |
| 13 | present: 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.
| colour | byte | colour | byte |
|---|---|---|---|
| black | 0x00 | red | 0xE0 |
| blue | 0x03 | magenta | 0xE3 |
| green | 0x1C | yellow | 0xFC |
| cyan | 0x1F | white | 0xFF |
| grey | 0x92 | orange | 0xF0 |
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:
| port | reading gives |
|---|---|
0x20 | 1 when a typed character is waiting on the character port, else 0 |
0x21 | 1 while the key whose code is in b is held down, else 0 |
0x22 | the code of the last key pressed, 0 before the first press |
0x23 | the 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.
| port | reading gives |
|---|---|
0x30 | the pointer's X in the selected view, in Screen pixels |
0x31 | its Y |
0x32 | the buttons and modifiers of that view |
0x33 | the 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.