docs: Provide translation for rgb_lcd

pull/14033/head
shenmengjing 2024-05-28 16:15:02 +08:00 zatwierdzone przez BOT
rodzic e9adde3485
commit 11c2bb85a8
2 zmienionych plików z 374 dodań i 152 usunięć

Wyświetl plik

@ -1,18 +1,20 @@
RGB Interfaced LCD
==================
:link_to_translation:`zh_CN:[中文]`
RGB LCD panel is allocated in one step: :cpp:func:`esp_lcd_new_rgb_panel`, with various configurations specified by :cpp:type:`esp_lcd_rgb_panel_config_t`.
- :cpp:member:`esp_lcd_rgb_panel_config_t::clk_src` selects the clock source for the RGB LCD controller. The available clock sources are listed in :cpp:type:`lcd_clock_source_t`.
- :cpp:member:`esp_lcd_rgb_panel_config_t::data_width` set number of data lines used by the RGB interface. Currently, the supported value can be 8 or 16.
- :cpp:member:`esp_lcd_rgb_panel_config_t::bits_per_pixel` set the number of bits per pixel. This is different from :cpp:member:`esp_lcd_rgb_panel_config_t::data_width`. By default, if you set this field to 0, the driver will automatically adjust the bpp to the :cpp:member:`esp_lcd_rgb_panel_config_t::data_width`. But in some cases, these two value must be different. For example, a Serial RGB interface LCD only needs ``8`` data lines, but the color width can reach to ``RGB888``, i.e., the :cpp:member:`esp_lcd_rgb_panel_config_t::bits_per_pixel` should be set to ``24``.
- :cpp:member:`esp_lcd_rgb_panel_config_t::hsync_gpio_num`, :cpp:member:`esp_lcd_rgb_panel_config_t::vsync_gpio_num`, :cpp:member:`esp_lcd_rgb_panel_config_t::de_gpio_num`, :cpp:member:`esp_lcd_rgb_panel_config_t::pclk_gpio_num`, :cpp:member:`esp_lcd_rgb_panel_config_t::disp_gpio_num` and :cpp:member:`esp_lcd_rgb_panel_config_t::data_gpio_nums` are the GPIO pins used by the RGB LCD controller. If some of them are not used, please set it to `-1`.
- :cpp:member:`esp_lcd_rgb_panel_config_t::dma_burst_size` set the DMA transfer burst size, the value must be a power of 2.
- :cpp:member:`esp_lcd_rgb_panel_config_t::bounce_buffer_size_px` set the size of bounce buffer. This is only necessary for a so-called "bounce buffer" mode. Please refer to :ref:`bounce_buffer_with_single_psram_frame_buffer` for more information.
- :cpp:member:`esp_lcd_rgb_panel_config_t::data_width` sets number of data lines used by the RGB interface. Currently, the supported value can be 8 or 16.
- :cpp:member:`esp_lcd_rgb_panel_config_t::bits_per_pixel` sets the number of bits per pixel. This is different from :cpp:member:`esp_lcd_rgb_panel_config_t::data_width`. By default, if you set this field to 0, the driver will automatically adjust the bpp to the value set in :cpp:member:`esp_lcd_rgb_panel_config_t::data_width`. But in some cases, these two values must be different. For example, a serial RGB interfaced LCD only needs ``8`` data lines, but the color width can reach to ``RGB888``, i.e., the :cpp:member:`esp_lcd_rgb_panel_config_t::bits_per_pixel` should be set to ``24``.
- :cpp:member:`esp_lcd_rgb_panel_config_t::hsync_gpio_num`, :cpp:member:`esp_lcd_rgb_panel_config_t::vsync_gpio_num`, :cpp:member:`esp_lcd_rgb_panel_config_t::de_gpio_num`, :cpp:member:`esp_lcd_rgb_panel_config_t::pclk_gpio_num`, :cpp:member:`esp_lcd_rgb_panel_config_t::disp_gpio_num` and :cpp:member:`esp_lcd_rgb_panel_config_t::data_gpio_nums` are GPIO pins used by the RGB LCD controller. If any of them are not used, please set them to `-1`.
- :cpp:member:`esp_lcd_rgb_panel_config_t::dma_burst_size` sets the DMA transfer burst size. The value must be a power of 2.
- :cpp:member:`esp_lcd_rgb_panel_config_t::bounce_buffer_size_px` sets the size of bounce buffer. This is only necessary for a so-called "bounce buffer" mode. Please refer to :ref:`bounce_buffer_with_single_psram_frame_buffer` for more information.
- :cpp:member:`esp_lcd_rgb_panel_config_t::timings` sets the LCD panel specific timing parameters. All required parameters are listed in the :cpp:type:`esp_lcd_rgb_timing_t`, including the LCD resolution and blanking porches. Please fill them according to the datasheet of your LCD.
- :cpp:member:`esp_lcd_rgb_panel_config_t::fb_in_psram` sets whether to allocate the frame buffer from PSRAM or not. Please refer to :ref:`single_frame_buffer_in_psram` for more information.
- :cpp:member:`esp_lcd_rgb_panel_config_t::num_fbs` sets the number of frame buffers allocated by the driver. For backward compatibility, ``0`` means to allocate ``one`` frame buffer. Please use :cpp:member:`esp_lcd_rgb_panel_config_t::no_fb` if you do not want to allocate any frame buffer.
- :cpp:member:`esp_lcd_rgb_panel_config_t::no_fb` if sets, no frame buffer will be allocated. This is also called the :ref:`bounce_buffer_only` mode.
- :cpp:member:`esp_lcd_rgb_panel_config_t::no_fb` determines whether frame buffer will be allocated. When it is set, no frame buffer will be allocated. This is also called the :ref:`bounce_buffer_only` mode.
RGB LCD Frame Buffer Operation Modes
------------------------------------
@ -24,86 +26,86 @@ Single Frame Buffer in Internal Memory
This is the default and simplest and you do not have to specify flags or bounce buffer options. A frame buffer is allocated from the internal memory. The frame data is read out by DMA to the LCD verbatim. It needs no CPU intervention to function, but it has the downside that it uses up a fair bit of the limited amount of internal memory.
.. code:: c
.. code:: c
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_rgb_panel_config_t panel_config = {
.data_width = 16, // RGB565 in parallel mode, thus 16bit in width
.clk_src = LCD_CLK_SRC_DEFAULT,
.disp_gpio_num = EXAMPLE_PIN_NUM_DISP_EN,
.pclk_gpio_num = EXAMPLE_PIN_NUM_PCLK,
.vsync_gpio_num = EXAMPLE_PIN_NUM_VSYNC,
.hsync_gpio_num = EXAMPLE_PIN_NUM_HSYNC,
.de_gpio_num = EXAMPLE_PIN_NUM_DE,
.data_gpio_nums = {
EXAMPLE_PIN_NUM_DATA0,
EXAMPLE_PIN_NUM_DATA1,
EXAMPLE_PIN_NUM_DATA2,
// other GPIOs
// The number of GPIOs here should be the same to the value of `data_width` above
...
},
// The timing parameters should refer to your LCD spec
.timings = {
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
.h_res = EXAMPLE_LCD_H_RES,
.v_res = EXAMPLE_LCD_V_RES,
.hsync_back_porch = 40,
.hsync_front_porch = 20,
.hsync_pulse_width = 1,
.vsync_back_porch = 8,
.vsync_front_porch = 4,
.vsync_pulse_width = 1,
},
};
ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle));
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_rgb_panel_config_t panel_config = {
.data_width = 16, // RGB565 in parallel mode, thus 16 bits in width
.clk_src = LCD_CLK_SRC_DEFAULT,
.disp_gpio_num = EXAMPLE_PIN_NUM_DISP_EN,
.pclk_gpio_num = EXAMPLE_PIN_NUM_PCLK,
.vsync_gpio_num = EXAMPLE_PIN_NUM_VSYNC,
.hsync_gpio_num = EXAMPLE_PIN_NUM_HSYNC,
.de_gpio_num = EXAMPLE_PIN_NUM_DE,
.data_gpio_nums = {
EXAMPLE_PIN_NUM_DATA0,
EXAMPLE_PIN_NUM_DATA1,
EXAMPLE_PIN_NUM_DATA2,
// other GPIOs
// The number of GPIOs here should be the same to the value of "data_width" above
...
},
// The timing parameters should refer to your LCD spec
.timings = {
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
.h_res = EXAMPLE_LCD_H_RES,
.v_res = EXAMPLE_LCD_V_RES,
.hsync_back_porch = 40,
.hsync_front_porch = 20,
.hsync_pulse_width = 1,
.vsync_back_porch = 8,
.vsync_front_porch = 4,
.vsync_pulse_width = 1,
},
};
ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle));
.. _single_frame_buffer_in_psram:
Single Frame Buffer in PSRAM
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you have PSRAM and want to store the frame buffer there rather than in the limited internal memory, the LCD peripheral will use EDMA to fetch frame data directly from the PSRAM, bypassing the internal cache. You can enable this feature by setting the :cpp:member:`esp_lcd_rgb_panel_config_t::fb_in_psram` to ``true``. The downside of this is that when both the CPU as well as EDMA need access to the PSRAM, the bandwidth will be **shared** between them, that is, EDMA gets half and the CPUs get the other half. If there are other peripherals using EDMA as well, with a high enough pixel clock this can lead to starvation of the LCD peripheral, leading to display corruption. However, if the pixel clock is low enough for this not to be an issue, this is a solution that uses almost no CPU intervention.
If you have PSRAM and want to store the frame buffer there rather than in the limited internal memory, the LCD peripheral will use EDMA to fetch frame data directly from the PSRAM, bypassing the internal cache. You can enable this feature by setting the :cpp:member:`esp_lcd_rgb_panel_config_t::fb_in_psram` to ``true``. The downside of this is that when both the CPU as well as EDMA need access to the PSRAM, the bandwidth will be **shared** between them, that is, EDMA gets half and the CPU gets the other half. If there are other peripherals using EDMA as well, with a high enough pixel clock, they may cause starvation of the LCD peripheral, resulting in display corruption. However, if the pixel clock is low enough to avoid this issue, it provides a solution with minimal CPU intervention.
.. only:: esp32s3
.. only:: esp32s3
The PSRAM shares the same SPI bus with the main Flash (the one stores your firmware binary). At one time, there only be one consumer of the SPI bus. When you also use the main flash to serve your file system (e.g., :doc:`SPIFFS </api-reference/storage/spiffs>`), the bandwidth of the underlying SPI bus will also be shared, leading to display corruption. You can use :cpp:func:`esp_lcd_rgb_panel_set_pclk` to update the pixel clock frequency to a lower value.
The PSRAM shares the same SPI bus with the main flash (the one stores your firmware binary). At any given time, there can only be one consumer of the SPI bus. When you also use the main flash to serve your file system (e.g., :doc:`SPIFFS </api-reference/storage/spiffs>`), the bandwidth of the underlying SPI bus will also be shared, leading to display corruption. You can use :cpp:func:`esp_lcd_rgb_panel_set_pclk` to update the pixel clock frequency to a lower value.
.. code:: c
.. code:: c
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_rgb_panel_config_t panel_config = {
.data_width = 16, // RGB565 in parallel mode, thus 16bit in width
.clk_src = LCD_CLK_SRC_DEFAULT,
.disp_gpio_num = EXAMPLE_PIN_NUM_DISP_EN,
.pclk_gpio_num = EXAMPLE_PIN_NUM_PCLK,
.vsync_gpio_num = EXAMPLE_PIN_NUM_VSYNC,
.hsync_gpio_num = EXAMPLE_PIN_NUM_HSYNC,
.de_gpio_num = EXAMPLE_PIN_NUM_DE,
.data_gpio_nums = {
EXAMPLE_PIN_NUM_DATA0,
EXAMPLE_PIN_NUM_DATA1,
EXAMPLE_PIN_NUM_DATA2,
// other GPIOs
// The number of GPIOs here should be the same to the value of `data_width` above
...
},
// The timing parameters should refer to your LCD spec
.timings = {
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
.h_res = EXAMPLE_LCD_H_RES,
.v_res = EXAMPLE_LCD_V_RES,
.hsync_back_porch = 40,
.hsync_front_porch = 20,
.hsync_pulse_width = 1,
.vsync_back_porch = 8,
.vsync_front_porch = 4,
.vsync_pulse_width = 1,
},
.flags.fb_in_psram = true, // allocate frame buffer from PSRAM
};
ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle));
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_rgb_panel_config_t panel_config = {
.data_width = 16, // RGB565 in parallel mode, thus 16 bits in width
.clk_src = LCD_CLK_SRC_DEFAULT,
.disp_gpio_num = EXAMPLE_PIN_NUM_DISP_EN,
.pclk_gpio_num = EXAMPLE_PIN_NUM_PCLK,
.vsync_gpio_num = EXAMPLE_PIN_NUM_VSYNC,
.hsync_gpio_num = EXAMPLE_PIN_NUM_HSYNC,
.de_gpio_num = EXAMPLE_PIN_NUM_DE,
.data_gpio_nums = {
EXAMPLE_PIN_NUM_DATA0,
EXAMPLE_PIN_NUM_DATA1,
EXAMPLE_PIN_NUM_DATA2,
// other GPIOs
// The number of GPIOs here should be the same to the value of "data_width" above
...
},
// The timing parameters should refer to your LCD spec
.timings = {
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
.h_res = EXAMPLE_LCD_H_RES,
.v_res = EXAMPLE_LCD_V_RES,
.hsync_back_porch = 40,
.hsync_front_porch = 20,
.hsync_pulse_width = 1,
.vsync_back_porch = 8,
.vsync_front_porch = 4,
.vsync_pulse_width = 1,
},
.flags.fb_in_psram = true, // allocate frame buffer from PSRAM
};
ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle));
.. _double_frame_buffer_in_psram:
@ -112,92 +114,92 @@ Double Frame Buffer in PSRAM
To avoid tearing effect, using two screen sized frame buffers is the easiest approach. In this mode, the frame buffer can only be allocated from PSRAM, because of the limited internal memory. The frame buffer that the CPU write to and the frame buffer that the EDMA read from are guaranteed to be different and independent. The EDMA will only switch between the two frame buffers when the previous write operation is finished and the current frame has been sent to the LCD. The downside of this mode is that, you have to maintain the synchronization between the two frame buffers.
.. code:: c
.. code:: c
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_rgb_panel_config_t panel_config = {
.data_width = 16, // RGB565 in parallel mode, thus 16bit in width
.num_fbs = 2, // allocate double frame buffer
.clk_src = LCD_CLK_SRC_DEFAULT,
.disp_gpio_num = EXAMPLE_PIN_NUM_DISP_EN,
.pclk_gpio_num = EXAMPLE_PIN_NUM_PCLK,
.vsync_gpio_num = EXAMPLE_PIN_NUM_VSYNC,
.hsync_gpio_num = EXAMPLE_PIN_NUM_HSYNC,
.de_gpio_num = EXAMPLE_PIN_NUM_DE,
.data_gpio_nums = {
EXAMPLE_PIN_NUM_DATA0,
EXAMPLE_PIN_NUM_DATA1,
EXAMPLE_PIN_NUM_DATA2,
// other GPIOs
// The number of GPIOs here should be the same to the value of `data_width` above
...
},
// The timing parameters should refer to your LCD spec
.timings = {
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
.h_res = EXAMPLE_LCD_H_RES,
.v_res = EXAMPLE_LCD_V_RES,
.hsync_back_porch = 40,
.hsync_front_porch = 20,
.hsync_pulse_width = 1,
.vsync_back_porch = 8,
.vsync_front_porch = 4,
.vsync_pulse_width = 1,
},
.flags.fb_in_psram = true, // allocate frame buffer from PSRAM
};
ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle));
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_rgb_panel_config_t panel_config = {
.data_width = 16, // RGB565 in parallel mode, thus 16 bits in width
.num_fbs = 2, // allocate double frame buffer
.clk_src = LCD_CLK_SRC_DEFAULT,
.disp_gpio_num = EXAMPLE_PIN_NUM_DISP_EN,
.pclk_gpio_num = EXAMPLE_PIN_NUM_PCLK,
.vsync_gpio_num = EXAMPLE_PIN_NUM_VSYNC,
.hsync_gpio_num = EXAMPLE_PIN_NUM_HSYNC,
.de_gpio_num = EXAMPLE_PIN_NUM_DE,
.data_gpio_nums = {
EXAMPLE_PIN_NUM_DATA0,
EXAMPLE_PIN_NUM_DATA1,
EXAMPLE_PIN_NUM_DATA2,
// other GPIOs
// The number of GPIOs here should be the same to the value of "data_width" above
...
},
// The timing parameters should refer to your LCD spec
.timings = {
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
.h_res = EXAMPLE_LCD_H_RES,
.v_res = EXAMPLE_LCD_V_RES,
.hsync_back_porch = 40,
.hsync_front_porch = 20,
.hsync_pulse_width = 1,
.vsync_back_porch = 8,
.vsync_front_porch = 4,
.vsync_pulse_width = 1,
},
.flags.fb_in_psram = true, // allocate frame buffer from PSRAM
};
ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle));
.. _bounce_buffer_with_single_psram_frame_buffer:
Bounce Buffer with Single PSRAM Frame Buffer
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This mode allocates two so-called ``bounce buffers`` from the internal memory, and a main frame buffer that is still in PSRAM. This mode is selected by setting the :cpp:member:`esp_lcd_rgb_panel_config_t::fb_in_psram` flag and additionally specifying a non-zero :cpp:member:`esp_lcd_rgb_panel_config_t::bounce_buffer_size_px` value. The bounce buffers only need to be large enough to hold a few lines of display data, which is significantly less than the main frame buffer. The LCD peripheral uses DMA to read data from one of the bounce buffers, and meanwhile an interrupt routine uses the CPU DCache to copy data from the main PSRAM frame buffer into the other bounce buffer. Once the LCD peripheral has finished reading the bounce buffer, the two buffers change place and the CPU can fill the others. The advantage of this mode is that, you can achieve higher pixel clock frequency. As the bounce buffers are larger than the FIFOs in the EDMA path, this method is also more robust against short bandwidth spikes. The downside is a major increase in CPU use and the LCD **CAN NOT** work if we disable the cache of the external memory, via e.g., OTA or NVS write to the main flash.
This mode allocates two so-called ``bounce buffers`` from the internal memory, and a main frame buffer that is still in PSRAM. This mode is selected by setting the :cpp:member:`esp_lcd_rgb_panel_config_t::fb_in_psram` flag and additionally specifying a non-zero :cpp:member:`esp_lcd_rgb_panel_config_t::bounce_buffer_size_px` value. The bounce buffers only need to be large enough to hold a few lines of display data, which is significantly less than the main frame buffer. The LCD peripheral uses DMA to read data from one of the bounce buffers, and meanwhile an interrupt routine uses the CPU DCache to copy data from the main PSRAM frame buffer into the other bounce buffer. Once the LCD peripheral has finished reading the bounce buffer, the two buffers change place and the CPU can fill the others. The advantage of this mode is that, you can achieve higher pixel clock frequency. As the bounce buffers are larger than the FIFOs in the EDMA path, this method is also more robust against short bandwidth spikes. The downside is a major increase in CPU use and that the LCD **CAN NOT** work if we disable the cache of the external memory, via e.g., OTA or NVS write to the main flash.
.. note::
.. note::
It is highly recommended to turn on the "PSRAM XIP (Execute In Place)" feature in this mode by enabling the Kconfig options: :ref:`CONFIG_SPIRAM_FETCH_INSTRUCTIONS` and :ref:`CONFIG_SPIRAM_RODATA`, which allows the CPU to fetch instructions and readonly data from the PSRAM instead of the main flash. What is more, the external memory cache will not be disabled even if you attempt to write to the main flash through SPI1. This makes it possible to display an OTA progress bar for your application.
It is highly recommended to turn on the "PSRAM XIP (Execute In Place)" feature in this mode by enabling the Kconfig options: :ref:`CONFIG_SPIRAM_FETCH_INSTRUCTIONS` and :ref:`CONFIG_SPIRAM_RODATA`, which allows the CPU to fetch instructions and readonly data from the PSRAM instead of the main flash. What is more, the external memory cache will not be disabled even if you attempt to write to the main flash through SPI 1. This makes it possible to display an OTA progress bar for your application.
.. note::
.. note::
This mode still has another problem which is also caused by insufficient PSRAM bandwidth. e.g., when your draw buffers are allocated from PSRAM, and their contents are copied into the internal frame buffer on CPU core 1. On CPU core 0, there is another memory copy happening in the DMA EOF ISR. In this situation, both CPUs are accessing the PSRAM by cache and sharing the bandwidth of the PSRAM. This increases the memory copy time that spent in the DMA EOF ISR significantly. The driver can not switch the bounce buffer in time, thus leading to a shift on the LCD screen. Although the driver can detect such a condition and perform a restart in the LCD's VSYNC interrupt handler, you still can see a flickering on the screen.
This mode still has another problem which is also caused by insufficient PSRAM bandwidth. For example, when your draw buffers are allocated from PSRAM, and their contents are copied into the internal frame buffer on CPU Core 1, on CPU Core 0, there is another memory copy happening in the DMA EOF ISR. In this situation, both CPUs are accessing the PSRAM by cache and sharing the bandwidth of the PSRAM. This increases the memory copy time that spent in the DMA EOF ISR significantly. The driver can not switch the bounce buffer in time, thus leading to a shift on the LCD screen. Although the driver can detect such a condition and perform a restart in the LCD's VSYNC interrupt handler, you still can see a flickering on the screen.
.. code:: c
.. code:: c
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_rgb_panel_config_t panel_config = {
.data_width = 16, // RGB565 in parallel mode, thus 16bit in width
.clk_src = LCD_CLK_SRC_DEFAULT,
.bounce_buffer_size_px = 10 * EXAMPLE_LCD_H_RES, // allocate 10 lines data as bounce buffer from internal memory
.disp_gpio_num = EXAMPLE_PIN_NUM_DISP_EN,
.pclk_gpio_num = EXAMPLE_PIN_NUM_PCLK,
.vsync_gpio_num = EXAMPLE_PIN_NUM_VSYNC,
.hsync_gpio_num = EXAMPLE_PIN_NUM_HSYNC,
.de_gpio_num = EXAMPLE_PIN_NUM_DE,
.data_gpio_nums = {
EXAMPLE_PIN_NUM_DATA0,
EXAMPLE_PIN_NUM_DATA1,
EXAMPLE_PIN_NUM_DATA2,
// other GPIOs
// The number of GPIOs here should be the same to the value of `data_width` above
...
},
// The timing parameters should refer to your LCD spec
.timings = {
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
.h_res = EXAMPLE_LCD_H_RES,
.v_res = EXAMPLE_LCD_V_RES,
.hsync_back_porch = 40,
.hsync_front_porch = 20,
.hsync_pulse_width = 1,
.vsync_back_porch = 8,
.vsync_front_porch = 4,
.vsync_pulse_width = 1,
},
.flags.fb_in_psram = true, // allocate frame buffer from PSRAM
};
ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle));
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_rgb_panel_config_t panel_config = {
.data_width = 16, // RGB565 in parallel mode, thus 16 bits in width
.clk_src = LCD_CLK_SRC_DEFAULT,
.bounce_buffer_size_px = 10 * EXAMPLE_LCD_H_RES, // allocate 10 lines data as bounce buffer from internal memory
.disp_gpio_num = EXAMPLE_PIN_NUM_DISP_EN,
.pclk_gpio_num = EXAMPLE_PIN_NUM_PCLK,
.vsync_gpio_num = EXAMPLE_PIN_NUM_VSYNC,
.hsync_gpio_num = EXAMPLE_PIN_NUM_HSYNC,
.de_gpio_num = EXAMPLE_PIN_NUM_DE,
.data_gpio_nums = {
EXAMPLE_PIN_NUM_DATA0,
EXAMPLE_PIN_NUM_DATA1,
EXAMPLE_PIN_NUM_DATA2,
// other GPIOs
// The number of GPIOs here should be the same to the value of "data_width" above
...
},
// The timing parameters should refer to your LCD spec
.timings = {
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
.h_res = EXAMPLE_LCD_H_RES,
.v_res = EXAMPLE_LCD_V_RES,
.hsync_back_porch = 40,
.hsync_front_porch = 20,
.hsync_pulse_width = 1,
.vsync_back_porch = 8,
.vsync_front_porch = 4,
.vsync_pulse_width = 1,
},
.flags.fb_in_psram = true, // allocate frame buffer from PSRAM
};
ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle));
Note that this mode also allows for a :cpp:member:`esp_lcd_rgb_panel_config_t::bb_invalidate_cache` flag to be set. Enabling this frees up the cache lines after they are used to read out the frame buffer data from PSRAM, but it may lead to slight corruption if the other core writes data to the frame buffer at the exact time the cache lines are freed up. (Technically, a write to the frame buffer can be ignored if it falls between the cache writeback and the cache invalidate calls.)
@ -206,12 +208,12 @@ Note that this mode also allows for a :cpp:member:`esp_lcd_rgb_panel_config_t::b
Bounce Buffer Only
^^^^^^^^^^^^^^^^^^
This mode is similar to the :ref:`bounce_buffer_with_single_psram_frame_buffer`, but there is no PSRAM frame buffer initialized by the LCD driver. Instead, the user supplies a callback function that is responsible for filling the bounce buffers. As this driver does not care where the written pixels come from, this allows for the callback doing e.g., on-the-fly conversion from a smaller, 8-bit-per-pixel PSRAM frame buffer to an 16-bit LCD, or even procedurally-generated frame-buffer-less graphics. This option is selected by setting the :cpp:member:`esp_lcd_rgb_panel_config_t::no_fb` flag and supplying a :cpp:member:`esp_lcd_rgb_panel_config_t::bounce_buffer_size_px` value. And then register the :cpp:member:`esp_lcd_rgb_panel_event_callbacks_t::on_bounce_empty` callback by calling :cpp:func:`esp_lcd_rgb_panel_register_event_callbacks`.
This mode is similar to :ref:`bounce_buffer_with_single_psram_frame_buffer`, but there is no PSRAM frame buffer initialized by the LCD driver. Instead, the user supplies a callback function that is responsible for filling the bounce buffers. As this driver does not care where the written pixels come from, this allows for the callback doing e.g., on-the-fly conversion from a smaller, 8-bit-per-pixel PSRAM frame buffer to a 16-bit LCD, or even procedurally generated frame-buffer-less graphics. This option is selected by setting the :cpp:member:`esp_lcd_rgb_panel_config_t::no_fb` flag and supplying a :cpp:member:`esp_lcd_rgb_panel_config_t::bounce_buffer_size_px` value. And then register the :cpp:member:`esp_lcd_rgb_panel_event_callbacks_t::on_bounce_empty` callback by calling :cpp:func:`esp_lcd_rgb_panel_register_event_callbacks`.
.. note::
.. note::
It should never happen in a well-designed embedded application, but it can in theory be possible that the DMA cannot deliver data as fast as the LCD consumes it. In the {IDF_TARGET_NAME} hardware, this leads to the LCD simply outputting dummy bytes while DMA waits for data. If we were to run DMA in a stream fashion, this would mean a de-sync between the LCD address the DMA reads the data for and the LCD address the LCD peripheral thinks it outputs data for, leading to a **permanently** shifted image.
In order to stop this from happening, you can either enable the :ref:`CONFIG_LCD_RGB_RESTART_IN_VSYNC` option, so the driver can restart the DMA in the VBlank interrupt automatically or call :cpp:func:`esp_lcd_rgb_panel_restart` to restart the DMA manually. Note :cpp:func:`esp_lcd_rgb_panel_restart` does not restart the DMA immediately, the DMA is still restarted in the next VSYNC event.
In a well-designed embedded application, situations where the DMA can not deliver data as fast as the LCD consumes it should be avoided. However, such scenarios can happen in theory. In the {IDF_TARGET_NAME} hardware, this leads to the LCD simply outputting dummy bytes while DMA waits for data. If we were to run DMA in a stream fashion, a desynchronization between the LCD address for which the DMA reads the data and the LCD address for which the LCD peripheral outputs data would occur, leading to a **permanently** shifted image.
In order to stop this from happening, you can either enable the :ref:`CONFIG_LCD_RGB_RESTART_IN_VSYNC` option, so the driver can restart the DMA in the VBlank interrupt automatically, or call :cpp:func:`esp_lcd_rgb_panel_restart` to restart the DMA manually. Note that :cpp:func:`esp_lcd_rgb_panel_restart` does not restart the DMA immediately; instead, the DMA will be restarted in the next VSYNC event.
API Reference
-------------

Wyświetl plik

@ -1 +1,221 @@
.. include:: ../../../../en/api-reference/peripherals/lcd/rgb_lcd.rst
RGB 接口的 LCD
==================
:link_to_translation:`en:[English]`
RGB LCD 面板的分配步骤只需一步,即调用函数 :cpp:func:`esp_lcd_new_rgb_panel`,其中包含通过 :cpp:type:`esp_lcd_rgb_panel_config_t` 指定的各种配置。
- :cpp:member:`esp_lcd_rgb_panel_config_t::clk_src` 选择 RGB LCD 控制器的时钟源。可用的时钟源参见 :cpp:type:`lcd_clock_source_t`
- :cpp:member:`esp_lcd_rgb_panel_config_t::data_width` 设置 RGB 接口使用的数据线数量。目前支持 8 根或 16 根。
- :cpp:member:`esp_lcd_rgb_panel_config_t::bits_per_pixel` 设置每像素的位数 (bpp)。该字段与 :cpp:member:`esp_lcd_rgb_panel_config_t::data_width` 有所不同。默认情况下,如果将此字段设置为 0驱动程序会自动调整 bpp 与 :cpp:member:`esp_lcd_rgb_panel_config_t::data_width` 等值。但在某些情况下bpp 与数据线数量必须不同。例如,串行 RGB 接口的 LCD 只需要 ``8`` 条数据线,但颜色深度可以达到 ``RGB888``,此时 :cpp:member:`esp_lcd_rgb_panel_config_t::bits_per_pixel` 应设置为 ``24``
- :cpp:member:`esp_lcd_rgb_panel_config_t::hsync_gpio_num`:cpp:member:`esp_lcd_rgb_panel_config_t::vsync_gpio_num`:cpp:member:`esp_lcd_rgb_panel_config_t::de_gpio_num`:cpp:member:`esp_lcd_rgb_panel_config_t::pclk_gpio_num`:cpp:member:`esp_lcd_rgb_panel_config_t::disp_gpio_num` 以及 :cpp:member:`esp_lcd_rgb_panel_config_t::data_gpio_nums` 都是 RGB LCD 控制器使用的 GPIO 管脚。未使用到的管脚需设置为 `-1`
- :cpp:member:`esp_lcd_rgb_panel_config_t::dma_burst_size` 设置 DMA 突发传输大小,该值必须是 2 的幂次方。
- :cpp:member:`esp_lcd_rgb_panel_config_t::bounce_buffer_size_px` 设置弹性 buffer 的大小。仅在“弹性 buffer”模式下需要设置此字段。详情请参阅 :ref:`bounce_buffer_with_single_psram_frame_buffer`
- :cpp:member:`esp_lcd_rgb_panel_config_t::timings` 设置 LCD 面板的特定时序参数。包括 LCD 分辨率和消隐间隔在内的必要参数列表见 :cpp:type:`esp_lcd_rgb_timing_t`,请依据 LCD 技术规格书填写参数。
- :cpp:member:`esp_lcd_rgb_panel_config_t::fb_in_psram` 设置是否从 PSRAM 中分配 frame buffer。详情请参阅 :ref:`single_frame_buffer_in_psram`
- :cpp:member:`esp_lcd_rgb_panel_config_t::num_fbs` 设置由驱动程序分配的 frame buffer 的数量。为了向后兼容,``0`` 表示分配 ``一个`` frame buffer。如果不想分配任何 frame buffer请设置 :cpp:member:`esp_lcd_rgb_panel_config_t::no_fb`
- :cpp:member:`esp_lcd_rgb_panel_config_t::no_fb` 可决定是否分配 frame buffer。设置该字段后将不分配 frame buffer。这也被称为 :ref:`bounce_buffer_only` 模式。
RGB LCD frame buffer 操作模式
------------------------------
大多数情况下RGB LCD 驱动程序应至少维护一个屏幕大小的 frame buffer。根据 frame buffer 数量和位置的不同,驱动程序提供了几种不同的 buffer 模式。
内部存储器中的单 frame buffer 模式
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
该模式为默认模式,操作最简单,且无需指定标志或弹性 buffer 选项。从内部存储器中分配 frame buffer帧数据通过 DMA 直接读取到 LCD 上,无需 CPU 干预即可起效,但会占用相当一部分内部存储器。
.. code:: c
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_rgb_panel_config_t panel_config = {
.data_width = 16, // 并行模式下像素格式为 RGB565数据宽度为 16 位
.clk_src = LCD_CLK_SRC_DEFAULT,
.disp_gpio_num = EXAMPLE_PIN_NUM_DISP_EN,
.pclk_gpio_num = EXAMPLE_PIN_NUM_PCLK,
.vsync_gpio_num = EXAMPLE_PIN_NUM_VSYNC,
.hsync_gpio_num = EXAMPLE_PIN_NUM_HSYNC,
.de_gpio_num = EXAMPLE_PIN_NUM_DE,
.data_gpio_nums = {
EXAMPLE_PIN_NUM_DATA0,
EXAMPLE_PIN_NUM_DATA1,
EXAMPLE_PIN_NUM_DATA2,
// 其他 GPIO
// 此处 GPIO 的数量应与上文中 "data_width" 的值相同
...
},
// 参照 LCD 规格书,填写时序参数
.timings = {
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
.h_res = EXAMPLE_LCD_H_RES,
.v_res = EXAMPLE_LCD_V_RES,
.hsync_back_porch = 40,
.hsync_front_porch = 20,
.hsync_pulse_width = 1,
.vsync_back_porch = 8,
.vsync_front_porch = 4,
.vsync_pulse_width = 1,
},
};
ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle));
.. _single_frame_buffer_in_psram:
PSRAM 中的单 frame buffer
^^^^^^^^^^^^^^^^^^^^^^^^^
如果不想将 frame buffer 存储在有限的内部存储器中,而是将其存储在 PSRAM 中,则 LCD 外设将绕过内部 cache使用 EDMA 直接从 PSRAM 中获取帧数据。将 :cpp:member:`esp_lcd_rgb_panel_config_t::fb_in_psram` 设置为 ``true`` 就可以启用此功能。该模式的缺点在于,当 CPU 和 EDMA 同时需要访问 PSRAM 时,二者将 **共享** 带宽,即 CPU 与 EDMA 各自获取一半的带宽。若此时还有其他外设也在使用 EDMA并且像素时钟频率很高则可能导致 LCD 外设的带宽不足,造成显示损坏。但如果像素时钟频率较低,就不会出现这种问题,且只需极少的 CPU 干预。
.. only:: esp32s3
PSRAM 与主 flash用来存储固件二进制文件共享同一个 SPI 总线,但二者不能同时使用总线。若主 flash 还用来存储其他文件(例如,:doc:`SPIFFS </api-reference/storage/spiffs>`),则将共享底层 SPI 总线的带宽,造成显示损坏。此时可调用 :cpp:func:`esp_lcd_rgb_panel_set_pclk` 降低像素时钟频率。
.. code:: c
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_rgb_panel_config_t panel_config = {
.data_width = 16, // 并行模式下像素格式为 RGB565数据宽度为 16 位
.clk_src = LCD_CLK_SRC_DEFAULT,
.disp_gpio_num = EXAMPLE_PIN_NUM_DISP_EN,
.pclk_gpio_num = EXAMPLE_PIN_NUM_PCLK,
.vsync_gpio_num = EXAMPLE_PIN_NUM_VSYNC,
.hsync_gpio_num = EXAMPLE_PIN_NUM_HSYNC,
.de_gpio_num = EXAMPLE_PIN_NUM_DE,
.data_gpio_nums = {
EXAMPLE_PIN_NUM_DATA0,
EXAMPLE_PIN_NUM_DATA1,
EXAMPLE_PIN_NUM_DATA2,
// 其他 GPIO
// 此处 GPIO 的数量应与上文中 "data_width" 的值相同
...
},
// 参照 LCD 规格书,填写时序参数
.timings = {
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
.h_res = EXAMPLE_LCD_H_RES,
.v_res = EXAMPLE_LCD_V_RES,
.hsync_back_porch = 40,
.hsync_front_porch = 20,
.hsync_pulse_width = 1,
.vsync_back_porch = 8,
.vsync_front_porch = 4,
.vsync_pulse_width = 1,
},
.flags.fb_in_psram = true, // 从 PSRAM 中分配 frame buffer
};
ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle));
.. _double_frame_buffer_in_psram:
PSRAM 中的双 frame buffer
^^^^^^^^^^^^^^^^^^^^^^^^^
为避免 LCD 显示撕裂的问题,可以使用两个屏幕大小的 frame buffer。在这种模式下由于内部存储器空间有限因而只能从 PSRAM 中分配 frame buffer。CPU 写入的 frame buffer 和 EDMA 读取的 frame buffer 是完全不同且相互独立的两个区域。只有当写入操作完成、且当前帧已发送到 LCD 时EDMA 才会在两个 frame buffer 之间切换。该模式的缺点在于,必须确保两个 frame buffer 之间同步。
.. code:: c
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_rgb_panel_config_t panel_config = {
.data_width = 16, // 并行模式下像素格式为 RGB565数据宽度为 16 位
.num_fbs = 2, // 分配双 frame buffer
.clk_src = LCD_CLK_SRC_DEFAULT,
.disp_gpio_num = EXAMPLE_PIN_NUM_DISP_EN,
.pclk_gpio_num = EXAMPLE_PIN_NUM_PCLK,
.vsync_gpio_num = EXAMPLE_PIN_NUM_VSYNC,
.hsync_gpio_num = EXAMPLE_PIN_NUM_HSYNC,
.de_gpio_num = EXAMPLE_PIN_NUM_DE,
.data_gpio_nums = {
EXAMPLE_PIN_NUM_DATA0,
EXAMPLE_PIN_NUM_DATA1,
EXAMPLE_PIN_NUM_DATA2,
// 其他 GPIO
// 此处 GPIO 的数量应与上文中 "data_width" 的值相同
...
},
// 参照 LCD 规格书,填写时序参数
.timings = {
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
.h_res = EXAMPLE_LCD_H_RES,
.v_res = EXAMPLE_LCD_V_RES,
.hsync_back_porch = 40,
.hsync_front_porch = 20,
.hsync_pulse_width = 1,
.vsync_back_porch = 8,
.vsync_front_porch = 4,
.vsync_pulse_width = 1,
},
.flags.fb_in_psram = true, // 从 PSRAM 中分配 frame buffer
};
ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle));
.. _bounce_buffer_with_single_psram_frame_buffer:
bounce buffer 与 PSRAM frame buffer
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
在该模式下,从内部存储器中分配出两个 ``bounce buffer`` 和一个位于 PSRAM 中的主 frame buffer。若想选择此模式可设置 :cpp:member:`esp_lcd_rgb_panel_config_t::fb_in_psram` 标志并额外指定 :cpp:member:`esp_lcd_rgb_panel_config_t::bounce_buffer_size_px`非零。bounce buffer 只要能容纳几行显示数据即可,存储量远远低于主 frame buffer。LCD 可通过 DMA 从其中一个 bounce buffer 里读取数据,与此同时中断例程通过 CPU DCache 将数据从主 PSRAM frame buffer 复制到另一个 bounce buffer 中。一旦 LCD 完成对 bounce buffer 的数据读取,两个 buffer 将交换位置CPU 可以填充另一个 bounce buffer。这种模式的优点在于可以实现更高的像素时钟频率。bounce buffer 的存储量比 EDMA 路径中的 FIFO 大,即便短时间内带宽需求激增,该模式下 bounce buffer 也能有效应对。而缺点在于CPU 使用量大幅增加,并且如果禁用外部存储器的 cache例如禁止通过 OTA 或 NVS 写入主 flashLCD 将 **无法** 工作。
.. note::
强烈建议在此模式下启用 Kconfig 选项::ref:`CONFIG_SPIRAM_FETCH_INSTRUCTIONS`:ref:`CONFIG_SPIRAM_RODATA`开启“PSRAM XIP就地执行”功能使 CPU 能从 PSRAM 里而不是主 flash 中提取指令和只读数据。此外,即使想通过 SPI 1 写入主 flash外部存储器 cache 也不会被禁用,应用程序便能正常显示 OTA 进度条。
.. note::
由于 PSRAM 带宽不足,此模式还可能存在另一个问题。例如,从 PSRAM 中分配绘图 buffer且 buffer 中的数据被复制到 CPU 核 1 上的内部 frame buffer 中,此时在 CPU 核 0 上DMA EOF ISR 也在进行内存复制。这种情况下,两个内核都通过 cache 访问 PSRAM 并共享 PSRAM 的带宽DMA EOF ISR 复制内存的时间大大增加。驱动程序无法及时切换 bounce buffer造成 LCD 屏幕移位。尽管驱动程序可以检测到这种情况并在 LCD 的 VSYNC 中断处理程序中执行重新启动,但仍会出现屏幕闪烁现象。
.. code:: c
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_rgb_panel_config_t panel_config = {
.data_width = 16, // 并行模式下像素格式为 RGB565数据宽度为 16 位
.clk_src = LCD_CLK_SRC_DEFAULT,
.bounce_buffer_size_px = 10 * EXAMPLE_LCD_H_RES, // 从内部存储器中分配 bounce buffer足够存储 10 行数据
.disp_gpio_num = EXAMPLE_PIN_NUM_DISP_EN,
.pclk_gpio_num = EXAMPLE_PIN_NUM_PCLK,
.vsync_gpio_num = EXAMPLE_PIN_NUM_VSYNC,
.hsync_gpio_num = EXAMPLE_PIN_NUM_HSYNC,
.de_gpio_num = EXAMPLE_PIN_NUM_DE,
.data_gpio_nums = {
EXAMPLE_PIN_NUM_DATA0,
EXAMPLE_PIN_NUM_DATA1,
EXAMPLE_PIN_NUM_DATA2,
// 其他 GPIO
// 此处 GPIO 的数量应与上文中 "data_width" 的值相同
...
},
// 参照 LCD 规格书,填写时序参数
.timings = {
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
.h_res = EXAMPLE_LCD_H_RES,
.v_res = EXAMPLE_LCD_V_RES,
.hsync_back_porch = 40,
.hsync_front_porch = 20,
.hsync_pulse_width = 1,
.vsync_back_porch = 8,
.vsync_front_porch = 4,
.vsync_pulse_width = 1,
},
.flags.fb_in_psram = true, // 从 PSRAM 中分配 frame buffer
};
ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle));
请注意,此模式下还可以设置 :cpp:member:`esp_lcd_rgb_panel_config_t::bb_invalidate_cache` 标志。启用此功能,从 PSRAM 中读取 frame buffer 数据后可以释放 cache 行。但如果在 cache 行被释放时,另一个内核恰好将数据写入 frame buffer 中,则可能导致轻微的损坏(从技术上讲,在 cache 写回和调用失效之间的时间窗口内,对 frame buffer 的写入操作会被忽略)。
.. _bounce_buffer_only:
只应用 bounce buffer
^^^^^^^^^^^^^^^^^^^^
该模式与 :ref:`bounce_buffer_with_single_psram_frame_buffer` 模式类似,但 LCD 驱动程序不会初始化 PSRAM frame buffer。相反该模式依赖用户提供的回调函数来填充 bounce buffer。LCD 驱动程序无需指定写入像素的来源,因此回调函数可以执行一些操作:例如,将较小的每像素 8 位 PSRAM frame buffer 即时转换为 16 位 LCD 数据,甚至还可以转换为无 frame buffer 图形。若想选择此模式,可以设置 :cpp:member:`esp_lcd_rgb_panel_config_t::no_fb` 标志并提供 :cpp:member:`esp_lcd_rgb_panel_config_t::bounce_buffer_size_px` 值。然后通过调用 :cpp:func:`esp_lcd_rgb_panel_register_event_callbacks` 注册回调函数 :cpp:member:`esp_lcd_rgb_panel_event_callbacks_t::on_bounce_empty`
.. note::
虽说在设计良好的嵌入式应用程序中, DMA 传递数据的速度不应该赶不上 LCD 读取数据的速度。但理论上,此种情况还是有可能出现的。在 {IDF_TARGET_NAME} 的硬件中,这种情况会导致 LCD 在 DMA 等待数据时单纯输出 dummy 字节。若以流式传输运行 DMA则 DMA 会将读取到的数据传输到某个 LCD 地址,同时 LCD 也会将数据输出到某个 LCD 地址,但上述两个地址可能会不同步,导致图像 **永久** 偏移。
为防止类似情况发生,可以启用 :ref:`CONFIG_LCD_RGB_RESTART_IN_VSYNC` 选项,以便驱动程序在 VBlank 中断时自动重启 DMA或者也可以调用 :cpp:func:`esp_lcd_rgb_panel_restart`,手动重启 DMA。请注意调用 :cpp:func:`esp_lcd_rgb_panel_restart` 不会立即重启 DMADMA 只会在下一个 VSYNC 事件中重启。
API 参考
--------
.. include-build-file:: inc/esp_lcd_panel_rgb.inc