Arduino Nano Tamagotchi

by heychaostheory in Circuits > Arduino

1499 Views, 8 Favorites, 0 Comments

Arduino Nano Tamagotchi

IMG_4133.jpeg

This is a Tamagotchi emulator running on an Arduino Nano inside a 3D printed chassis and shell.

Tamagotchis are digital pets from the late 20th century, and people still love them today! Did you know that they were pioneers in several technological spheres?

  1. Affective computing (ie making us care about little pixels)
  2. Autonomous behaviour (AI who??)
  3. Infrared communication (in later models)
  4. Micro-hardware in toys

So let's go back to the 90s and make our own Gen 1 Tamagotchi!

Also, I added a switch to the OG design to prevent the 90s trauma of having to bury your Tamagotchi in a cemetery when it dies.

-

I've recently entered this into an Instructables contest, and there's a prize for best use of Autodesk software. I would like to nominate myself instead for worst use of Autodesk software.

-

This is a Nano-adapted version of ArduinoGotchi, based on GPL-licensed firmware by Gary Kwok, modified for a compact handheld build.

Please note: I am a total 3D-modelling noob and expect that many improvements could be made to these files. I would only recommend attempting this project if you have a passing knowledge of Arduino coding, circuitry, and 3D modelling.

-

Don't believe me about the noob thing? Watch the video...

Supplies

I built a Tamagotchi from scratch (and it nearly killed me)

Note: You can use any brand for your components, but if you don't use the same ones I did, you may need to make some minor adjustments to the 3D file or your assembly to fit them in. There isn't a lot of wiggle room in this build!


Tools

  1. Soldering iron (+ solder, flux)
  2. 3D printer
  3. 5/64" hex allen key and/or screwdriver (both recommended)
  4. Hot glue
  5. Multimeter
  6. Micro-screwdriver (size of your boost converter's potentiometer)
  7. Super glue (cyanoacrylate)
  8. Git - Getting started installing Git
  9. Arduino IDE - Download and Install
  10. Java 8 Runtime - Download and install
  11. Optional: breadboard and Dupont prototyping cables
  12. Optional: airbrush, paint brushes

Supplies

  1. 1x Arduino Nano (or clone)
  2. 1x Boost converter/charging module
  3. 1x 3.7V Lithium polymer battery
  4. 1x Micro-USB charging cable
  5. 3x 9x6x6 mm push button switches
  6. 1x 168x64 OLED display
  7. 1x M3 25 mm screw - hex head
  8. 1x M3 5 mm screw - hex head
  9. 1x Slide switch
  10. Wires (~22 AWG)
  11. Filament (PETG recommended)
  12. Optional: acrylic paint, charms, or other decorative elements
  13. Optional: filler primer, fine-grit sandpaper, enamel clear coat

The Code

Follow the code only instructions on the ArduinoGotchi page (do not follow the circuit diagram).

Replace the code on the main program (ArduinoGotchi.ino) with the following:


/*
* CassaGotchi (modified firmware)
*
* Based on ArduinoGotchi by Gary Kwok
* Original project: https://github.com/GaryZ88/ArduinoGotchi
*
* Original license:
* GNU General Public License v2 or later (GPL-2.0-or-later)
* https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Modifications by: Cassidy S. (Chaos Theory) (modifications done in 2025)
* - Adapted for Arduino Nano
* - Button system changed to INPUT_PULLUP (active LOW)
* - Sound system removed for flash optimization
* - EEPROM autosave interval adjusted
* - Display rendering optimized for U8g2 page loop
*
* This firmware is free software; you may redistribute and/or modify it
* under the terms of the GNU GPL as published by the Free Software Foundation.

* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.

*/


#include <U8g2lib.h>
#include <Wire.h>
#include <EEPROM.h>
#include "tamalib.h"
#include "hw.h"
#include "bitmaps.h"

/***** U8g2 SSD1306 Library Setting *****/
#define DISPLAY_I2C_ADDRESS 0x3C
#define SCREEN_WIDTH 128 // OLED width
#define SCREEN_HEIGHT 64 // OLED height
/****************************************/

/***** Tama Setting and Features *****/
#define TAMA_DISPLAY_FRAMERATE 3
#define ENABLE_TAMA_SOUND
#define ENABLE_AUTO_SAVE_STATUS
#define AUTO_SAVE_MINUTES 15
#define ENABLE_LOAD_STATE_FROM_EEPROM
/***************************/

/***** Display orientation *****/
#define U8G2_LAYOUT_NORMAL
/****************************************/

U8G2_SSD1306_128X64_NONAME_2_HW_I2C display(U8G2_R0);

/**** TamaLib Variables ****/
static uint16_t current_freq = 0;
static bool_t matrix_buffer[LCD_HEIGHT][LCD_WIDTH/8] = {{0}};
static bool_t icon_buffer[ICON_NUM] = {0};
static cpu_state_t cpuState;
static unsigned long lastSaveTimestamp = 0;

/**** HAL Functions ****/
static void hal_halt(void) {}

static void hal_log(log_level_t level, const char* buff, ...) {
Serial.println(buff);
}

static void hal_sleep_until(timestamp_t ts) {}

static timestamp_t hal_get_timestamp(void) {
return millis() * 1000;
}

static void hal_update_screen(void) {
displayTama();
}

static void hal_set_lcd_matrix(u8_t x, u8_t y, bool_t val) {
uint8_t mask;
if (val) {
mask = 0b10000000 >> (x % 8);
matrix_buffer[y][x/8] |= mask;
} else {
mask = 0b01111111;
for (byte i = 0; i < (x % 8); i++)
mask = (mask >> 1) | 0b10000000;
matrix_buffer[y][x/8] &= mask;
}
}

static void hal_set_lcd_icon(u8_t icon, bool_t val) {
icon_buffer[icon] = val;
}

static void hal_set_frequency(u32_t freq) {
current_freq = freq;
}

static void hal_play_frequency(bool_t en) {
// Disabled tone to save flash
}

static bool_t button4state = 0;

static int hal_handler(void) {
// Read buttons with internal pull-ups: LOW = pressed, HIGH = released
hw_set_button(BTN_LEFT, digitalRead(2) == LOW ? BTN_STATE_PRESSED : BTN_STATE_RELEASED);
hw_set_button(BTN_MIDDLE, digitalRead(3) == LOW ? BTN_STATE_PRESSED : BTN_STATE_RELEASED);
hw_set_button(BTN_RIGHT, digitalRead(4) == LOW ? BTN_STATE_PRESSED : BTN_STATE_RELEASED);
if (digitalRead(5) == LOW && button4state == 0) {
saveStateToEEPROM();
Serial.println("Manual save triggered!");
button4state = 1;
}

#ifdef ENABLE_AUTO_SAVE_STATUS
// Button 4 for manual save
if (digitalRead(5) == LOW && button4state == 0) {
saveStateToEEPROM();
button4state = 1;
}
if (digitalRead(5) == HIGH)
button4state = 0;
#endif

return 0;
}

static hal_t hal = {
.halt = &hal_halt,
.log = &hal_log,
.sleep_until = &hal_sleep_until,
.get_timestamp = &hal_get_timestamp,
.update_screen = &hal_update_screen,
.set_lcd_matrix = &hal_set_lcd_matrix,
.set_lcd_icon = &hal_set_lcd_icon,
.set_frequency = &hal_set_frequency,
.play_frequency = &hal_play_frequency,
.handler = &hal_handler
};

/**** Drawing Functions ****/
void drawTriangle(uint8_t x, uint8_t y) {
display.drawLine(x+1, y+1, x+5, y+1);
display.drawLine(x+2, y+2, x+4, y+2);
display.drawLine(x+3, y+3, x+3, y+3);
}

void drawTamaRow(uint8_t tamaLCD_y, uint8_t ActualLCD_y, uint8_t thick) {
for (uint8_t i = 0; i < LCD_WIDTH; i++) {
uint8_t mask = 0b10000000 >> (i % 8);
if (matrix_buffer[tamaLCD_y][i/8] & mask)
display.drawBox(i + i + i + 16, ActualLCD_y, 2, thick);
}
}

void drawTamaSelection(uint8_t y) {
for (uint8_t i = 0; i < ICON_NUM; i++) {
if (icon_buffer[i])
drawTriangle(i*16 + 5, y);
display.drawXBMP(i*16 + 4, y+6, 16, 9, bitmaps + i*18);
}
}

void displayTama() {
display.firstPage();
do {
for (uint8_t j = 0; j < LCD_HEIGHT; j++) {
drawTamaRow(j, j+j+j, j == 5 ? 1 : 2);
}
drawTamaSelection(49);
} while (display.nextPage());
}

/**** EEPROM Functions ****/
#ifdef ENABLE_AUTO_SAVE_STATUS
void saveStateToEEPROM() {
// Initialize EEPROM if needed
if (EEPROM.read(0) != 12)
for (int i = 0; i < EEPROM.length(); i++)
EEPROM.write(i, 0);

EEPROM.update(0, 12);

cpu_get_state(&cpuState);

// Save cpuState struct byte by byte
uint8_t *statePtr = (uint8_t*)&cpuState;
for (unsigned int i = 0; i < sizeof(cpu_state_t); i++)
EEPROM.update(1 + i, statePtr[i]);

// Save memory array byte by byte
for (int i = 0; i < MEMORY_SIZE; i++)
EEPROM.update(1 + sizeof(cpu_state_t) + i, cpuState.memory[i]);
}
#endif

#ifdef ENABLE_LOAD_STATE_FROM_EEPROM
void loadStateFromEEPROM() {
cpu_get_state(&cpuState);
u4_t *memTemp = cpuState.memory;
EEPROM.get(1, cpuState);
cpu_set_state(&cpuState);
for (int i = 0; i < MEMORY_SIZE; i++)
memTemp[i] = EEPROM.read(1 + sizeof(cpu_state_t) + i);
}
#endif

void setup() {
Serial.begin(9600);

// Use internal pull-ups so buttons read LOW when pressed
pinMode(2, INPUT_PULLUP);
pinMode(3, INPUT_PULLUP);
pinMode(4, INPUT_PULLUP);
pinMode(5, INPUT_PULLUP);

display.setI2CAddress(DISPLAY_I2C_ADDRESS*2);
display.begin();

tamalib_register_hal(&hal);
tamalib_set_framerate(TAMA_DISPLAY_FRAMERATE);
tamalib_init(1000000);

#ifdef ENABLE_LOAD_STATE_FROM_EEPROM
if (EEPROM.read(0) == 12)
loadStateFromEEPROM();
#endif
}

void loop() {
tamalib_mainloop_step_by_step();

#ifdef ENABLE_AUTO_SAVE_STATUS
unsigned long now = millis();
if ((now - lastSaveTimestamp) > (AUTO_SAVE_MINUTES * 60 * 1000UL)) {
lastSaveTimestamp = now;
saveStateToEEPROM();
}
#endif
}

Breadboarding

LEFT (2).png
Screenshot 2026-05-03 at 4.03.23 PM.png

Upload the code to your Nano. If it compiles successfully, test your circuit using this wiring diagram. Press the middle button to bring your screen to life.

Power

LEFT (3).png
IMG_4171.jpg

Disconnect your Nano from your computer.

Once you've got your screen working, temporarily hook your circuit up to the battery, switch, and boost module.

Adjust the output of your boost converter to 5V.


Print and Fit-test Panels, Frame, and Switch Cover

1 (13).png

Print the frame, switch cover, and panels 1, 2, 3, and 4.

If you need to make any changes to the design, the Fusion 360 file is here.

Make sure your electronics fit in as per the diagram. The frame snaps on top of the OLED once it's on panel 1.

They should more or less friction fit, although it doesn't have to be perfect. If the components you have don't fit, you may need to adjust the 3D file or go crazy with the hot glue.

Glue Panels Together

1 (2).png

Superglue the flat backs of panels 1+2 together, and then do panels 3+4. Make sure the holes are aligned!

Solder and Thread Panel 1

1 (5).png
IMG_3736.jpeg
  1. Solder wires onto the 4 prongs of the OLED display.
  2. Solder wires onto the buttons. Make sure they're in the correct orientation!

Thread them through the holes in panels 1/2.


Solder and Thread Panels 2+3

1 (7).png
  1. Solder the LiPo battery positive lead (red) to the first prong on the slide switch.
  2. Solder the middle prong of the slide switch to BAT+ on the boost converter.
  3. Solder the LiPo battery negative lead (black) to BAT- on the boost converter.
  4. Solder the ground wires of the buttons together, forming a common ground.
  5. Solder a positive (red) lead to OUT+ on the boost converter.
  6. Solder a negative (black) lead to OUT- on the boost converter.

Thread the wires through panel 3, as depicted. You may want to use hot glue to keep your wires in place and organized.

Solder Panel 4

1 (1).png
IMG_4172.PNG
  1. Solder the positive leads (red) to Nano 5V.
  2. Solder the negative black (black) to Nano GND.
  3. Solder the left button lead (purple) to Nano D2.
  4. Solder the middle button lead (pink) to Nano D3.
  5. Solder the right button lead (green) to Nano D4.
  6. Solder the OLED SDA (yellow) to Nano A4.
  7. Solder the OLED SCL (blue) to Nano A5.

Shell

1 (3).png
1 (10).png

Print the front and back shell files.

Snap the frame over the OLED. No glue needed!

Snap the switch cover onto the switch. No glue needed!

Then, snap together the front and back shells over your inner chassis.

Screw the shell on. The long screw goes on the left (through the panels), and the short screw goes on the right.

Decorate / Paint (Optional)

Screenshot 2026-05-07 at 2.53.10 PM.png
Screenshot 2026-05-07 at 2.53.41 PM.png

Here's how I did mine, but you can decorate however you wish.

Getting an injection-moulded look from a 3D print is a labour of love. Because the weather was working against me, I had to turn my bathroom into a makeshift spray booth (don't do this. fumes and all).

With the electronics removed, I applied a base coat of filler spray primer. I wet-sanded the primer with 400-grit sandpaper, and repeated until the surface felt smooth.

I wanted a vibrant, 90s-retro aesthetic, so I chose a bold gradient:

  1. Vallejo Fluorescent Magenta
  2. Vallejo Light Yellow
  3. Vallejo Light Orange
  4. Vallejo Cold White
  5. Vallejo Royal Blue

I used an airbrush to create a smooth transition between the orange, magenta, and blue.

I then went in with a small paintbrush to add the yellow details and white stars. I also cleaned up some areas where my masking didn't hold up.

Pro tip: If you're painting a yellow logo like me, paint it white first and then layer the yellow on top.

I also added some charms: My friend got me this charm specifically for the Tamagotchi for my birthday!

Once it was all dry I sprayed it with some enamel clear coat.

Next Steps

Here are some changes you could explore:

  1. Right now, the charging port looks kind of janky. I'd love to know if anyone has an easy solution for that. I think using separate boost and charge modules or using a loose USB port that can go to the edge of the shell would work well.
  2. The front and back shells snap fit together. This is kind of iffy with printer tolerances, so I'd love to make a version where the front shell is also held on with a mechanical fastener.
  3. D5 is coded to be a manual save button. Adding that would allow you to extend the auto-save time length, saving EEPROM. I was going to do that, but ran out of space lol.

I'd love to know what changes you would make!

Firmware Credit

This build is based on ArduinoGotchi by Gary Kwok, licensed under the GNU GPL v2 or later.

Here's the base emulator library (TamaLib)


Modifications include:

- Nano adaptation

- INPUT_PULLUP button wiring

- Sound/buzzer removal

- EEPROM autosave adjustments

Original project:

https://github.com/GaryZ88/ArduinoGotchi


License:

GNU GPL v2 or later

https://www.gnu.org/licenses/old-licenses/gpl-2.0.html