From 952bea23f86ff4e621d3498d0fb55dee17e47386 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Mon, 21 Apr 2025 14:26:36 -0700 Subject: [PATCH] Run background tasks during display refresh This allows audio buffers to be filled during display refresh. Howevere, this only works during explicit refreshes though because background tasks cannot be recursive. Also, on RP2, disable a finished audio DMA so it isn't accidentally triggered and restart the channels if needed. --- ports/raspberrypi/audio_dma.c | 14 ++++++++++++++ shared-module/busdisplay/BusDisplay.c | 7 +++++-- shared-module/epaperdisplay/EPaperDisplay.c | 7 +++++-- shared-module/framebufferio/FramebufferDisplay.c | 6 ++++-- 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/ports/raspberrypi/audio_dma.c b/ports/raspberrypi/audio_dma.c index 1d9642a16ea10..d9adc3064a841 100644 --- a/ports/raspberrypi/audio_dma.c +++ b/ports/raspberrypi/audio_dma.c @@ -159,9 +159,13 @@ static void audio_dma_load_next_block(audio_dma_t *dma, size_t buffer_idx) { !dma_channel_is_busy(dma->channel[1])) { // No data has been read, and both DMA channels have now finished, so it's safe to stop. audio_dma_stop(dma); + dma->dma_result = AUDIO_DMA_OK; + return; } } } + // Enable the channel so that it can be played. + dma_hw->ch[dma_channel].al1_ctrl |= DMA_CH1_CTRL_TRIG_EN_BITS; dma->dma_result = AUDIO_DMA_OK; } @@ -462,18 +466,26 @@ static void dma_callback_fun(void *arg) { // Load the blocks for the requested channels. uint32_t channel = 0; + size_t filled_count = 0; while (channels_to_load_mask) { if (channels_to_load_mask & 1) { if (dma->channel[0] == channel) { audio_dma_load_next_block(dma, 0); + filled_count++; } if (dma->channel[1] == channel) { audio_dma_load_next_block(dma, 1); + filled_count++; } } channels_to_load_mask >>= 1; channel++; } + // If we had to fill both buffers, then we missed the trigger from the other + // buffer. So restart the DMA. + if (filled_count == 2) { + dma_channel_start(dma->channel[0]); + } } void __not_in_flash_func(isr_dma_0)(void) { @@ -491,6 +503,8 @@ void __not_in_flash_func(isr_dma_0)(void) { audio_dma_t *dma = MP_STATE_PORT(playing_audio)[i]; // Record all channels whose DMA has completed; they need loading. dma->channels_to_load_mask |= mask; + // Disable the channel so that we don't play it without filling it. + dma_hw->ch[i].al1_ctrl &= ~DMA_CH0_CTRL_TRIG_EN_BITS; background_callback_add(&dma->callback, dma_callback_fun, (void *)dma); } if (MP_STATE_PORT(background_pio_read)[i] != NULL) { diff --git a/shared-module/busdisplay/BusDisplay.c b/shared-module/busdisplay/BusDisplay.c index 001f2f20c03e3..ac57f3bf3e019 100644 --- a/shared-module/busdisplay/BusDisplay.c +++ b/shared-module/busdisplay/BusDisplay.c @@ -296,8 +296,11 @@ static bool _refresh_area(busdisplay_busdisplay_obj_t *self, const displayio_are _send_pixels(self, (uint8_t *)buffer, subrectangle_size_bytes); displayio_display_bus_end_transaction(&self->bus); - // TODO(tannewt): Make refresh displays faster so we don't starve other - // background tasks. + // Run background tasks so they can run during an explicit refresh. + // Auto-refresh won't run background tasks here because it is a background task itself. + RUN_BACKGROUND_TASKS; + + // Run USB background tasks so they can run during an implicit refresh. #if CIRCUITPY_TINYUSB usb_background(); #endif diff --git a/shared-module/epaperdisplay/EPaperDisplay.c b/shared-module/epaperdisplay/EPaperDisplay.c index 14fbc3341b5ff..86ecff29b1102 100644 --- a/shared-module/epaperdisplay/EPaperDisplay.c +++ b/shared-module/epaperdisplay/EPaperDisplay.c @@ -360,8 +360,11 @@ static bool epaperdisplay_epaperdisplay_refresh_area(epaperdisplay_epaperdisplay self->bus.send(self->bus.bus, DISPLAY_DATA, self->chip_select, (uint8_t *)buffer, subrectangle_size_bytes); displayio_display_bus_end_transaction(&self->bus); - // TODO(tannewt): Make refresh displays faster so we don't starve other - // background tasks. + // Run background tasks so they can run during an explicit refresh. + // Auto-refresh won't run background tasks here because it is a background task itself. + RUN_BACKGROUND_TASKS; + + // Run USB background tasks so they can run during an implicit refresh. #if CIRCUITPY_TINYUSB usb_background(); #endif diff --git a/shared-module/framebufferio/FramebufferDisplay.c b/shared-module/framebufferio/FramebufferDisplay.c index c9fe52e0e40b1..4ba2b1325815c 100644 --- a/shared-module/framebufferio/FramebufferDisplay.c +++ b/shared-module/framebufferio/FramebufferDisplay.c @@ -200,9 +200,11 @@ static bool _refresh_area(framebufferio_framebufferdisplay_obj_t *self, const di dest += rowstride; src += rowsize; } + // Run background tasks so they can run during an explicit refresh. + // Auto-refresh won't run background tasks here because it is a background task itself. + RUN_BACKGROUND_TASKS; - // TODO(tannewt): Make refresh displays faster so we don't starve other - // background tasks. + // Run USB background tasks so they can run during an implicit refresh. #if CIRCUITPY_TINYUSB usb_background(); #endif