2026-02-14 · 4 min read
WebAssembly & Embedded C++17STM32TouchGFXWebAssemblyEmscriptenSDL2

Bringing STM32 TouchGFX Chess to the Browser with WebAssembly

A high-level look at the follow-up project that used AI-assisted development to shim ST’s TouchGFX framework and FatFs SD-card persistence over SDL2 and Emscripten, making our STM32H7B3I-DK C++ chess firmware playable in the browser.

Two Projects: Original STM32 Firmware vs. Browser WebAssembly Port

In 2024, Mathias Gorm Petersen and I built team-muppet/TouchGFX-Chess—an embedded chess game written in C and C++ for the STM32H7B3I-DK microcontroller using STMicroelectronics’ TouchGFX GUI framework, FreeRTOS, a recursive Alpha-Beta Minimax AI, and RapidJSON + FatFs microSD persistence. (You can read the full engineering breakdown of the original hardware project on the STM32 TouchGFX Chess showcase page.)

Embedded microcontroller projects are notoriously hard to showcase on a web portfolio: a static screenshot or video clip proves the firmware existed, but visitors cannot actually play against the Minimax AI or test the UI unless they have the physical STM32H7B3I-DK board on their desk.

To solve that for this portfolio, I created a separate follow-up repository—Nephim/ams-chess-wasm—and used AI-assisted development to build a lightweight C++ compatibility shim and compile our original gui/ and generated/ C++ sources directly to WebAssembly with Emscripten.


High-Level Architecture of the WebAssembly Shim

The guiding constraint for the port was simple: do not rewrite the chess engine or TouchGFX view classes in JavaScript. Instead, the original C++ files (ChessGameView.cpp, Board.cpp, BoardState.cpp, SquareRenderer.cpp, PieceSelector.cpp, Snackbar.cpp, ChessAI.cpp, and GameStateSerializer.cpp) are compiled as-is against a drop-in touchgfx_shim/ layer:

HIGH-LEVEL OVERVIEW // STM32 C++ FIRMWARE TO WEBASSEMBLY UNMODIFIED GUI/ C++ SOURCES
1. ORIGINAL STM32 C++ CODE
TouchGFX-Chess (gui/ & generated/)
  • ChessGameView & Board
  • SquareRenderer & Snackbar
  • ChessAI (Alpha-Beta Minimax)
  • GameStateSerializer (RapidJSON)
TouchGFX API → fatfs.h
2. AI-ASSISTED C++ SHIM
touchgfx_shim/ (SDL2 + Emscripten)
  • Replaces proprietary Cortex-M7 TouchGFX lib
  • Maps widget tree, click events & fade animations
  • SDL_RenderSetLogicalSize(480, 272)
  • Shims WriteToSDCARD to localStorage
em++ → WebGL
3. BROWSER STAGE
Interactive Portfolio Runner
  • 1440×816 HiDPI WebGL canvas
  • Mounted inside the STM32H7B3I-DK bezel
  • 60Hz requestAnimationFrame tick loop
  • Physical B1 NRST hardware reset button

1. Shimming the TouchGFX Widget Hierarchy

TouchGFX Designer generates C++ base classes (ChessGameViewBase) that construct widget trees (Image, Box, TextAreaWithOneWildcard, DigitalClock, ButtonWithLabel, Slider, FadeAnimator) and wire up strongly typed callbacks. Rather than linking against ST’s proprietary ARM Cortex-M7 binary library, touchgfx_shim/ implements only the subset of touchgfx::* classes used by our chess game on top of SDL2, SDL_image, and SDL_ttf.

2. High-DPI Rendering Without Breaking 480×272 Coordinates

All widget positions in ChessGameViewBase.cpp and board hit-test calculations in ChessGameView.cpp (int position = e.getX() / 34 + (e.getY() / 34) * 8) assume the physical 480×272 resolution of the STM32H7B3I-DK screen.

To keep the board crisp on modern desktop and phone displays without touching a single coordinate in the C++ code, the SDL2 runner creates a 3× physical WebGL canvas (1440×816) and locks the logical coordinate system to 480×272:

SDL_RenderSetLogicalSize(g_renderer, 480, 272);

SDL2 automatically translates mouse and touch coordinates back into 480×272 space for the C++ hit-testing logic while rendering PNG piece sprites and TrueType fonts at 3× physical resolution.

3. Redirecting FatFs MicroSD Saves to window.localStorage

On the physical STM32H7B3I-DK board, clicking Save or Load serializes the game with RapidJSON and calls WriteToSDCARD() / ReadFromSDCARD() to read and write GAME.TXT on a microSD card via FatFs. In the WebAssembly build, those two C functions are intercepted with inline Emscripten EM_ASM bridges that persist the JSON string directly to window.localStorage, so saving and loading works seamlessly in the browser.