espressif / esp-idf

Espressif IoT Development Framework. Official development framework for Espressif SoCs.
Apache License 2.0
13.29k stars 7.2k forks source link

esp_lcd_panel_draw_bitmap produces artifacts without vTaskDelay min 10 / portTICK_PERIOD_MS (IDFGH-11188) #12355

Open greenaddress opened 11 months ago

greenaddress commented 11 months ago

Answers checklist.

IDF version.

v5.1.1

Espressif SoC revision.

ESP32-D0WDQ6-V3 (revision v3.0)

Operating System used.

Linux

How did you build your project?

Command line with idf.py

If you are using Windows, please specify command line type.

None

Development Kit.

ESP32 T-Display

Power Supply used.

USB

What is the expected behavior?

Expected behavior is the display shows is black with a green filled rectangle in the middle.

What is the actual behavior?

Actual behavior is that the display gets artifacts: in the last line drawn of the rectangle one or more pixels have not been painted - at the same those green pixels appear on the last row of the display where they were not intended to be.

If we add a vTaskDelay(10 / portTICK_PERIOD_MS); every so often of esp_lcd_panel_draw_bitmap then the artifact is not visible.

Notice that only one task is running and that this occurs with and without mutex. Notable it occurs even if running in single core mode.

Steps to reproduce.

  1. Run code attached with the vTaskDelay commented and without, you will see the artifacts

Debug Logs.

No response

More Information.

No response

greenaddress commented 11 months ago

Code sample to reproduce

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include <inttypes.h>
#include <stdio.h>
#include <string.h>

#include "esp_timer.h"
#include <driver/gpio.h>
#include <driver/spi_master.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <esp_lcd_panel_vendor.h>
#include <esp_random.h>
#include <freertos/semphr.h>

#define DISP_PIN_NUM_MISO 2
#define DISP_PIN_NUM_MOSI 19
#define DISP_PIN_NUM_CLK 18
#define DISP_PIN_NUM_CS 5
#define DISP_PIN_NUM_DC 16
#define DISP_PIN_NUM_TCS -1
#define DISP_PIN_NUM_RST 23
#define DISP_PIN_NUM_BCKL 4

#define DISP_X_GAP 40
#define DISP_Y_GAP 53
#define DISP_WIDTH 240
#define DISP_HEIGHT 135
#define LCD_PIXEL_CLOCK_HZ 10000000

#define PIXEL_SIZE uint16_t

static esp_lcd_panel_handle_t panel_handle = NULL;
static SemaphoreHandle_t esp_lcd_mutex = NULL;
static DMA_ATTR PIXEL_SIZE display_buffer[DISP_WIDTH];

void prepare_display(void)
{

    spi_bus_config_t buscfg = {
        .sclk_io_num = DISP_PIN_NUM_CLK,
        .mosi_io_num = DISP_PIN_NUM_MOSI,
        .miso_io_num = DISP_PIN_NUM_MISO,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
        .max_transfer_sz = DISP_WIDTH * sizeof(PIXEL_SIZE),
    };

    ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));

    esp_lcd_panel_io_handle_t io_handle = NULL;
    esp_lcd_panel_io_spi_config_t io_config = {
        .dc_gpio_num = DISP_PIN_NUM_DC,
        .cs_gpio_num = DISP_PIN_NUM_CS,
        .pclk_hz = LCD_PIXEL_CLOCK_HZ,
        .lcd_cmd_bits = 8,
        .lcd_param_bits = 8,
        .spi_mode = 0,
        .trans_queue_depth = 10,
    };

    ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)SPI2_HOST, &io_config, &io_handle));

    esp_lcd_panel_dev_config_t panel_config = {
        .reset_gpio_num = DISP_PIN_NUM_RST,
        .rgb_endian = LCD_RGB_ENDIAN_RGB,
        .bits_per_pixel = sizeof(PIXEL_SIZE) * 8,
    };

    ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));
    gpio_config_t bk_gpio_config = { .mode = GPIO_MODE_OUTPUT, .pin_bit_mask = 1ULL << DISP_PIN_NUM_BCKL };
    ESP_ERROR_CHECK(gpio_config(&bk_gpio_config));
    ESP_ERROR_CHECK(gpio_set_level(DISP_PIN_NUM_BCKL, 0));
    ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
    ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
    ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
    ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_handle, true));
    ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, true, false));
    ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true));
    ESP_ERROR_CHECK(esp_lcd_panel_set_gap(panel_handle, DISP_X_GAP, DISP_Y_GAP));
    ESP_ERROR_CHECK(gpio_set_level(DISP_PIN_NUM_BCKL, 1));
}

static void fill_rect(int x, int y, int w, int h, PIXEL_SIZE color)
{
    if (xSemaphoreTake(esp_lcd_mutex, 500 / portTICK_PERIOD_MS) == pdTRUE) {
        for (int i = 0; i < w; ++i) {
            display_buffer[i] = color;
        }
        const int startx = x - DISP_X_GAP;
        const int endx = startx + w;
        for (int i = 0; i < h; ++i) {
            const int starty = (y - DISP_Y_GAP) + i;
            const int endy = starty + 1;
            ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(panel_handle, startx, starty, endx, endy, display_buffer));
        }
        xSemaphoreGive(esp_lcd_mutex);
    }
    // FIXME: without this delay you get artifacts on the display
    vTaskDelay(10 / portTICK_PERIOD_MS);
}

void app_main(void)
{
    /* mutex can be removed if using the display from just one task */
    esp_lcd_mutex = xSemaphoreCreateMutex();
    prepare_display();
    fill_rect(40, 53, 240, 135, 0);
    fill_rect(40, 73, 240, 111, 0);
    fill_rect(40, 97, 240, 44, 19460);
    fill_rect(64, 97, 48, 44, 19460);
    fill_rect(112, 97, 144, 44, 19460);
    fill_rect(40, 161, 21, 26, 0);
    fill_rect(61, 161, 105, 26, 0);
    fill_rect(166, 161, 112, 26, 0);
    fill_rect(40, 53, 240, 24, 0);
    fill_rect(40, 53, 160, 24, 0);
    vTaskSuspend(NULL);
}
kriegste commented 11 months ago

I had a similar problem. You overwrite the buffer with new data while it is still not finished drawing (DMA).

greenaddress commented 11 months ago

@kriegste thanks, indeed.

For what is worth this happens regardless of weather the buffer I pass in esp_lcd_panel_draw_bitmap is DMA allocated memory.

Internally the code, if you don't provide a DMA buffer but have setup the display with SPI_DMA_CH_AUTO, seems to copy the data over a DMA buffer anyway and then send that off to the display.

In any case there is some bug somewhere in the DMA handling in esp_lcd or spi master.

kriegste commented 11 months ago

Try using a second buffer and alternate between them:

fill buffer 0 draw buffer 0 fill buffer 1 draw buffer 1 fill buffer 0 ...

greenaddress commented 11 months ago

@kriegste Unfortunately it still happens even if I alternate buffers with a flag or even if I allocate a new one for each drawing (both caps DMA and not) and free it after.

suda-morris commented 11 months ago

You overwrite the buffer with new data while it is still not finished drawing (DMA).

This explanation is correct. If you want to reuse the same draw buffer, please make sure it's old data has been send out by the DMA. You can register a "color trans done" callback by esp_lcd_panel_io_register_event_callbacks.