Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion docs/ports/esp32.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,18 @@ Behavioral contract (proven on Qualia S3 + TL040HDS20):
|-------|----------|
| Scanout | Continuous DMA (`refresh_on_demand=0`); panel-owned FB |
| `refresh()` | Cache writeback only (`esp_cache_msync`) |
| Large panels | Bounce buffer `20 * h_res` px + dirty-row msync |
| Large panels | Bounce buffers `bounce_rows * h_res` px each (`bounce_rows=`, 4..20, default 20; stepped down if internal DMA RAM will not hold two) + dirty-row msync |
| Buffer protocol | typecode `'B'` (MP has no `memoryview.cast`) |
| Fast paths | Native `blit` / `fill_rect` (expose via custom `attr`) |
| Lifecycle | Idempotent `deinit` / `__del__` / ctor + soft-reset teardown |

Both bounce buffers come out of internal DMA RAM, which Wi-Fi, hardware AES
and a USB host need too: an 800-wide panel takes 64 KB of it at 20 rows. A
board that runs those beside the panel asks for fewer, e.g.
`DotClockFramebuffer(..., bounce_rows=10)` on the Waveshare
ESP32-S3-Touch-LCD-7 (32 KB). Fewer rows mean more refill interrupts per
frame, not less throughput: the same bytes are copied either way.

See [soft-reset-and-bring-up.md](../soft-reset-and-bring-up.md#reference-dotclockframebufferdotclockframebuffer-on-qualia-esp32-s3).

### `mipidsi` (ESP32-P4 DSI)
Expand Down
34 changes: 28 additions & 6 deletions src/ports/esp32/mod_dotclockframebuffer.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ typedef struct _dotclockframebuffer_obj_t {
bool pclk_active_high;
bool pclk_idle_high;
int overscan_left;
// Upper bound on bounce-buffer rows (0 = DCFB_BOUNCE_ROWS_MAX).
uint32_t bounce_rows_max;
uint8_t bits_per_pixel;
bool deinited;
#if defined(ESP_PLATFORM) && SOC_LCD_RGB_SUPPORTED
Expand Down Expand Up @@ -209,14 +211,14 @@ static void dotclockframebuffer_fill_data_pins(dotclockframebuffer_obj_t *self,
// Returns the number of rows per bounce buffer, or 0 if even the smallest
// pair will not fit. Always writes the sizes it would have accepted and the
// largest free block it saw, so the caller can name all three in the error.
static uint32_t dcfb_probe_bounce_rows(uint32_t h_res, uint32_t v_res,
static uint32_t dcfb_probe_bounce_rows(uint32_t h_res, uint32_t v_res, uint32_t rows_max,
size_t *out_preferred, size_t *out_smallest, size_t *out_largest) {
const uint32_t caps = MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA | MALLOC_CAP_8BIT;
*out_largest = heap_caps_get_largest_free_block(caps);
*out_preferred = 0;
*out_smallest = 0;

for (uint32_t rows = DCFB_BOUNCE_ROWS_MAX; rows >= DCFB_BOUNCE_ROWS_MIN; rows--) {
for (uint32_t rows = rows_max; rows >= DCFB_BOUNCE_ROWS_MIN; rows--) {
if (v_res == 0 || v_res % rows != 0) {
// ESP-IDF: "frame buffer size must be multiple of bounce buffer size".
continue;
Expand Down Expand Up @@ -290,15 +292,16 @@ static void dotclockframebuffer_init_panel(dotclockframebuffer_obj_t *self) {
size_t bb_preferred = 0;
size_t bb_smallest = 0;
size_t bb_largest = 0;
uint32_t bounce_rows = dcfb_probe_bounce_rows(h_res, (uint32_t)self->height,
uint32_t rows_max = self->bounce_rows_max;
uint32_t bounce_rows = dcfb_probe_bounce_rows(h_res, (uint32_t)self->height, rows_max,
&bb_preferred, &bb_smallest, &bb_largest);
if (bb_preferred == 0) {
// No row count between DCFB_BOUNCE_ROWS_MIN and _MAX divides v_res, so
// ESP-IDF's "frame buffer must be a whole number of bounce buffers" rule
// cannot be satisfied at any size. Not a memory problem — say which.
mp_raise_msg_varg(&mp_type_ValueError,
MP_ERROR_TEXT("RGB panel height %u has no bounce-buffer row count between %u and %u that divides it"),
(unsigned int)self->height, (unsigned int)DCFB_BOUNCE_ROWS_MIN, (unsigned int)DCFB_BOUNCE_ROWS_MAX);
(unsigned int)self->height, (unsigned int)DCFB_BOUNCE_ROWS_MIN, (unsigned int)rows_max);
}
if (bounce_rows == 0) {
mp_raise_msg_varg(&mp_type_MemoryError,
Expand All @@ -307,13 +310,13 @@ static void dotclockframebuffer_init_panel(dotclockframebuffer_obj_t *self) {
"Start the display before Wi-Fi, or hard reset."),
(unsigned int)bb_preferred, (unsigned int)bb_smallest, (unsigned int)bb_largest);
}
if (bounce_rows != DCFB_BOUNCE_ROWS_MAX) {
if (bounce_rows != rows_max) {
// Say so rather than degrading silently: fewer rows means more refill
// interrupts per frame, and it is the tell that internal DRAM is tight.
mp_printf(MP_PYTHON_PRINTER,
"dotclockframebuffer: internal DMA RAM is tight (largest free block %u), "
"using %u-row bounce buffers instead of %u\n",
(unsigned int)bb_largest, (unsigned int)bounce_rows, (unsigned int)DCFB_BOUNCE_ROWS_MAX);
(unsigned int)bb_largest, (unsigned int)bounce_rows, (unsigned int)rows_max);
}

esp_lcd_rgb_panel_config_t panel_config = {
Expand Down Expand Up @@ -464,6 +467,7 @@ static mp_obj_t dotclockframebuffer_make(const mp_obj_type_t *type, size_t n_arg
ARG_pclk_active_high,
ARG_pclk_idle_high,
ARG_overscan_left,
ARG_bounce_rows,
};
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_de, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ, { .u_obj = MP_OBJ_NULL } },
Expand All @@ -488,6 +492,12 @@ static mp_obj_t dotclockframebuffer_make(const mp_obj_type_t *type, size_t n_arg
{ MP_QSTR_pclk_active_high, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_BOOL, { .u_bool = false } },
{ MP_QSTR_pclk_idle_high, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_BOOL, { .u_bool = false } },
{ MP_QSTR_overscan_left, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = 0 } },
// Largest bounce buffer, in rows (default 20). Two of them come out of
// internal DMA RAM, which Wi-Fi, TLS and a USB host also need: at 20
// rows an 800-wide panel takes 64 KB of it. A board that runs those
// alongside the panel asks for fewer rows; the probe still steps down
// from here if even that does not fit.
{ MP_QSTR_bounce_rows, MP_ARG_KW_ONLY | MP_ARG_INT, { .u_int = 0 } },
};
mp_arg_val_t vals[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all_kw_array(n_args, n_kw, args, MP_ARRAY_SIZE(allowed_args), allowed_args, vals);
Expand Down Expand Up @@ -529,6 +539,18 @@ static mp_obj_t dotclockframebuffer_make(const mp_obj_type_t *type, size_t n_arg
self->pclk_active_high = vals[ARG_pclk_active_high].u_bool;
self->pclk_idle_high = vals[ARG_pclk_idle_high].u_bool;
self->overscan_left = vals[ARG_overscan_left].u_int;
{
mp_int_t rows = vals[ARG_bounce_rows].u_int;
if (rows == 0) {
rows = DCFB_BOUNCE_ROWS_MAX;
}
if (rows < DCFB_BOUNCE_ROWS_MIN || rows > DCFB_BOUNCE_ROWS_MAX) {
mp_raise_msg_varg(&mp_type_ValueError,
MP_ERROR_TEXT("bounce_rows must be %u..%u"),
(unsigned int)DCFB_BOUNCE_ROWS_MIN, (unsigned int)DCFB_BOUNCE_ROWS_MAX);
}
self->bounce_rows_max = (uint32_t)rows;
}
self->bits_per_pixel = 16;
self->deinited = false;
#if defined(ESP_PLATFORM) && SOC_LCD_RGB_SUPPORTED
Expand Down
Loading