chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
MIPI DSI Interfaced LCD
|
||||
=======================
|
||||
|
||||
:link_to_translation:`zh_CN:[中文]`
|
||||
|
||||
#. Create a DSI bus, and it will initialize the D-PHY as well.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
esp_lcd_dsi_bus_handle_t mipi_dsi_bus = NULL;
|
||||
esp_lcd_dsi_bus_config_t bus_config = {
|
||||
.bus_id = 0, // index from 0, specify the DSI host to use
|
||||
.num_data_lanes = 2, // Number of data lanes to use, can't set a value that exceeds the chip's capability
|
||||
.lane_bit_rate_mbps = EXAMPLE_MIPI_DSI_LANE_BITRATE_MBPS, // Bit rate of the data lanes, in Mbps
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus));
|
||||
|
||||
#. Derive the DBI interface from the DSI bus. The DBI interface mostly is used as the control IO layer in the esp_lcd component. This interface provides the functions to read or write the configuration registers inside the LCD device. In this step, you need to provide the following information:
|
||||
|
||||
- :cpp:member:`esp_lcd_dbi_io_config_t::virtual_channel` sets the virtual channel number to use. The virtual channel is a logical channel that is used to multiplex the data from different sources. If you only have one LCD connected, you can set this to ``0``.
|
||||
- :cpp:member:`esp_lcd_dbi_io_config_t::lcd_cmd_bits` and :cpp:member:`esp_lcd_dbi_io_config_t::lcd_param_bits` set the bit width of the command and parameter that recognized by the LCD controller chip. This is chip specific, you should refer to your LCD spec in advance.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
esp_lcd_panel_io_handle_t mipi_dbi_io = NULL;
|
||||
esp_lcd_dbi_io_config_t dbi_config = {
|
||||
.virtual_channel = 0,
|
||||
.lcd_cmd_bits = 8, // according to the LCD spec
|
||||
.lcd_param_bits = 8, // according to the LCD spec
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &mipi_dbi_io));
|
||||
|
||||
#. Install the LCD controller driver. The LCD controller driver is responsible for sending the commands and parameters to the LCD controller chip. In this step, you need to specify the MIPI DBI IO handle that allocated in the last step, and some panel specific configurations:
|
||||
|
||||
- :cpp:member:`esp_lcd_panel_dev_config_t::bits_per_pixel` sets the bit width of each pixel. The LCD driver uses this value to calculate the number of bytes to send to the LCD controller chip.
|
||||
- :cpp:member:`esp_lcd_panel_dev_config_t::reset_gpio_num` sets the GPIO number of the reset pin. If the LCD controller chip does not have a reset pin, you can set this value to ``-1``.
|
||||
- :cpp:member:`esp_lcd_panel_dev_config_t::rgb_ele_order` sets the RGB element order of the pixel data, it can be **RGB** or **BGR**.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
esp_lcd_panel_handle_t ili9881c_ctrl_panel = NULL;
|
||||
esp_lcd_panel_dev_config_t lcd_dev_config = {
|
||||
.bits_per_pixel = 24, // MIPI LCD usually uses 24 bit (i.e., RGB888) per pixel
|
||||
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
|
||||
.reset_gpio_num = EXAMPLE_PIN_NUM_LCD_RST,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_ili9881c(mipi_dbi_io, &lcd_dev_config, &ili9881c_ctrl_panel));
|
||||
|
||||
#. With the LCD control panel that returned in the last step, you can reset the LCD device followed by a basic initialization. After that, you can turn on the display.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_reset(ili9881c_ctrl_panel));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_init(ili9881c_ctrl_panel));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(ili9881c_ctrl_panel, true));
|
||||
|
||||
#. However, you still can't send pixel data to the MIPI LCD with the control panel, because MIPI LCD has a high resolution and there's no GRAM in the LCD controller. We need to maintain the LCD frame buffer and flush it to the LCD via the MIPI DSI DPI interface. To allocate a DPI data panel, you need to provide many essential parameters, including the DPI clock frequency, the pixel format, the video timing, and so on.
|
||||
|
||||
- :cpp:member:`esp_lcd_dpi_panel_config_t::virtual_channel` sets the virtual channel number to use. Like the DBI interface, we also need to set the virtual channel for the DPI interface. If you only have one LCD connected, you can set this to ``0``.
|
||||
- :cpp:member:`esp_lcd_dpi_panel_config_t::dpi_clk_src` sets the clock source for the DPI interface. The available clock sources are listed in :cpp:type:`mipi_dsi_dpi_clock_source_t`.
|
||||
- :cpp:member:`esp_lcd_dpi_panel_config_t::dpi_clock_freq_mhz` sets the DPI clock frequency in MHz. Higher pixel clock frequency results in higher refresh rate, but may cause flickering if the DMA bandwidth is not sufficient or the LCD controller chip does not support high pixel clock frequency.
|
||||
- :cpp:member:`esp_lcd_dpi_panel_config_t::in_color_format` sets the pixel format of the input pixel data. The available pixel formats are listed in :cpp:type:`lcd_color_format_t`. We usually use **RGB888** for MIPI LCD to get the best color depth.
|
||||
- :cpp:member:`esp_lcd_dpi_panel_config_t::video_timing` sets the LCD panel specific timing parameters. All required parameters are listed in the :cpp:type:`esp_lcd_video_timing_t`, including the LCD resolution and blanking porches. Please fill them according to the datasheet of your LCD.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
esp_lcd_panel_handle_t mipi_dpi_panel = NULL;
|
||||
esp_lcd_dpi_panel_config_t dpi_config = {
|
||||
.virtual_channel = 0,
|
||||
.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT,
|
||||
.dpi_clock_freq_mhz = 1 * 1000,
|
||||
.in_color_format = LCD_COLOR_FMT_RGB888,
|
||||
.video_timing = {
|
||||
.h_size = EXAMPLE_MIPI_DSI_LCD_H_RES,
|
||||
.v_size = EXAMPLE_MIPI_DSI_LCD_V_RES,
|
||||
.hsync_back_porch = EXAMPLE_MIPI_DSI_LCD_HBP,
|
||||
.hsync_pulse_width = EXAMPLE_MIPI_DSI_LCD_HSYNC,
|
||||
.hsync_front_porch = EXAMPLE_MIPI_DSI_LCD_HFP,
|
||||
.vsync_back_porch = EXAMPLE_MIPI_DSI_LCD_VBP,
|
||||
.vsync_pulse_width = EXAMPLE_MIPI_DSI_LCD_VSYNC,
|
||||
.vsync_front_porch = EXAMPLE_MIPI_DSI_LCD_VFP,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_dpi(mipi_dsi_bus, &dpi_config, &mipi_dpi_panel));
|
||||
ESP_ERROR_CHECK(esp_lcd_panel_init(mipi_dpi_panel));
|
||||
|
||||
#. Configure draw bitmap hook function (optional)
|
||||
|
||||
If you want to use DMA2D to implement draw bitmap, the driver has already implemented the DMA2D draw bitmap hook function, you only need to call :func:`esp_lcd_dpi_panel_enable_dma2d` to enable it.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
ESP_ERROR_CHECK(esp_lcd_dpi_panel_enable_dma2d(mipi_dpi_panel));
|
||||
|
||||
.. note::
|
||||
|
||||
Due to hardware limitation, if external memory encryption is enabled, DMA2D can only access address and length that are aligned to {IDF_TARGET_SOC_MEMSPI_ENCRYPTION_ALIGNMENT} bytes. You need to ensure that your draw buffer's address and length are aligned to {IDF_TARGET_SOC_MEMSPI_ENCRYPTION_ALIGNMENT} bytes. :example:`peripherals/lcd/mipi_dsi` shows how to use LVGL to constrain the redrawn area to ensure alignment.
|
||||
|
||||
If you need more advanced applications, you can add a custom hook for draw bitmap, such as using PPA to implement rotation, scaling, etc.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
esp_lcd_panel_hooks_t hooks = {
|
||||
.draw_bitmap_hook = custom_draw_bitmap_hook,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_dpi_panel_register_hooks(mipi_dpi_panel, &hooks, &user_ctx));
|
||||
|
||||
Power Supply for MIPI DPHY
|
||||
--------------------------
|
||||
|
||||
The MIPI DPHY on {IDF_TARGET_NAME} requires a dedicated 2.5V power supply. Please refer to your schematic and ensure that the power pin (often labeled ``VDD_MIPI_DPHY``) is properly connected to a 2.5V power source before using the MIPI DSI driver.
|
||||
|
||||
.. only:: SOC_GP_LDO_SUPPORTED
|
||||
|
||||
On {IDF_TARGET_NAME}, the MIPI DPHY can be powered by the internal adjustable LDO. Connect the output pin of the LDO channel to the MIPI DPHY power pin. Before initializing the DSI driver, use the API provided in :doc:`/api-reference/peripherals/ldo_regulator` to configure the LDO output voltage to 2.5V.
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
.. include-build-file:: inc/esp_lcd_mipi_dsi.inc
|
||||
@@ -0,0 +1,58 @@
|
||||
I2C Interfaced LCD
|
||||
==================
|
||||
|
||||
:link_to_translation:`zh_CN:[中文]`
|
||||
|
||||
#. Create I2C bus. Please refer to :doc:`I2C API doc </api-reference/peripherals/i2c>` for more details.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
i2c_master_bus_handle_t i2c_bus = NULL;
|
||||
i2c_master_bus_config_t bus_config = {
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.glitch_ignore_cnt = 7,
|
||||
.i2c_port = I2C_BUS_PORT,
|
||||
.sda_io_num = EXAMPLE_PIN_NUM_SDA,
|
||||
.scl_io_num = EXAMPLE_PIN_NUM_SCL,
|
||||
.flags.enable_internal_pullup = true,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &i2c_bus));
|
||||
|
||||
#. Allocate an LCD IO device handle from the I2C bus. In this step, you need to provide the following information:
|
||||
|
||||
- :cpp:member:`esp_lcd_panel_io_i2c_config_t::dev_addr` sets the I2C device address of the LCD controller chip. The LCD driver uses this address to communicate with the LCD controller chip.
|
||||
- :cpp:member:`esp_lcd_panel_io_i2c_config_t::scl_speed_hz` sets the I2C clock frequency in Hz. The value should not exceed the range recommended in the LCD spec.
|
||||
- :cpp:member:`esp_lcd_panel_io_i2c_config_t::lcd_cmd_bits` and :cpp:member:`esp_lcd_panel_io_i2c_config_t::lcd_param_bits` set the bit width of the command and parameter recognized by the LCD controller chip. This is chip specific, you should refer to your LCD spec in advance.
|
||||
- :cpp:member:`esp_lcd_panel_io_i2c_config_t::transaction_timeout_ms` sets the timeout (in milliseconds) for each underlying I2C transfer. Setting this to 0 or -1 means to wait indefinitely. If a positive value is specified, panel IO calls will return ``ESP_ERR_TIMEOUT`` when the timeout is reached. This is useful for cases like shared buses or when a slave device could potentially hang the bus.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
esp_lcd_panel_io_handle_t io_handle = NULL;
|
||||
esp_lcd_panel_io_i2c_config_t io_config = {
|
||||
.dev_addr = EXAMPLE_I2C_HW_ADDR,
|
||||
.scl_speed_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
|
||||
.control_phase_bytes = 1, // refer to LCD spec
|
||||
.dc_bit_offset = 6, // refer to LCD spec
|
||||
.lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS,
|
||||
.lcd_param_bits = EXAMPLE_LCD_CMD_BITS,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus, &io_config, &io_handle));
|
||||
|
||||
#. Install the LCD controller driver. The LCD controller driver is responsible for sending the commands and parameters to the LCD controller chip. In this step, you need to specify the I2C IO device handle that allocated in the last step, and some panel specific configurations:
|
||||
|
||||
- :cpp:member:`esp_lcd_panel_dev_config_t::reset_gpio_num` sets the LCD's hardware reset GPIO number. If the LCD does not have a hardware reset pin, set this to ``-1``.
|
||||
- :cpp:member:`esp_lcd_panel_dev_config_t::bits_per_pixel` sets the bit width of the pixel color data. The LCD driver uses this value to calculate the number of bytes to send to the LCD controller chip.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
esp_lcd_panel_handle_t panel_handle = NULL;
|
||||
esp_lcd_panel_dev_config_t panel_config = {
|
||||
.bits_per_pixel = 1,
|
||||
.reset_gpio_num = EXAMPLE_PIN_NUM_RST,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(io_handle, &panel_config, &panel_handle));
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
.. include-build-file:: inc/esp_lcd_io_i2c.inc
|
||||
@@ -0,0 +1,88 @@
|
||||
I80 Interfaced LCD
|
||||
==================
|
||||
|
||||
:link_to_translation:`zh_CN:[中文]`
|
||||
|
||||
#. Create I80 bus by :cpp:func:`esp_lcd_new_i80_bus`. You need to set up the following parameters for an Intel 8080 parallel bus:
|
||||
|
||||
- :cpp:member:`esp_lcd_i80_bus_config_t::clk_src` sets the clock source of the I80 bus. Note, the default clock source may be different between ESP targets.
|
||||
- :cpp:member:`esp_lcd_i80_bus_config_t::wr_gpio_num` sets the GPIO number of the pixel clock (also referred as ``WR`` in some LCD spec)
|
||||
- :cpp:member:`esp_lcd_i80_bus_config_t::dc_gpio_num` sets the GPIO number of the data or command select pin (also referred as ``RS`` in some LCD spec)
|
||||
- :cpp:member:`esp_lcd_i80_bus_config_t::bus_width` sets the bit width of the data bus (only support ``8`` or ``16``)
|
||||
- :cpp:member:`esp_lcd_i80_bus_config_t::data_gpio_nums` is the array of the GPIO number of the data bus. The number of GPIOs should be equal to the :cpp:member:`esp_lcd_i80_bus_config_t::bus_width` value.
|
||||
- :cpp:member:`esp_lcd_i80_bus_config_t::max_transfer_bytes` sets the maximum number of bytes that can be transferred in one transaction.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
esp_lcd_i80_bus_handle_t i80_bus = NULL;
|
||||
esp_lcd_i80_bus_config_t bus_config = {
|
||||
.clk_src = LCD_CLK_SRC_DEFAULT,
|
||||
.dc_gpio_num = EXAMPLE_PIN_NUM_DC,
|
||||
.wr_gpio_num = EXAMPLE_PIN_NUM_PCLK,
|
||||
.data_gpio_nums = {
|
||||
EXAMPLE_PIN_NUM_DATA0,
|
||||
EXAMPLE_PIN_NUM_DATA1,
|
||||
EXAMPLE_PIN_NUM_DATA2,
|
||||
EXAMPLE_PIN_NUM_DATA3,
|
||||
EXAMPLE_PIN_NUM_DATA4,
|
||||
EXAMPLE_PIN_NUM_DATA5,
|
||||
EXAMPLE_PIN_NUM_DATA6,
|
||||
EXAMPLE_PIN_NUM_DATA7,
|
||||
},
|
||||
.bus_width = 8,
|
||||
.max_transfer_bytes = EXAMPLE_LCD_H_RES * 100 * sizeof(uint16_t), // transfer 100 lines of pixels (assume pixel is RGB565) at most in one transaction
|
||||
.dma_burst_size = EXAMPLE_DMA_BURST_SIZE,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));
|
||||
|
||||
#. Allocate an LCD IO device handle from the I80 bus. In this step, you need to provide the following information:
|
||||
|
||||
- :cpp:member:`esp_lcd_panel_io_i80_config_t::cs_gpio_num` sets the GPIO number of the chip select pin.
|
||||
- :cpp:member:`esp_lcd_panel_io_i80_config_t::pclk_hz` sets the pixel clock frequency in Hz. Higher pixel clock frequency results in higher refresh rate, but may cause flickering if the DMA bandwidth is not sufficient or the LCD controller chip does not support high pixel clock frequency.
|
||||
- :cpp:member:`esp_lcd_panel_io_i80_config_t::lcd_cmd_bits` and :cpp:member:`esp_lcd_panel_io_i80_config_t::lcd_param_bits` set the bit width of the command and parameter that recognized by the LCD controller chip. This is chip specific, you should refer to your LCD spec in advance.
|
||||
- :cpp:member:`esp_lcd_panel_io_i80_config_t::trans_queue_depth` sets the maximum number of transactions that can be queued in the LCD IO device. A bigger value means more transactions can be queued up, but it also consumes more memory.
|
||||
|
||||
.. note::
|
||||
|
||||
The output pixel clock PCLK frequency has an upper limit that depends on the data bus width:
|
||||
|
||||
- When :cpp:member:`esp_lcd_i80_bus_config_t::bus_width` is 8, the PCLK frequency is recommended to be less than 80 MHz.
|
||||
- When :cpp:member:`esp_lcd_i80_bus_config_t::bus_width` is 16, the PCLK frequency is recommended to be less than 40 MHz.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
esp_lcd_panel_io_handle_t io_handle = NULL;
|
||||
esp_lcd_panel_io_i80_config_t io_config = {
|
||||
.cs_gpio_num = EXAMPLE_PIN_NUM_CS,
|
||||
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
|
||||
.trans_queue_depth = 10,
|
||||
.dc_levels = {
|
||||
.dc_idle_level = 0,
|
||||
.dc_cmd_level = 0,
|
||||
.dc_dummy_level = 0,
|
||||
.dc_data_level = 1,
|
||||
},
|
||||
.lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS,
|
||||
.lcd_param_bits = EXAMPLE_LCD_PARAM_BITS,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handle));
|
||||
|
||||
#. Install the LCD controller driver. The LCD controller driver is responsible for sending the commands and parameters to the LCD controller chip. In this step, you need to specify the I80 IO device handle that allocated in the last step, and some panel specific configurations:
|
||||
|
||||
- :cpp:member:`esp_lcd_panel_dev_config_t::bits_per_pixel` sets the bit width of the pixel color data. The LCD driver uses this value to calculate the number of bytes to send to the LCD controller chip.
|
||||
- :cpp:member:`esp_lcd_panel_dev_config_t::reset_gpio_num` sets the GPIO number of the reset pin. If the LCD controller chip does not have a reset pin, you can set this value to ``-1``.
|
||||
- :cpp:member:`esp_lcd_panel_dev_config_t::rgb_ele_order` sets the color order the pixel color data.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
esp_lcd_panel_dev_config_t panel_config = {
|
||||
.reset_gpio_num = EXAMPLE_PIN_NUM_RST,
|
||||
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
|
||||
.bits_per_pixel = 16,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
.. include-build-file:: inc/esp_lcd_io_i80.inc
|
||||
@@ -0,0 +1,108 @@
|
||||
LCD
|
||||
===
|
||||
|
||||
:link_to_translation:`zh_CN:[中文]`
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
ESP chips can generate various kinds of timings needed by common LCDs on the market, like SPI LCD, I2C LCD, Parallel LCD (Intel 8080), RGB/SRGB LCD, MIPI DSI LCD, etc. The ``esp_lcd`` component offers an abstracted driver framework to support them in an unified way.
|
||||
|
||||
An LCD typically consists of two main planes:
|
||||
|
||||
* **Control Plane**: This plane allows us to read and write to the internal registers of the LCD device controller. Host typically uses this plane for tasks such as initializing the LCD power supply and performing gamma calibration.
|
||||
* **Data Plane**: The data plane is responsible for transmitting pixel data to the LCD device.
|
||||
|
||||
Functional Overview
|
||||
-------------------
|
||||
|
||||
In the context of ``esp_lcd``, both the data plane and the control plane are represented by the :cpp:type:`esp_lcd_panel_handle_t` type.
|
||||
|
||||
On some LCDs, these two planes may be combined into a single plane. In this configuration, pixel data is transmitted through the control plane, achieving functionality similar to that of the data plane. This merging is common in SPI LCDs and I2C LCDs.
|
||||
|
||||
Additionally, there are LCDs that do not require a separate control plane. For instance, certain RGB LCDs automatically execute necessary initialization procedures after power-up. Host devices only need to continuously refresh pixel data through the data plane. However, it's essential to note that not all RGB LCDs eliminate the control plane entirely. Some LCD devices can simultaneously support multiple interfaces, requiring the Host to send specific commands via the control plane (such as those based on the SPI interface) to enable the RGB mode.
|
||||
|
||||
This document will discuss how to create the control plane and data plane, as mentioned earlier, based on different types of LCDs.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
:SOC_GPSPI_SUPPORTED: spi_lcd
|
||||
:SOC_I2C_SUPPORTED: i2c_lcd
|
||||
:SOC_LCD_I80_SUPPORTED: i80_lcd
|
||||
:SOC_LCD_RGB_SUPPORTED: rgb_lcd
|
||||
:SOC_MIPI_DSI_SUPPORTED: dsi_lcd
|
||||
:SOC_PARLIO_LCD_SUPPORTED: parl_lcd
|
||||
|
||||
.. note::
|
||||
|
||||
ESP-IDF provides only a limited number of LCD device controller drivers out of the box (e.g., ST7789). More drivers are available in the `ESP Component Registry <https://components.espressif.com/components?q=esp_lcd>`__.
|
||||
|
||||
LCD Control Panel Operations
|
||||
----------------------------
|
||||
|
||||
* :cpp:func:`esp_lcd_panel_reset` can reset the LCD control panel.
|
||||
* :cpp:func:`esp_lcd_panel_init` performs a basic initialization of the control panel. To perform more manufacturer specific initialization, please refer to :ref:`steps_add_manufacture_init`.
|
||||
* By combining using :cpp:func:`esp_lcd_panel_swap_xy` and :cpp:func:`esp_lcd_panel_mirror`, you can achieve the functionality of rotating or mirroring the LCD screen.
|
||||
* :cpp:func:`esp_lcd_panel_disp_on_off` can turn on or off the LCD screen by cutting down the output path from the frame buffer to the LCD screen. Please note, this is not controlling the LCD backlight. Backlight control is not covered by the ``esp_lcd`` driver.
|
||||
* :cpp:func:`esp_lcd_panel_disp_sleep` can reduce the power consumption of the LCD screen by entering the sleep mode. The internal frame buffer is still retained.
|
||||
|
||||
LCD Data Panel Operations
|
||||
-------------------------
|
||||
|
||||
* :cpp:func:`esp_lcd_panel_reset` can reset the LCD data panel.
|
||||
* :cpp:func:`esp_lcd_panel_init` performs a basic initialization of the data panel.
|
||||
* :cpp:func:`esp_lcd_panel_draw_bitmap` is the function which does the magic to flush the user draw buffer to the LCD screen, where the target draw window is configurable. Please note, this function expects that the draw buffer is a 1-D array and there's no stride in between each lines.
|
||||
* :cpp:func:`esp_lcd_panel_draw_bitmap_2d` is the function which does the magic to flush the user draw buffer to the LCD screen, where the source and target draw windows are configurable. Please note, the draw buffer can be a 2-D array or a 1-D array with no stride in between each lines.
|
||||
|
||||
Advanced Frame Buffer Debugging
|
||||
-------------------------------
|
||||
|
||||
For advanced debugging, ESP-IDF can load the ``idf-drivers-gdb`` Python package in a GDB session. This package provides the ``framebuffer_display`` command, which reads LCD frame buffer memory from the target and renders it on the host side.
|
||||
|
||||
This is useful when the panel does not show the expected image and you need to check whether the frame buffer content is already wrong, or whether the problem is in the LCD interface timing, GPIO routing, panel initialization, or color format configuration.
|
||||
|
||||
For installation steps, supported pixel formats, command syntax, examples, and troubleshooting tips, see the `idf-drivers-gdb package documentation <https://pypi.org/project/idf-drivers-gdb/>`__.
|
||||
|
||||
.. _steps_add_manufacture_init:
|
||||
|
||||
Steps to Add Manufacturer Specific Initialization
|
||||
-------------------------------------------------
|
||||
|
||||
The LCD controller drivers (e.g., st7789) in ESP-IDF only provide basic initialization in the :cpp:func:`esp_lcd_panel_init`, leaving the vast majority of settings to the default values. Some LCD modules need to set a bunch of manufacturer specific configurations before it can display normally. These configurations usually include gamma, power voltage and so on. If you want to add manufacturer specific initialization, please follow the steps below:
|
||||
|
||||
.. code:: c
|
||||
|
||||
esp_lcd_panel_reset(panel_handle);
|
||||
esp_lcd_panel_init(panel_handle);
|
||||
// set extra configurations e.g., gamma control
|
||||
// with the underlying IO handle
|
||||
// please consult your manufacturer for special commands and corresponding values
|
||||
esp_lcd_panel_io_tx_param(io_handle, GAMMA_CMD, (uint8_t[]) {
|
||||
GAMMA_ARRAY
|
||||
}, N);
|
||||
// turn on the display
|
||||
esp_lcd_panel_disp_on_off(panel_handle, true);
|
||||
|
||||
Application Example
|
||||
-------------------
|
||||
|
||||
.. list::
|
||||
|
||||
* :example:`peripherals/lcd/tjpgd` shows how to decode a JPEG image and display it on an SPI-interfaced LCD, and rotate the image periodically.
|
||||
:SOC_GPSPI_SUPPORTED: * :example:`peripherals/lcd/spi_lcd_touch` demonstrates how to drive the LCD and touch panel on the same SPI bus, and display a simple GUI using the LVGL library.
|
||||
:SOC_LCD_I80_SUPPORTED: * :example:`peripherals/lcd/i80_controller` demonstrates how to port the LVGL library onto the `esp_lcd` driver layer to create GUIs.
|
||||
:SOC_LCD_RGB_SUPPORTED: * :example:`peripherals/lcd/rgb_panel` demonstrates how to install an RGB panel driver, display a scatter chart on the screen based on the LVGL library.
|
||||
:SOC_I2C_SUPPORTED: * :example:`peripherals/lcd/i2c_oled` demonstrates how to use the SSD1306 panel driver from the `esp_lcd` component to facilitate the porting of LVGL library and display a scrolling text on the OLED screen.
|
||||
:SOC_MIPI_DSI_SUPPORTED: * :example:`peripherals/lcd/mipi_dsi` demonstrates the general process of installing a MIPI DSI LCD driver, and displays a LVGL widget on the screen.
|
||||
:SOC_PARLIO_LCD_SUPPORTED: * :example:`peripherals/lcd/parlio_simulate` demonstrates how to use Parallel IO peripheral to drive an SPI or I80 Interfaced LCD.
|
||||
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
.. include-build-file:: inc/lcd_types.inc
|
||||
.. include-build-file:: inc/esp_lcd_types.inc
|
||||
.. include-build-file:: inc/esp_lcd_panel_io.inc
|
||||
.. include-build-file:: inc/esp_lcd_panel_ops.inc
|
||||
.. include-build-file:: inc/esp_lcd_panel_vendor.inc
|
||||
@@ -0,0 +1,76 @@
|
||||
Parallel IO simulation of SPI or I80 Interfaced LCD
|
||||
===================================================
|
||||
|
||||
:link_to_translation:`zh_CN:[中文]`
|
||||
|
||||
Parallel IO is not a bus-type peripheral. The driver directly creates a Parallel IO device for the LCD. Currently the driver supports SPI (1 bit data width) and I80 (8 bit data width) modes.
|
||||
|
||||
#. Create Parallel IO device by :cpp:func:`esp_lcd_new_panel_io_parl`. You need to set up the following parameters for a Parallel IO device:
|
||||
|
||||
- :cpp:member:`esp_lcd_panel_io_parl_config_t::clk_src` sets the clock source of the Parallel IO device. Note, the default clock source may be different between ESP targets.
|
||||
- :cpp:member:`esp_lcd_panel_io_parl_config_t::clk_gpio_num` sets the GPIO number of the pixel clock (also referred as ``WR`` or ``SCLK`` in some LCD spec)
|
||||
- :cpp:member:`esp_lcd_panel_io_parl_config_t::dc_gpio_num` sets the GPIO number of the data or command select pin (also referred as ``RS`` in some LCD spec)
|
||||
- :cpp:member:`esp_lcd_panel_io_parl_config_t::cs_gpio_num` sets the GPIO number of the chip select pin. (Note that the Parallel IO LCD driver only supports a single LCD device).
|
||||
- :cpp:member:`esp_lcd_panel_io_parl_config_t::data_width` sets the bit width of the data bus (only support ``1`` or ``8``)
|
||||
- :cpp:member:`esp_lcd_panel_io_parl_config_t::data_gpio_nums` is the array of the GPIO number of the data bus. The number of GPIOs should be equal to the :cpp:member:`esp_lcd_panel_io_parl_config_t::data_width` value.
|
||||
- :cpp:member:`esp_lcd_panel_io_parl_config_t::max_transfer_bytes` sets the maximum number of bytes that can be transferred in one transaction.
|
||||
- :cpp:member:`esp_lcd_panel_io_parl_config_t::dma_burst_size` sets the number of bytes transferred by dma burst.
|
||||
- :cpp:member:`esp_lcd_panel_io_parl_config_t::pclk_hz` sets the pixel clock frequency in Hz. Higher pixel clock frequency results in higher refresh rate, but may cause display abnormalities if the DMA bandwidth is not sufficient or the LCD controller chip does not support high pixel clock frequency.
|
||||
- :cpp:member:`esp_lcd_panel_io_parl_config_t::dc_levels` sets the effective level for DC data selection and command selection.
|
||||
- :cpp:member:`esp_lcd_panel_io_parl_config_t::lcd_cmd_bits` and :cpp:member:`esp_lcd_panel_io_parl_config_t::lcd_param_bits` set the bit width of the command and parameter that recognized by the LCD controller chip. This is chip specific, you should refer to your LCD spec in advance.
|
||||
- :cpp:member:`esp_lcd_panel_io_parl_config_t::trans_queue_depth` sets the maximum number of transactions that can be queued in the Parallel IO device. A bigger value means more transactions can be queued up, but it also consumes more memory.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
esp_lcd_panel_io_handle_t io_handle = NULL;
|
||||
esp_lcd_panel_io_parl_config_t io_config = {
|
||||
.clk_src = PARLIO_CLK_SRC_DEFAULT,
|
||||
.dc_gpio_num = EXAMPLE_PIN_NUM_DC,
|
||||
.clk_gpio_num = EXAMPLE_PIN_NUM_PCLK,
|
||||
.data_gpio_nums = {
|
||||
EXAMPLE_PIN_NUM_DATA0, // set DATA0 to drive SPI interfaced LCD or set DATA0~7 to drive I80 interfaced LCD
|
||||
},
|
||||
.data_width = 1, // set 1 to drive SPI interfaced LCD or set 8 to drive I80 interfaced LCD
|
||||
.max_transfer_bytes = EXAMPLE_LCD_H_RES * 100 * sizeof(uint16_t), // transfer 100 lines of pixels (assume pixel is RGB565) at most in one transaction
|
||||
.dma_burst_size = EXAMPLE_DMA_BURST_SIZE,
|
||||
.cs_gpio_num = EXAMPLE_PIN_NUM_CS,
|
||||
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
|
||||
.trans_queue_depth = 10,
|
||||
.dc_levels = {
|
||||
.dc_cmd_level = 0,
|
||||
.dc_data_level = 1,
|
||||
},
|
||||
.lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS,
|
||||
.lcd_param_bits = EXAMPLE_LCD_PARAM_BITS,
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_parl(&io_config, io_handle));
|
||||
|
||||
.. only:: not SOC_PARLIO_SUPPORT_I80_LCD
|
||||
|
||||
.. note::
|
||||
|
||||
Due to hardware limitations, {IDF_TARGET_NAME} can not drive I80 interfaced LCD by Parallel IO.
|
||||
|
||||
#. Install the LCD controller driver. The LCD controller driver is responsible for sending the commands and parameters to the LCD controller chip. In this step, you need to specify the Parallel IO device handle that allocated in the last step, and some panel specific configurations:
|
||||
|
||||
- :cpp:member:`esp_lcd_panel_dev_config_t::reset_gpio_num` sets the LCD's hardware reset GPIO number. If the LCD does not have a hardware reset pin, set this to ``-1``.
|
||||
- :cpp:member:`esp_lcd_panel_dev_config_t::rgb_ele_order` sets the RGB element order of each color data.
|
||||
- :cpp:member:`esp_lcd_panel_dev_config_t::bits_per_pixel` sets the bit width of the pixel color data. The LCD driver uses this value to calculate the number of bytes to send to the LCD controller chip.
|
||||
- :cpp:member:`esp_lcd_panel_dev_config_t::data_endian` specifies the data endian to be transmitted to the screen. No need to specify for color data within one byte, like RGB232. For drivers that do not support specifying data endian, this field would be ignored.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
esp_lcd_panel_handle_t panel_handle = NULL;
|
||||
esp_lcd_panel_dev_config_t panel_config = {
|
||||
.reset_gpio_num = EXAMPLE_PIN_NUM_RST,
|
||||
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR,
|
||||
.bits_per_pixel = 16,
|
||||
};
|
||||
// Create LCD panel handle for ST7789, with the Parallel IO device handle
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
.. include-build-file:: inc/esp_lcd_io_parl.inc
|
||||
@@ -0,0 +1,316 @@
|
||||
RGB Interfaced LCD
|
||||
==================
|
||||
|
||||
:link_to_translation:`zh_CN:[中文]`
|
||||
|
||||
RGB LCD panel is created by :cpp:func:`esp_lcd_new_rgb_panel`, with various configurations specified in :cpp:type:`esp_lcd_rgb_panel_config_t`.
|
||||
|
||||
- :cpp:member:`esp_lcd_rgb_panel_config_t::clk_src` selects the clock source of 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` sets number of data lines consumed by the RGB interface. It can be 8/16/24.
|
||||
- :cpp:member:`esp_lcd_rgb_panel_config_t::bits_per_pixel` specifies the number of bits per pixel. This differs from :cpp:member:`esp_lcd_rgb_panel_config_t::data_width`. By default, if this field is set to 0, the driver will automatically match the bpp to the value set in :cpp:member:`esp_lcd_rgb_panel_config_t::data_width`. However, in some scenarios, these values need to be different. For instance, a serial RGB interfaced LCD might only require ``8`` data lines, but the color depth could be ``RGB888``, meaning :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 consumed 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` specifies the size of the DMA transfer burst. Ensure this value is a power of 2.
|
||||
- :cpp:member:`esp_lcd_rgb_panel_config_t::bounce_buffer_size_px` specifies the size of the bounce buffer. This is required only for the "bounce buffer" mode. For more details, see :ref:`bounce_buffer_with_single_psram_frame_buffer`.
|
||||
- :cpp:member:`esp_lcd_rgb_panel_config_t::timings` specifies the timing parameters unique to the LCD panel. These parameters, detailed in :cpp:type:`esp_lcd_rgb_timing_t`, include the LCD resolution and blanking porches. Ensure they are set according to your LCD's datasheet.
|
||||
- :cpp:member:`esp_lcd_rgb_panel_config_t::fb_in_psram` determines if the frame buffer should be allocated from PSRAM. For further details, see :ref:`single_frame_buffer_in_psram`.
|
||||
- :cpp:member:`esp_lcd_rgb_panel_config_t::num_fbs` specifies how many frame buffers the driver should allocate. For backward compatibility, setting this to ``0`` will allocate a single frame buffer. If you don't want to allocate any frame buffer, use :cpp:member:`esp_lcd_rgb_panel_config_t::no_fb` instead.
|
||||
- :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.
|
||||
|
||||
.. note::
|
||||
|
||||
- When :cpp:member:`esp_lcd_rgb_panel_config_t::data_width` is 8:
|
||||
|
||||
- The PCLK frequency is recommended to be less than 80 MHz.
|
||||
- If YUV-RGB format conversion is also configured via :cpp:func:`esp_lcd_rgb_panel_set_yuv_conversion`, the PCLK frequency is recommended to be less than 60 MHz.
|
||||
|
||||
- When :cpp:member:`esp_lcd_rgb_panel_config_t::data_width` is 16:
|
||||
|
||||
- The PCLK frequency is recommended to be less than 40 MHz.
|
||||
- If YUV-RGB format conversion is also configured, the PCLK frequency is recommended to be less than 30 MHz.
|
||||
|
||||
GPIO Matrix and IOMUX Pins
|
||||
--------------------------
|
||||
|
||||
.. only:: esp32s31
|
||||
|
||||
On {IDF_TARGET_NAME}, the RGB LCD driver can use IOMUX automatically when all required signals are mapped to dedicated pins:
|
||||
|
||||
- If ``data_gpio_nums``, ``hsync_gpio_num``, ``vsync_gpio_num``, ``pclk_gpio_num``, and ``de_gpio_num`` all match their dedicated IOMUX pins, the driver bypasses the GPIO matrix automatically.
|
||||
- If any one of these signals does not match the dedicated IOMUX pin, the driver falls back to GPIO matrix routing automatically.
|
||||
|
||||
For higher pixel clock configurations, using IOMUX pins is recommended for better timing robustness.
|
||||
|
||||
Dedicated RGB IOMUX pins on {IDF_TARGET_NAME} are listed below:
|
||||
|
||||
- When ``data_width = 8``, ``DATA[0:7]`` is used
|
||||
- When ``data_width = 16``, ``DATA[0:15]`` is used
|
||||
- When ``data_width = 24``, ``DATA[0:23]`` is used
|
||||
|
||||
.. list-table::
|
||||
:widths: 35 65
|
||||
:header-rows: 1
|
||||
|
||||
* - Signal
|
||||
- GPIO
|
||||
* - DATA[0:7]
|
||||
- 8, 9, 10, 11, 12, 13, 14, 15
|
||||
* - DATA[8:15]
|
||||
- 16, 17, 18, 19, 33, 34, 35, 36
|
||||
* - DATA[16:23]
|
||||
- 37, 38, 39, 2, 3, 4, 5, 7
|
||||
* - HSYNC
|
||||
- 44
|
||||
* - VSYNC
|
||||
- 45
|
||||
* - PCLK
|
||||
- 40
|
||||
* - DE
|
||||
- 43
|
||||
|
||||
.. only:: not esp32s31
|
||||
|
||||
On {IDF_TARGET_NAME}, RGB signals are routed through the GPIO matrix.
|
||||
|
||||
RGB LCD Frame Buffer Operation Modes
|
||||
------------------------------------
|
||||
|
||||
Most of the time, the RGB LCD driver should maintain at least one screen sized frame buffer. According to the number and location of the frame buffer, the driver provides several different buffer modes.
|
||||
|
||||
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
|
||||
|
||||
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 prefer to store the frame buffer there instead of using the limited internal memory, the LCD peripheral can utilize EDMA to fetch frame data directly from PSRAM, bypassing the internal cache. This can be enabled by setting :cpp:member:`esp_lcd_rgb_panel_config_t::fb_in_psram` to ``true``. The trade-off is that when both the CPU and EDMA need access to PSRAM, the bandwidth is **shared** between them, meaning EDMA and the CPU each get half. If other peripherals are also using EDMA, a high pixel clock might cause LCD peripheral starvation, leading to display corruption. However, with a sufficiently low pixel clock, this approach minimizes CPU intervention.
|
||||
|
||||
.. only:: esp32s3
|
||||
|
||||
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
|
||||
|
||||
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:
|
||||
|
||||
Double Frame Buffer in PSRAM
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To prevent tearing effects, the simplest method is to use two screen-sized frame buffers. Given the limited internal memory, these buffers must be allocated from PSRAM. This ensures that the frame buffer being written to by the CPU and the one being read by the EDMA are always distinct and independent. The EDMA will only switch between the two buffers once the current write operation is complete and the frame has been fully transmitted to the LCD. The main drawback of this approach is the need to maintain synchronization between the two frame buffers.
|
||||
|
||||
.. 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 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));
|
||||
|
||||
.. _user_custom_frame_buffer:
|
||||
|
||||
User Custom Frame Buffer
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
User can provide their own frame buffer instead of letting the driver allocate it. In this mode, user needs to manage the lifecycle of the frame buffer by themselves.
|
||||
|
||||
.. 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 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,
|
||||
},
|
||||
.user_fbs[0] = user_frame_buffer, // use user custom frame buffer
|
||||
};
|
||||
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 "bounce buffers" from internal memory and a main frame buffer in PSRAM. To enable this mode, set the :cpp:member:`esp_lcd_rgb_panel_config_t::fb_in_psram` flag and specify a non-zero value for :cpp:member:`esp_lcd_rgb_panel_config_t::bounce_buffer_size_px`. The bounce buffers only need to hold a few lines of display data, which is much smaller than the main frame buffer. The LCD peripheral uses DMA to read data from one bounce buffer while 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 finishes reading from the bounce buffer, the buffers swap roles, allowing the CPU to fill the other one. The advantage of this mode is achieving a higher pixel clock frequency. Since 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 significant increase in CPU usage, and the LCD **CANNOT** function if the external memory cache is disabled, such as during OTA or NVS writes to the main flash.
|
||||
|
||||
.. note::
|
||||
|
||||
For optimal performance in this mode, it is highly recommended to enable the "PSRAM XIP (Execute In Place)" feature by turning on the Kconfig option: :ref:`CONFIG_SPIRAM_XIP_FROM_PSRAM`. This allows the CPU to fetch instructions and read-only data directly from PSRAM instead of the main flash. Additionally, the external memory cache remains active even when writing to the main flash via SPI 1, making it feasible to display an OTA progress bar during your application updates.
|
||||
|
||||
.. note::
|
||||
|
||||
This mode also faces issues due to limited PSRAM bandwidth. For instance, if your draw buffers are in PSRAM and their contents are copied to the internal frame buffer by CPU Core 1, while CPU Core 0 is performing another memory copy in the DMA EOF ISR, both CPUs will be accessing PSRAM via cache, sharing its bandwidth. This significantly increases the memory copy time in the DMA EOF ISR, causing the driver to fail in switching the bounce buffer promptly, resulting in a screen shift. Although the driver can detect this condition and restart in the LCD's VSYNC interrupt handler, you may still notice flickering on the screen.
|
||||
|
||||
.. 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 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));
|
||||
|
||||
.. _bounce_buffer_only:
|
||||
|
||||
Bounce Buffer Only
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
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::
|
||||
|
||||
In a well-designed embedded application, situations where the DMA cannot deliver data as fast as the LCD consumes it should be avoided. However, such scenarios can theoretically occur. In the {IDF_TARGET_NAME} hardware, this results in the LCD outputting dummy bytes while the DMA waits for data. If the DMA were to run in a continuous stream, it could cause a desynchronization between the LCD address from which the DMA reads data and the address from which the LCD peripheral outputs data, leading to a **permanently** shifted image.
|
||||
To prevent this, you can either enable the :ref:`CONFIG_LCD_RGB_RESTART_IN_VSYNC` option, allowing the driver to automatically restart the DMA during the VBlank interrupt, or call :cpp:func:`esp_lcd_rgb_panel_restart` to manually restart the DMA. Note that :cpp:func:`esp_lcd_rgb_panel_restart` does not restart the DMA immediately; instead, the DMA will be restarted at the next VSYNC event.
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
.. include-build-file:: inc/esp_lcd_panel_rgb.inc
|
||||
@@ -0,0 +1,69 @@
|
||||
SPI Interfaced LCD
|
||||
==================
|
||||
|
||||
:link_to_translation:`zh_CN:[中文]`
|
||||
|
||||
#. Create an SPI bus. Please refer to :doc:`SPI Master API doc </api-reference/peripherals/spi_master>` for more details.
|
||||
|
||||
Currently the driver supports SPI, Quad SPI and Octal SPI (simulate Intel 8080 timing) modes.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
spi_bus_config_t buscfg = {
|
||||
.sclk_io_num = EXAMPLE_PIN_NUM_SCLK,
|
||||
.mosi_io_num = EXAMPLE_PIN_NUM_MOSI,
|
||||
.miso_io_num = EXAMPLE_PIN_NUM_MISO,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
.max_transfer_sz = EXAMPLE_LCD_H_RES * 80 * sizeof(uint16_t), // transfer 80 lines of pixels (assume pixel is RGB565) at most in one SPI transaction
|
||||
};
|
||||
ESP_ERROR_CHECK(spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO)); // Enable the DMA feature
|
||||
|
||||
#. Allocate an LCD IO device handle from the SPI bus. In this step, you need to provide the following information:
|
||||
|
||||
- :cpp:member:`esp_lcd_panel_io_spi_config_t::dc_gpio_num` sets the GPIO number for the DC signal line (some LCD calls this ``RS`` line). The LCD driver uses this GPIO to switch between sending command and sending data.
|
||||
- :cpp:member:`esp_lcd_panel_io_spi_config_t::cs_gpio_num` sets the GPIO number for the CS signal line. The LCD driver uses this GPIO to select the LCD chip. If the SPI bus only has one device attached (i.e., this LCD), you can set the GPIO number to ``-1`` to occupy the bus exclusively.
|
||||
- :cpp:member:`esp_lcd_panel_io_spi_config_t::pclk_hz` sets the frequency of the pixel clock, in Hz. The value should not exceed the range recommended in the LCD spec.
|
||||
- :cpp:member:`esp_lcd_panel_io_spi_config_t::spi_mode` sets the SPI mode. The LCD driver uses this mode to communicate with the LCD. For the meaning of the SPI mode, please refer to the :doc:`SPI Master API doc </api-reference/peripherals/spi_master>`.
|
||||
- :cpp:member:`esp_lcd_panel_io_spi_config_t::lcd_cmd_bits` and :cpp:member:`esp_lcd_panel_io_spi_config_t::lcd_param_bits` set the bit width of the command and parameter that recognized by the LCD controller chip. This is chip specific, you should refer to your LCD spec in advance.
|
||||
- :cpp:member:`esp_lcd_panel_io_spi_config_t::trans_queue_depth` sets the depth of the SPI transaction queue. A bigger value means more transactions can be queued up, but it also consumes more memory.
|
||||
- :cpp:member:`esp_lcd_panel_io_spi_config_t::cs_ena_pretrans` sets the amount of SPI bit-cycles which the cs should be activated before the transmission (0-16).
|
||||
- :cpp:member:`esp_lcd_panel_io_spi_config_t::cs_ena_posttrans` sets the amount of SPI bit-cycles which the cs should stay active after the transmission (0-16).
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
esp_lcd_panel_io_handle_t io_handle = NULL;
|
||||
esp_lcd_panel_io_spi_config_t io_config = {
|
||||
.dc_gpio_num = EXAMPLE_PIN_NUM_LCD_DC,
|
||||
.cs_gpio_num = EXAMPLE_PIN_NUM_LCD_CS,
|
||||
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
|
||||
.lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS,
|
||||
.lcd_param_bits = EXAMPLE_LCD_PARAM_BITS,
|
||||
.spi_mode = 0,
|
||||
.trans_queue_depth = 10,
|
||||
};
|
||||
// Attach the LCD to the SPI bus
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, &io_handle));
|
||||
|
||||
#. Install the LCD controller driver. The LCD controller driver is responsible for sending the commands and parameters to the LCD controller chip. In this step, you need to specify the SPI IO device handle that allocated in the last step, and some panel specific configurations:
|
||||
|
||||
- :cpp:member:`esp_lcd_panel_dev_config_t::reset_gpio_num` sets the LCD's hardware reset GPIO number. If the LCD does not have a hardware reset pin, set this to ``-1``.
|
||||
- :cpp:member:`esp_lcd_panel_dev_config_t::rgb_ele_order` sets the RGB element order of each color data.
|
||||
- :cpp:member:`esp_lcd_panel_dev_config_t::bits_per_pixel` sets the bit width of the pixel color data. The LCD driver uses this value to calculate the number of bytes to send to the LCD controller chip.
|
||||
- :cpp:member:`esp_lcd_panel_dev_config_t::data_endian` specifies the data endian to be transmitted to the screen. No need to specify for color data within one byte, like RGB232. For drivers that do not support specifying data endian, this field would be ignored.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
esp_lcd_panel_handle_t panel_handle = NULL;
|
||||
esp_lcd_panel_dev_config_t panel_config = {
|
||||
.reset_gpio_num = EXAMPLE_PIN_NUM_RST,
|
||||
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR,
|
||||
.bits_per_pixel = 16,
|
||||
};
|
||||
// Create LCD panel handle for ST7789, with the SPI IO device handle
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
.. include-build-file:: inc/esp_lcd_io_spi.inc
|
||||
Reference in New Issue
Block a user