This project is based on my experience adding a display and keyboard to a retro computer, but the ideas presented here are applicable to any MCU of your choice. No soldering is required unless your specific project needs it.
Need for a Display and KeyboardIn my previous project, I combined a RetroShield Z80, an Arduino Mega 2560, a 16x2 LCD display shield, a 4x4 matrix keypad, and an 8-channel cable to come up with a remake of the 1988 Soviet DIY computer "UT-88" ("ЮТ-88") in its minimal configuration.
After playing most of the available "UT-88" titles like "Tic-Tac-Toe", "Reaction", and "Labyrinth", I wanted more than what the six-digit display and 4x4 keypad could offer. In other words, I needed a more advanced display and keyboard.
DisplayAdding a display is straightforward. Typically, a portion of RAM is designated as "screen RAM" – modifying it immediately changes on-screen characters or individual pixels, depending on whether the display mode is alphanumerical or graphical. In the "UT-88", screen RAM spans addresses from E800
h to EFFF
h, which is enough to store one-byte characters for a 64x32 alphanumerical display.
Rendering characters for a retro computer on a modern graphical screen might be too slow on the 16-MHz Arduino Mega 2560. Instead, let's delegate this task to the Adafruit Feather RP2040 with DVI Output Port, which actually has an HDMI port. We just need to connect the I2C ports of the two MCU boards with this cable.
Well, almost. The Adafruit Feather operates at 3.3V, while the Mega 2560 works at 5V. To reconcile these operating voltages, we need to insert the Adafruit QT 5V to 3V Level Shifter Breakout in between.
In my remake of the "UT-88", to render a character on screen, I send to the Adafruit Feather two bytes of the address in the screen RAM and one byte representing the character to be rendered on screen.
Scrolling and Clearing the ScreenIf you've added a display to an Arduino-based remake of a retro computer, you may notice that the display scrolls slowly. For instance, it takes my Zilog Z80/Arduino Mega 2560-based "UT-88" about a second to scroll the screen one line up. This is expected since the Z80 CPU has to move roughly 2K bytes within the screen RAM, and each byte store within the screen RAM involves transmitting three bytes over the I2C bus to the Adafruit Feather for rendering the corresponding character, as described in the previous paragraph.
One way to speed this up is to locate the address of the subroutine (in the ROM) that executes the scrolling. Once this address appears on the address bus, have the Mega 2560 write the C9
h opcode of the Z80's RET
instruction on the data bus. This will cause the Z80 to return from the subroutine immediately. You can then handle the scrolling yourself, without involving the Z80. A similar optimization can be used for clearing the screen.
In my Arduino sketch, I send a single byte to instruct the Adafruit Feather to scroll the display, while I update the screen RAM on the Mega 2560 accordingly:
...
if (addr == 0xFD19) // the address of the scrolling subroutine
{
Wire.beginTransmission(0x33); // 0x33 = I2C address I assigned to the Adafruit DVI
Wire.write(uint8_t(0x00)); // 0x00 - tell the Adafruit DVI to scroll
Wire.endTransmission();
// Move the screen RAM accordingly
const uint8_t* src = ram::screen::bytes + 0x40; // LD HL,E840
uint8_t* dst = ram::screen::bytes; // LD DE,E800
do
{
*dst = *src; // LD A,(HL) : LD (DE),A
++dst; // INC DE
++src; // INC HL
} while (src != ram::screen::bytes + 0x0700); // LD A,H : CP EF : JP NZ,FD1F
// Clear the bottom line
dst = ram::screen::bytes + 0x06C0; // LD HL,EEC0
do
{
*dst = 0x20; // LD A,20 : LD (HL),A
++dst; // INC L
} while (dst != ram::screen::bytes + 0x0700); // JP NZ,FD2E
DATA_OUT = 0xC9; // RET
}
...
A couple of notes on the code:
- The original "UT-88" had an Intel 8080 as its CPU, which explains the absence of the Z80's
LDIR
block transfer instructions in the UT-88 subroutine, as shown in the assembly code in the comments. - The ATmega2560 is a RISC-based microcontroller, which doesn't have block transfer instructions either, so on the Mega 2560 side, for performance reasons, it didn't make much sense to use the
memcpy
C/C++ function.
If your Arduino project doesn't involve providing a retro CPU with screen RAM, take the optimization further by sending render instructions to the Adafruit Feather along with relevant parameters. This is akin to debugging a display-less game server using a client machine with a display by having the server "render" to the network instead of its non-existent screen.
More RAM for Arduino Mega 2560The Arduino Mega 2560 has only 8K bytes of RAM, which can be limiting, especially for remaking a retro computer. However, with the Adafruit Feather RP2040 with DVI, you can use some of its 264K bytes of RAM for your retro computer: instead of rendering a character, the Adafruit Feather could instead store it in its RAM and send it back over the I2C bus upon request from the Arduino board.
KeyboardThere are many options when adding a keyboard, such as:
- To solder or not to solder: Without soldering, we can't add a USB host (for USB keyboards) to the Adafruit Feather, such as the Adafruit USB Host FeatherWing with MAX3421E.
- USB host location: If a USB host is used, should it be on the Arduino or Adafruit side?
- Keyboard type: If a USB host is not used, do we connect the keyboard to the I2C port, or is it a matrix-based keyboard like the 4x4 keypad (or something else)?
- Touch screen: Are we ok with a touch screen? Through I2C, we can connect various touch screens like the 2.8" TFT Touch Shield for Arduino with Capacitive Touch (which also needs SPI), the 3.5" Adafruit PyPortal Titano, or even the 7" Raspberry Pi Touch Display (probably along with a Raspberry Pi). We could then render a keyboard on the screen and interpret screen touches as key presses.
- Size: For instance, is the credit-card-sized M5Stack CardKB suitable?
For my "UT-88" project, I used a random TEC-1G Matrix TACTILE Keyboard (at the bottom of the above photo).
Its wiring was not what the software of the "UT-88" expected. So, I pressed every key on my keyboard and recorded the codes of the keys, which I printed in the Serial Monitor of the Arduino IDE. Then I looked up the wiring of the original "UT-88" keyboard and mapped my key codes to the original key codes to trick the existing software into thinking that I was using the original keyboard.
By the way, for retro computer remakes, the choice of keyboard generally has little impact on performance since the keyboard service code runs only for a small fraction of the time.
Comments
Please log in or sign up to comment.