Build the ¥enPocket 80 – A Retro Handheld Console from a VHS Tape!
Hi, I'm Miguel (aka @miguel_drmr), a 16-year-old indie creator from Brazil, and I’m super excited to share my DIY console:
The ¥enPocket 80 — a tiny retro-style handheld game console made from recycled VHS tapes, powered by an Arduino UNO, and super easy to build!
No 3D printer? No soldering experience? No problem. Let’s bring retro vibes to life with just a few cheap parts and some creativity.
What You’ll Need:
- 1x Arduino UNO (clone works!) The brain of the console
- 1x OLED Display 0.96" (I2C) The tiny screen
- 1x Pushbutton To control the game
- Jumper wires (male-male) For wiring everything up
- 1x Old VHS tape (used) The console shell! Reuse & recycle!
- 1x Simple box (optional) To pack or transport
Wiring the Console:
Let’s wire it up!
OLED Display:
VCC → 5V
GND → GND
SCL → SCL
SDA → SDA
Pushbutton:
One leg → Digital Pin 2
Other leg → GND
(We’ll use INPUT_PULLUP in the code)
Step 1: Prepare the Shell
Grab your VHS tape and open it up (trust me, this part is fun). Remove the reels and internal gears carefully.
Cut a hole where the screen will go — usually in the middle window of the tape.
Add a small hole for the button, and maybe one more for the USB cable if needed.
Step 2: Connect Everything
Use jumper wires to hook up the display and the button to your Arduino.
You can test the display using any basic I2C screen sketch.
Once it’s working, fix the screen behind the VHS window using glue or tape. Do the same for the button.
Step 3: Upload the Game
Use the Arduino IDE to upload the built-in game. You can use a Dino Run clone, a reaction tester, or any 1-button game.
I’ll link my open-source version here: ¥enPocket-Github
Step 4: Close It Up
Reassemble the tape and seal it — either with the original screws or with tape.
You now have a fully working game console inside a VHS tape!
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 32
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
#define BTN_PIN 2
// Splash screen config
unsigned long splashStart = 0;
const unsigned long splashDuration = 10000; // 10 segundos
// Estados do sistema
enum State {
SPLASH,
MENU,
GAME,
GAMEOVER
};
State currentState = SPLASH;
// Dino Game config
int playerX = 10;
int playerY = 24; // chão
int playerVY = 0;
bool isJumping = false;
unsigned long lastFrameTime = 0;
const unsigned long frameInterval = 50; // 20 FPS
int obstacleX = SCREEN_WIDTH;
const int obstacleY = 24;
const int obstacleWidth = 8;
const int obstacleHeight = 8;
bool gameOverFlag = false;
int score = 0;
// Para debouncing do botão
bool lastButtonState = HIGH;
bool buttonPressed = false;
unsigned long lastDebounceTime = 0;
const unsigned long debounceDelay = 50;
void setup() {
pinMode(BTN_PIN, INPUT_PULLUP);
Wire.begin();
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
while(1);
}
display.clearDisplay();
splashStart = millis();
lastFrameTime = millis();
}
void drawSplash() {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
const char *text = "== YenPocket ==";
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
int16_t x = (SCREEN_WIDTH - w) / 2;
int16_t y = (SCREEN_HEIGHT - h) / 2;
display.setCursor(x, y);
display.println(text);
display.display();
}
void drawMenu() {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(20, 12);
display.println("Press BTN to Start");
display.display();
}
void startGame() {
playerX = 10;
playerY = 24;
playerVY = 0;
isJumping = false;
obstacleX = SCREEN_WIDTH;
score = 0;
gameOverFlag = false;
currentState = GAME;
}
void drawGame() {
display.clearDisplay();
// Chão
display.drawLine(0, 31, SCREEN_WIDTH, 31, SSD1306_WHITE);
// Player
display.fillRect(playerX, playerY - 8, 8, 8, SSD1306_WHITE);
// Obstáculo
display.fillRect(obstacleX, obstacleY - obstacleHeight, obstacleWidth, obstacleHeight, SSD1306_WHITE);
// Score
display.setTextSize(1);
display.setCursor(SCREEN_WIDTH - 40, 0);
display.print("Score: ");
display.print(score);
display.display();
}
void drawGameOver() {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(20, 10);
display.println("GAME OVER!");
display.setCursor(20, 20);
display.print("Score: ");
display.println(score);
display.setCursor(0, 30);
display.println("Press BTN to restart");
display.display();
}
// Função para debouncing e leitura única do botão
bool readButton() {
bool reading = digitalRead(BTN_PIN);
if (reading != lastButtonState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > debounceDelay) {
if (reading == LOW && !buttonPressed) {
buttonPressed = true;
lastButtonState = reading;
return true;
}
if (reading == HIGH) {
buttonPressed = false;
}
}
lastButtonState = reading;
return false;
}
void loop() {
unsigned long now = millis();
switch (currentState) {
case SPLASH:
drawSplash();
if (now - splashStart >= splashDuration) {
currentState = MENU;
}
break;
case MENU:
drawMenu();
if (readButton()) {
startGame();
}
break;
case GAME:
if (now - lastFrameTime < frameInterval) return;
lastFrameTime = now;
// Pulo
if (readButton() && !isJumping) {
isJumping = true;
playerVY = -6;
}
if (isJumping) {
playerY += playerVY;
playerVY += 1;
if (playerY >= 24) { ẞ
playerY = 24;
playerVY = 0;
isJumping = false;
}
}
obstacleX -= 3;
if (obstacleX < -obstacleWidth) {
obstacleX = SCREEN_WIDTH;
score++;
}
// Colisão
if (playerX + 8 > obstacleX && playerX < obstacleX + obstacleWidth) {
if (playerY >= obstacleY - obstacleHeight) {
gameOverFlag = true;
currentState = GAMEOVER;
}
}
drawGame();
break;
case GAMEOVER:
drawGameOver();
if (readButton()) {
startGame();
}
break;
}
}
Share Your Games:
You can share your games made for your ¥enPocket 80 on my Official Forum:
This page (¥enPocket 80 - A Handheld Arduino Console Inside Of An VHS Tape) was last updated on May 20, 2025.