Start here: haitch.diy
On 7 August 2026 we spent a day building an e-paper pomodoro timer for the Elecrow CrowPanel ESP32 2.13-inch display. Twenty-six builds, fourteen of them successful, a working UI with work and break states, and a full EPD refresh loop.
Not one of those builds was compiled for a CrowPanel.
The toolchain picked a substitute board every time, silently, and every build log reported success against a target that was not the hardware on the desk. This is a writeup of that failure mode, because it is common, it is quiet, and the numbers it hands you are confidently wrong.
When your build system does not have a definition for your specific board, it does not stop. It picks something close and carries on. On this project:
None of this produces an error. You get a green build, a valid binary, and a memory report that describes a board you do not own.
From Elecrow's own schematic for the CrowPanel ESP32 Display 2.13 inch (E), the main IC block is labelled plainly:
ESP32-S3R8. The R8 suffix is the important part — 8 MB of embedded PSRAM in the package.
The rest of the board is unremarkable and well laid out: USB Type-C, a CH340 USB-to-serial bridge, a battery input with charge control, a 3.3 V rail, BOOT and RESET and EXIT keys, UART0 and UART1 level translation, a GPIO header, and a dedicated EPD interface block driving the panel.
That EPD block is the whole reason to buy this board rather than a devkit and a bare panel. It is also the part that no generic board profile knows anything about.
A pomodoro timer. The firmware is real and the compiled object still carries its strings:
Built against EPD.cpp and EPD_Init.cpp — Elecrow's own demo driver filenames — with GxEPD2 pulled in as a library dependency. This was a genuine attempt at the board, using the vendor's driver, not a toy.
Here is every board target the project was compiled against, taken from the build records rather than from memory:
| Build window | Target board | Builds | What it actually describes |
|---|---|---|---|
| 01:00Z | esp32dev | 1 | ESP32, Xtensa LX6, single or dual core, no native USB |
| 02:44Z – 22:14Z | esp32-s3-devkitc-1 | 13 | ESP32-S3-N8, 8 MB flash, no PSRAM |
The first one is the more alarming of the two. esp32dev is not a near-miss for an ESP32-S3 board. It is a different instruction set generation, a different USB story, and a different peripheral map. A firmware built for it and flashed onto an S3 does not run.
The second is subtler and therefore worse, because it mostly works. You get correct instructions and a correct toolchain. What you lose is everything the board profile is supposed to tell you: the 8 MB of PSRAM the S3R8 has and the devkitc-1-N8 does not, the flash geometry, and every board-specific rail — including whatever the EPD interface block needs to power the panel.
The clearest illustration is the memory report, because it is the number developers actually read and trust.
| Target | Flash used | Flash percent | RAM used | RAM percent |
|---|---|---|---|---|
| esp32dev | 297,373 bytes | 22.7% | 25,712 bytes | 7.8% |
| esp32-s3-devkitc-1 | 304,705 – 307,617 bytes | 9.1 – 9.2% | 22,944 – 23,136 bytes | 7.0 – 7.1% |
The application barely changed. The percentage did, by two and a half times, because "percent full" is computed against the partition table of whatever board the toolchain assumed. If you were using that number to decide whether you had room for a font, an OTA slot, or a second partition, you were reading a measurement of a board you do not have.
The absolute byte counts moved too, which is the compiler doing genuinely different work for a different target — 297 KB of LX6 code versus roughly 305 KB of LX7 code for the same program.
Three concrete things you lose to a generic profile, in rough order of how much time they cost:
1. Board-specific power and enable rails. Panels almost never come up on SPI alone. There is usually a power gate, a backlight or bias enable, sometimes a reset with a required hold time. A generic profile has no idea these pins exist, so the generated code does not touch them and the panel stays blank while every log says success.
2. PSRAM. An R8 part with a no-PSRAM profile gives you a build that never enables the 8 MB. Framebuffers that should live in PSRAM either fail to allocate or quietly fall back to internal SRAM.
3. Flash and partition geometry. As above: your headroom numbers are fiction, and OTA layouts sized against them will not fit.
The board substitution is the interesting failure, but the day had ordinary ones too, and they are worth listing because they are what an honest build log looks like. Twenty-six records between 01:00Z and 22:14Z: 14 succeeded, 9 failed, 3 were cancelled.
A missing header, caught in 2.4 seconds.
A .cpp referencing a .h that was never emitted alongside it. Cheap to detect, cheap to fix, and the fastest failure of the day by an order of magnitude.
A 902-second toolchain stall on the very first build, producing no binary. Cold PlatformIO cache. Not a code problem, but it is fifteen minutes of nothing before you learn that.
A lost build session: Build session was lost (server restart or process change). Please run Build again.
A cluster of six failures and three cancellations inside 33 minutes, between 07:51Z and 08:24Z, each 15 to 51 seconds. That shape — short, repeated, interleaved with cancels — is what iterating on a compile error looks like from the outside.
This matters more than anything above, so it gets its own heading.
We never flashed a physical CrowPanel. There is no serial capture, no esptool log, no photograph of a rendered frame from this project. Everything in this article comes from build records and compiled artifacts.
That means we cannot tell you the panel's real refresh time, whether partial refresh is usable, how bad the ghosting is after N updates, how the board behaves on battery, or whether the EPD interface needs a power sequence the way the Heltec T190's TFT does. We have opinions. We do not have measurements, and a board review built on opinions is worth nothing.
If you want the version of this article with a scope trace and a refresh-rate table, that is a different day's work and we will write it when we have done it.
We wrote the board profile system in Haitch because of days like this one.
When a generation request names a board, the generator resolves it against a profile carrying the real pin map, required build defines, PlatformIO target, and hardware constraints — and where a profile carries required defines, a build that omits them is refused at generate time with the reason, rather than shipping a binary that behaves wrongly. The Heltec Vision Master T190 has such a profile now, built from the vendor schematic and reference sketches.
The CrowPanel does not yet, which is precisely why this project got the generic treatment. That is the honest state of it: the mechanism exists, the coverage is a board at a time, and a board without a profile still falls back to a close-enough target. Knowing which of those you are in is the difference between a day of work and a day of confusing work.
Nothing here is evidence either way. The schematic is clean and the integration is sensible. This article is about a toolchain failure, not a hardware verdict.
Probably, in the sense that the instructions are valid for the silicon. Whether the panel would have lit up is a different question, and it is the one we cannot answer without flashing it — which is the point of the section above.
The record does not say. What it does say is that nothing objected, and the build reported success.
PlatformIO reads board JSON files from a boards/ directory in your project. Copy the closest official definition, correct the flash size, PSRAM type, and partition table against your schematic, and point board at your new file.
Yes, and it is harder to see, because the board menu shows a friendly name rather than the profile contents. The variant it selects is doing the same job with less of it on screen.
1. Elecrow CrowPanel ESP32 2.13-inch E-Paper HMI display
2. GxEPD2 e-paper display library
3. Espressif ESP32-S3 series datasheet
4. PlatformIO custom board definitions
5. Heltec Vision Master T190: Getting the Display to Boot
6. Haitch -- AI-native hardware design platform