Text Controller
12.1 Overview
The tilemap's layer0 and layer1 (see Tilemap Memory) are enough to implement a functional text terminal or console output. However, implementing this entirely in software on an 8-bit system can be non-trivial. Cursor position calculations may require multiplications, modulo operations, and additional logic for scrolling and wrapping.
The text controller simplifies this process by providing:
- A hardware cursor
- Automatic scrolling
- Newline handling
- Additional helper functions for text display
Like other controllers, the Text Controller can be mapped into the device bank starting at address 0xA0 on the I/O port. Its device index is 0.
12.2 Hardware Cursor
The text controller includes a customizable hardware cursor. The cursor is a tile (8x12 px) that can display any character from Font Memory, selected via the CURSOR_CHARACTER register.
Its colors are configured via CURSOR_COLORS, which works like layer1 entries:
- Upper nibble (4-bit) = background color index
- Lower nibble (4-bit) = foreground color index
Both indices refer to the first 16 entries in Palette Memory.
| Name | Description | Offset | Access |
| CURSOR_Y | Set the cursor vertical position. | 0x01 | R/W |
| CURSOR_X | Set the cursor horizontal position. | 0x02 | R/W |
| CURSOR_BLINK_TIMING | Blinking speed for the cursor. Controls how many frames cursor is visible/invisible. | 0x06 | R/W |
| CURSOR_CHARACTER | Character glyph to use for displaying the cursor | 0x07 | R/W |
| CURSOR_COLORS | Cursor colors, formatted like CURRENT_COLOR | 0x08 | R/W |
Cursor Blinking
Blink timing is set with CURSOR_BLINK_TIMING, in frames:
- For example,
30= visible for 30 frames (~500 ms at 60 Hz), hidden for 30 frames 0is a special value meaning always hidden255(0xFF) is a special value meaning always visible
When hidden, the character beneath remains visible, making the cursor behave like an overlay on top of layer0, similar to a sprite in graphics mode.
Position Tracking
The cursor's current column and row (in character units, not pixels) are stored in CURSOR_X and CURSOR_Y.
These registers are read-write, so for example, setting CURSOR_X to 1 moves the cursor to the second column.
Save & Restore
Two bits in CTRL make it easy to temporarily reposition the cursor:
SAVE_CURSORstores current X and YRESTORE_CURSOR, restores stored X and Y
Only the position is saved, the cursor's character and colors are unaffected.
12.3 Character Output
Writing any 8-bit value to PRINT_CHAR displays the corresponding glyph (from Font Memory) at the cursor's position, using the colors set in CURRENT_COLOR.
Internally, the character code is written to layer0, the CURRENT_COLOR value is written to layer1, with the offset calculated from the cursor position and current scroll values.
After writing, cursor X increments. If X passes the last column (80 for 640x480 mode, 40 for 320x240 mode), it will wrap to start of next line, or wrap to the top of the screen if it was already on the last line.
Auto-Scroll
Hardware auto-scrolling is also supported. Two control bits of the CTRL register modify this behavior:
AUTO_SCROLL_X: when set, reaching the last visible column scrolls horizontally instead of wrapping (cursor X stays the same)AUTO_SCROLL_Y: when set, reaching the last line scrolls vertically
If vertical scrolling occurs with AUTO_SCROLL_Y enabled, the SCYF flag in CTRL is set, letting the CPU know it may need to clear the new line.
Scroll offsets are stored in SCROLL_X and SCROLL_Y. These are read-write, so they can also be written manually, but must not exceed the boundaries:
- 80x40 mode: X ≤ 79, Y ≤ 39
- 40x20 mode: X ≤ 39, Y ≤ 19
The cursor position is not affected by scroll settings.
In most cases, to simulate a terminal,
AUTO_SCROLL_Xis disabled whileAUTO_SCROLL_Yis enabled.
12.4 Newline
Go to Next Line
Values written to PRINT_CHAR are not interpreted as ASCII, they are simply written to layer0 as-is.
While the default Font Memory uses an ASCII-like layout, this is purely conventional.
Implementing a newline in software would require the following:
- Reading
CURSOR_Y - Incrementing it and setting
CURSOR_Xto 0 - If already on last line:
- If vertical scroll is enabled: increment
SCROLL_Y(wrap if needed), setSCYFflag - Else: wrap
CURSOR_Yto 0
- If vertical scroll is enabled: increment
This can get costly on an 8-bit CPU. To simplify this, the DO_NL bit in CTRL performs the entire sequence:
- Increment Y, set X = 0
- If Y ≤ max visible row: done
- Else:
- If vertical scroll enabled: increment
SCROLL_Y(wrap if needed), setSCYF - Else: set Y = 0
- If vertical scroll enabled: increment
Deferred Wrap
In default mode, reaching the last column causes an immediate wrap to the start of the next line. This can cause the "newline glitch": printing exactly 80 characters followed by a newline results in two lines being skipped instead of one.
This is reproducible easy with the following example:
- Start at cursor at (0,0)
- Print 80 characters → wraps cursor to (0,1)
- Print newline → cursor moves to (0,2)
Many terminals instead ignore the newline if the cursor already wrapped.
To support this, the WAIT_AND_WRAP bit in CTRL delays wrapping. After reaching the last column, cursor stays out of bounds in X until next action:
- If next action is
DO_NL: newline is ignored, cursor wraps to next line only - If next action is
PRINT_CHAR: wraps first, then prints character - If next action is writing to
CURSOR_Y: Y changes, X stays out of bounds
12.5 Registers Summary
| Name | Description | Base-relative address | Access |
| PRINT_CHAR | Write a character to the current cursor location | 0x00 | WO |
| CURSOR_Y | Set the cursor vertical position. | 0x01 | R/W |
| CURSOR_X | Set the cursor horizontal position. | 0x02 | R/W |
| SCROLL_Y | Vertical scroll of the text layer. | 0x03 | R/W |
| SCROLL_X | Horizontal scroll of the text layer. | 0x04 | R/W |
| CURRENT_COLOR | Current text color for printed characters | 0x05 | R/W |
| CURSOR_BLINK_TIMING | Blinking speed for the cursor. Controls how many frames cursor is visible/invisible. | 0x06 | R/W |
| CURSOR_CHARACTER | Character glyph to use for displaying the cursor | 0x07 | R/W |
| CURSOR_COLORS | Cursor colors, formatted like CURRENT_COLOR | 0x08 | R/W |
| CTRL | Text controller control register with flags for behavior and commands | 0x09 | varies |
12.6 Registers
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
On write, manually insert a newline, triggers same effect as line wrap. (R/W)