Not a 1.29 regression — corrected 2026-09-09
The original report claimed the ESP32-S3-Touch-LCD-4.3 panel was broken on MicroPython v1.29.0 + ESP-IDF 5.5.4 and that anyone upgrading would hit it. That was wrong. On a hard-booted board board_config imports cleanly, the panel comes up 800x480, and a 400x240 fill_rect runs in 12.3 ms through its native path. Verified on the bench, red and blue quadrants drawn and seen.
What was real is narrower and still worth knowing.
The panel needs ~64 KB of internal DMA RAM, in two contiguous 32 KB pieces
mod_dotclockframebuffer.c:236 requests bounce_buffer_size_px = 20 * h_res. On an 800-wide panel that is 16,000 px = 32,000 bytes per bounce buffer, and ESP-IDF allocates RGB_LCD_PANEL_BOUNCE_BUF_NUM = 2 of them (esp_lcd_panel_rgb.c:75, allocated at :188 via heap_caps_aligned_calloc from internal memory, returning ESP_ERR_NO_MEM at :190).
Measured on this board:
| state |
internal free |
largest contiguous |
| fresh hard boot |
187,879 |
110,592 |
after 92 mip installs and a long run of exec sessions |
57,304 |
38,912 |
Two 32,000-byte allocations need 64,000. Fresh boot has ample room. The busy board does not — the first allocation fits in the 38,912 block and the second fails, which is exactly the ESP_ERR_NO_MEM seen. A soft reset does not recover it; a hard reset does.
So constructing the panel is sensitive to internal-RAM fragmentation from prior activity, and it fails with an ESP-IDF error code that says nothing about which allocation or why. PSRAM is a red herring throughout: it had 6.7 MB contiguous free the whole time, which is what made "plenty of memory" so misleading.
Worth considering (not decided): size the bounce buffer against what is actually free — try 20 rows, fall back to fewer, and disable bounce rather than failing construction, since with fb_in_psram the panel can scan out of PSRAM without it. And whatever the driver does, the error deserves a message naming the bounce buffer rather than a bare IDF code.
Backends should expose fill_rect where they have a fast path
Unchanged from the original report, and independent of the above.
FBDisplay.fill_rect tries, in order: bitmaptools + Bitmap (the CircuitPython Qualia path), a native fill_rect on the raw buffer, and — as of pydevices 3b38c0c — a native blit, before falling back to per-row memoryview assigns.
The two boards land differently, and the question is why the S3's backend has fill_rect while the P4's does not:
| board |
raw buffer type |
fill_rect |
blit |
| ESP32-S3-Touch-LCD-4.3 |
DotClockFramebuffer (displayif) |
yes |
— |
| ESP32-P4 Touch-LCD-4B |
Display (mipidsi) |
no |
yes |
The P4's object exposes only blit, deinit, height, refresh, refresh_rect, row_stride, width. Without a native fill it was doing ~14 ms per row into SPIRAM: a 100x100 fill measured 1.387 s, and paint.py's palette visibly drew block by block. Routing through the native blit took the same fill to 2,352 us, and a full 720x720 screen to 20.8 ms. For comparison the S3, which has a native fill, does 400x240 in 12.3 ms.
The ask: every displayif backend that can fill in C should expose fill_rect. Worth auditing the set — mipidsi, dotclockframebuffer, i80bus, spibus, rgbmatrix, busdisplay — for which have a fast fill available and which are silently falling back.
Not a 1.29 regression — corrected 2026-09-09
The original report claimed the ESP32-S3-Touch-LCD-4.3 panel was broken on MicroPython v1.29.0 + ESP-IDF 5.5.4 and that anyone upgrading would hit it. That was wrong. On a hard-booted board
board_configimports cleanly, the panel comes up 800x480, and a 400x240fill_rectruns in 12.3 ms through its native path. Verified on the bench, red and blue quadrants drawn and seen.What was real is narrower and still worth knowing.
The panel needs ~64 KB of internal DMA RAM, in two contiguous 32 KB pieces
mod_dotclockframebuffer.c:236requestsbounce_buffer_size_px = 20 * h_res. On an 800-wide panel that is 16,000 px = 32,000 bytes per bounce buffer, and ESP-IDF allocatesRGB_LCD_PANEL_BOUNCE_BUF_NUM = 2of them (esp_lcd_panel_rgb.c:75, allocated at :188 viaheap_caps_aligned_callocfrom internal memory, returningESP_ERR_NO_MEMat :190).Measured on this board:
execsessionsTwo 32,000-byte allocations need 64,000. Fresh boot has ample room. The busy board does not — the first allocation fits in the 38,912 block and the second fails, which is exactly the
ESP_ERR_NO_MEMseen. A soft reset does not recover it; a hard reset does.So constructing the panel is sensitive to internal-RAM fragmentation from prior activity, and it fails with an ESP-IDF error code that says nothing about which allocation or why. PSRAM is a red herring throughout: it had 6.7 MB contiguous free the whole time, which is what made "plenty of memory" so misleading.
Worth considering (not decided): size the bounce buffer against what is actually free — try 20 rows, fall back to fewer, and disable bounce rather than failing construction, since with
fb_in_psramthe panel can scan out of PSRAM without it. And whatever the driver does, the error deserves a message naming the bounce buffer rather than a bare IDF code.Backends should expose
fill_rectwhere they have a fast pathUnchanged from the original report, and independent of the above.
FBDisplay.fill_recttries, in order:bitmaptools+Bitmap(the CircuitPython Qualia path), a nativefill_recton the raw buffer, and — as ofpydevices3b38c0c — a nativeblit, before falling back to per-row memoryview assigns.The two boards land differently, and the question is why the S3's backend has
fill_rectwhile the P4's does not:fill_rectblitDotClockFramebuffer(displayif)Display(mipidsi)The P4's object exposes only
blit,deinit,height,refresh,refresh_rect,row_stride,width. Without a native fill it was doing ~14 ms per row into SPIRAM: a 100x100 fill measured 1.387 s, and paint.py's palette visibly drew block by block. Routing through the nativeblittook the same fill to 2,352 us, and a full 720x720 screen to 20.8 ms. For comparison the S3, which has a native fill, does 400x240 in 12.3 ms.The ask: every displayif backend that can fill in C should expose
fill_rect. Worth auditing the set —mipidsi,dotclockframebuffer,i80bus,spibus,rgbmatrix,busdisplay— for which have a fast fill available and which are silently falling back.