Vor über 40 Jahren war der Sinclair ZX81 ein Meilenstein. Dieser Homecomputer weckte Begeisterung für Computertechnik. Mit diesem Retro-Projekt [1] wurde der ZX81 als Emulation (picozx81) auf modernen Komponenten wiederbelebt. Das Gehäuse ist 3D-gedruckt, die Folientastatur originalgetreu. Ziel war es, Nostalgie zu bewahren und gleichzeitig moderne Vorteile zu nutzen. Dazu gehören ein HDMI-Anschluss, schnelles Laden und Speichern auf SD-Karte sowie Joystick-Unterstützung. Nachfolgend wird der Weg zum funktionierenden ZX81-Emulator beschrieben.
Projektüberblick
Der ZX81 wurde möglichst authentisch nachgebaut, jedoch mit moderner Technik:
- Gehäuse: 3D-gedruckt, mit originalen Abmessungen des ZX81.
- Tastatur: Originalgetreue Folientastatur, trotz schwierigem Tippgefühl unverzichtbar für die Nostalgie.
- Emulation: picozx81-Emulator auf einem Raspberry Pi Pico.
- Hardware: Olimex RP2040-PICO-PC als Motherboard, Arduino Pro Micro für Tastatursteuerung, weitere Kleinteile.
- Zusatzfunktionen: Joystick-Port, Funktionstasten für Emulator-Steuerung, USB-C-Stromversorgung.
Bauteile und Vorbereitung
Für das Projekt sind einige Bauteile notwendig. Ein 3D-Drucker wird vorausgesetzt, um das Gehäuse zu drucken (ca. 6 € Druckkosten). Dann benötigen Sie eine Folientastatur (ca. 27 €) und passende Steckleisten (ca. 7 €). Der Raspberry Pi Pico 2 (ca. 6 €) wird auf ein Motherboard (ca. 12 €) gesteckt, das alle notwendigen Anschlüsse wie HDMI-Monitor und USB-Tastatur bereitstellt. Damit die Anschlüsse der Folientastatur als USB-Tastatur verwendet werden können, wird ein Arduino Pro Micro (ca. 6 €) mit passendem Sketch programmiert. Mit einer kleinen Lochrasterplatine und den notwendigen elektrischen Bauteilen (Kabel, Buchsen, Taster, LEDs, Dioden) (ca. 15 €) kann die Schaltung zum Arduino Pro Micro einfach aufgebaut werden. Damit ergeben sich Gesamtkosten von ca. 79 € für das Projekt.
Hier die Auflistung der notwendigen Komponenten:
- 3D-gedrucktes Gehäuse [2] und Folientastatur [3] mit Anschlussleisten [4].
- Raspberry Pi Pico [5] mit picozx81-Emulator [6].
- Arduino Pro Micro für Tastatursteuerung (fungiert als USB-Tastatur) [7].
- Olimex RP2040-PICO-PC als Basisplatine [8].
- Lochrasterplatine, Dioden, Kabel, Pfostenstecker und USB-C-auf-Micro-USB-Kabel [9].
- 9-poliger Sub-D-Stecker mit Flachbandkabel für Joystick-Anschluss (via UEXT1-Port) [10].
Im GitHub-Repository des „picozx81-Emulators“ können Sie die passende UF2-Datei (picozx81_olimexpc_rp2350.uf2 oder picozx81_olimexpc_hdmi_sound_rp2350.uf2) für das Raspberry Pi Pico 2 Board herunterladen. Alle Infos zur Installation finden Sie ebenfalls dort. Nach der Installation stecken Sie den Pico auf den Olimex RP2040-PICO-PC auf. Nun lässt sich ein erster Start mittels angeschlossenem HDMI-Monitor und normaler USB-Tastatur durchführen.
Der Arduino Pro Micro kann bereits vorab über die Arduino IDE [13] programmiert werden. Das notwendige Sketch wird weiter unten beschrieben. Einmal programmiert, verhält sich der Arduino Pro Micro wie eine USB-Tastatur, wenn die Tasten der Folientastatur gedrückt werden.
Aufbau der Tastaturschaltung
Die Tastatur ist das Herzstück des Projekts. Eine originale Folientastatur wurde gewählt, um Nostalgie zu bewahren, trotz ihres berüchtigten Tippgefühls. Aber es gibt auch alternative Tastaturen [12], wenn etwas mehr Komfort gewünscht wird. Die Schaltung basiert auf einem YouTube-Tutorial [11].
Schritte:
- Löten der Platine: Auf die Lochrasterplatine wurden Arduino Pro Micro, acht Dioden und zwei Pfostenstecker für die Folientastatur gelötet. Verbindungen entstanden mit WireWrap-Draht für präzise Lötstellen.
- Montage: Die Platine wurde mit Heißkleber am Gehäusedeckel befestigt.
- Anschluss: Ein USB-C-auf-Micro-USB-Kabel verbindet Arduino und Raspberry Pi Pico.
Herausforderung: Die Leitungen für Tastaturreihen und -spalten waren vertauscht. Daher wurde der Arduino-Sketch angepasst.
Alternative Tastatur: Die ZX8-KDLX-Tastatur [12] mit SMD-Mikrotastern verbessert das Tippgefühl. Dennoch wurde das Original für maximale Authentizität bevorzugt.
Joystick-Integration
Ein weiteres Highlight ist der Joystick-Port. Dieser ist über den UEXT1-Anschluss des RP2040-PICO-PC angeschlossen. Ein 9-poliger Sub-D-Stecker (inklusive Flachbandkabel) [10] ermöglicht den Anschluss klassischer Atari- oder Competition-Pro-Joysticks.
Hier die Zuordnung der Verbindungen: Joystick-Pin-Nummer -> UEXT1-Pin-Nummer
- 1 → 3
- 2 → 4
- 3 → 6
- 4 → 5
- 6 → 10
- 8 → 2
Um den Joystick zu aktivieren, setzen Sie den Parameter „NinePinJoystick“ im Abschnitt [default] der Datei config.ini im Stammverzeichnis auf „On“.
Umsetzung:
- Die sechs Leitungen des Flachbandkabels wurden gemäß UEXT1-Pinbelegung angeschlossen und verlötet.
- Der Sub-D-Stecker wurde ins Gehäuse geklebt.
Hinweis: Ein günstiger Atari-Joystick wird noch gesucht. Vorschläge für Bezugsquellen sind willkommen.
Funktionstasten für den Emulator
Der picozx81-Emulator bietet praktische Funktionen, die über die Tasten F1–F9 und Escape aufgerufen werden:
- F1: Zurücksetzen
- F2: Laden
- F3: Emulator-Konfiguration anzeigen
- F4: Pause
- F5: Tastatur-Overlay anzeigen
- F6: Ändern
- F7: Neustart
- F8: Reboot
- F9: Schnappschuss speichern
- ESC: Menü verlassen
Da die ZX81 Folientastatur keine Funktionstasten hat, wurde ein seitlicher Taster eingebaut. Wird dieser gedrückt und gleichzeitig eine Zifferntaste (1–9) betätigt, werden die Funktionstasten F1–F9 ausgelöst. Mit der Zifferntaste (0) wird die Taste Escape simuliert. Der angepasste Arduino-Sketch ist weiter unten einzusehen.
Abschluss und Ausblick
Nach über 40 Jahren den ZX81 wieder in Händen zu halten, kleine Basic-Programme zu schreiben, alte Spiele zu zocken ist ein tolle Sache – vor allem wenn man die Vorteile der Emulation genießen kann, wie Verwendung moderner Monitore, Load und Save auf SD-Karte ohne Wartezeit, 16 kB RAM Erweiterung, Speichern der ZX81 Games auf SD Karte, Anlegen von SnapShots via Funktionstaste, Joystick-Port, Stromversorgung via USB-C Kabel.
Was ich in den Jahren verdrängt hatte, ist die Tatsache wie langsam die Abarbeitung der Basic-Programme ist. Wenn man eine Variable hochzählt und anzeigen lässt, geht das gefühlt im Sekundentakt. Will man „flüssige“ Spiele damit programmieren muss man zwangsläufig in die Z80 Assembler Programmierung einsteigen.
Vielleicht spüren einige Retro-Begeisterte Leser nun ebenfalls den Wunsch, den ZX81 wieder zum Leben zu erwecken – wenn ja, ist dies die passende Anleitung dazu.
Dieser Ansatz wäre auch für den ZX Spectrum [14] denkbar, und möglicherweise gibt es eine Emulation des C64 für den Pico.
Übrigens habe ich mir zu Weihnachten den „The Spectrum“ [15] gegönnt. Vielleicht ein Thema, über das im LOAD-Magazin berichtet werden könnte…
Arduino-Sketch für die Tastatur
Die aktualisierte Version des Sketches [16] für den Arduino Pro Micro wurde an die Tastaturverkabelung und Funktionstasten angepasst:
/*
Arduino Pro Micro based USB keyboard driver
3D print your own ZX81 and turn it into a USB keyboard for emulation
ZX81 Version
04.04.2025 Manfred Becker v1.0: Adjustment rows and columns
07.04.2025 Manfred Becker v1.1: Adjustment additional button
*/
#include <Arduino.h>
#include <Keyboard.h>
// global variables
#define SERIAL_BAUDRATE 9600
#define NUM_ROWS 8
#define NUM_COLUMNS 5
#define DEBOUNCE_SCANS 5
// 8 row drivers - outputs
const uint8_t row1 = 2;
const uint8_t row2 = 3;
const uint8_t row3 = 4;
const uint8_t row4 = 5;
const uint8_t row5 = 6;
const uint8_t row6 = 7;
const uint8_t row7 = 8;
const uint8_t row8 = 9;
uint8_t rowPins[NUM_ROWS] = {row1, row2, row3, row4, row5, row6, row7, row8};
// 5 column inputs - pulled high
const uint8_t column1 = 18;
const uint8_t column2 = 15;
const uint8_t column3 = 14;
const uint8_t column4 = 16;
const uint8_t column5 = 10;
uint8_t columnPins[NUM_COLUMNS] = {column1, column2, column3, column4, column5};
const uint8_t resetRow = row7;
const uint8_t resetColumn = column1;
char keyMap[NUM_ROWS][NUM_COLUMNS] = {
{'1','2','3','4','5'},
{'q','w','e','r','t'},
{'0','9','8','7','6'},
{'a','s','d','f','g'},
{'p','o','i','u','y'},
{KEY_LEFT_SHIFT,'z','x','c','v'},
{KEY_RETURN,'l','k','j','h'},
{' ','.','m','n','b'},
};
const uint8_t shiftKeyRow = 5;
const uint8_t shiftKeyColumn = 0;
char keyMapShifted[NUM_ROWS][NUM_COLUMNS] = {
{'\0','\0','\0','\0','\0'},
{'\0','\0','\0','\0','\0'},
{'\0','\0','\0','\0','\0'},
{'\0','\0','\0','\0','\0'},
{'\0','\0','\0','\0','\0'},
{'\0','\0','\0','\0','\0'},
{'\0','\0','\0','\0','\0'},
{'\0',',','\0','\0','\0'},
};
// Additional button for F1-F10
const uint8_t functionKeyPin = A1; // Pin 19 auf Pro Micro
// Function key mapping for digits 0-9
const uint8_t functionKeys[10] = {
KEY_ESC, KEY_F1, KEY_F2, KEY_F3, KEY_F4,
KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9
};
// class definitions
class KeyboardKey {
public:
int row;
int column;
int lastState;
int debounceCount;
char keyCode;
char keyCodeShifted;
uint8_t lastFunctionKey; // Neue Variable für Funktionstasten
// default constructor
KeyboardKey() {
lastFunctionKey = 0; // Initialisiere mit 0 (keine Taste)
}
// init constructor;
KeyboardKey(int rowNum, int colNum){
// log matrix position
row = rowNum;
column = colNum;
// get keycode from keyMap array
keyCode = keyMap[row][column];
keyCodeShifted = keyMapShifted[row][column];
// initialise state and debounce
lastState = 0;
debounceCount = 0;
lastFunctionKey = 0; // Initialisiere mit 0
}
void updateKey(KeyboardKey shiftKey){
// row driver set by matrix handler
// scan key
int thisState = digitalRead(columnPins[column]);
int shiftKeyState;
if(thisState != lastState) {
// state changing
debounceCount ++;
// is debounce finished
if (debounceCount >= DEBOUNCE_SCANS){
// key state has changed
lastState = thisState;
debounceCount = 0;
// high = not pressed, low = pressed
if(thisState == 1){
//key released
releaseKeys();
//Serial.println(keyCode);
//delay(10);
}
else {
// key pressed
// Prüfe den Zustand der Zusatztaste
int functionKeyState = digitalRead(functionKeyPin);
// decide which key to pree based on shift key state
shiftKeyState = shiftKey.lastState;
if (functionKeyState == LOW) { // Additional key pressed
// Check whether it is a numeric key (0-9)
if (keyCode >= '0' && keyCode <= '9') {
uint8_t digit = keyCode - '0'; // Convert char to index (0-9)
lastFunctionKey = functionKeys[digit]; // Save the sent key
Keyboard.press(lastFunctionKey); // Send F1-F9 or ESC
return; // Exit the method to skip normal processing
}
}
if((shiftKeyState == 1) || (keyCodeShifted == '\0')) {
// no shift
Keyboard.press(keyCode);
}
else {
// shift key pressed - press shift keycode
// release shift key
shiftKey.releaseKeys();
Keyboard.press(keyCodeShifted);
}
//Serial.println(keyCode);
//delay(10);
}
}
}
else {
// no change in state
// make sure debounce reset
debounceCount = 0;
}
}
void releaseKeys(){
Keyboard.release(keyCode);
Keyboard.release(keyCodeShifted);
if (lastFunctionKey != 0) { // If a function key has been sent...
Keyboard.release(lastFunctionKey); // release and
lastFunctionKey = 0; // reset
}
}
}; // end class KeyboardKey
// global key handler array
// initialised in setup
KeyboardKey keyHandlers[NUM_ROWS][NUM_COLUMNS];
class MatrixDriver{
public:
MatrixDriver() {}
void scanMatrix() {
int row;
int column;
for(row = 0; row < NUM_ROWS; row ++){
// trun on this row line
activateRowLine(row);
// do column keys
for(column = 0; column < NUM_COLUMNS; column ++){
keyHandlers[row][column].updateKey(keyHandlers[shiftKeyRow][shiftKeyColumn]);
}
}
}
void activateRowLine(int rowNum){
// rowNum is zero based to match arrays
int row;
for(row = 0; row < NUM_ROWS; row ++){
if(row == rowNum){
// turn on this row
digitalWrite(rowPins[row], LOW);
}
else {
// turn off this row
digitalWrite(rowPins[row], HIGH);
}
}
}
}; // end class Matrix Driver
// global matrix driver instance
MatrixDriver matrix = MatrixDriver();
void setup() {
int row;
int column;
// Init serial port and clean garbage
Serial.begin(SERIAL_BAUDRATE);
// Zusatztaste als Eingang mit Pull-up
pinMode(functionKeyPin, INPUT_PULLUP);
// reset button
pinMode(resetColumn, INPUT_PULLUP);
pinMode(resetRow, OUTPUT);
delay(10);
// Check whether the additional key is pressed at the start
if (digitalRead(functionKeyPin) == LOW) {
// Additional key pressed: Wait for ENTER for programmability
Serial.println("Additional key pressed - wait for ENTER...");
while(digitalRead(resetColumn)){
delay(100);
}
Serial.println("ENTER pressed - start keyboard");
} else {
Serial.println("No additional key pressed - start immediately");
}
// start keyboard
Keyboard.begin();
// init columns
pinMode(column1, INPUT_PULLUP);
pinMode(column2, INPUT_PULLUP);
pinMode(column3, INPUT_PULLUP);
pinMode(column4, INPUT_PULLUP);
pinMode(column5, INPUT_PULLUP);
// init rows as output and set high - inactive
pinMode(row1, OUTPUT);
digitalWrite(row1, HIGH);
pinMode(row2, OUTPUT);
digitalWrite(row2, HIGH);
pinMode(row3, OUTPUT);
digitalWrite(row3, HIGH);
pinMode(row4, OUTPUT);
digitalWrite(row4, HIGH);
pinMode(row5, OUTPUT);
digitalWrite(row5, HIGH);
pinMode(row6, OUTPUT);
digitalWrite(row6, HIGH);
pinMode(row7, OUTPUT);
digitalWrite(row7, HIGH);
pinMode(row8, OUTPUT);
digitalWrite(row8, HIGH);
// setup key array
for(row = 0; row < NUM_ROWS; row ++){
for(column = 0; column < NUM_COLUMNS; column ++){
keyHandlers[row][column] = KeyboardKey(row, column);
}
}
Serial.println("finished setup");
delay(10);
}
void loop() {
// scan key matrix every 1 ms (+ execution time)
//Serial.println("scanning");
matrix.scanMatrix();
delay(1);
}
Über den Autor
Manfred (ManiB) ist ein Retro-Computing-Enthusiast und seit April 2025 aktiv im Vereinsforum des VzEkC e.V. Er liebt es, alte Computer wie den ZX81 mit moderner Technik wiederzubeleben und hat auch weitere Projekte realisiert:
02.04.2025: ZX81 Emulator (picozx81) im 3D gedrucktem Gehäuse und Folientastatur
14.04.2025: PicoMiteHDMIUSB motherboard reference design
29.04.2025: PicoMite VGA/PS2 Reference Design
20.05.2025: Colour Maximite 2 Generation 2 Version 2 Reference Design
25.07.2025: VersaTerm – Ein DIY-Serielles Terminal (RP2040) – Reference Design
Bildverzeichnis
- Absatz “ Projektüberblick ”, Datei “zx81-emulator-01.jpg”: Der fertig aufgebaute ZX81 Emulator
- Absatz “ Bauteile und Vorbereitung”, Datei “zx81-emulator-02.jpg”: Ein paar notwendige Elektronik Kleinteile
- Absatz “ Aufbau der Tastaturschaltung”, Datei “zx81-emulator-03.jpg”: Die USB-Tastaturschaltung angeschlossen an der Folientastatur
- Absatz “ Joystick-Integration ”, Datei “zx81-emulator-04.jpg”: Auf der Platine aufgesteckte 9 pol. Joystick –Port
Links
- Zugehöriger Thread im VzEkC e. V. Forum
https://forum.classic-computing.de/forum/index.php?thread/35482-zx81-emulator-picozx81-im-3d-gedrucktem-geh%C3%A4use-und-folientastatur/ - Sinclair ZX81 Gehäuse
https://www.printables.com/model/321667-sinclair-zx81-case?lang=de - Sinclair ZX81 Tastaturfolie
https://www.ebay.de/itm/193682625447 - Sinclair ZX81 Anschussleisten für Tastaturfolie links/rechts
https://www.ebay.de/itm/385851124606 - Raspberry Pi Pico 2, RP2350 Mikrocontroller-Board
https://www.amazon.de/dp/B0DCKH85WR?ref=ppx_yo2ov_dt_b_fed_asin_title - picozx81-Emulator, a Sinclair ZX81 and ZX80 Emulator for the Raspberry Pi Pico
https://github.com/ikjordan/picozx81 - Arduino Pro Micro Entwicklungsboard
https://www.amazon.de/dp/B0CXXPNQS1?ref=ppx_yo2ov_dt_b_fed_asin_title - Olimex RP2040-PICO-PC
https://www.mouser.de/ProductDetail/Olimex-Ltd/RP2040-PICO-PC?qs=By6Nw2ByBD0sUAtT4YjHxw%3D%3D - USB-C-auf-Micro-USB-Kabel Adapter
https://www.amazon.de/dp/B0DJ173F5L - Adapter-Kabel – IDC 10-Pin zu D-Sub 9
https://www.amazon.de/DIGITUS-Seriell-Port-Slotblech-Adapter-Kabel-Flachband-Kabel/dp/B006DYQNIK/ref=sr_1_28 - YouTube-Tutorial zum ZX81 USB-Tastaturaufbau:
https://youtu.be/_qXddJCthWs - ZX8-KDLX-Tastatur für verbessertes Tippgefühl:
https://www.ginger-electronic.com/en/retro-computer/24-40-zx8-kdlx-keyboard-for-sinclair-zx81-and-zx80.html#/26-model-zx81 - Arduino IDE für die Programmierung des Arduino Pro Micro:
https://www.arduino.cc/en/software/ - 48k/128k ZX Spectrum for Raspberry Pico Pi RP2040:
https://github.com/fruit-bat/pico-zxspectrum - Retrogames: The Spectrum
https://retrogames.biz/products/thespectrum/ - Arduino-Sketch für die Tastatur, ArduinoKeyboard_ZX81.ino
https://mikrocontroller.bplaced.net/wordpress/wp-content/uploads/2025/09/ArduinoKeyboard_ZX81.zip