Skip to main content

Your first board

Shipping an app has two halves that meet at one generated artifact:

  1. The app. You build your JSX with the embedded-react CLI. This produces the artifact and never touches your firmware.
  2. The firmware. A C project that brings up the display and touch input then hands the artifact to the engine. You will usually start from one of the example projects.

Pick a flow​

Your board decides this for you more often than not.

Flow A: runtimeFlow B: ahead of time
Choose it whenYour board has external RAM for the JavaScript heap: PSRAM on an ESP32-S3, or SDRAM on an STM32H7Your chip has only its internal RAM (classic ESP32, RP2040, most STM32 boards)
Build commandnpx embedded-react buildnpx embedded-react build --aot
Producesdist/app.erpkg: bytecode, assets and a checksum in one filedist/app.erpkg: a compiled program, assets and a checksum in one file
Firmware uses it byLoading it at runtime from flashVerifying it and running it from a flash slot
To update the UIReplace app.erpkg. No firmware rebuildnpx embedded-react flash. No firmware rebuild
APIEverythingA subset

The two flows explains what happens on the device in each case.

Verified boards​

Each of these has an example firmware project in the repository and a guide here.

BoardDisplayFlowToolchainGuide
Waveshare ESP32-S3-Touch-LCD-7800×480 RGB, capacitive touchA or BESP-IDF v6ESP32-S3
ESP32-2432S028R "Cheap Yellow Display"240×320 SPI, resistive touchBESP-IDF v5.3+ESP32 CYD
Waveshare RP2040-Touch-LCD-1.69240×280 SPI, capacitive touchBPico SDKRP2040
Linux, macOS or Windows desktopSDL windowA or BCMake, SDL2Linux

No board yet? The desktop host runs the same firmware-side code in a desktop window, which makes it a good way to learn the C side before hardware arrives.

Flow A: runtime​

The firmware and your app are two independent artifacts. Flash the firmware once; after that, changing the UI means replacing one file.

1. Build and flash the firmware. From a copy of examples/esp32/esp32-s3, with ESP-IDF exported in your shell:

idf.py set-target esp32s3
idf.py build flash monitor

With no app loaded yet, the panel shows "No config loaded". That is the firmware working.

2. Build your app and write it to the board. From your app project:

npx embedded-react build
parttool.py write_partition --partition-name=config --input dist/app.erpkg

parttool.py ships with ESP-IDF. The board restarts into your app, and the log shows config loaded. From now on, repeat only step 2.

Hot reload on the device

Firmware built with -DER_HOTRELOAD=1 accepts live updates over USB: npm run dev:device re-packs and streams your app on every save. It is a development-only option, off by default. See Hot reload.

Flow B: ahead of time​

Here the app is compiled to a package on your computer, and a small fixed runtime in the firmware checks it and runs it. The firmware is built once.

1. Build and flash the firmware. For the Cheap Yellow Display:

cd examples/esp32/esp32-2432s028r
idf.py set-target esp32
idf.py -p PORT flash

With no package installed yet, the board shows an error screen saying so.

2. Compile your app. The package holds a layout for every panel size, and the board's resolution picks one:

npx embedded-react build --aot

This writes dist/app.erpkg and prints the RAM and flash it needs. The thermostat and watch-face templates wrap this as npm run build:aot.

3. Write it to the board.

npx embedded-react flash --board esp32-2432s028r --port PORT

The board restarts into your app. After an edit, repeat steps 2 and 3. A package that fails verification doesn't replace the one before it: the board shows the reason, then runs the last good package.

The board guides cover each example's wiring and flash layout.

The ahead-of-time compiler accepts a subset of the API and tells you at build time, with the file and line, when an app steps outside it. The AOT subset lists what is and is not supported.

Bringing up a different board​

The engine is not tied to the boards above. It needs two things from you: a backend, which is a handful of callbacks that fill, copy and blend pixels and push them to your display, and a frame loop that feeds it touch input and time. Several backends already exist (RGB parallel and SPI LCDs on ESP32, SPI on the RP2040, Chrom-ART on STM32, SDL on the desktop), so a new board is often a matter of pin and panel configuration.