ATtiny85 Mini-Arcade: Snake – Handheld OLED-Spielekonsole selbst bauen
Komponenten und Materialien
| Microchip ATtiny85 | 1 |
| Grafik-OLED 128×64 | 1 |
| SparkFun microB USB Breakout | 1 |
| Arduino Nano R3 (zum Programmieren des ATtiny85) | 1 |
Benötigte Werkzeuge und Maschinen
- Lötkolben
- 3D-Drucker
Apps und Online-Dienste
- Autodesk Fusion 360
- Arduino IDE
Über dieses Projekt
Inspiration und Vorprojekt
Dezember 2017: Ich baute eine Handheld-Arduino-Pong-Konsole mit Arduino Nano, OLED und zwei Tasten.
Damals okay, aber zu groß und klobig. Jetzt wollte ich eine winzige Konsole für Snake bauen.
Die gewählten Komponenten
Für kleine Größe kein Arduino-Dev-Board → ATtiny85.
Genug I/O für das Spiel: zwei ADC-Pins, I2C-Port, ein GPIO-Pin. Einfaches 2-Achsen-Joystick/Switch-Modul (3 Pins für Signale).
Display: DFRobots 128×64 OLED – klein, aber ausreichend Auflösung.
System verlöten
ATtiny85 auf SOP-8-auf-DIP-8-Breakout-PCB löten, auf kleines Lochrasterboard. Zwei Header: einer zum Programmieren, einer fürs Display. Joystick-Pins anlöten und verdrahten. Zum Schluss micro-USB-Breakout für Stromversorgung anschließen.
Gehäuse designen
In Fusion 360 designed. Zuerst echte Bauteile platzieren, dann Gehäuse drumherum.
Ziel: 80er-Jahre-Arcade-Optik, aber winzig. Renderings:
Spiel programmieren
Snake ist einfach zu programmieren. Maximale Schlangenlänge: 30 (RAM sparen) → nach 29 gefressenen Pixeln gewinnt der Spieler. 2D-Array speichert Koordinaten jedes Segments.
Kopf bewegt sich → vorherige Positionen kaskadieren nach unten. Neues Segment bei Fressen an zufälliger Position. Kollisionsprüfung: Iteration durch alle Segmente, Prüfung ob Kopfkoordinaten gleich. Kollision mit Wänden = Game Over.
Snake spielen
Konsole einschalten, warten bis Futter und erstes Segment geladen. Joystick steuern, Snake frisst Futter. Macht Spaß, toller Zeitvertreib im Miniformat.
Code
ATtiny85 Code (C/C++)
U8g2lib vorher installieren!
//#include <TinyWireM.h>
#include <U8g2lib.h>
U8G2_SSD1306_128X64_NONAME_1_SW_I2C u8g(U8G2_R0, /* clock=*/ 2, /* data=*/ 0, /* reset=*/ U8X8_PIN_NONE);
#define MAX_LENGTH 30
#define X 0
#define Y 1
#define JOYSTICK_X 2
#define JOYSTICK_Y 3
#define DIR_THRESH 300
uint8_t segmentPositions[MAX_LENGTH][2];
uint8_t headPosition[2] = {63, 31};
uint8_t foodPosition[2];
uint8_t tempPosition0[2];
uint8_t tempPosition1[2];
int segmentLength = 1;
void gameUpdate();
enum DIRECTIONS { RIGHT, DOWN, LEFT, UP } currentDirection;
void setup() {
//TinyWireM.begin();
u8g.begin();
u8g.setPowerSave(0);
pinMode(JOYSTICK_X, INPUT);
pinMode(JOYSTICK_Y, INPUT);
randomSeed(analogRead(0));
beginGame();
}
void loop() {
u8g.firstPage();
do {
gameUpdate();
u8g.setColorIndex(1);
} while(u8g.nextPage());
}
void beginGame(){
currentDirection = RIGHT;
spawnFood();
delay(1000);
}
bool checkCollisions(){
for(int i=1; i<segmentLength; i++){
if(segmentPositions[i][X] == headPosition[X]){
if(segmentPositions[i][Y] == headPosition[Y]){
return 1;
}
}
}
if(headPosition[X] <= 0 || headPosition[X] >= 128) return 1;
else if(headPosition[Y] <= 0 || headPosition[Y] >= 64) return 1;
return false;
}
void spawnFood(){
int randomX = random(5, 123);
int randomY = random(5, 60);
foodPosition[X] = randomX;
foodPosition[Y] = randomY;
}
void checkFoodEaten(){
if(headPosition[X] == foodPosition[X] || headPosition[Y] == foodPosition[Y]){
segmentLength += 1;
spawnFood();
}
}
void updateDirection(){
int joy_x_val = analogRead(JOYSTICK_X);
int joy_y_val = analogRead(JOYSTICK_Y);
if(joy_x_val <= DIR_THRESH) currentDirection = LEFT;
else if(joy_x_val >= 1023-DIR_THRESH) currentDirection = RIGHT;
else if(joy_y_val <= DIR_THRESH) currentDirection = UP;
else if(joy_y_val >= 1023-DIR_THRESH) currentDirection = DOWN;
}
void displaySegments(){
for(int segment=0; segment < segmentLength; segment++){
u8g.drawPixel(segmentPositions[segment][X], segmentPositions[segment][Y]);
}
u8g.drawPixel(foodPosition[X], foodPosition[Y]);
}
void gameUpdate(){
updateDirection();
tempPosition0[X] = headPosition[X];
tempPosition0[Y] = headPosition[Y];
switch(currentDirection){
case UP: headPosition[Y] -= 1; break;
case DOWN: headPosition[Y] += 1; break;
case LEFT: headPosition[X] -= 1; break;
case RIGHT: headPosition[X] += 1; break;
}
for(int segment=1; segment < segmentLength; segment+= 2){
tempPosition1[X] = segmentPositions[segment][X];
tempPosition1[Y] = segmentPositions[segment][Y];
segmentPositions[segment][X] = tempPosition0[X];
segmentPositions[segment][Y] = tempPosition0[Y];
tempPosition0[X] = segmentPositions[segment+1][X];
tempPosition0[Y] = segmentPositions[segment+1][Y];
segmentPositions[segment+1][X] = tempPosition1[X];
segmentPositions[segment+1][Y] = tempPosition1[Y];
}
displaySegments();
checkFoodEaten();
if(checkCollisions()) endGame();
if(segmentLength >= MAX_LENGTH) endGame();
delay(50);
}
void endGame(){
segmentLength = 1;
headPosition[0] = 63;
headPosition[1] = 31;
beginGame();
}
Benutzerdefinierte Teile und Gehäuse
(STL-Dateien im Originalprojekt verlinkt)
Schaltpläne

Herstellungsprozess
- TinyML Spracherkennung mit Edge Impulse auf Arduino Nano 33 BLE Sense
- Echtzeit-Gyroskop-Spiel mit Arduino Nano & MPU-6050
- Arduino Digitaler Würfel: LCD-Würfel mit Zufallszahlen (1–6)
- 37-LED-Roulette-Spiel mit Arduino Nano bauen – Europäisches Layout
- Tragbarer Entfernungsmesser – Arduino Nano DIY-Kit mit HC-SR04, 18650-Akku und 3D-gedrucktem Gehäuse
- Audio-reaktive Schreibtischlampe mit Arduino Nano bauen
- Kupfer-Galvanik-Projekt: Intelligentes System mit Arduino UNO bauen
- NeoPixel-Matrix Pong auf Arduino Nano: Neon-Pong selbst bauen
- Beeindruckende Lichtsequenzen mit Arduino und Schieberegister erstellen
- Smart Plug: 120 V Arduino-Steckdose mit Echtzeituhr