Your interface works in the simulator. What will make it work on the real screen?

You have a screen layout that looks right on your computer. Now you want to put it into a small instrument, a controller or a prototype that someone can hold. The next obstacle is often surprisingly basic: finding firmware that matches the board, the display and the touch controller you actually bought.

A screen demonstration is a useful milestone because it brings those choices together. Our engineers built LVGL firmware for MicroPython on three screen assemblies (an ESP32-S3 board with a two-inch display, a Raspberry Pi Pico 2 carrier and an ESP32-WROOM board) and worked through failed downloads, completed builds and firmware installation until labels and sliders appeared on the physical displays. This note covers what that took and what it did not prove.

Identify the screen assembly before choosing firmware

"ESP32 touchscreen" does not describe enough of the hardware. The processor runs the application, a display controller receives the pixels, and a separate touch controller reports where a finger lands. Firmware has to support each of those parts and their connections.

The two-inch ESP32-S3 assembly used an ST7789 display driver and a CST816S touch controller. The Pico 2 and ESP32-WROOM setups paired an ST7789 display with GT911 touch. The finished screens all show a similar slider, but the configurations are different.

This is a lasting source of confusion. In a November 2025 LVGL forum thread, a developer had spent hours trying to build firmware for an ESP32 display and wanted a ready-made binary. Replies first had to distinguish between two similarly named MicroPython/LVGL repositories. Other boards sold under similar names use yet another touch controller, which is why the exact board matters.

Before a build, record the board revision, processor, flash capacity, external RAM, display controller and resolution, touch controller and pin assignments. A photo of the front usually cannot supply that. Keep the supplier's board documentation with the project.

Keep one complete software configuration together

We used the lvgl-micropython/lvgl_micropython project, a separate implementation from lvgl/lv_micropython. Its build script and driver interfaces belong together. Mixing a build command from one project with an example from the other can leave you debugging a mismatch before your application has even started.

Our builds ran in Ubuntu. For the two-inch ESP32-S3 assembly, the build command was:

python3 make.py esp32 --flash-size=16 --optimize-size --enable-cdc-repl=y BOARD=ESP32_GENERIC_S3 BOARD_VARIANT=SPIRAM_OCT DISPLAY=st7789 INDEV=cst816s

That is the configuration for that assembly, not a universal command for any ESP32-S3 screen. BOARD selects the target board family, BOARD_VARIANT the memory configuration (here octal external RAM), and DISPLAY and INDEV the display and input drivers. Check the instructions for the repository revision you are using.

Keep the repository URL and commit, tool versions, full build command and resulting firmware together. For an update, keep the working checkout and make a separate fresh checkout for comparison. That gives you a way back if an updated driver changes behavior.

Read the first meaningful failure

Our build history included interrupted Git downloads and a missing dependency directory (an mbedTLS include path). These failures happen before a screen can be tested. Changing display pins will not repair an incomplete download, just as downloading dependencies again will not correct a wrong backlight pin.

The useful habit is to identify which stage failed: fetching dependencies, compiling, linking, creating the image, transferring it, booting or drawing. Save the first relevant error and the command that produced it. For network or certificate problems, fix the network or trust configuration; disabling Git's certificate verification globally is not a sound setup step.

The Pico 2 build also hit a missing generated API file, and we switched to a third-party rp2-fix branch to get past it. That is a version-specific workaround, not proof that every current checkout needs the same repair. If you rely on such a branch, keep it and its modifications so the result can be reproduced.

A completed build can still leave little room for changes

The ESP32-S3 log reported a completed build with 2,770,944 application bytes and only 2,048 bytes remaining in the application partition. That is a useful warning, but it does not mean the entire 16 MB flash chip has only 2 KB left. The application partition and the filesystem are separate allocations within flash.

The ESP32-WROOM build first exceeded its application partition, then produced a successful rebuilt image: 2,721,760 application bytes with 2,080 bytes remaining. Judge headroom from the final build log and its partition table, not from a hand calculation.

For the next prototype iteration, check where additional fonts, images and Python files will live. A frozen module enlarges the firmware; a file copied to the filesystem uses a different allocation. Neither number tells you how much working RAM remains while the interface is running.

Prove the image on the board, then prove the screen

After building, we identified the connected chip, wrote the generated image, entered the MicroPython prompt and imported the display-related modules before running the interface. These are separate checkpoints: transfer success, a running interpreter and available drivers. A successful import shows a module is present, not that touch works.

Use the flash addresses given for the exact image you built. A merged image and its separate bootloader, partition table and application files are not interchangeable. Espressif's esptool documentation also distinguishes normal write-area erasure from --erase-all, which removes every flash sector. Back up existing application files and settings before a procedure that erases them.

For a running screen, the program created the bus, allocated drawing buffers, initialized the display, turned on the backlight and created a label and a slider. Touch setup adds another bus and driver. Start with that small interface before adding menus, network activity or sensor updates.

The Pico 2 example used two buffers, each sized as 240 * 320 * 2 // 5: 30,720 bytes per buffer, or 61,440 bytes together. That describes partial drawing buffers, not the whole program's memory use. It is one reason that copying a desktop interface unchanged can overlook important hardware choices.

The Pico 2 display showed "HELLO WORLD!" and a slider in the touch-enabled example. A still result like that shows the label and slider reached the physical display. It is not a complete touch test, and it says nothing about frame rate or long-run stability. We also needed a hard reset before rerunning the Pico 2 configuration; treat that as a limitation of this software and hardware combination to investigate, not a rule about every RP2350 application. The ESP32-WROOM result had its own pins, buffer size and touch setup, and its working configuration is stored separately from the other two.

Make the next test resemble the product

Once a label and slider appear, check the tasks a person will actually perform. Drag the control across its full range, touch the corners after rotation, restart the board, and repeat the test while the intended sensor or communication work is running. Record what succeeds and what fails. These are the next tests to run; our photos of the screens do not establish their results.

For a team preparing a user-test prototype, the useful handover is a known firmware image, matching application files, wiring and a short repeatable screen test. That package is worth more than an unnamed binary that happened to light up one board, and none of this yet establishes production readiness.