A Pokemon Gen-1 Tamagotchi for the Waveshare ESP32-S3-Touch-AMOLED-1.75 round display — open firmware + one-click web installer. Case remixed from yoyothechicken's Pokeball.
121
stars
95
commits
C++
primary language
Sep 13, 2026
updated
A gen-1-Pokémon-inspired tamagotchi for the Waveshare ESP32-S3-Touch-AMOLED-1.75 (round 466×466 AMOLED, CO5300 driver over QSPI, CST9217 touch over I2C). Raise any of the 151, evolve it, train it and complete them all (shinies included).
Personal, non-commercial fan project. Code is MIT; the sprites are from PMD SpriteCollab (CC BY-NC, Pokémon © Nintendo/Game Freak), and the 3D case is CC BY-NC-SA. See License and Credits.
🔴 3D-printed Pokéball case + print profiles → on MakerWorld · flash it in your browser → web installer
Running on hardware. Implemented: the 151 + shinies animated from microSD, full life cycle (egg by rarity → evolution → farewell/release/runaway, each gated behind a decision dialog), bred-Pokédex with gallery, battle stats (genes + training), retention hooks (streak / bond / medals / name), biome + real-time backgrounds, ball minigame, training bag, animated bath, RTC with offline progression, battery (AXP2101) and PWR button, anti-burn-in dimming, sound (ES8311), 7 UI languages (English default), starter choice on first run, and a one-click web installer.
Pending: wild encounters / battle (designed, not implemented), 3D case, soak test. See Roadmap.
A quick reference to how the game really works (values straight from the code).
Needs: FOOD, JOY, ENE (energy), HYG (hygiene). Start 80 / 80 / 80 / 100. While awake, per minute:
| Stat | Drain/min | Notes |
|---|---|---|
| FOOD | −2 | |
| ENE | −1 | −1 extra if overweight (weight > 50 → sluggish) |
| HYG | −1 | −4 more per poop on screen (max 3 poops) |
| JOY | −1 | −2 extra if FOOD < 30, −2 extra if HYG < 30 |
| Tier | Base chance | After a proper goodbye | # species |
|---|---|---|---|
| ✨ Legendary | ~3 %* | ~10 % | 5 |
| 🔵 Rare | ~27 % | ~45 % | 27 |
| ⚪ Common | the rest | the rest | 47 |
* Legendaries only start appearing once you've registered ≥ 25 Pokémon.
After any ending, a new egg appears.
ATK / DEF / SPD = real Gen-1 base × genes + level + training (STRENGTH ← bag, SPEED ← minigame, DEFENSE ← 12 h of unbroken good care). (Battles: on the roadmap.)
pin_config.h)| Library | Author | Use |
|---|---|---|
GFX Library for Arduino (Arduino_GFX) | moononournation | CO5300 over QSPI + framebuffer in PSRAM |
| SensorLib | Lewis He | CST9217 touch + PCF85063 RTC |
| XPowersLib | Lewis He | AXP2101 PMU (battery, brightness, PWR button) |
| U8g2 | olikraus | CJK glyphs (unifont_t_japanese3, the only Japanese subset that also carries !?。、「」); only the font data is used, not its display driver |
| ESP_I2S (bundled in the ESP32 core) | Espressif | I2S to the ES8311 codec |
16M Flash (3MB APP/9MB FATFS)) ·
USB CDC On Boot EnabledFQBN="esp32:esp32:esp32s3:CDCOnBoot=cdc,FlashSize=16M,PSRAM=opi,PartitionScheme=app3M_fat9M_16MB"
arduino-cli compile --fqbn "$FQBN" .
arduino-cli upload -p /dev/cu.usbmodemXXXX --fqbn "$FQBN" .
web/index.html flashes the firmware (ESP Web Tools) and pushes the sprites to
the SD over Web Serial, no Arduino needed. Serve it over HTTPS or localhost
(secure context) and open it in Chrome/Edge. See web/README.md.
All sprites come from PMD SpriteCollab (CC BY-NC). You can regenerate the whole set and load it onto your board with the pipeline below — the firmware accepts files over USB (PUT protocol with per-block ACK), so you don't have to remove the card (it formats the SD to FAT if needed).
python3 tools/pack_pmd.py # fetch + pack PMD sprites: the 151 + shiny -> tools/sdcard/mons/p[s]NNN.bin
python3 tools/make_thumbs.py # Pokédex thumbnails (from the PMD sprites) -> thumbs.bin
python3 tools/send_sd.py # send tools/sdcard/mons/* to the board's SD over USB
To make the one-click web-installer bundle instead of sending over USB:
python3 tools/pack_bundle.py # bundle tools/sdcard/mons/* into web/sprites.pak
Then load it from the web installer's "Load sprites" button (or send_sd.py
above). pack_pmd.py also takes individual dex numbers, e.g. pack_pmd.py 7 25.
(~40 MB total, all PMD. Versioned under tools/sdcard/.)
On first run you choose a starter (Bulbasaur / Charmander / Squirtle). After that you start with an egg. Tap it 3 times or wait and it hatches. From then on, care for your companion:
Four stats that decay: FOOD, JOY, ENE (energy), HYG (hygiene). If one bottoms out it counts as a slip-up.
Buttons (bottom arc, icons):
Touch gestures:
Physical PWR button: short = screen on/off · long (4 s) = full power-off (the RTC stays alive, so time passes even while it's off).
The three life-cycle endings and evolution don't happen on their own — when the conditions are met a button appears and you tap it (so you're present to witness it), each opening a two-option dialog:
tools/pack_pmd.py packs
actions (Idle, Walk L/R, Sleep, Eat, Hurt, Attack, Pose, Nod, DeepBreath) into
the multi-action TPK2 format (/mons/pNNN.bin). The engine in TamaPoke.ino
makes the creature wander, gesture, curl up to sleep, chew and wince. Anchored by
the feet (lowest content row), not the canvas. The Pokédex thumbnails
(thumbs.bin, TPTH) are derived from these by tools/make_thumbs.py.tools/sprites.py): 9 primitive-drawn sprites as a
no-SD fallback + the UI icons. Generates species.h. Preview in
tools/sheet.png, emit with python3 tools/sprites.py emit.sdmon.h/.cpp loads the PMD sprites into PSRAM (PmdMon for TPK2) plus the
thumbnails (SdThumbs). SdMon (TPK1) remains as a dormant legacy fallback only.
tools/dex_data.py is the single source: name, slug, type (accent colour +
background biome), evolution line with gen-1 levels, rarities and starters.
tools/dex_stats.py has the real base stats (from PokéAPI). tools/gen_names.py
pulls the official localized names from PokéAPI into tools/dex_names.py
(only French and German differ in gen 1; Spanish, Italian and Portuguese use the
English ones). gen_dex.py emits dex.h (the DEX_TBL[152] table plus the
per-language name tables and the dexName() accessor). The pet's identity is its
Pokédex number (persisted in NVS).
Each creature has ATK/DEF/SPD = real gen-1 base × genes (90–110 %, rolled at hatch) + level + training:
Shown on the Battle page of the stat card. The (hidden) weight goes up with candy and burns off with training.
High streak and bond improve the egg roll (rarity and shiny): caring well always pays off.
The life cycle lasts 3 days of play. Three endings (all leave a new egg): farewell (final form + 3 days), release (long press), runaway (all 4 bars at zero for 1 h). Each bred species is recorded in the bred Pokédex (normal and shiny separately).
The egg rolls rarity over the ~79 base forms (47 common / 27 rare / 5 legendary), biased towards the lines you're missing (all 151 are completable), blessed by a farewell and punished by a runaway. Legendaries only with 25+ registered. Shiny 1/48 (better with streak/bond/farewell).
Languages: the UI ships in 7 languages — English (default), Spanish, French, German, Italian, Portuguese and Japanese — switchable from the settings screen (swipe down). Pokémon names are localized too: French, German and Japanese show the official names (Bulbizarre, Bisasam, フシギダネ...). Spanish, Italian and Portuguese use the English ones, which is what those regions officially use for gen 1.
The idle screen paints the sky from the RTC's real time (dawn / day / dusk / night with moon and stars) and the ground from the type's biome (meadow, beach, forest, volcano, mountain, snow). Sleeping forces night.
TamaPoke.ino — init, game loop, render of every screen, gestures, serial console, audiopet.h / pet.cpp — pet state and logic (stats, evolution, life cycle, streak/bond/medals, NVS)sdmon.h / sdmon.cpp — TPK1 (animated) and TPK2 (PMD) sprites + thumbnails, and file reception over USB (PUT/LS)rtcbat.h / rtcbat.cpp — PCF85063 RTC + AXP2101 PMU (battery, brightness, PWR button)audio.h / audio.cpp — ES8311 + I2S + Game-Boy-style tone synth (non-blocking task)i18n.h / i18n.cpp — the 7-language string tablesdex.h — GENERATED (gen_dex.py): the 151 tablespecies.h — GENERATED (sprites.py): fallback sprites, UI icons, colourspin_config.h — the board's official pinstools/ — pipeline: dex_data.py (data), dex_stats.py, dex_names.py +
gen_names.py (localized names), gen_dex.py,
sprites.py (workshop), pack_pmd.py / make_thumbs.py
(packers), pack_bundle.py (web bundle), send_sd.py (SD upload), touch_log.pytools/sdcard/mons/ — the generated .bin files (animated, shiny, PMD, thumbnails)web/ — the browser installer (ESP Web Tools + Web Serial sprite loader)STATS (full state) · SPEC <dex> (change species) · LVL <n> · HATCH ·
SHINY · NICK <x> · BYE / RUN (farewell / runaway) · ABANDON (force the
runaway-ready state) · WIPE (factory reset → new game) · BEEP (audio test) ·
REG (Pokédex) · EGGS (simulate 20 eggs) · GAL (gallery) · CAREDAY ·
TIME <epoch> / RTCSET <epoch> · HEALTH (uptime + heap for the soak test) ·
LS / PUT (SD files).
To test fast: lower PET_TICK_MS, MINUTES_PER_LEVEL and FAREWELL_AGE_MIN in pet.h.
HEALTH command/heartbeat).(Done: 3D-printed case published on MakerWorld; repo public with the browser installer + one-click sprite bundle.)
All sprites: PMD SpriteCollab
(community, CC BY-NC). Base stats: PokéAPI. Pokémon is a ™ of
Nintendo / Game Freak / The Pokémon Company. Non-commercial, personal-use project.
Full list in CREDITS.md. Version history in
CHANGELOG.md — most of the fixes there came from people
who built one and reported what they found.
This is an unofficial fan project, not affiliated with or endorsed by Nintendo.
95 commits
C++
59.1%
Python
23.9%
C
13.3%
HTML
3.0%
A Pokemon Gen-1 Tamagotchi for the Waveshare ESP32-S3-Touch-AMOLED-1.75 round display — open firmware + one-click web installer. Case remixed from yoyothechicken's Pokeball.
121
stars
95
commits
C++
primary language
Sep 13, 2026
updated
A gen-1-Pokémon-inspired tamagotchi for the Waveshare ESP32-S3-Touch-AMOLED-1.75 (round 466×466 AMOLED, CO5300 driver over QSPI, CST9217 touch over I2C). Raise any of the 151, evolve it, train it and complete them all (shinies included).
Personal, non-commercial fan project. Code is MIT; the sprites are from PMD SpriteCollab (CC BY-NC, Pokémon © Nintendo/Game Freak), and the 3D case is CC BY-NC-SA. See License and Credits.
🔴 3D-printed Pokéball case + print profiles → on MakerWorld · flash it in your browser → web installer
Running on hardware. Implemented: the 151 + shinies animated from microSD, full life cycle (egg by rarity → evolution → farewell/release/runaway, each gated behind a decision dialog), bred-Pokédex with gallery, battle stats (genes + training), retention hooks (streak / bond / medals / name), biome + real-time backgrounds, ball minigame, training bag, animated bath, RTC with offline progression, battery (AXP2101) and PWR button, anti-burn-in dimming, sound (ES8311), 7 UI languages (English default), starter choice on first run, and a one-click web installer.
Pending: wild encounters / battle (designed, not implemented), 3D case, soak test. See Roadmap.
A quick reference to how the game really works (values straight from the code).
Needs: FOOD, JOY, ENE (energy), HYG (hygiene). Start 80 / 80 / 80 / 100. While awake, per minute:
| Stat | Drain/min | Notes |
|---|---|---|
| FOOD | −2 | |
| ENE | −1 | −1 extra if overweight (weight > 50 → sluggish) |
| HYG | −1 | −4 more per poop on screen (max 3 poops) |
| JOY | −1 | −2 extra if FOOD < 30, −2 extra if HYG < 30 |
| Tier | Base chance | After a proper goodbye | # species |
|---|---|---|---|
| ✨ Legendary | ~3 %* | ~10 % | 5 |
| 🔵 Rare | ~27 % | ~45 % | 27 |
| ⚪ Common | the rest | the rest | 47 |
* Legendaries only start appearing once you've registered ≥ 25 Pokémon.
After any ending, a new egg appears.
ATK / DEF / SPD = real Gen-1 base × genes + level + training (STRENGTH ← bag, SPEED ← minigame, DEFENSE ← 12 h of unbroken good care). (Battles: on the roadmap.)
pin_config.h)| Library | Author | Use |
|---|---|---|
GFX Library for Arduino (Arduino_GFX) | moononournation | CO5300 over QSPI + framebuffer in PSRAM |
| SensorLib | Lewis He | CST9217 touch + PCF85063 RTC |
| XPowersLib | Lewis He | AXP2101 PMU (battery, brightness, PWR button) |
| U8g2 | olikraus | CJK glyphs (unifont_t_japanese3, the only Japanese subset that also carries !?。、「」); only the font data is used, not its display driver |
| ESP_I2S (bundled in the ESP32 core) | Espressif | I2S to the ES8311 codec |
16M Flash (3MB APP/9MB FATFS)) ·
USB CDC On Boot EnabledFQBN="esp32:esp32:esp32s3:CDCOnBoot=cdc,FlashSize=16M,PSRAM=opi,PartitionScheme=app3M_fat9M_16MB"
arduino-cli compile --fqbn "$FQBN" .
arduino-cli upload -p /dev/cu.usbmodemXXXX --fqbn "$FQBN" .
web/index.html flashes the firmware (ESP Web Tools) and pushes the sprites to
the SD over Web Serial, no Arduino needed. Serve it over HTTPS or localhost
(secure context) and open it in Chrome/Edge. See web/README.md.
All sprites come from PMD SpriteCollab (CC BY-NC). You can regenerate the whole set and load it onto your board with the pipeline below — the firmware accepts files over USB (PUT protocol with per-block ACK), so you don't have to remove the card (it formats the SD to FAT if needed).
python3 tools/pack_pmd.py # fetch + pack PMD sprites: the 151 + shiny -> tools/sdcard/mons/p[s]NNN.bin
python3 tools/make_thumbs.py # Pokédex thumbnails (from the PMD sprites) -> thumbs.bin
python3 tools/send_sd.py # send tools/sdcard/mons/* to the board's SD over USB
To make the one-click web-installer bundle instead of sending over USB:
python3 tools/pack_bundle.py # bundle tools/sdcard/mons/* into web/sprites.pak
Then load it from the web installer's "Load sprites" button (or send_sd.py
above). pack_pmd.py also takes individual dex numbers, e.g. pack_pmd.py 7 25.
(~40 MB total, all PMD. Versioned under tools/sdcard/.)
On first run you choose a starter (Bulbasaur / Charmander / Squirtle). After that you start with an egg. Tap it 3 times or wait and it hatches. From then on, care for your companion:
Four stats that decay: FOOD, JOY, ENE (energy), HYG (hygiene). If one bottoms out it counts as a slip-up.
Buttons (bottom arc, icons):
Touch gestures:
Physical PWR button: short = screen on/off · long (4 s) = full power-off (the RTC stays alive, so time passes even while it's off).
The three life-cycle endings and evolution don't happen on their own — when the conditions are met a button appears and you tap it (so you're present to witness it), each opening a two-option dialog:
tools/pack_pmd.py packs
actions (Idle, Walk L/R, Sleep, Eat, Hurt, Attack, Pose, Nod, DeepBreath) into
the multi-action TPK2 format (/mons/pNNN.bin). The engine in TamaPoke.ino
makes the creature wander, gesture, curl up to sleep, chew and wince. Anchored by
the feet (lowest content row), not the canvas. The Pokédex thumbnails
(thumbs.bin, TPTH) are derived from these by tools/make_thumbs.py.tools/sprites.py): 9 primitive-drawn sprites as a
no-SD fallback + the UI icons. Generates species.h. Preview in
tools/sheet.png, emit with python3 tools/sprites.py emit.sdmon.h/.cpp loads the PMD sprites into PSRAM (PmdMon for TPK2) plus the
thumbnails (SdThumbs). SdMon (TPK1) remains as a dormant legacy fallback only.
tools/dex_data.py is the single source: name, slug, type (accent colour +
background biome), evolution line with gen-1 levels, rarities and starters.
tools/dex_stats.py has the real base stats (from PokéAPI). tools/gen_names.py
pulls the official localized names from PokéAPI into tools/dex_names.py
(only French and German differ in gen 1; Spanish, Italian and Portuguese use the
English ones). gen_dex.py emits dex.h (the DEX_TBL[152] table plus the
per-language name tables and the dexName() accessor). The pet's identity is its
Pokédex number (persisted in NVS).
Each creature has ATK/DEF/SPD = real gen-1 base × genes (90–110 %, rolled at hatch) + level + training:
Shown on the Battle page of the stat card. The (hidden) weight goes up with candy and burns off with training.
High streak and bond improve the egg roll (rarity and shiny): caring well always pays off.
The life cycle lasts 3 days of play. Three endings (all leave a new egg): farewell (final form + 3 days), release (long press), runaway (all 4 bars at zero for 1 h). Each bred species is recorded in the bred Pokédex (normal and shiny separately).
The egg rolls rarity over the ~79 base forms (47 common / 27 rare / 5 legendary), biased towards the lines you're missing (all 151 are completable), blessed by a farewell and punished by a runaway. Legendaries only with 25+ registered. Shiny 1/48 (better with streak/bond/farewell).
Languages: the UI ships in 7 languages — English (default), Spanish, French, German, Italian, Portuguese and Japanese — switchable from the settings screen (swipe down). Pokémon names are localized too: French, German and Japanese show the official names (Bulbizarre, Bisasam, フシギダネ...). Spanish, Italian and Portuguese use the English ones, which is what those regions officially use for gen 1.
The idle screen paints the sky from the RTC's real time (dawn / day / dusk / night with moon and stars) and the ground from the type's biome (meadow, beach, forest, volcano, mountain, snow). Sleeping forces night.
TamaPoke.ino — init, game loop, render of every screen, gestures, serial console, audiopet.h / pet.cpp — pet state and logic (stats, evolution, life cycle, streak/bond/medals, NVS)sdmon.h / sdmon.cpp — TPK1 (animated) and TPK2 (PMD) sprites + thumbnails, and file reception over USB (PUT/LS)rtcbat.h / rtcbat.cpp — PCF85063 RTC + AXP2101 PMU (battery, brightness, PWR button)audio.h / audio.cpp — ES8311 + I2S + Game-Boy-style tone synth (non-blocking task)i18n.h / i18n.cpp — the 7-language string tablesdex.h — GENERATED (gen_dex.py): the 151 tablespecies.h — GENERATED (sprites.py): fallback sprites, UI icons, colourspin_config.h — the board's official pinstools/ — pipeline: dex_data.py (data), dex_stats.py, dex_names.py +
gen_names.py (localized names), gen_dex.py,
sprites.py (workshop), pack_pmd.py / make_thumbs.py
(packers), pack_bundle.py (web bundle), send_sd.py (SD upload), touch_log.pytools/sdcard/mons/ — the generated .bin files (animated, shiny, PMD, thumbnails)web/ — the browser installer (ESP Web Tools + Web Serial sprite loader)STATS (full state) · SPEC <dex> (change species) · LVL <n> · HATCH ·
SHINY · NICK <x> · BYE / RUN (farewell / runaway) · ABANDON (force the
runaway-ready state) · WIPE (factory reset → new game) · BEEP (audio test) ·
REG (Pokédex) · EGGS (simulate 20 eggs) · GAL (gallery) · CAREDAY ·
TIME <epoch> / RTCSET <epoch> · HEALTH (uptime + heap for the soak test) ·
LS / PUT (SD files).
To test fast: lower PET_TICK_MS, MINUTES_PER_LEVEL and FAREWELL_AGE_MIN in pet.h.
HEALTH command/heartbeat).(Done: 3D-printed case published on MakerWorld; repo public with the browser installer + one-click sprite bundle.)
All sprites: PMD SpriteCollab
(community, CC BY-NC). Base stats: PokéAPI. Pokémon is a ™ of
Nintendo / Game Freak / The Pokémon Company. Non-commercial, personal-use project.
Full list in CREDITS.md. Version history in
CHANGELOG.md — most of the fixes there came from people
who built one and reported what they found.
This is an unofficial fan project, not affiliated with or endorsed by Nintendo.
95 commits
C++
59.1%
Python
23.9%
C
13.3%
HTML
3.0%