Close
0%
0%

Braino! — a 40-game learning console on a CYD

Open-source ESP32 handheld with 40 learning games, from Maths to Chess over Bluetooth. No Accounts, No Telemetry. Flashes from a browser.

Similar projects worth following
51 views
0 followers
Braino! is open-source firmware that turns a cheap ESP32 touchscreen board (the "Cheap Yellow Display" family, 2.8 to 4 inch) into a handheld learning console for young players.

https://www.youtube.com/watch?v=bESbkhq

Forty games are baked into flash: early maths, flags and geography, the periodic table, finger-traced handwriting, and full-rules Chess, Go, Backgammon, Ludo and Sea Battle.

No SD card, no accounts, no telemetry, no data collection. Bluetooth is off by default.

- Two consoles play each other with no pairing: moves ride a 31-byte BLE advertisement.
- Ludo seats four consoles and the dice never go on the air. Each console derives every roll from a shared seed.
- Every sound is synthesised, including a spoken greeting at boot. No audio files.
- No heap allocation anywhere in the firmware.

Five boards supported, each run on hardware. Printable cases included.
Flash it from a browser: https://iamankushpandit.github.io/Gume/



Braino! is firmware that turns a 2.8 to 4-inch ESP32 touchscreen board (the "Cheap Yellow Display" family) into a handheld learning console for young players. Forty games are baked into flash: early maths, flags and geography, the periodic table, finger-traced handwriting, and full-rules Chess, Go, Backgammon, Ludo and Sea Battle.

WHAT IT DOES NOT DO:


No SD card, no accounts, no telemetry, no data collection. Wi-Fi fetches the time and checks whether a newer firmware exists, and that request says nothing about the device. Bluetooth is off by default, and the device shows you the exact bytes it transmits when you turn it on.

THE PARTS HACKADAY READERS MAY ENJOY:


- Two consoles play each other with no pairing and no connection. Moves ride a non-connectable BLE advertisement that is exactly 31 bytes, so a chess move had to displace the score field rather than add to it.
- Ludo seats up to four consoles, and the dice never go on the air. Every console derives each roll from a shared seed and refuses any move that does not fit it.
- Every sound is synthesised, including a spoken "Let's play Braino!" at boot made from a hand-written phoneme table and three formant filters. There is no audio file anywhere; the whole vocabulary costs under a kilobyte.
- There is not one new, delete, malloc or free in the firmware. Every screen is a static instance, so heap fragmentation cannot build up over hours of play.
- The image is about 2.5 MB of a 3 MB app partition, roughly 30% of it flag and map artwork.
- A board is described in one profile header. A board that cannot run it fails to compile with a message naming what is missing.

BUILD ONE:


Five boards are supported and each has been run on hardware. Open the web installer in Chrome or Edge, pick your board, plug in USB and press the button. Printable cases are in the repository.

Up to five player profiles plus a Guest that saves nothing, spaced repetition on the quiz games, nine themes, and an admin PIN on settings. GPL-3.0-or-later.

BLE_BEACON_SPEC.md

BLE Spec.

markdown - 20.01 kB - 10/12/2026 at 18:09

Download

PORTING.md

Porting Guidelines.

markdown - 12.51 kB - 10/12/2026 at 18:09

Download

braino-poster-A2.pdf

Adobe Portable Document Format - 2.49 MB - 10/12/2026 at 18:07

Preview

E32R32P-Case.zip

x-zip-compressed - 411.48 kB - 10/12/2026 at 18:06

Download

E32R40T-Case.zip

x-zip-compressed - 350.29 kB - 10/12/2026 at 18:06

Download

View all 6 files

  • 1 × E32R28T-1 ESP32 touchscreen board (reference board) ESP32-32E, 2.8-inch ILI9341 320×240 TFT, XPT2046 resistive touch, 4 MB flash, no PSRAM, USB-C, onboard LiPo charger, RGB LED and speaker connector. Pick this or one of the four alternatives below.
  • 1 × Alternative: E32R32P 3.2-inch ST7789P3 panel, resistive touch on the display’s SPI bus, USB-C, battery and speaker connectors.
  • 1 × Alternative: E32R40T 4-inch ST7796 480×320 panel, resistive touch. Games are scaled up to fill it, for players who need a bigger screen.
  • 1 × Alternative: Freenove FNK0104B (also sold as LCDWIKI ES3C28P) ESP32-S3, 2.8-inch ILI9341, FT6336U capacitive touch, 16 MB flash, 8 MB PSRAM, ES8311 audio codec.
  • 1 × Alternative: ESP32-2432S028 dual-USB (inverted panel) 2.8-inch resistive board with USB-C and micro-USB. No battery sensing, so run it from USB power or a power bank.

View all 17 components

  • The 4-inch board that bricks itself on reset, and what actually recovers it

    Ankush Pandit • a day ago • 0 comments

    The 4-inch board has a failure that looks like a dead board. After the reset button, a serial tool's RTS reset, a USB power surge, or most often the hard reset at the end of an upload, the ROM loops 'flash read err, 988' every 350 milliseconds with the panel dark.

    The first guess was the GPIO12 strap, which on this board also serves as the shared MISO line. Wrong: the ROM prints boot:0x17, whose MTDI bit is clear, so 3.3 V flash was selected correctly.

    The second guess was a damaged image. Also wrong. Read back while the board was looping, the bootloader at 0x1000 was byte for byte correct, and the failing read differed from it by three bits at the same address. It is a marginal read at the moment of certain resets, not corrupted flash.

    The cause is still unknown. What recovers it without pulling the battery is a reset that comes from download mode rather than from the running ROM: ask esptool for the flash id with no reset afterwards, then ask for the MAC with no reset before and a hard reset after. Repeat the pair if it fails, never the second step alone.

    The flashing tool now asks every board for its build after an upload and runs that recovery on any whose serial shows the loop, so a flash no longer leaves a dark 4-inch board behind.

    If you own one of these and have seen the same thing, I would like to hear it.

  • A web installer that can only install the newest build has no way back

    Ankush Pandit • a day ago • 0 comments

    The installer used to offer exactly one firmware: whatever main last built. Then a release shipped a defect that made the two 2.8-inch boards untouchable, and the only thing anyone could install was the broken one.

    A page that can only install the newest build has no way back from a bad release, which is precisely the moment somebody needs one.

    So it grew a version selector. Four things about it are load-bearing.

    The bytes are copied, never rebuilt. What is served for an old version is what was published as that version, so an old version cannot quietly become a new build of old source, and a tag that no longer compiles today is still installable.

    They are copied rather than linked because of CORS. The obvious implementation points the manifest at the release asset URLs. That does not work: the asset host sends no Access-Control-Allow-Origin header at either hop, so the browser's fetch is blocked after the board is connected and the flash has already begun. Serving the bytes from the page's own origin removes the question.

    The version list belongs to the board, not the page. A board added in a later release has no earlier build, and switching to it falls back to latest rather than leaving a stale manifest armed.

    A release missing any of its four parts is skipped entirely rather than half-published, because a partial image fails after the erase has started.

  • There is no malloc in this firmware, and leaks are not the reason

    Ankush Pandit • a day ago • 0 comments

    There is not one new, delete, malloc or free in Braino. Every screen is a static instance and everything else is stack or a fixed member.

    The obvious reading is that it therefore cannot leak. True, but leaks were never what kills a device like this.

    FreeRTOS gives you a heap that can never be compacted. Many small allocations of differing sizes, made and freed over and over, chop the free space into pieces too small to satisfy a later request. Free heap looks healthy right up to the allocation that fails, hours in, in somebody else's hands. The only warning beforehand is the fragmentation percentage, which is why the System Info screen shows it.

    The usual offender is Arduino String. Every concatenation allocates, every assignment may reallocate, and a String member rewritten each frame is a long-lived block being freed and remade 27 times a second.

    The worked example is the row list every settings screen uses. It was 48 rows times two Strings, rebuilt every frame: about 96 long-lived allocations churning at frame rate. It is now flat char buffers that allocate nothing, ever. That cost 864 bytes of static RAM and saved 5.5 KB of flash.

    Trading a few hundred bytes of always-resident RAM for zero heap traffic is nearly always the right call on a device that has to survive a long afternoon without an adult rebooting it.

  • Four consoles, one Ludo table, and dice that never go on the air

    Ankush Pandit • a day ago • 0 comments

    Chess between two consoles was one thing. Ludo needs four, and it needs a die.

    The die is the hard part. If one console rolls and transmits the number, every other console has to trust it, and the first player to patch their firmware wins every game. Worse, a dropped advertisement is a roll nobody heard.

    So the die never travels. Every console derives each roll from the table's seed and the turn number, and the rules refuse any move that does not fit the roll it computed for itself. When two consoles disagree, the move is rejected rather than producing two different boards.

    That decision cascades.

    Who moves first comes from the seed too. Between two players a coin toss inside the invitation works, because the console that asks for a game must not also claim the first move. A toss cannot seat four, so the deal becomes a shuffle nobody chooses.

    A console replaces its turn only once every other console has acknowledged it. On a medium that drops things, that is what makes a bonus roll safe.

    A console that goes quiet pauses the table, and once it has been silent long enough to count as gone the host may take its seat over and play it as a computer player. That is a numbered ply in values nothing else uses, so it needed no new bytes.

    The wire never changed. Ludo spells its meaning into the same fields chess uses: a seat and a token, or a presence, or the host's start word.

  • Two consoles play chess in 31 bytes, and never connect

    Ankush Pandit • a day ago • 0 comments

    A BLE advertisement carries 31 bytes. Not 31 bytes of free space: 31 bytes total, counting every AD structure's own length and type byte. With the flags, the manufacturer block header and a name long enough to be recognisable, Braino's advertisement was already exactly 31 bytes before a chess move existed.

    So a move could not be added. It had to displace something.

    What went was the score. When two consoles are playing each other, neither cares what the other's best score in Memory is, so those four bytes became a session number, a move number, two square numbers and an ack. Thirty-two bits is enough, because a square is six bits and there are 64 of them.

    Three things follow.

    The game is a broadcast, not a connection. No pairing, no bonding, no GATT, no central and no peripheral. Both consoles advertise and both scan. Everyone in range hears every move and only the two playing act on them. Walk out of range and the game pauses rather than dropping.

    Every received move is checked for legality in the receiver's own position before it is played, so a hostile advertiser can waste a scan window but cannot corrupt a board.

    Ending a game rides the same field. A move whose source and destination are the same square is never legal, so it cannot be mistaken for a real one, and it costs nothing in a payload with nothing to spare.

    Backgammon, Sea Battle and Ludo now use the same transport. None of them knows what the others' moves mean.

  • Change Log Before Hackaday page

    Ankush Pandit • a day ago • 0 comments

    5.12.3 (15 Sep 2026)

    Wording only: the documents no longer put ages on the people using the console. Players are players.

    5.12.2 (15 Sep 2026)
    A new landing page that leads with the product on the boards people own, six stills where there were ninety-one, and "administrator" in place of "adult" throughout.

    5.12.1 (13 Sep 2026)
    A licence release. Every file now states its own licence and copyright holder, checked in CI. The console behaves exactly as 5.12.0.

    5.12.0 (13 Sep 2026)
    Two new games, Space and Roman, bring the catalogue to 40. Math gains word problems, Shapes gains three harder rounds, and going idle now ends an admin session.

    5.11.0 (12 Sep 2026)
    Go, on 9x9 and on 19x19 for the 4-inch console. Chess gets a computer opponent at two levels, and Find my Braino makes a nearby console ring. A nearby game now pauses and says so when the other console goes quiet.

    5.10.0 (11 Sep 2026)
    Ludo for up to four consoles and Backgammon for two, with dice that never go on the air. Sound arrives on the two 2.8-inch boards, mute is one tap away, and every theme's colours are measured against contrast floors.

    5.9.1 (8 Sep 2026)
    A hotfix. 5.9.0 was withdrawn because a diagnostic read in the boot log corrupted the display on two of the seven boards then offered.

    5.9.0 (8 Sep 2026)
    Two consoles can play each other: Chess and the new Sea Battle run over the Bluetooth beacon. Cursive handwriting tracing arrives, and Chess remembers an unfinished game.

    5.8.0 (7 Sep 2026)
    Piano and Chess, the first two apps that are not drills. One octave with note names, and the full rules of chess for two players sharing a device.

    5.7.0 (6 Sep 2026)
    Nine display themes and a once-a-day "newer firmware available" notice. Fixes sound cut short on two boards, a beacon crash, and a 4-inch board that could not wake from sleep.

    5.6.0 (6 Sep 2026)
    A new board, the 3.2-inch E32R32P. Name the consoles around you, poke one from the Nearby list, and install an older release from the web installer.

    5.5.1 (5 Sep 2026)
    Fixes a 5.5.0 defect that left the two 2.8-inch boards unable to read touch. The pin clash behind it is now a compile error.

    5.5.0 (5 Sep 2026)
    Screens repaint what changed instead of the whole panel, which ends the visible flashing. Sound comes to the 4-inch board from the same synthesiser.

    5.4.0 (4 Sep 2026)
    The 4-inch E32R40T is supported, with games scaled up to fill it. The console gets a spoken boot phrase and a sound vocabulary, and the BOOT key becomes Home.

    5.3.0 (2 Sep 2026)
    The Freenove FNK0104B, an ESP32-S3 with capacitive touch, brought up on real hardware. Braino makes sounds for the first time.

    5.2.0 (27 Aug 2026)
    The ESP32-2432S028 joins the installer's board picker. The build is pinned and reproducible, and the privacy statement is tightened to match the hardware.

    5.1.0 (27 Aug 2026)
    Releasing becomes a tag: CI builds and publishes every image. Adds a security policy, a code of conduct and issue templates.

    5.0.0 (26 Aug 2026)
    An admin profile behind a four-digit PIN, hold-to-unlock, and Elements, the periodic table, as game 31. A board is now described in two files, printable cases arrive, and the project gets its licence.

    4.2.0 (17 Aug 2026)
    Battery work: a clearer gauge and a charge-me warning at 15%.

    4.1.0 (15 Aug 2026)
    The console is renamed Braino!. Adds Dice, Coin Flip and Nearby, an opt-in anonymous score exchange between consoles, and moves every game onto the hardware random number generator.

    4.0.0 (15 Aug 2026)
    A web page that flashes the board from a browser. Adds Percent and GRE Words, screen sleep, and a redesigned Trace.

    3.0.0 (13 Aug 2026)
    The opt-in Bluetooth beacon, with a System Info screen that shows the exact bytes on the air. A performance pass removes flash reads and blocking delays from the frame loop.

    2.0.1 (11 Aug 2026)
    Screen brightness control and a 180-degree screen flip. Fixes a Light theme that was partly invisible.

    2.0.0 (11 Aug 2026)
    Flags, Countries and Number Line. Spaced repetition and adaptive difficulty arrive,...

    Read more »

View all 6 project logs

  • 1
    Step 1: Pick a board

    Braino runs on five off-the-shelf ESP32 touchscreen boards from the "Cheap Yellow Display" family: the 2.8-inch E32R28T-1, the 2.8-inch dual-USB ESP32-2432S028, the 2.8-inch Freenove FNK0104B (capacitive touch), the 3.2-inch E32R32P and the 4-inch E32R40T. If you are not sure, get the 4-inch one: it has the biggest screen. Nothing needs soldering or wiring. The board arrives with the screen and the ESP32 already joined.

  • 2
    Step 2: Plug it into a computer

    Use a USB-C cable that carries data. A charge-only cable looks identical and behaves like a dead board. Windows, Mac, Linux and Chromebooks all work. If the board does not show up as a serial port, install the CH340 or CP210x USB-serial driver for your operating system.

  • 3
    Step 3: Install Braino from the browser

    Open https://iamankushpandit.github.io/Gume/ in desktop Chrome, Edge or Opera and go to the Install tab. Pick the board that matches yours, then press Install. About a minute later the board reboots into Braino. Phones, tablets, Firefox and Safari cannot do this, because they have no Web Serial.

    Pick the board carefully. Two boards sold under the same name can carry different display controllers, and the wrong image gives a blank or wrongly coloured screen, not an error. Check the model printed on the back of the board against the list.

View all 8 instructions

Enjoy this project?

Share

Discussions

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates