Fix double buffering #755
The existing way of delaying the timing the 'flip' to the back-buffer based on an ESP32 hardware level interrupt doesn't work properly for a variety of reasons, namely that the interrupt takes too long to process. As such, when flipDMABuffer() is called, it happens immediately. It is up to the user to ensure that they don't flip so quickly that they introduce flicker from excessive flipping! Double-buffer example updated accordingly.
This commit is contained in:
parent
d121fa10ce
commit
f4357acd2d
6 changed files with 67 additions and 74 deletions
examples/3_DoubleBuffer
src
|
@ -1,8 +1,24 @@
|
|||
// Example uses the following configuration: mxconfig.double_buff = true;
|
||||
// to enable double buffering, which means display->flipDMABuffer(); is required.
|
||||
/**
|
||||
Example uses the following configuration: mxconfig.double_buff = true;
|
||||
to enable double buffering, which means display->flipDMABuffer(); is required.
|
||||
|
||||
Bounce squares around the screen, doing the re-drawing in the background back-buffer.
|
||||
|
||||
Double buffering is usually required. It is most useful when you have a complex drawing routine
|
||||
that you want to quickly 'flip to' without being able to see the frame being drawn.
|
||||
|
||||
Please note that double buffering isn't a silver bullet, and may still result in flickering
|
||||
if you end up 'flipping' the buffer quicker than the physical HUB75 refresh output rate.
|
||||
|
||||
Refer to the runtime debug output to see, i.e:
|
||||
|
||||
[ 2103][I][ESP32-HUB75-MatrixPanel-I2S-DMA.cpp:85] setupDMA(): [I2S-DMA] Minimum visual refresh rate (scan rate from panel top to bottom) requested: 60 Hz
|
||||
[ 2116][W][ESP32-HUB75-MatrixPanel-I2S-DMA.cpp:105] setupDMA(): [I2S-DMA] lsbMsbTransitionBit of 0 gives 57 Hz refresh rate.
|
||||
[ 2128][W][ESP32-HUB75-MatrixPanel-I2S-DMA.cpp:105] setupDMA(): [I2S-DMA] lsbMsbTransitionBit of 1 gives 110 Hz refresh rate.
|
||||
[ 2139][W][ESP32-HUB75-MatrixPanel-I2S-DMA.cpp:118] setupDMA(): [I2S-DMA] lsbMsbTransitionBit of 1 used to achieve refresh rate of 60 Hz.
|
||||
|
||||
**/
|
||||
|
||||
// Bounce squares around the screen, doing the re-drawing in the background back-buffer.
|
||||
// Double buffering is not always required in reality.
|
||||
|
||||
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
|
||||
#include <array>
|
||||
|
@ -38,7 +54,7 @@ void setup()
|
|||
Serial.println("...Starting Display");
|
||||
HUB75_I2S_CFG mxconfig;
|
||||
mxconfig.double_buff = true; // <------------- Turn on double buffer
|
||||
//mxconfig.clkphase = false;
|
||||
//mxconfig.clkphase = false; // <------------- Turn off double buffer and it'll look flickery
|
||||
|
||||
// OK, now we can create our matrix object
|
||||
display = new MatrixPanel_I2S_DMA(mxconfig);
|
||||
|
@ -68,11 +84,19 @@ void setup()
|
|||
|
||||
void loop()
|
||||
{
|
||||
|
||||
display->flipDMABuffer(); // Show the back buffer, set currently output buffer to the back (i.e. no longer being sent to LED panels)
|
||||
display->clearScreen(); // Now clear the back-buffer
|
||||
|
||||
delay(16); // <----------- Shouldn't see this clearscreen occur as it happens on the back buffer when double buffering is enabled.
|
||||
// Flip all future drawPixel calls to write to the back buffer which is NOT being displayed.
|
||||
display->flipDMABuffer();
|
||||
|
||||
// SUPER IMPORTANT: Wait at least long enough to ensure that a "frame" has been displayed on the LED Matrix Panel before the next flip!
|
||||
delay(1000/display->calculated_refresh_rate);
|
||||
|
||||
// Now clear the back-buffer we are drawing to.
|
||||
display->clearScreen();
|
||||
|
||||
// This is here to demonstrate flicker if double buffering is disabled. Emulates a long draw routine that would typically occur after a 'clearscreen'.
|
||||
delay(25);
|
||||
|
||||
|
||||
for (int i = 0; i < numSquares; i++)
|
||||
{
|
||||
|
|
|
@ -473,12 +473,17 @@ public:
|
|||
|
||||
// Flush the DMA buffers prior to configuring DMA - Avoid visual artefacts on boot.
|
||||
resetbuffers(); // Must fill the DMA buffer with the initial output bit sequence or the panel will display garbage
|
||||
ESP_LOGV("being()", "Completed resetbuffers()");
|
||||
|
||||
flipDMABuffer(); // display back buffer 0, draw to 1, ignored if double buffering isn't enabled.
|
||||
ESP_LOGV("being()", "Completed flipDMABuffer()");
|
||||
|
||||
// Start output output
|
||||
dma_bus.init();
|
||||
ESP_LOGV("being()", "Completed dma_bus.init()");
|
||||
|
||||
dma_bus.dma_transfer_start();
|
||||
ESP_LOGV("being()", "Completed dma_bus.dma_transfer_start()");
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -50,6 +50,7 @@ Modified heavily for the ESP32 HUB75 DMA library by:
|
|||
// Get CPU freq function.
|
||||
#include <soc/rtc.h>
|
||||
|
||||
/*
|
||||
|
||||
volatile bool previousBufferFree = true;
|
||||
|
||||
|
@ -68,7 +69,7 @@ Modified heavily for the ESP32 HUB75 DMA library by:
|
|||
bool DRAM_ATTR i2s_parallel_is_previous_buffer_free() {
|
||||
return previousBufferFree;
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
// Static
|
||||
i2s_dev_t* getDev()
|
||||
|
@ -402,11 +403,14 @@ Modified heavily for the ESP32 HUB75 DMA library by:
|
|||
// If we have double buffering, then allocate an interrupt service routine function
|
||||
// that can be used for I2S0/I2S1 created interrupts.
|
||||
|
||||
/*
|
||||
// Setup I2S Interrupt
|
||||
SET_PERI_REG_BITS(I2S_INT_ENA_REG(ESP32_I2S_DEVICE), I2S_OUT_EOF_INT_ENA_V, 1, I2S_OUT_EOF_INT_ENA_S);
|
||||
|
||||
// Allocate a level 1 intterupt: lowest priority, as ISR isn't urgent and may take a long time to complete
|
||||
esp_intr_alloc(irq_source, (int)(ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LEVEL1), i2s_isr, NULL, NULL);
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#if defined (CONFIG_IDF_TARGET_ESP32S2)
|
||||
|
@ -481,6 +485,7 @@ Modified heavily for the ESP32 HUB75 DMA library by:
|
|||
|
||||
ESP_LOGD("ESP32/S2", "Allocating %d bytes of memory for DMA descriptors.", (int)sizeof(HUB75_DMA_DESCRIPTOR_T) * len);
|
||||
|
||||
/*
|
||||
// New - Temporary blank descriptor for transitions between DMA buffer
|
||||
_dmadesc_blank = (HUB75_DMA_DESCRIPTOR_T*)heap_caps_malloc(sizeof(HUB75_DMA_DESCRIPTOR_T) * 1, MALLOC_CAP_DMA);
|
||||
_dmadesc_blank->size = 1024*2;
|
||||
|
@ -491,6 +496,7 @@ Modified heavily for the ESP32 HUB75 DMA library by:
|
|||
_dmadesc_blank->owner = 1;
|
||||
_dmadesc_blank->qe.stqe_next = (lldesc_t*) _dmadesc_blank;
|
||||
_dmadesc_blank->offset = 0;
|
||||
*/
|
||||
|
||||
return true;
|
||||
|
||||
|
@ -595,23 +601,24 @@ Modified heavily for the ESP32 HUB75 DMA library by:
|
|||
|
||||
if ( buffer_id == 1) {
|
||||
|
||||
_dmadesc_a[_dmadesc_last].qe.stqe_next = &_dmadesc_b[0]; // Start sending out _dmadesc_b (or buffer 1)
|
||||
//fix _dmadesc_ loop issue #407
|
||||
//need to connect the up comming _dmadesc_ not the old one
|
||||
_dmadesc_b[_dmadesc_last].qe.stqe_next = &_dmadesc_b[0];
|
||||
|
||||
//fix _dmadesc_ loop issue #407
|
||||
//need to connect the up comming _dmadesc_ not the old one
|
||||
_dmadesc_b[_dmadesc_last].qe.stqe_next = &_dmadesc_b[0];
|
||||
_dmadesc_a[_dmadesc_last].qe.stqe_next = &_dmadesc_b[0]; // Start sending out _dmadesc_b (or buffer 1)
|
||||
|
||||
} else {
|
||||
|
||||
_dmadesc_b[_dmadesc_last].qe.stqe_next = &_dmadesc_a[0];
|
||||
|
||||
_dmadesc_a[_dmadesc_last].qe.stqe_next = &_dmadesc_a[0];
|
||||
|
||||
_dmadesc_b[_dmadesc_last].qe.stqe_next = &_dmadesc_a[0]; // Start sending out _dmadesc_a (or buffer 0)
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
previousBufferFree = false;
|
||||
//while (i2s_parallel_is_previous_buffer_free() == false) {}
|
||||
while (!previousBufferFree);
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -151,8 +151,10 @@ i2s_dev_t* getDev();
|
|||
HUB75_DMA_DESCRIPTOR_T* _dmadesc_a = nullptr;
|
||||
HUB75_DMA_DESCRIPTOR_T* _dmadesc_b = nullptr;
|
||||
|
||||
/*
|
||||
HUB75_DMA_DESCRIPTOR_T* _dmadesc_blank = nullptr;
|
||||
uint16_t _blank_data[1024] = {0};
|
||||
*/
|
||||
|
||||
volatile i2s_dev_t* _dev;
|
||||
|
||||
|
|
|
@ -29,12 +29,6 @@
|
|||
#include "esp_idf_version.h"
|
||||
|
||||
/*
|
||||
dma_descriptor_t desc; // DMA descriptor for testing
|
||||
|
||||
uint8_t data[8][312]; // Transmit buffer (2496 bytes total)
|
||||
uint16_t* dmabuff2;
|
||||
*/
|
||||
|
||||
DRAM_ATTR volatile bool previousBufferFree = true;
|
||||
|
||||
// End-of-DMA-transfer callback
|
||||
|
@ -58,6 +52,7 @@
|
|||
|
||||
return true;
|
||||
}
|
||||
*/
|
||||
|
||||
lcd_cam_dev_t* getDev()
|
||||
{
|
||||
|
@ -87,8 +82,6 @@
|
|||
LCD_CAM.lcd_user.lcd_reset = 1;
|
||||
esp_rom_delay_us(1000);
|
||||
|
||||
// uint32_t lcd_clkm_div_num = ((160000000 + 1) / _cfg.bus_freq);
|
||||
// ESP_LOGI("", "Clock divider is %d", lcd_clkm_div_num);
|
||||
|
||||
// Configure LCD clock. Since this program generates human-perceptible
|
||||
// output and not data for LED matrices or NeoPixels, use almost the
|
||||
|
@ -204,42 +197,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
const struct {
|
||||
int8_t pin;
|
||||
uint8_t signal;
|
||||
} mux[] = {
|
||||
{ 43, LCD_DATA_OUT0_IDX }, // These are 8 consecutive pins down one
|
||||
{ 42, LCD_DATA_OUT1_IDX }, // side of the ESP32-S3 Feather. The ESP32
|
||||
{ 2, LCD_DATA_OUT2_IDX }, // has super flexible pin MUX capabilities,
|
||||
{ 9, LCD_DATA_OUT3_IDX }, // so any signal can go to any pin!
|
||||
{ 10, LCD_DATA_OUT4_IDX },
|
||||
{ 11, LCD_DATA_OUT5_IDX },
|
||||
{ 12, LCD_DATA_OUT6_IDX },
|
||||
{ 13, LCD_DATA_OUT7_IDX },
|
||||
};
|
||||
for (int i = 0; i < 8; i++) {
|
||||
esp_rom_gpio_connect_out_signal(mux[i].pin, LCD_DATA_OUT0_IDX + i, false, false);
|
||||
gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[mux[i].pin], PIN_FUNC_GPIO);
|
||||
gpio_set_drive_capability((gpio_num_t)mux[i].pin, (gpio_drive_cap_t)3);
|
||||
}
|
||||
*/
|
||||
// Clock
|
||||
esp_rom_gpio_connect_out_signal(_cfg.pin_wr, LCD_PCLK_IDX, _cfg.invert_pclk, false);
|
||||
gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[_cfg.pin_wr], PIN_FUNC_GPIO);
|
||||
gpio_set_drive_capability((gpio_num_t)_cfg.pin_wr, (gpio_drive_cap_t)3);
|
||||
|
||||
// This program has a known fixed-size data buffer (2496 bytes) that fits
|
||||
// in a single DMA descriptor (max 4095 bytes). Large transfers would
|
||||
// require a linked list of descriptors, but here it's just one...
|
||||
|
||||
/*
|
||||
desc.dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA;
|
||||
desc.dw0.suc_eof = 0; // Last descriptor
|
||||
desc.next = &desc; // No linked list
|
||||
*/
|
||||
|
||||
// Remaining descriptor elements are initialized before each DMA transfer.
|
||||
|
||||
// Allocate DMA channel and connect it to the LCD peripheral
|
||||
static gdma_channel_alloc_config_t dma_chan_config = {
|
||||
|
@ -276,13 +238,14 @@
|
|||
gdma_set_transfer_ability(dma_chan, &ability);
|
||||
#endif
|
||||
|
||||
/*
|
||||
// Enable DMA transfer callback
|
||||
static gdma_tx_event_callbacks_t tx_cbs = {
|
||||
// .on_trans_eof is literally the only gdma tx event type available
|
||||
.on_trans_eof = gdma_on_trans_eof_callback
|
||||
};
|
||||
gdma_register_tx_event_callbacks(dma_chan, &tx_cbs, NULL);
|
||||
|
||||
*/
|
||||
|
||||
// This uses a busy loop to wait for each DMA transfer to complete...
|
||||
// but the whole point of DMA is that one's code can do other work in
|
||||
|
@ -406,6 +369,7 @@
|
|||
|
||||
if (_dmadesc_a_idx == _dmadesc_count-1) {
|
||||
_dmadesc_a[_dmadesc_a_idx].next = (dma_descriptor_t *) &_dmadesc_a[0];
|
||||
ESP_LOGV("S3", "Creating last _dmadesc_a descriptor which loops back to _dmadesc_a[0]!");
|
||||
}
|
||||
else {
|
||||
_dmadesc_a[_dmadesc_a_idx].next = (dma_descriptor_t *) &_dmadesc_a[_dmadesc_a_idx+1];
|
||||
|
@ -439,29 +403,21 @@
|
|||
|
||||
void Bus_Parallel16::flip_dma_output_buffer(int back_buffer_id)
|
||||
{
|
||||
|
||||
// if ( _double_dma_buffer == false) return;
|
||||
|
||||
|
||||
if ( back_buffer_id == 1) // change across to everything 'b''
|
||||
{
|
||||
_dmadesc_a[_dmadesc_count-1].next = (dma_descriptor_t *) &_dmadesc_b[0];
|
||||
_dmadesc_b[_dmadesc_count-1].next = (dma_descriptor_t *) &_dmadesc_b[0];
|
||||
_dmadesc_b[_dmadesc_count-1].next = (dma_descriptor_t *) &_dmadesc_b[0]; // setup loop
|
||||
_dmadesc_a[_dmadesc_count-1].next = (dma_descriptor_t *) &_dmadesc_b[0]; // flip across
|
||||
}
|
||||
else
|
||||
{
|
||||
_dmadesc_b[_dmadesc_count-1].next = (dma_descriptor_t *) &_dmadesc_a[0];
|
||||
_dmadesc_a[_dmadesc_count-1].next = (dma_descriptor_t *) &_dmadesc_a[0];
|
||||
_dmadesc_a[_dmadesc_count-1].next = (dma_descriptor_t *) &_dmadesc_a[0]; // setup loop
|
||||
_dmadesc_b[_dmadesc_count-1].next = (dma_descriptor_t *) &_dmadesc_a[0]; // flip across
|
||||
}
|
||||
|
||||
//current_back_buffer_id ^= 1;
|
||||
|
||||
/*
|
||||
previousBufferFree = false;
|
||||
|
||||
//while (i2s_parallel_is_previous_buffer_free() == false) {}
|
||||
while (!previousBufferFree);
|
||||
|
||||
|
||||
|
||||
*/
|
||||
|
||||
} // end flip
|
||||
|
||||
|
|
|
@ -167,7 +167,6 @@
|
|||
HUB75_DMA_DESCRIPTOR_T* _dmadesc_b = nullptr;
|
||||
|
||||
bool _double_dma_buffer = false;
|
||||
//bool _dmadesc_a_active = true;
|
||||
|
||||
esp_lcd_i80_bus_handle_t _i80_bus = nullptr;
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue