mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
Merge branch 'feature/esp_lcd_mipi_dsi' into 'master'
esp lcd mipi dsi driver Closes IDF-6499 See merge request espressif/esp-idf!28273
This commit is contained in:
commit
01f1434fdd
@ -31,6 +31,11 @@ if(CONFIG_SOC_LCDCAM_SUPPORTED)
|
||||
list(APPEND srcs "i80/esp_lcd_panel_io_i80.c" "rgb/esp_lcd_panel_rgb.c")
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_MIPI_DSI_SUPPORTED)
|
||||
list(APPEND includes "dsi/include")
|
||||
list(APPEND srcs "dsi/esp_lcd_mipi_dsi_bus.c" "dsi/esp_lcd_panel_io_dbi.c" "dsi/esp_lcd_panel_dpi.c")
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
INCLUDE_DIRS ${includes}
|
||||
PRIV_INCLUDE_DIRS "priv_include"
|
||||
|
@ -60,6 +60,26 @@ classDiagram
|
||||
-on_color_trans_done(void* user_data) bool
|
||||
}
|
||||
|
||||
esp_lcd_panel_io_dbi_t --|> esp_lcd_panel_io_t : Inheritance
|
||||
class esp_lcd_panel_io_dbi_t {
|
||||
-esp_lcd_dsi_bus_t* bus
|
||||
-int virtual_channel
|
||||
}
|
||||
|
||||
esp_lcd_dpi_panel_t --|> esp_lcd_panel_t : Inheritance
|
||||
class esp_lcd_dpi_panel_t {
|
||||
-esp_lcd_dsi_bus_t* bus
|
||||
-int virtual_channel
|
||||
-void *frame_buffer
|
||||
-dw_gdma_channel_handle_t dma_chan
|
||||
}
|
||||
|
||||
esp_lcd_dsi_bus_t "1" --> "1..*" esp_lcd_panel_io_dbi_t : Has
|
||||
esp_lcd_dsi_bus_t "1" --> "1..*" esp_lcd_dpi_panel_t : Has
|
||||
class esp_lcd_dsi_bus_t {
|
||||
-int bus_id
|
||||
}
|
||||
|
||||
esp_lcd_panel_io_i80_t --|> esp_lcd_panel_io_t : Inheritance
|
||||
class esp_lcd_panel_io_i80_t {
|
||||
-esp_lcd_i80_bus_t* bus
|
||||
|
130
components/esp_lcd/dsi/esp_lcd_mipi_dsi_bus.c
Normal file
130
components/esp_lcd/dsi/esp_lcd_mipi_dsi_bus.c
Normal file
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_lcd_mipi_dsi.h"
|
||||
#include "esp_clk_tree.h"
|
||||
#include "mipi_dsi_priv.h"
|
||||
|
||||
static const char *TAG = "lcd.dsi.bus";
|
||||
|
||||
#define MIPI_DSI_DEFAULT_TIMEOUT_CLOCK_FREQ_MHZ 10
|
||||
// TxClkEsc frequency must be configured between 2 and 20 MHz
|
||||
#define MIPI_DSI_DEFAULT_ESCAPE_CLOCK_FREQ_MHZ 10
|
||||
|
||||
esp_err_t esp_lcd_new_dsi_bus(const esp_lcd_dsi_bus_config_t *bus_config, esp_lcd_dsi_bus_handle_t *ret_bus)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
ESP_RETURN_ON_FALSE(bus_config && ret_bus, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE(bus_config->lane_bit_rate_mbps >= MIPI_DSI_LL_MIN_PHY_MBPS &&
|
||||
bus_config->lane_bit_rate_mbps <= MIPI_DSI_LL_MAX_PHY_MBPS, ESP_ERR_INVALID_ARG, TAG,
|
||||
"invalid lane bit rate %"PRIu32, bus_config->lane_bit_rate_mbps);
|
||||
|
||||
// we don't use an bus allocator here, because different DSI bus uses different PHY.
|
||||
// And each PHY has its own associated PINs, which is not changeable.
|
||||
// So user HAS TO specify the bus ID by themselves, according to their PCB design.
|
||||
int bus_id = bus_config->bus_id;
|
||||
ESP_RETURN_ON_FALSE(bus_id >= 0 && bus_id < MIPI_DSI_LL_NUM_BUS, ESP_ERR_INVALID_ARG, TAG, "invalid bus ID %d", bus_id);
|
||||
esp_lcd_dsi_bus_t *dsi_bus = heap_caps_calloc(1, sizeof(esp_lcd_dsi_bus_t), DSI_MEM_ALLOC_CAPS);
|
||||
ESP_RETURN_ON_FALSE(dsi_bus, ESP_ERR_NO_MEM, TAG, "no memory for DSI bus");
|
||||
dsi_bus->bus_id = bus_id;
|
||||
|
||||
// Enable the APB clock for accessing the DSI host and bridge registers
|
||||
DSI_RCC_ATOMIC() {
|
||||
mipi_dsi_ll_enable_bus_clock(bus_id, true);
|
||||
mipi_dsi_ll_reset_register(bus_id);
|
||||
}
|
||||
|
||||
// if the clock source is not assigned, fallback to the default clock source
|
||||
mipi_dsi_phy_clock_source_t phy_clk_src = bus_config->phy_clk_src;
|
||||
if (phy_clk_src == 0) {
|
||||
phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT;
|
||||
}
|
||||
// enable the clock source for DSI PHY
|
||||
DSI_CLOCK_SRC_ATOMIC() {
|
||||
// set clock source for DSI PHY
|
||||
mipi_dsi_ll_set_phy_clock_source(bus_id, phy_clk_src);
|
||||
// the configuration clock is used for all modes except the shutdown mode
|
||||
mipi_dsi_ll_enable_phy_config_clock(bus_id, true);
|
||||
// enable the clock for generating the serial clock
|
||||
mipi_dsi_ll_enable_phy_reference_clock(bus_id, true);
|
||||
}
|
||||
|
||||
// initialize HAL context
|
||||
mipi_dsi_hal_config_t hal_config = {
|
||||
.bus_id = bus_id,
|
||||
.lane_bit_rate_mbps = bus_config->lane_bit_rate_mbps,
|
||||
.num_data_lanes = bus_config->num_data_lanes,
|
||||
};
|
||||
mipi_dsi_hal_init(&dsi_bus->hal, &hal_config);
|
||||
mipi_dsi_hal_context_t *hal = &dsi_bus->hal;
|
||||
|
||||
// get the frequency of the PHY clock source
|
||||
uint32_t phy_clk_src_freq_hz = 0;
|
||||
ESP_GOTO_ON_ERROR(esp_clk_tree_src_get_freq_hz(phy_clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED,
|
||||
&phy_clk_src_freq_hz), err, TAG, "get phy clock source frequency failed");
|
||||
// configure the PHY PLL
|
||||
mipi_dsi_hal_configure_phy_pll(hal, phy_clk_src_freq_hz, bus_config->lane_bit_rate_mbps);
|
||||
|
||||
// wait for PHY initialization done
|
||||
while (!mipi_dsi_phy_ll_is_pll_locked(hal->host)) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
}
|
||||
while (!mipi_dsi_phy_ll_are_lanes_stoped(hal->host)) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
}
|
||||
|
||||
// initialize the DSI operation mode: command mode
|
||||
mipi_dsi_host_ll_enable_video_mode(hal->host, false);
|
||||
// place the clock lane in low power mode, we will switch to high speed mode later when DPI stream is ready
|
||||
mipi_dsi_host_ll_set_clock_lane_state(hal->host, MIPI_DSI_LL_CLOCK_LANE_STATE_LP);
|
||||
// Set the time that is required by the clock and data lanes to go from high-speed to low-power and from low-power to high-speed
|
||||
mipi_dsi_phy_ll_set_switch_time(hal->host, 50, 104, 46, 128);
|
||||
|
||||
// enable CRC reception and ECC reception, error correction, and reporting
|
||||
mipi_dsi_host_ll_enable_rx_crc(hal->host, true);
|
||||
mipi_dsi_host_ll_enable_rx_ecc(hal->host, true);
|
||||
// enable sending the EoTp packet at the end of each transmission
|
||||
mipi_dsi_host_ll_enable_tx_eotp(hal->host, true, true);
|
||||
|
||||
// Set the divider to get the Time Out clock, clock source is the high-speed byte clock
|
||||
mipi_dsi_host_ll_set_timeout_clock_division(hal->host, bus_config->lane_bit_rate_mbps / 8 / MIPI_DSI_DEFAULT_TIMEOUT_CLOCK_FREQ_MHZ);
|
||||
// Set the divider to get the TX Escape clock, clock source is the high-speed byte clock
|
||||
mipi_dsi_host_ll_set_escape_clock_division(hal->host, bus_config->lane_bit_rate_mbps / 8 / MIPI_DSI_DEFAULT_ESCAPE_CLOCK_FREQ_MHZ);
|
||||
// set the timeout intervals to zero, means to disable the timeout mechanism
|
||||
mipi_dsi_host_ll_set_timeout_count(hal->host, 0, 0, 0, 0, 0, 0, 0);
|
||||
// DSI host will wait indefinitely for a read response from the DSI device
|
||||
mipi_dsi_phy_ll_set_max_read_time(hal->host, 6000);
|
||||
// set how long the DSI host will wait before sending the next transmission
|
||||
mipi_dsi_phy_ll_set_stop_wait_time(hal->host, 0x3F);
|
||||
|
||||
*ret_bus = dsi_bus;
|
||||
return ESP_OK;
|
||||
err:
|
||||
if (dsi_bus) {
|
||||
esp_lcd_del_dsi_bus(dsi_bus);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t esp_lcd_del_dsi_bus(esp_lcd_dsi_bus_handle_t bus)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(bus, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
int bus_id = bus->bus_id;
|
||||
// disable the clock source for DSI PHY
|
||||
DSI_CLOCK_SRC_ATOMIC() {
|
||||
mipi_dsi_ll_enable_phy_reference_clock(bus_id, false);
|
||||
mipi_dsi_ll_enable_phy_config_clock(bus_id, false);
|
||||
}
|
||||
// disable the APB clock for accessing the DSI peripheral registers
|
||||
DSI_RCC_ATOMIC() {
|
||||
mipi_dsi_ll_enable_bus_clock(bus_id, false);
|
||||
}
|
||||
free(bus);
|
||||
return ESP_OK;
|
||||
}
|
335
components/esp_lcd/dsi/esp_lcd_panel_dpi.c
Normal file
335
components/esp_lcd/dsi/esp_lcd_panel_dpi.c
Normal file
@ -0,0 +1,335 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_lcd_panel_interface.h"
|
||||
#include "esp_lcd_mipi_dsi.h"
|
||||
#include "esp_clk_tree.h"
|
||||
#include "esp_cache.h"
|
||||
#include "mipi_dsi_priv.h"
|
||||
#include "esp_private/dw_gdma.h"
|
||||
#include "hal/cache_hal.h"
|
||||
#include "hal/cache_ll.h"
|
||||
|
||||
static const char *TAG = "lcd.dsi.dpi";
|
||||
|
||||
typedef struct esp_lcd_dpi_panel_t esp_lcd_dpi_panel_t;
|
||||
|
||||
static esp_err_t dpi_panel_del(esp_lcd_panel_t *panel);
|
||||
static esp_err_t dpi_panel_init(esp_lcd_panel_t *panel);
|
||||
static esp_err_t dpi_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data);
|
||||
|
||||
struct esp_lcd_dpi_panel_t {
|
||||
esp_lcd_panel_t base; // Base class of generic lcd panel
|
||||
esp_lcd_dsi_bus_handle_t bus; // DSI bus handle
|
||||
uint8_t virtual_channel; // Virtual channel ID, index from 0
|
||||
void *frame_buffer; // Frame buffer
|
||||
uint32_t h_pixels; // Horizontal pixels
|
||||
uint32_t v_pixels; // Vertical pixels
|
||||
size_t frame_buffer_size; // Frame buffer size
|
||||
size_t bytes_per_pixel; // Bytes per pixel
|
||||
dw_gdma_channel_handle_t dma_chan; // DMA channel
|
||||
dw_gdma_link_list_handle_t link_list; // DMA link list
|
||||
};
|
||||
|
||||
static bool dma_list_invalid_block_cb(dw_gdma_channel_handle_t chan, const dw_gdma_break_event_data_t *event_data, void *user_data)
|
||||
{
|
||||
dw_gdma_lli_handle_t lli = event_data->invalid_lli;
|
||||
dw_gdma_block_markers_t markers = {
|
||||
.is_valid = true, // mark the block as valid so that the DMA can continue the transfer
|
||||
};
|
||||
dw_gdma_lli_set_block_markers(lli, markers);
|
||||
// after the item is marked as valid again, tell the DMA to continue the transfer
|
||||
dw_gdma_channel_continue(chan);
|
||||
return false;
|
||||
}
|
||||
|
||||
static esp_err_t dpi_panel_create_dma_link(esp_lcd_dpi_panel_t *dpi_panel)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
dw_gdma_channel_handle_t dma_chan = NULL;
|
||||
dw_gdma_link_list_handle_t link_list = NULL;
|
||||
// sending image stream from memory to the DSI bridge
|
||||
dw_gdma_channel_alloc_config_t dma_alloc_config = {
|
||||
.src = {
|
||||
.block_transfer_type = DW_GDMA_BLOCK_TRANSFER_LIST,
|
||||
.role = DW_GDMA_ROLE_MEM,
|
||||
.handshake_type = DW_GDMA_HANDSHAKE_HW,
|
||||
.num_outstanding_requests = 5,
|
||||
},
|
||||
.dst = {
|
||||
.block_transfer_type = DW_GDMA_BLOCK_TRANSFER_LIST,
|
||||
.role = DW_GDMA_ROLE_PERIPH_DSI,
|
||||
.handshake_type = DW_GDMA_HANDSHAKE_HW,
|
||||
.num_outstanding_requests = 2,
|
||||
},
|
||||
.flow_controller = DW_GDMA_FLOW_CTRL_DST, // the DSI bridge as the DMA flow controller
|
||||
.chan_priority = 1,
|
||||
};
|
||||
ESP_GOTO_ON_ERROR(dw_gdma_new_channel(&dma_alloc_config, &dma_chan), err, TAG, "create DMA channel failed");
|
||||
|
||||
// create a linked list for the DMA channel
|
||||
dw_gdma_link_list_config_t link_list_config = {
|
||||
.num_items = 1, // Assume one link item can carry the whole image
|
||||
.link_type = DW_GDMA_LINKED_LIST_TYPE_CIRCULAR,
|
||||
};
|
||||
ESP_GOTO_ON_ERROR(dw_gdma_new_link_list(&link_list_config, &link_list), err, TAG, "create DMA link list failed");
|
||||
|
||||
// register DMA ISR callbacks
|
||||
dw_gdma_event_callbacks_t dsi_dma_cbs = {
|
||||
.on_invalid_block = dma_list_invalid_block_cb,
|
||||
};
|
||||
ESP_GOTO_ON_ERROR(dw_gdma_channel_register_event_callbacks(dma_chan, &dsi_dma_cbs, NULL), err, TAG, "register DMA callbacks failed");
|
||||
|
||||
dpi_panel->dma_chan = dma_chan;
|
||||
dpi_panel->link_list = link_list;
|
||||
return ESP_OK;
|
||||
err:
|
||||
if (dma_chan) {
|
||||
dw_gdma_del_channel(dma_chan);
|
||||
}
|
||||
if (link_list) {
|
||||
dw_gdma_del_link_list(link_list);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t esp_lcd_new_panel_dpi(esp_lcd_dsi_bus_handle_t bus, const esp_lcd_dpi_panel_config_t *panel_config, esp_lcd_panel_handle_t *ret_panel)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
void *frame_buffer = NULL;
|
||||
esp_lcd_dpi_panel_t *dpi_panel = NULL;
|
||||
ESP_RETURN_ON_FALSE(bus && panel_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE(panel_config->virtual_channel < 4, ESP_ERR_INVALID_ARG, TAG, "invalid virtual channel %d", panel_config->virtual_channel);
|
||||
ESP_RETURN_ON_FALSE(panel_config->dpi_clock_freq_mhz, ESP_ERR_INVALID_ARG, TAG, "invalid DPI clock frequency %"PRIu32, panel_config->dpi_clock_freq_mhz);
|
||||
int bus_id = bus->bus_id;
|
||||
mipi_dsi_hal_context_t *hal = &bus->hal;
|
||||
|
||||
dpi_panel = heap_caps_calloc(1, sizeof(esp_lcd_dpi_panel_t), DSI_MEM_ALLOC_CAPS);
|
||||
ESP_GOTO_ON_FALSE(dpi_panel, ESP_ERR_NO_MEM, err, TAG, "no memory for DPI panel");
|
||||
dpi_panel->virtual_channel = panel_config->virtual_channel;
|
||||
dpi_panel->bus = bus;
|
||||
|
||||
// allocate frame buffer from PSRAM
|
||||
size_t bytes_per_pixel = 0;
|
||||
switch (panel_config->pixel_format) {
|
||||
case LCD_COLOR_PIXEL_FORMAT_RGB565:
|
||||
bytes_per_pixel = 2;
|
||||
break;
|
||||
case LCD_COLOR_PIXEL_FORMAT_RGB666:
|
||||
bytes_per_pixel = 3;
|
||||
break;
|
||||
case LCD_COLOR_PIXEL_FORMAT_RGB888:
|
||||
bytes_per_pixel = 3;
|
||||
break;
|
||||
}
|
||||
uint32_t cache_line_size = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_EXT_MEM, CACHE_TYPE_DATA);
|
||||
// DMA doesn't have requirement on the buffer alignment, but the cache does
|
||||
uint32_t alignment = cache_line_size;
|
||||
size_t frame_buffer_size = panel_config->video_timing.h_size * panel_config->video_timing.v_size * bytes_per_pixel;
|
||||
frame_buffer = heap_caps_aligned_calloc(alignment, 1, frame_buffer_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||
ESP_GOTO_ON_FALSE(frame_buffer, ESP_ERR_NO_MEM, err, TAG, "no memory for frame buffer");
|
||||
dpi_panel->frame_buffer = frame_buffer;
|
||||
dpi_panel->frame_buffer_size = frame_buffer_size;
|
||||
dpi_panel->bytes_per_pixel = bytes_per_pixel;
|
||||
dpi_panel->h_pixels = panel_config->video_timing.h_size;
|
||||
dpi_panel->v_pixels = panel_config->video_timing.v_size;
|
||||
// preset the frame buffer with black color
|
||||
ESP_GOTO_ON_ERROR(esp_cache_msync(frame_buffer, frame_buffer_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M), err, TAG, "cache write back failed");
|
||||
|
||||
// if the clock source is not assigned, fallback to the default clock source
|
||||
mipi_dsi_dpi_clock_source_t dpi_clk_src = panel_config->dpi_clk_src;
|
||||
if (dpi_clk_src == 0) {
|
||||
dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT;
|
||||
}
|
||||
// get the clock source frequency
|
||||
uint32_t dpi_clk_src_freq_hz = 0;
|
||||
ESP_GOTO_ON_ERROR(esp_clk_tree_src_get_freq_hz(dpi_clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED,
|
||||
&dpi_clk_src_freq_hz), err, TAG, "get clock source frequency failed");
|
||||
// divide the source clock to get the final DPI clock
|
||||
uint32_t dpi_div = mipi_dsi_hal_host_dpi_calculate_divider(hal, dpi_clk_src_freq_hz / 1000 / 1000, panel_config->dpi_clock_freq_mhz);
|
||||
// set the clock source, set the divider, and enable the dpi clock
|
||||
DSI_CLOCK_SRC_ATOMIC() {
|
||||
mipi_dsi_ll_set_dpi_clock_source(bus_id, dpi_clk_src);
|
||||
mipi_dsi_ll_set_dpi_clock_div(bus_id, dpi_div);
|
||||
mipi_dsi_ll_enable_dpi_clock(bus_id, true);
|
||||
}
|
||||
|
||||
// create DMA resources
|
||||
ESP_GOTO_ON_ERROR(dpi_panel_create_dma_link(dpi_panel), err, TAG, "initialize DMA link failed");
|
||||
|
||||
mipi_dsi_host_ll_dpi_set_vcid(hal->host, panel_config->virtual_channel);
|
||||
mipi_dsi_hal_host_dpi_set_color_coding(hal, panel_config->pixel_format, 0);
|
||||
// these signals define how the DPI interface interacts with the controller
|
||||
mipi_dsi_host_ll_dpi_set_timing_polarity(hal->host, false, false, false, false, false);
|
||||
// configure the low-power transitions: defines the video periods which are permitted to goto low-power if the time available to do so
|
||||
mipi_dsi_host_ll_dpi_enable_lp_horizontal_timing(hal->host, true, true);
|
||||
mipi_dsi_host_ll_dpi_enable_lp_vertical_timing(hal->host, true, true, true, true);
|
||||
// after sending a frame, the DSI device should return an ack
|
||||
mipi_dsi_host_ll_dpi_enable_frame_ack(hal->host, true);
|
||||
// commands are transmitted in low-power mode
|
||||
mipi_dsi_host_ll_dpi_enable_lp_command(hal->host, true);
|
||||
// using the burst mode because it's energy-efficient
|
||||
mipi_dsi_host_ll_dpi_set_video_burst_type(hal->host, MIPI_DSI_LL_VIDEO_BURST_WITH_SYNC_PULSES);
|
||||
// configure the size of the active lin period, measured in pixels
|
||||
mipi_dsi_host_ll_dpi_set_video_packet_pixel_num(hal->host, panel_config->video_timing.h_size);
|
||||
// disable multi-packets
|
||||
mipi_dsi_host_ll_dpi_set_trunks_num(hal->host, 0);
|
||||
// disable "null packets"
|
||||
mipi_dsi_host_ll_dpi_set_null_packet_size(hal->host, 0);
|
||||
// set horizontal and vertical timing configuration
|
||||
mipi_dsi_hal_host_dpi_set_horizontal_timing(hal, panel_config->video_timing.hsync_pulse_width,
|
||||
panel_config->video_timing.hsync_back_porch,
|
||||
panel_config->video_timing.h_size,
|
||||
panel_config->video_timing.hsync_front_porch);
|
||||
mipi_dsi_hal_host_dpi_set_vertical_timing(hal, panel_config->video_timing.vsync_pulse_width,
|
||||
panel_config->video_timing.vsync_back_porch,
|
||||
panel_config->video_timing.v_size,
|
||||
panel_config->video_timing.vsync_front_porch);
|
||||
mipi_dsi_brg_ll_set_num_pixel_bits(hal->bridge, panel_config->video_timing.h_size * panel_config->video_timing.v_size * 16);
|
||||
mipi_dsi_brg_ll_set_underrun_discard_count(hal->bridge, panel_config->video_timing.h_size);
|
||||
// let the DSI bridge as the DMA flow controller
|
||||
mipi_dsi_brg_ll_set_flow_controller(hal->bridge, MIPI_DSI_LL_FLOW_CONTROLLER_BRIDGE);
|
||||
mipi_dsi_brg_ll_set_burst_len(hal->bridge, 256);
|
||||
mipi_dsi_brg_ll_set_empty_threshold(hal->bridge, 1024 - 256);
|
||||
// enable DSI bridge
|
||||
mipi_dsi_brg_ll_enable(hal->bridge, true);
|
||||
mipi_dsi_brg_ll_update_dpi_config(hal->bridge);
|
||||
|
||||
dpi_panel->base.del = dpi_panel_del;
|
||||
dpi_panel->base.init = dpi_panel_init;
|
||||
dpi_panel->base.draw_bitmap = dpi_panel_draw_bitmap;
|
||||
*ret_panel = &dpi_panel->base;
|
||||
ESP_LOGD(TAG, "new dpi panel @%p, fb@%p", dpi_panel, dpi_panel->frame_buffer);
|
||||
return ESP_OK;
|
||||
err:
|
||||
if (dpi_panel) {
|
||||
dpi_panel_del(&dpi_panel->base);
|
||||
}
|
||||
if (frame_buffer) {
|
||||
free(frame_buffer);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t dpi_panel_del(esp_lcd_panel_t *panel)
|
||||
{
|
||||
esp_lcd_dpi_panel_t *dpi_panel = __containerof(panel, esp_lcd_dpi_panel_t, base);
|
||||
esp_lcd_dsi_bus_handle_t bus = dpi_panel->bus;
|
||||
int bus_id = bus->bus_id;
|
||||
mipi_dsi_hal_context_t *hal = &bus->hal;
|
||||
// disable the DPI clock
|
||||
DSI_CLOCK_SRC_ATOMIC() {
|
||||
mipi_dsi_ll_enable_dpi_clock(bus_id, false);
|
||||
}
|
||||
// disable the DSI bridge
|
||||
mipi_dsi_brg_ll_enable(hal->bridge, false);
|
||||
if (dpi_panel->dma_chan) {
|
||||
dw_gdma_del_channel(dpi_panel->dma_chan);
|
||||
}
|
||||
if (dpi_panel->frame_buffer) {
|
||||
free(dpi_panel->frame_buffer);
|
||||
}
|
||||
if (dpi_panel->link_list) {
|
||||
dw_gdma_del_link_list(dpi_panel->link_list);
|
||||
}
|
||||
free(dpi_panel);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t dpi_panel_init(esp_lcd_panel_t *panel)
|
||||
{
|
||||
esp_lcd_dpi_panel_t *dpi_panel = __containerof(panel, esp_lcd_dpi_panel_t, base);
|
||||
esp_lcd_dsi_bus_handle_t bus = dpi_panel->bus;
|
||||
mipi_dsi_hal_context_t *hal = &bus->hal;
|
||||
dw_gdma_channel_handle_t dma_chan = dpi_panel->dma_chan;
|
||||
dw_gdma_link_list_handle_t link_list = dpi_panel->link_list;
|
||||
|
||||
ESP_RETURN_ON_ERROR(dw_gdma_channel_use_link_list(dma_chan, link_list), TAG, "use DMA link list failed");
|
||||
dw_gdma_block_transfer_config_t dma_transfer_config = {
|
||||
.src = {
|
||||
.addr = (uint32_t)(dpi_panel->frame_buffer),
|
||||
.burst_mode = DW_GDMA_BURST_MODE_INCREMENT,
|
||||
.burst_items = DW_GDMA_BURST_ITEMS_512,
|
||||
.burst_len = 16,
|
||||
.width = DW_GDMA_TRANS_WIDTH_64,
|
||||
},
|
||||
.dst = {
|
||||
.addr = MIPI_DSI_MEM_BASE,
|
||||
.burst_mode = DW_GDMA_BURST_MODE_FIXED,
|
||||
.burst_items = DW_GDMA_BURST_ITEMS_256,
|
||||
.burst_len = 16,
|
||||
.width = DW_GDMA_TRANS_WIDTH_64,
|
||||
},
|
||||
.size = dpi_panel->frame_buffer_size * 8 / 64,
|
||||
};
|
||||
dw_gdma_lli_config_transfer(dw_gdma_link_list_get_item(link_list, 0), &dma_transfer_config);
|
||||
dw_gdma_block_markers_t markers = {
|
||||
.is_valid = true,
|
||||
};
|
||||
dw_gdma_lli_set_block_markers(dw_gdma_link_list_get_item(link_list, 0), markers);
|
||||
dw_gdma_channel_enable_ctrl(dma_chan, true);
|
||||
|
||||
// enable the video mode
|
||||
mipi_dsi_host_ll_enable_video_mode(hal->host, true);
|
||||
// switch the clock lane to high speed mode
|
||||
mipi_dsi_host_ll_set_clock_lane_state(hal->host, MIPI_DSI_LL_CLOCK_LANE_STATE_AUTO);
|
||||
|
||||
// enable the DPI output of the DSI bridge
|
||||
mipi_dsi_brg_ll_enable_dpi_output(hal->bridge, true);
|
||||
mipi_dsi_brg_ll_update_dpi_config(hal->bridge);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t dpi_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data)
|
||||
{
|
||||
esp_lcd_dpi_panel_t *dpi_panel = __containerof(panel, esp_lcd_dpi_panel_t, base);
|
||||
uint8_t *frame_buffer = dpi_panel->frame_buffer;
|
||||
size_t frame_buffer_size = dpi_panel->frame_buffer_size;
|
||||
|
||||
// TODO: memory copy by 2D-DMA
|
||||
size_t bytes_per_pixel = dpi_panel->bytes_per_pixel;
|
||||
const uint8_t *from = (const uint8_t *)color_data;
|
||||
uint8_t *to = frame_buffer + (y_start * dpi_panel->h_pixels + x_start) * bytes_per_pixel;
|
||||
uint32_t copy_bytes_per_line = (x_end - x_start) * bytes_per_pixel;
|
||||
uint32_t bytes_per_line = bytes_per_pixel * dpi_panel->h_pixels;
|
||||
for (int y = y_start; y < y_end; y++) {
|
||||
memcpy(to, from, copy_bytes_per_line);
|
||||
to += bytes_per_line;
|
||||
from += copy_bytes_per_line;
|
||||
}
|
||||
ESP_RETURN_ON_ERROR(esp_cache_msync(frame_buffer, frame_buffer_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M), TAG, "cache write back failed");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_lcd_dpi_panel_set_pattern(esp_lcd_panel_handle_t panel, mipi_dsi_pattern_type_t pattern)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
esp_lcd_dpi_panel_t *dpi_panel = __containerof(panel, esp_lcd_dpi_panel_t, base);
|
||||
esp_lcd_dsi_bus_handle_t bus = dpi_panel->bus;
|
||||
mipi_dsi_hal_context_t *hal = &bus->hal;
|
||||
|
||||
if (pattern != MIPI_DSI_PATTERN_NONE) {
|
||||
// stop the DSI bridge from generating the DPI stream
|
||||
mipi_dsi_brg_ll_enable_dpi_output(hal->bridge, false);
|
||||
mipi_dsi_brg_ll_update_dpi_config(hal->bridge);
|
||||
}
|
||||
|
||||
// set the pattern type and enable the pattern generator for the DSI host controller
|
||||
mipi_dsi_host_ll_dpi_set_pattern_type(hal->host, pattern);
|
||||
|
||||
if (pattern == MIPI_DSI_PATTERN_NONE) {
|
||||
// reenable the DSI bridge to generate the DPI stream
|
||||
mipi_dsi_brg_ll_enable_dpi_output(hal->bridge, true);
|
||||
mipi_dsi_brg_ll_update_dpi_config(hal->bridge);
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
95
components/esp_lcd/dsi/esp_lcd_panel_io_dbi.c
Normal file
95
components/esp_lcd/dsi/esp_lcd_panel_io_dbi.c
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_lcd_panel_io_interface.h"
|
||||
#include "esp_lcd_mipi_dsi.h"
|
||||
#include "mipi_dsi_priv.h"
|
||||
|
||||
static const char *TAG = "lcd.dsi.dbi";
|
||||
|
||||
typedef struct esp_lcd_dbi_io_t esp_lcd_dbi_io_t;
|
||||
|
||||
struct esp_lcd_dbi_io_t {
|
||||
esp_lcd_panel_io_t base; // Base class of generic lcd panel IO
|
||||
esp_lcd_dsi_bus_handle_t bus; // DSI bus handle
|
||||
uint8_t virtual_channel; // Virtual channel ID, index from 0
|
||||
int lcd_cmd_bits; // Bit-width of LCD command
|
||||
int lcd_param_bits; // Bit-width of LCD parameter
|
||||
};
|
||||
|
||||
static esp_err_t panel_io_dbi_del(esp_lcd_panel_io_t *io);
|
||||
static esp_err_t panel_io_dbi_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size);
|
||||
static esp_err_t panel_io_dbi_rx_param(esp_lcd_panel_io_t *io, int lcd_cmd, void *param, size_t param_size);
|
||||
|
||||
esp_err_t esp_lcd_new_panel_io_dbi(esp_lcd_dsi_bus_handle_t bus, const esp_lcd_dbi_io_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(bus && io_config && ret_io, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE(io_config->virtual_channel < 4, ESP_ERR_INVALID_ARG, TAG, "invalid virtual channel %d", io_config->virtual_channel);
|
||||
mipi_dsi_hal_context_t *hal = &bus->hal;
|
||||
|
||||
esp_lcd_dbi_io_t *dbi_io = heap_caps_calloc(1, sizeof(esp_lcd_dbi_io_t), DSI_MEM_ALLOC_CAPS);
|
||||
ESP_RETURN_ON_FALSE(dbi_io, ESP_ERR_NO_MEM, TAG, "no memory for DBI IO");
|
||||
dbi_io->virtual_channel = io_config->virtual_channel;
|
||||
dbi_io->bus = bus;
|
||||
|
||||
// Tear Effect is not supported
|
||||
mipi_dsi_host_ll_enable_te_ack(hal->host, false);
|
||||
// enable command ack, to ensure the reliability and integrity of the data transmission
|
||||
mipi_dsi_host_ll_enable_cmd_ack(hal->host, true);
|
||||
// using low power mode for sending generic MIPI DSI packets
|
||||
mipi_dsi_host_ll_set_gen_short_wr_speed_mode(hal->host, 0, MIPI_DSI_LL_TRANS_SPEED_LP);
|
||||
mipi_dsi_host_ll_set_gen_short_wr_speed_mode(hal->host, 1, MIPI_DSI_LL_TRANS_SPEED_LP);
|
||||
mipi_dsi_host_ll_set_gen_short_wr_speed_mode(hal->host, 2, MIPI_DSI_LL_TRANS_SPEED_LP);
|
||||
mipi_dsi_host_ll_set_gen_long_wr_speed_mode(hal->host, MIPI_DSI_LL_TRANS_SPEED_LP);
|
||||
mipi_dsi_host_ll_set_gen_short_rd_speed_mode(hal->host, 0, MIPI_DSI_LL_TRANS_SPEED_LP);
|
||||
mipi_dsi_host_ll_set_gen_short_rd_speed_mode(hal->host, 1, MIPI_DSI_LL_TRANS_SPEED_LP);
|
||||
mipi_dsi_host_ll_set_gen_short_rd_speed_mode(hal->host, 2, MIPI_DSI_LL_TRANS_SPEED_LP);
|
||||
mipi_dsi_host_ll_set_dcs_short_wr_speed_mode(hal->host, 0, MIPI_DSI_LL_TRANS_SPEED_LP);
|
||||
mipi_dsi_host_ll_set_dcs_short_wr_speed_mode(hal->host, 1, MIPI_DSI_LL_TRANS_SPEED_LP);
|
||||
mipi_dsi_host_ll_set_dcs_long_wr_speed_mode(hal->host, MIPI_DSI_LL_TRANS_SPEED_LP);
|
||||
mipi_dsi_host_ll_set_dcs_short_rd_speed_mode(hal->host, 0, MIPI_DSI_LL_TRANS_SPEED_LP);
|
||||
mipi_dsi_host_ll_set_mrps_speed_mode(hal->host, MIPI_DSI_LL_TRANS_SPEED_LP);
|
||||
|
||||
dbi_io->base.del = panel_io_dbi_del;
|
||||
dbi_io->base.tx_param = panel_io_dbi_tx_param;
|
||||
dbi_io->base.rx_param = panel_io_dbi_rx_param;
|
||||
dbi_io->lcd_cmd_bits = io_config->lcd_cmd_bits;
|
||||
dbi_io->lcd_param_bits = io_config->lcd_param_bits;
|
||||
*ret_io = &dbi_io->base;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t panel_io_dbi_del(esp_lcd_panel_io_t *io)
|
||||
{
|
||||
esp_lcd_dbi_io_t *dbi_io = __containerof(io, esp_lcd_dbi_io_t, base);
|
||||
free(dbi_io);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t panel_io_dbi_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size)
|
||||
{
|
||||
esp_lcd_dbi_io_t *dbi_io = __containerof(io, esp_lcd_dbi_io_t, base);
|
||||
esp_lcd_dsi_bus_handle_t bus = dbi_io->bus;
|
||||
mipi_dsi_hal_context_t *hal = &bus->hal;
|
||||
|
||||
mipi_dsi_hal_host_gen_write_dcs_command(hal, dbi_io->virtual_channel, lcd_cmd, dbi_io->lcd_cmd_bits / 8, param, param_size);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t panel_io_dbi_rx_param(esp_lcd_panel_io_t *io, int lcd_cmd, void *param, size_t param_size)
|
||||
{
|
||||
esp_lcd_dbi_io_t *dbi_io = __containerof(io, esp_lcd_dbi_io_t, base);
|
||||
esp_lcd_dsi_bus_handle_t bus = dbi_io->bus;
|
||||
mipi_dsi_hal_context_t *hal = &bus->hal;
|
||||
|
||||
mipi_dsi_hal_host_gen_read_dcs_command(hal, dbi_io->virtual_channel, lcd_cmd, dbi_io->lcd_cmd_bits / 8, param, param_size);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
118
components/esp_lcd/dsi/include/esp_lcd_mipi_dsi.h
Normal file
118
components/esp_lcd/dsi/include/esp_lcd_mipi_dsi.h
Normal file
@ -0,0 +1,118 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "esp_err.h"
|
||||
#include "esp_lcd_types.h"
|
||||
|
||||
typedef struct esp_lcd_dsi_bus_t *esp_lcd_dsi_bus_handle_t; /*!< Type of MIPI DSI bus handle */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief MIPI DSI bus configuration structure
|
||||
*/
|
||||
typedef struct {
|
||||
int bus_id; /*!< Select which DSI controller, index from 0 */
|
||||
uint8_t num_data_lanes; /*!< Number of data lanes */
|
||||
mipi_dsi_phy_clock_source_t phy_clk_src; /*!< MIPI DSI PHY clock source */
|
||||
uint32_t lane_bit_rate_mbps; /*!< Lane bit rate in Mbps */
|
||||
} esp_lcd_dsi_bus_config_t;
|
||||
|
||||
/**
|
||||
* @brief Create MIPI DSI bus handle
|
||||
*
|
||||
* @param[in] bus_config Bus configuration
|
||||
* @param[out] ret_bus Returned bus handle
|
||||
* @return
|
||||
* - ESP_OK: Create MIPI DSI bus successfully
|
||||
* - ESP_ERR_INVALID_ARG: Create MIPI DSI bus failed because of invalid argument
|
||||
* - ESP_ERR_NO_MEM: Create MIPI DSI bus failed because of out of memory
|
||||
* - ESP_ERR_NOT_FOUND: Create MIPI DSI bus failed because no more free DSI hardware instance
|
||||
* - ESP_FAIL: Create MIPI DSI bus failed because of other error
|
||||
*/
|
||||
esp_err_t esp_lcd_new_dsi_bus(const esp_lcd_dsi_bus_config_t *bus_config, esp_lcd_dsi_bus_handle_t *ret_bus);
|
||||
|
||||
/**
|
||||
* @brief Destroy MIPI DSI bus handle
|
||||
*
|
||||
* @param[in] bus MIPI DSI bus handle, returned from `esp_lcd_new_dsi_bus`
|
||||
* @return
|
||||
* - ESP_OK: Destroy MIPI DSI bus successfully
|
||||
* - ESP_ERR_INVALID_ARG: Destroy MIPI DSI bus failed because of invalid argument
|
||||
* - ESP_FAIL: Destroy MIPI DSI bus failed because of other error
|
||||
*/
|
||||
esp_err_t esp_lcd_del_dsi_bus(esp_lcd_dsi_bus_handle_t bus);
|
||||
|
||||
/**
|
||||
* @brief Panel IO configuration structure, for MIPI DSI command interface
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t virtual_channel; /*!< Virtual channel ID, index from 0 */
|
||||
int lcd_cmd_bits; /*!< Bit-width of LCD command */
|
||||
int lcd_param_bits; /*!< Bit-width of LCD parameter */
|
||||
} esp_lcd_dbi_io_config_t;
|
||||
|
||||
/**
|
||||
* @brief Create LCD panel IO, for MIPI DSI DBI interface
|
||||
*
|
||||
* @note Although we call it "DBI", internally the driver is using a co-called "generic" interface for transmitting/receiving LCD commands and parameters.
|
||||
*
|
||||
* @param[in] bus MIPI DSI bus handle, returned from `esp_lcd_new_dsi_bus`
|
||||
* @param[in] io_config IO configuration
|
||||
* @param[out] ret_io Returned panel IO handle
|
||||
* @return
|
||||
* - ESP_OK: Create MIPI DSI command IO successfully
|
||||
* - ESP_ERR_INVALID_ARG: Create MIPI DSI command IO failed because of invalid argument
|
||||
* - ESP_ERR_NO_MEM: Create MIPI DSI command IO failed because of out of memory
|
||||
* - ESP_FAIL: Create MIPI DSI command IO failed because of other error
|
||||
*/
|
||||
esp_err_t esp_lcd_new_panel_io_dbi(esp_lcd_dsi_bus_handle_t bus, const esp_lcd_dbi_io_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io);
|
||||
|
||||
/**
|
||||
* @brief MIPI DSI DPI panel configuration structure
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t virtual_channel; /*!< Virtual channel ID, index from 0 */
|
||||
mipi_dsi_dpi_clock_source_t dpi_clk_src; /*!< MIPI DSI DPI clock source */
|
||||
uint32_t dpi_clock_freq_mhz; /*!< DPI clock frequency in MHz */
|
||||
lcd_color_rgb_pixel_format_t pixel_format; /*!< Pixel format */
|
||||
esp_lcd_video_timing_t video_timing; /*!< Video timing */
|
||||
} esp_lcd_dpi_panel_config_t;
|
||||
|
||||
/**
|
||||
* @brief Create LCD panel for MIPI DSI DPI interface
|
||||
*
|
||||
* @param[in] bus MIPI DSI bus handle, returned from `esp_lcd_new_dsi_bus`
|
||||
* @param[in] panel_config DSI data panel configuration
|
||||
* @param[out] ret_panel Returned LCD panel handle
|
||||
* @return
|
||||
* - ESP_OK: Create MIPI DSI data panel successfully
|
||||
* - ESP_ERR_INVALID_ARG: Create MIPI DSI data panel failed because of invalid argument
|
||||
* - ESP_ERR_NO_MEM: Create MIPI DSI data panel failed because of out of memory
|
||||
* - ESP_FAIL: Create MIPI DSI data panel failed because of other error
|
||||
*/
|
||||
esp_err_t esp_lcd_new_panel_dpi(esp_lcd_dsi_bus_handle_t bus, const esp_lcd_dpi_panel_config_t *panel_config, esp_lcd_panel_handle_t *ret_panel);
|
||||
|
||||
/**
|
||||
* @brief Set pre-defined pattern to the screen for testing or debugging purpose
|
||||
*
|
||||
* @param[in] dbi_panel MIPI DBI panel handle, returned from `esp_lcd_new_panel_dpi`
|
||||
* @param[in] pattern Pattern type
|
||||
* @return
|
||||
* - ESP_OK: Set pattern successfully
|
||||
* - ESP_ERR_INVALID_ARG: Set pattern failed because of invalid argument
|
||||
* - ESP_FAIL: Set pattern failed because of other error
|
||||
*/
|
||||
esp_err_t esp_lcd_dpi_panel_set_pattern(esp_lcd_panel_handle_t dbi_panel, mipi_dsi_pattern_type_t pattern);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
38
components/esp_lcd/dsi/mipi_dsi_priv.h
Normal file
38
components/esp_lcd/dsi/mipi_dsi_priv.h
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "hal/mipi_dsi_hal.h"
|
||||
#include "hal/mipi_dsi_ll.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_private/periph_ctrl.h"
|
||||
|
||||
#if SOC_PERIPH_CLK_CTRL_SHARED
|
||||
#define DSI_CLOCK_SRC_ATOMIC() PERIPH_RCC_ATOMIC()
|
||||
#else
|
||||
#define DSI_CLOCK_SRC_ATOMIC()
|
||||
#endif
|
||||
|
||||
#if !SOC_RCC_IS_INDEPENDENT
|
||||
#define DSI_RCC_ATOMIC() PERIPH_RCC_ATOMIC()
|
||||
#else
|
||||
#define DSI_RCC_ATOMIC()
|
||||
#endif
|
||||
|
||||
#define DSI_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct esp_lcd_dsi_bus_t {
|
||||
int bus_id;
|
||||
mipi_dsi_hal_context_t hal;
|
||||
} esp_lcd_dsi_bus_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
@ -5,13 +5,28 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "hal/lcd_types.h"
|
||||
#include "esp_assert.h"
|
||||
#include "hal/lcd_types.h"
|
||||
#include "hal/mipi_dsi_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Timing parameters for the video data transmission
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t h_size; /*!< Horizontal resolution, i.e. the number of pixels in a line */
|
||||
uint32_t v_size; /*!< Vertical resolution, i.e. the number of lines in the frame */
|
||||
uint32_t hsync_pulse_width; /*!< Horizontal sync width, in pixel clock */
|
||||
uint32_t hsync_back_porch; /*!< Horizontal back porch, number of pixel clock between hsync and start of line active data */
|
||||
uint32_t hsync_front_porch; /*!< Horizontal front porch, number of pixel clock between the end of active data and the next hsync */
|
||||
uint32_t vsync_pulse_width; /*!< Vertical sync width, in number of lines */
|
||||
uint32_t vsync_back_porch; /*!< Vertical back porch, number of invalid lines between vsync and start of frame */
|
||||
uint32_t vsync_front_porch; /*!< Vertical front porch, number of invalid lines between the end of frame and the next vsync */
|
||||
} esp_lcd_video_timing_t;
|
||||
|
||||
typedef struct esp_lcd_panel_io_t *esp_lcd_panel_io_handle_t; /*!< Type of LCD panel IO handle */
|
||||
typedef struct esp_lcd_panel_t *esp_lcd_panel_handle_t; /*!< Type of LCD panel handle */
|
||||
|
||||
|
@ -30,6 +30,16 @@ components/esp_lcd/test_apps/i80_lcd:
|
||||
disable:
|
||||
- if: SOC_LCD_I80_SUPPORTED != 1
|
||||
|
||||
components/esp_lcd/test_apps/mipi_dsi_lcd:
|
||||
depends_components:
|
||||
- esp_lcd
|
||||
disable:
|
||||
- if: SOC_LCD_MIPI_DSI_SUPPORTED != 1
|
||||
disable_test:
|
||||
- if: IDF_TARGET == "esp32p4"
|
||||
temporary: true
|
||||
reason: lack of runners
|
||||
|
||||
components/esp_lcd/test_apps/rgb_lcd:
|
||||
depends_components:
|
||||
- esp_lcd
|
||||
|
8
components/esp_lcd/test_apps/mipi_dsi_lcd/CMakeLists.txt
Normal file
8
components/esp_lcd/test_apps/mipi_dsi_lcd/CMakeLists.txt
Normal file
@ -0,0 +1,8 @@
|
||||
# This is the project CMakeLists.txt file for the test subproject
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
set(COMPONENTS main)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(mipi_dsi_lcd_panel_test)
|
4
components/esp_lcd/test_apps/mipi_dsi_lcd/README.md
Normal file
4
components/esp_lcd/test_apps/mipi_dsi_lcd/README.md
Normal file
@ -0,0 +1,4 @@
|
||||
| Supported Targets | ESP32-P4 |
|
||||
| ----------------- | -------- |
|
||||
|
||||
This test app is used to test MIPI DSI interfaced LCDs.
|
@ -0,0 +1,9 @@
|
||||
set(srcs "test_app_main.c"
|
||||
"test_mipi_dsi_board.c"
|
||||
"test_mipi_dsi_panel.c")
|
||||
|
||||
# In order for the cases defined by `TEST_CASE` to be linked into the final elf,
|
||||
# the component can be registered as WHOLE_ARCHIVE
|
||||
idf_component_register(SRCS ${srcs}
|
||||
PRIV_REQUIRES esp_lcd unity
|
||||
WHOLE_ARCHIVE)
|
@ -0,0 +1,2 @@
|
||||
dependencies:
|
||||
esp_lcd_ili9881c: ">=0.1.0"
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "unity_test_runner.h"
|
||||
#include "esp_heap_caps.h"
|
||||
|
||||
// Some resources are lazy allocated in LCD driver, the threshold is left for that case
|
||||
#define TEST_MEMORY_LEAK_THRESHOLD (-500)
|
||||
|
||||
static size_t before_free_8bit;
|
||||
static size_t before_free_32bit;
|
||||
|
||||
static void check_leak(size_t before_free, size_t after_free, const char *type)
|
||||
{
|
||||
ssize_t delta = after_free - before_free;
|
||||
printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta);
|
||||
TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak");
|
||||
}
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
|
||||
before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
|
||||
size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
|
||||
check_leak(before_free_8bit, after_free_8bit, "8BIT");
|
||||
check_leak(before_free_32bit, after_free_32bit, "32BIT");
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
// __ __ ___ ____ ___ ____ ____ ___ _____ _
|
||||
// | \/ |_ _| _ \_ _| | _ \/ ___|_ _| |_ _|__ ___| |_
|
||||
// | |\/| || || |_) | | | | | \___ \| | | |/ _ \/ __| __|
|
||||
// | | | || || __/| | | |_| |___) | | | | __/\__ \ |_
|
||||
// |_| |_|___|_| |___| |____/|____/___| |_|\___||___/\__|
|
||||
printf(" __ __ ___ ____ ___ ____ ____ ___ _____ _\r\n");
|
||||
printf("| \\/ |_ _| _ \\_ _| | _ \\/ ___|_ _| |_ _|__ ___| |_\r\n");
|
||||
printf("| |\\/| || || |_) | | | | | \\___ \\| | | |/ _ \\/ __| __|\r\n");
|
||||
printf("| | | || || __/| | | |_| |___) | | | | __/\\__ \\ |_\r\n");
|
||||
printf("|_| |_|___|_| |___| |____/|____/___| |_|\\___||___/\\__|\r\n");
|
||||
unity_run_menu();
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "test_mipi_dsi_board.h"
|
||||
#include "esp_private/esp_ldo.h"
|
||||
|
||||
static esp_ldo_unit_handle_t phy_pwr_unit = NULL;
|
||||
|
||||
void test_bsp_enable_dsi_phy_power(void)
|
||||
{
|
||||
// Turn on the power for MIPI DSI PHY, so it can go from "No Power" state to "Shutdown" state
|
||||
esp_ldo_unit_init_cfg_t ldo_cfg = {
|
||||
.unit_id = TEST_MIPI_DSI_PHY_PWR_LDO_UNIT,
|
||||
.cfg = {
|
||||
.voltage_mv = TEST_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV,
|
||||
},
|
||||
};
|
||||
TEST_ESP_OK(esp_ldo_init_unit(&ldo_cfg, &phy_pwr_unit));
|
||||
TEST_ESP_OK(esp_ldo_enable_unit(phy_pwr_unit));
|
||||
}
|
||||
|
||||
void test_bsp_disable_dsi_phy_power(void)
|
||||
{
|
||||
TEST_ESP_OK(esp_ldo_disable_unit(phy_pwr_unit));
|
||||
TEST_ESP_OK(esp_ldo_deinit_unit(phy_pwr_unit));
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// FPS = 80000000/(40+140+40+800)/(4+16+16+1280) = 60Hz
|
||||
#define MIPI_DSI_DPI_CLK_MHZ 80
|
||||
#define MIPI_DSI_LCD_H_RES 800
|
||||
#define MIPI_DSI_LCD_V_RES 1280
|
||||
#define MIPI_DSI_LCD_HSYNC 40
|
||||
#define MIPI_DSI_LCD_HBP 140
|
||||
#define MIPI_DSI_LCD_HFP 40
|
||||
#define MIPI_DSI_LCD_VSYNC 4
|
||||
#define MIPI_DSI_LCD_VBP 16
|
||||
#define MIPI_DSI_LCD_VFP 16
|
||||
|
||||
#define TEST_MIPI_DSI_PHY_PWR_LDO_UNIT 3
|
||||
#define TEST_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV 2500
|
||||
|
||||
void test_bsp_enable_dsi_phy_power(void);
|
||||
void test_bsp_disable_dsi_phy_power(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
@ -0,0 +1,165 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "unity.h"
|
||||
#include "esp_lcd_mipi_dsi.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
#include "esp_lcd_panel_io.h"
|
||||
#include "esp_random.h"
|
||||
#include "esp_attr.h"
|
||||
#include "test_mipi_dsi_board.h"
|
||||
#include "esp_lcd_ili9881c.h"
|
||||
|
||||
TEST_CASE("MIPI DSI Pattern Generator (ILI9881C)", "[mipi_dsi]")
|
||||
{
|
||||
esp_lcd_dsi_bus_handle_t mipi_dsi_bus;
|
||||
esp_lcd_panel_io_handle_t mipi_dbi_io;
|
||||
esp_lcd_panel_handle_t mipi_dpi_panel;
|
||||
esp_lcd_panel_handle_t ili9881c_ctrl_panel;
|
||||
|
||||
test_bsp_enable_dsi_phy_power();
|
||||
|
||||
esp_lcd_dsi_bus_config_t bus_config = {
|
||||
.bus_id = 0,
|
||||
.num_data_lanes = 2,
|
||||
.phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT,
|
||||
.lane_bit_rate_mbps = 1000, // 1000 Mbps
|
||||
};
|
||||
TEST_ESP_OK(esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus));
|
||||
|
||||
esp_lcd_dbi_io_config_t dbi_config = {
|
||||
.virtual_channel = 0,
|
||||
.lcd_cmd_bits = 8,
|
||||
.lcd_param_bits = 8,
|
||||
};
|
||||
TEST_ESP_OK(esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &mipi_dbi_io));
|
||||
|
||||
esp_lcd_panel_dev_config_t lcd_dev_config = {
|
||||
.bits_per_pixel = 16,
|
||||
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
|
||||
.reset_gpio_num = -1,
|
||||
};
|
||||
TEST_ESP_OK(esp_lcd_new_panel_ili9881c(mipi_dbi_io, &lcd_dev_config, &ili9881c_ctrl_panel));
|
||||
TEST_ESP_OK(esp_lcd_panel_reset(ili9881c_ctrl_panel));
|
||||
TEST_ESP_OK(esp_lcd_panel_init(ili9881c_ctrl_panel));
|
||||
// turn on display
|
||||
TEST_ESP_OK(esp_lcd_panel_disp_on_off(ili9881c_ctrl_panel, true));
|
||||
|
||||
esp_lcd_dpi_panel_config_t dpi_config = {
|
||||
.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT,
|
||||
.dpi_clock_freq_mhz = MIPI_DSI_DPI_CLK_MHZ,
|
||||
.virtual_channel = 0,
|
||||
.pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565,
|
||||
.video_timing = {
|
||||
.h_size = MIPI_DSI_LCD_H_RES,
|
||||
.v_size = MIPI_DSI_LCD_V_RES,
|
||||
.hsync_back_porch = MIPI_DSI_LCD_HBP,
|
||||
.hsync_pulse_width = MIPI_DSI_LCD_HSYNC,
|
||||
.hsync_front_porch = MIPI_DSI_LCD_HFP,
|
||||
.vsync_back_porch = MIPI_DSI_LCD_VBP,
|
||||
.vsync_pulse_width = MIPI_DSI_LCD_VSYNC,
|
||||
.vsync_front_porch = MIPI_DSI_LCD_VFP,
|
||||
},
|
||||
};
|
||||
TEST_ESP_OK(esp_lcd_new_panel_dpi(mipi_dsi_bus, &dpi_config, &mipi_dpi_panel));
|
||||
TEST_ESP_OK(esp_lcd_panel_init(mipi_dpi_panel));
|
||||
|
||||
TEST_ESP_OK(esp_lcd_dpi_panel_set_pattern(mipi_dpi_panel, MIPI_DSI_PATTERN_BAR_HORIZONTAL));
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
TEST_ESP_OK(esp_lcd_dpi_panel_set_pattern(mipi_dpi_panel, MIPI_DSI_PATTERN_BAR_VERTICAL));
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
TEST_ESP_OK(esp_lcd_dpi_panel_set_pattern(mipi_dpi_panel, MIPI_DSI_PATTERN_BER_VERTICAL));
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
TEST_ESP_OK(esp_lcd_dpi_panel_set_pattern(mipi_dpi_panel, MIPI_DSI_PATTERN_NONE));
|
||||
|
||||
TEST_ESP_OK(esp_lcd_panel_del(mipi_dpi_panel));
|
||||
TEST_ESP_OK(esp_lcd_panel_del(ili9881c_ctrl_panel));
|
||||
TEST_ESP_OK(esp_lcd_panel_io_del(mipi_dbi_io));
|
||||
TEST_ESP_OK(esp_lcd_del_dsi_bus(mipi_dsi_bus));
|
||||
|
||||
test_bsp_disable_dsi_phy_power();
|
||||
}
|
||||
|
||||
#define TEST_IMG_SIZE (100 * 100 * sizeof(uint16_t))
|
||||
|
||||
TEST_CASE("MIPI DSI draw bitmap (ILI9881C)", "[mipi_dsi]")
|
||||
{
|
||||
esp_lcd_dsi_bus_handle_t mipi_dsi_bus;
|
||||
esp_lcd_panel_io_handle_t mipi_dbi_io;
|
||||
esp_lcd_panel_handle_t mipi_dpi_panel;
|
||||
esp_lcd_panel_handle_t ili9881c_ctrl_panel;
|
||||
|
||||
test_bsp_enable_dsi_phy_power();
|
||||
|
||||
uint8_t *img = malloc(TEST_IMG_SIZE);
|
||||
TEST_ASSERT_NOT_NULL(img);
|
||||
|
||||
esp_lcd_dsi_bus_config_t bus_config = {
|
||||
.bus_id = 0,
|
||||
.num_data_lanes = 2,
|
||||
.phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT,
|
||||
.lane_bit_rate_mbps = 1000, // 1000 Mbps
|
||||
};
|
||||
TEST_ESP_OK(esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus));
|
||||
|
||||
esp_lcd_dbi_io_config_t dbi_config = {
|
||||
.virtual_channel = 0,
|
||||
.lcd_cmd_bits = 8,
|
||||
.lcd_param_bits = 8,
|
||||
};
|
||||
TEST_ESP_OK(esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &mipi_dbi_io));
|
||||
|
||||
esp_lcd_panel_dev_config_t lcd_dev_config = {
|
||||
.bits_per_pixel = 16,
|
||||
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
|
||||
.reset_gpio_num = -1,
|
||||
};
|
||||
TEST_ESP_OK(esp_lcd_new_panel_ili9881c(mipi_dbi_io, &lcd_dev_config, &ili9881c_ctrl_panel));
|
||||
TEST_ESP_OK(esp_lcd_panel_reset(ili9881c_ctrl_panel));
|
||||
TEST_ESP_OK(esp_lcd_panel_init(ili9881c_ctrl_panel));
|
||||
// turn on display
|
||||
TEST_ESP_OK(esp_lcd_panel_disp_on_off(ili9881c_ctrl_panel, true));
|
||||
|
||||
esp_lcd_dpi_panel_config_t dpi_config = {
|
||||
.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT,
|
||||
.dpi_clock_freq_mhz = MIPI_DSI_DPI_CLK_MHZ,
|
||||
.virtual_channel = 0,
|
||||
.pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565,
|
||||
.video_timing = {
|
||||
.h_size = MIPI_DSI_LCD_H_RES,
|
||||
.v_size = MIPI_DSI_LCD_V_RES,
|
||||
.hsync_back_porch = MIPI_DSI_LCD_HBP,
|
||||
.hsync_pulse_width = MIPI_DSI_LCD_HSYNC,
|
||||
.hsync_front_porch = MIPI_DSI_LCD_HFP,
|
||||
.vsync_back_porch = MIPI_DSI_LCD_VBP,
|
||||
.vsync_pulse_width = MIPI_DSI_LCD_VSYNC,
|
||||
.vsync_front_porch = MIPI_DSI_LCD_VFP,
|
||||
},
|
||||
};
|
||||
TEST_ESP_OK(esp_lcd_new_panel_dpi(mipi_dsi_bus, &dpi_config, &mipi_dpi_panel));
|
||||
TEST_ESP_OK(esp_lcd_panel_init(mipi_dpi_panel));
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
uint8_t color_byte = rand() & 0xFF;
|
||||
int x_start = rand() % (MIPI_DSI_LCD_H_RES - 100);
|
||||
int y_start = rand() % (MIPI_DSI_LCD_V_RES - 100);
|
||||
memset(img, color_byte, TEST_IMG_SIZE);
|
||||
esp_lcd_panel_draw_bitmap(mipi_dpi_panel, x_start, y_start, x_start + 100, y_start + 100, img);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
|
||||
TEST_ESP_OK(esp_lcd_panel_del(mipi_dpi_panel));
|
||||
TEST_ESP_OK(esp_lcd_panel_del(ili9881c_ctrl_panel));
|
||||
TEST_ESP_OK(esp_lcd_panel_io_del(mipi_dbi_io));
|
||||
TEST_ESP_OK(esp_lcd_del_dsi_bus(mipi_dsi_bus));
|
||||
free(img);
|
||||
|
||||
test_bsp_disable_dsi_phy_power();
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
# SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
|
||||
|
||||
@pytest.mark.esp32p4
|
||||
@pytest.mark.generic
|
||||
def test_rgb_lcd(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases()
|
@ -0,0 +1,3 @@
|
||||
CONFIG_ESP_TASK_WDT_INIT=n
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
CONFIG_IDF_EXPERIMENTAL_FEATURES=y
|
@ -0,0 +1,3 @@
|
||||
CONFIG_SPIRAM=y
|
||||
CONFIG_SPIRAM_MODE_HEX=y
|
||||
CONFIG_SPIRAM_SPEED_200M=y
|
@ -35,6 +35,10 @@ config SOC_PCNT_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_MIPI_DSI_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_MCPWM_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
@ -28,7 +28,7 @@
|
||||
#define SOC_GPTIMER_SUPPORTED 1
|
||||
#define SOC_PCNT_SUPPORTED 1
|
||||
// #define SOC_LCDCAM_SUPPORTED 1 // TODO: IDF-7465
|
||||
// #define SOC_MIPI_DSI_SUPPORTED 1 // TODO: IDF-7085
|
||||
#define SOC_MIPI_DSI_SUPPORTED 1
|
||||
#define SOC_MCPWM_SUPPORTED 1
|
||||
#define SOC_TWAI_SUPPORTED 1
|
||||
#define SOC_ETM_SUPPORTED 1
|
||||
|
@ -149,6 +149,12 @@ examples/peripherals/lcd/i80_controller:
|
||||
- esp_lcd
|
||||
- esp_driver_i2s
|
||||
|
||||
examples/peripherals/lcd/mipi_dsi:
|
||||
disable:
|
||||
- if: SOC_LCD_MIPI_DSI_SUPPORTED != 1
|
||||
depends_components:
|
||||
- esp_lcd
|
||||
|
||||
examples/peripherals/lcd/rgb_panel:
|
||||
disable:
|
||||
- if: SOC_LCD_RGB_SUPPORTED != 1
|
||||
|
4
examples/peripherals/lcd/mipi_dsi/CMakeLists.txt
Normal file
4
examples/peripherals/lcd/mipi_dsi/CMakeLists.txt
Normal file
@ -0,0 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(mipi_dsi_panel)
|
77
examples/peripherals/lcd/mipi_dsi/README.md
Normal file
77
examples/peripherals/lcd/mipi_dsi/README.md
Normal file
@ -0,0 +1,77 @@
|
||||
| Supported Targets | ESP32-P4 |
|
||||
| ----------------- | -------- |
|
||||
|
||||
# MIPI DSI LCD Panel Example
|
||||
|
||||
[esp_lcd](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/lcd.html) supports MIPI DSI interfaced LCD panel, with frame buffer(s) managed by the driver itself.
|
||||
|
||||
This example shows the general process of installing a MIPI DSI LCD driver, and displays a LVGL widget on the screen.
|
||||
|
||||
## How to use the example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
* An ESP development board, which with MIPI DSI peripheral supported
|
||||
* A general MIPI DSI LCD panel, with 2 data lanes and 1 clock lane, this example will use the [ILI9881C](https://components.espressif.com/components/espressif/esp_lcd_ili9881c) for demonstration
|
||||
* An USB cable for power supply and programming
|
||||
|
||||
### Hardware Connection
|
||||
|
||||
The connection between ESP Board and the LCD is as follows:
|
||||
|
||||
```text
|
||||
ESP Board MIPI DSI LCD Panel
|
||||
+-----------------------+ +-------------------+
|
||||
| GND +--------------+ GND |
|
||||
| | | |
|
||||
| 3V3 +--------------+ VCC |
|
||||
| | | |
|
||||
| DSI_CLK_P +--------------+ DSI_CLK_P |
|
||||
| DSI_CLK_N + + DSI_CLK_N |
|
||||
| | | |
|
||||
| DSI_DAT0_P +--------------+ DSI_DAT0_P |
|
||||
| DAI_DAT0_N + + DAI_DAT0_N |
|
||||
| | | |
|
||||
| DSI_DAT1_P +--------------+ DSI_DAT1_P |
|
||||
| DSI_DAT1_N + + DSI_DAT1_N |
|
||||
| | | |
|
||||
| | | |
|
||||
| BK_LIGHT +--------------+ BLK |
|
||||
| | | |
|
||||
| Reset +--------------+ Reset |
|
||||
| | | |
|
||||
+-----------------------+ +-------------------+
|
||||
```
|
||||
|
||||
Before testing your LCD, you also need to read your LCD spec carefully, and then adjust the values like "resolution" and "blank time" in the [main](./main/mipi_dsi_lcd_example_main.c) file.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Run `idf.py -p PORT build flash monitor` to build, flash and monitor the project. A LVGL widget should show up on the LCD as expected.
|
||||
|
||||
The first time you run `idf.py` for the example will cost extra time as the build system needs to address the component dependencies and downloads the missing components from registry into `managed_components` folder.
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
### Example Output
|
||||
|
||||
```bash
|
||||
...
|
||||
I (1629) example: MIPI DSI PHY Powered on
|
||||
I (1629) example: Install MIPI DSI LCD control panel
|
||||
I (1639) ili9881c: ID1: 0x98, ID2: 0x81, ID3: 0x5c
|
||||
I (1779) example: Install MIPI DSI LCD data panel
|
||||
I (1799) example: Initialize LVGL library
|
||||
I (1799) example: Allocate separate LVGL draw buffers from PSRAM
|
||||
I (1809) example: Use esp_timer as LVGL tick timer
|
||||
I (1809) example: Create LVGL task
|
||||
I (1809) example: Starting LVGL task
|
||||
I (1919) example: Display LVGL Meter Widget
|
||||
...
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.
|
2
examples/peripherals/lcd/mipi_dsi/main/CMakeLists.txt
Normal file
2
examples/peripherals/lcd/mipi_dsi/main/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "mipi_dsi_lcd_example_main.c" "lvgl_demo_ui.c"
|
||||
INCLUDE_DIRS ".")
|
3
examples/peripherals/lcd/mipi_dsi/main/idf_component.yml
Normal file
3
examples/peripherals/lcd/mipi_dsi/main/idf_component.yml
Normal file
@ -0,0 +1,3 @@
|
||||
dependencies:
|
||||
lvgl/lvgl: "~8.3.0"
|
||||
esp_lcd_ili9881c: ">=0.1.0"
|
68
examples/peripherals/lcd/mipi_dsi/main/lvgl_demo_ui.c
Normal file
68
examples/peripherals/lcd/mipi_dsi/main/lvgl_demo_ui.c
Normal file
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
// This demo UI is adapted from LVGL official example: https://docs.lvgl.io/8.3/widgets/extra/meter.html#a-meter-with-multiple-arcs
|
||||
|
||||
#include "lvgl.h"
|
||||
|
||||
static lv_obj_t * meter;
|
||||
|
||||
static void set_value(void *indic, int32_t v)
|
||||
{
|
||||
lv_meter_set_indicator_end_value(meter, indic, v);
|
||||
}
|
||||
|
||||
void example_lvgl_demo_ui(lv_disp_t *disp)
|
||||
{
|
||||
lv_obj_t *scr = lv_disp_get_scr_act(disp);
|
||||
meter = lv_meter_create(scr);
|
||||
lv_obj_center(meter);
|
||||
lv_obj_set_size(meter, 600, 600);
|
||||
|
||||
/*Remove the circle from the middle*/
|
||||
lv_obj_remove_style(meter, NULL, LV_PART_INDICATOR);
|
||||
|
||||
/*Add a scale first*/
|
||||
lv_meter_scale_t * scale = lv_meter_add_scale(meter);
|
||||
lv_meter_set_scale_ticks(meter, scale, 11, 2, 10, lv_palette_main(LV_PALETTE_GREY));
|
||||
lv_meter_set_scale_major_ticks(meter, scale, 1, 2, 30, lv_color_hex3(0xeee), 15);
|
||||
lv_meter_set_scale_range(meter, scale, 0, 100, 270, 90);
|
||||
|
||||
/*Add a four arc indicator*/
|
||||
lv_meter_indicator_t * indic1 = lv_meter_add_arc(meter, scale, 30, lv_palette_main(LV_PALETTE_RED), 0);
|
||||
lv_meter_indicator_t * indic2 = lv_meter_add_arc(meter, scale, 30, lv_palette_main(LV_PALETTE_GREEN), -30);
|
||||
lv_meter_indicator_t * indic3 = lv_meter_add_arc(meter, scale, 30, lv_palette_main(LV_PALETTE_BLUE), -60);
|
||||
lv_meter_indicator_t * indic4 = lv_meter_add_arc(meter, scale, 30, lv_palette_main(LV_PALETTE_YELLOW), -90);
|
||||
|
||||
/*Create an animation to set the value*/
|
||||
lv_anim_t a;
|
||||
lv_anim_init(&a);
|
||||
lv_anim_set_exec_cb(&a, set_value);
|
||||
lv_anim_set_values(&a, 0, 100);
|
||||
lv_anim_set_repeat_delay(&a, 100);
|
||||
lv_anim_set_playback_delay(&a, 100);
|
||||
lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
|
||||
|
||||
lv_anim_set_time(&a, 2000);
|
||||
lv_anim_set_playback_time(&a, 500);
|
||||
lv_anim_set_var(&a, indic1);
|
||||
lv_anim_start(&a);
|
||||
|
||||
lv_anim_set_time(&a, 1000);
|
||||
lv_anim_set_playback_time(&a, 1000);
|
||||
lv_anim_set_var(&a, indic2);
|
||||
lv_anim_start(&a);
|
||||
|
||||
lv_anim_set_time(&a, 1000);
|
||||
lv_anim_set_playback_time(&a, 2000);
|
||||
lv_anim_set_var(&a, indic3);
|
||||
lv_anim_start(&a);
|
||||
|
||||
lv_anim_set_time(&a, 3000);
|
||||
lv_anim_set_playback_time(&a, 2000);
|
||||
lv_anim_set_var(&a, indic4);
|
||||
lv_anim_start(&a);
|
||||
}
|
@ -0,0 +1,264 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_private/esp_ldo.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_lcd_panel_ops.h"
|
||||
#include "esp_lcd_mipi_dsi.h"
|
||||
#include "esp_lcd_ili9881c.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "lvgl.h"
|
||||
|
||||
static const char *TAG = "example";
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////// Please update the following configuration according to your LCD Spec //////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// FPS = 80000000/(40+140+40+800)/(4+16+16+1280) = 60Hz
|
||||
#define EXAMPLE_MIPI_DSI_DPI_CLK_MHZ 80
|
||||
#define EXAMPLE_MIPI_DSI_LCD_H_RES 800
|
||||
#define EXAMPLE_MIPI_DSI_LCD_V_RES 1280
|
||||
#define EXAMPLE_MIPI_DSI_LCD_HSYNC 40
|
||||
#define EXAMPLE_MIPI_DSI_LCD_HBP 140
|
||||
#define EXAMPLE_MIPI_DSI_LCD_HFP 40
|
||||
#define EXAMPLE_MIPI_DSI_LCD_VSYNC 4
|
||||
#define EXAMPLE_MIPI_DSI_LCD_VBP 16
|
||||
#define EXAMPLE_MIPI_DSI_LCD_VFP 16
|
||||
|
||||
#define EXAMPLE_MIPI_DSI_LANE_NUM 2 // 2 data lanes
|
||||
#define EXAMPLE_MIPI_DSI_LANE_BITRATE_MBPS 1000 // 1Gbps
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////// Please update the following configuration according to your Board Design //////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// The "VDD_MIPI_DPHY" should be supplied with 2.5V, it can source from the integrated LDO unit or from external LDO chip
|
||||
#define EXAMPLE_MIPI_DSI_PHY_PWR_LDO_UNIT 3 // LDO_VO3 is connected to VDD_MIPI_DPHY
|
||||
#define EXAMPLE_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV 2500
|
||||
#define EXAMPLE_LCD_BK_LIGHT_ON_LEVEL 1
|
||||
#define EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL !EXAMPLE_LCD_BK_LIGHT_ON_LEVEL
|
||||
#define EXAMPLE_PIN_NUM_BK_LIGHT -1
|
||||
#define EXAMPLE_PIN_NUM_LCD_RST -1
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////// Please update the following configuration according to your Application ///////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define EXAMPLE_LVGL_DRAW_BUF_LINES 200 // number of display lines in each draw buffer
|
||||
#define EXAMPLE_LVGL_TICK_PERIOD_MS 2
|
||||
#define EXAMPLE_LVGL_TASK_MAX_DELAY_MS 500
|
||||
#define EXAMPLE_LVGL_TASK_MIN_DELAY_MS 1
|
||||
#define EXAMPLE_LVGL_TASK_STACK_SIZE (4 * 1024)
|
||||
#define EXAMPLE_LVGL_TASK_PRIORITY 2
|
||||
|
||||
static SemaphoreHandle_t lvgl_api_mux = NULL;
|
||||
|
||||
extern void example_lvgl_demo_ui(lv_disp_t *disp);
|
||||
|
||||
static void example_lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map)
|
||||
{
|
||||
esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t) drv->user_data;
|
||||
int offsetx1 = area->x1;
|
||||
int offsetx2 = area->x2;
|
||||
int offsety1 = area->y1;
|
||||
int offsety2 = area->y2;
|
||||
// pass the draw buffer to the driver
|
||||
esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, color_map);
|
||||
lv_disp_flush_ready(drv);
|
||||
}
|
||||
|
||||
static void example_increase_lvgl_tick(void *arg)
|
||||
{
|
||||
/* Tell LVGL how many milliseconds has elapsed */
|
||||
lv_tick_inc(EXAMPLE_LVGL_TICK_PERIOD_MS);
|
||||
}
|
||||
|
||||
static bool example_lvgl_lock(int timeout_ms)
|
||||
{
|
||||
// Convert timeout in milliseconds to FreeRTOS ticks
|
||||
// If `timeout_ms` is set to -1, the program will block until the condition is met
|
||||
const TickType_t timeout_ticks = (timeout_ms == -1) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms);
|
||||
return xSemaphoreTakeRecursive(lvgl_api_mux, timeout_ticks) == pdTRUE;
|
||||
}
|
||||
|
||||
static void example_lvgl_unlock(void)
|
||||
{
|
||||
xSemaphoreGiveRecursive(lvgl_api_mux);
|
||||
}
|
||||
|
||||
static void example_lvgl_port_task(void *arg)
|
||||
{
|
||||
ESP_LOGI(TAG, "Starting LVGL task");
|
||||
uint32_t task_delay_ms = EXAMPLE_LVGL_TASK_MAX_DELAY_MS;
|
||||
while (1) {
|
||||
// Lock the mutex due to the LVGL APIs are not thread-safe
|
||||
if (example_lvgl_lock(-1)) {
|
||||
task_delay_ms = lv_timer_handler();
|
||||
// Release the mutex
|
||||
example_lvgl_unlock();
|
||||
}
|
||||
if (task_delay_ms > EXAMPLE_LVGL_TASK_MAX_DELAY_MS) {
|
||||
task_delay_ms = EXAMPLE_LVGL_TASK_MAX_DELAY_MS;
|
||||
} else if (task_delay_ms < EXAMPLE_LVGL_TASK_MIN_DELAY_MS) {
|
||||
task_delay_ms = EXAMPLE_LVGL_TASK_MIN_DELAY_MS;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(task_delay_ms));
|
||||
}
|
||||
}
|
||||
|
||||
static void example_bsp_enable_dsi_phy_power(void)
|
||||
{
|
||||
// Turn on the power for MIPI DSI PHY, so it can go from "No Power" state to "Shutdown" state
|
||||
esp_ldo_unit_handle_t phy_pwr_unit = NULL;
|
||||
#if EXAMPLE_MIPI_DSI_PHY_PWR_LDO_UNIT > 0
|
||||
esp_ldo_unit_init_cfg_t ldo_cfg = {
|
||||
.unit_id = EXAMPLE_MIPI_DSI_PHY_PWR_LDO_UNIT,
|
||||
.cfg = {
|
||||
.voltage_mv = EXAMPLE_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_ldo_init_unit(&ldo_cfg, &phy_pwr_unit));
|
||||
ESP_ERROR_CHECK(esp_ldo_enable_unit(phy_pwr_unit));
|
||||
|
||||
ESP_LOGI(TAG, "MIPI DSI PHY Powered on");
|
||||
#endif
|
||||
}
|
||||
|
||||
static void example_bsp_init_lcd_backlight(void)
|
||||
{
|
||||
#if EXAMPLE_PIN_NUM_BK_LIGHT >= 0
|
||||
gpio_config_t bk_gpio_config = {
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pin_bit_mask = 1ULL << EXAMPLE_PIN_NUM_BK_LIGHT
|
||||
};
|
||||
ESP_ERROR_CHECK(gpio_config(&bk_gpio_config));
|
||||
#endif
|
||||
}
|
||||
|
||||
static void example_bsp_set_lcd_backlight(uint32_t level)
|
||||
{
|
||||
#if EXAMPLE_PIN_NUM_BK_LIGHT >= 0
|
||||
gpio_set_level(EXAMPLE_PIN_NUM_BK_LIGHT, level);
|
||||
#endif
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
static lv_disp_draw_buf_t disp_buf; // contains internal graphic buffer(s) called draw buffer(s)
|
||||
static lv_disp_drv_t disp_drv; // contains callback functions
|
||||
|
||||
example_bsp_enable_dsi_phy_power();
|
||||
example_bsp_init_lcd_backlight();
|
||||
example_bsp_set_lcd_backlight(EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL);
|
||||
|
||||
// create MIPI DSI bus first, it will initialize the DSI PHY as well
|
||||
esp_lcd_dsi_bus_handle_t mipi_dsi_bus;
|
||||
esp_lcd_dsi_bus_config_t bus_config = {
|
||||
.bus_id = 0,
|
||||
.num_data_lanes = EXAMPLE_MIPI_DSI_LANE_NUM,
|
||||
.phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT,
|
||||
.lane_bit_rate_mbps = EXAMPLE_MIPI_DSI_LANE_BITRATE_MBPS,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus));
|
||||
|
||||
ESP_LOGI(TAG, "Install MIPI DSI LCD control panel");
|
||||
esp_lcd_panel_io_handle_t mipi_dbi_io;
|
||||
// we use DBI interface to send LCD commands and parameters
|
||||
esp_lcd_dbi_io_config_t dbi_config = {
|
||||
.virtual_channel = 0,
|
||||
.lcd_cmd_bits = 8, // according to the LCD ILI9881C spec
|
||||
.lcd_param_bits = 8, // according to the LCD ILI9881C spec
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &mipi_dbi_io));
|
||||
// create ILI9881C control panel
|
||||
esp_lcd_panel_handle_t ili9881c_ctrl_panel;
|
||||
esp_lcd_panel_dev_config_t lcd_dev_config = {
|
||||
.bits_per_pixel = 16,
|
||||
.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));
|
||||
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));
|
||||
|
||||
ESP_LOGI(TAG, "Install MIPI DSI LCD data panel");
|
||||
esp_lcd_panel_handle_t mipi_dpi_panel;
|
||||
esp_lcd_dpi_panel_config_t dpi_config = {
|
||||
.virtual_channel = 0,
|
||||
.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT,
|
||||
.dpi_clock_freq_mhz = EXAMPLE_MIPI_DSI_DPI_CLK_MHZ,
|
||||
.pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565,
|
||||
.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));
|
||||
|
||||
// turn on backlight
|
||||
example_bsp_set_lcd_backlight(EXAMPLE_LCD_BK_LIGHT_ON_LEVEL);
|
||||
|
||||
ESP_LOGI(TAG, "Initialize LVGL library");
|
||||
lv_init();
|
||||
void *buf1 = NULL;
|
||||
void *buf2 = NULL;
|
||||
ESP_LOGI(TAG, "Allocate separate LVGL draw buffers from PSRAM");
|
||||
buf1 = heap_caps_malloc(EXAMPLE_MIPI_DSI_LCD_H_RES * EXAMPLE_LVGL_DRAW_BUF_LINES * sizeof(lv_color_t), MALLOC_CAP_SPIRAM);
|
||||
assert(buf1);
|
||||
buf2 = heap_caps_malloc(EXAMPLE_MIPI_DSI_LCD_H_RES * EXAMPLE_LVGL_DRAW_BUF_LINES * sizeof(lv_color_t), MALLOC_CAP_SPIRAM);
|
||||
assert(buf2);
|
||||
// initialize LVGL draw buffers
|
||||
lv_disp_draw_buf_init(&disp_buf, buf1, buf2, EXAMPLE_MIPI_DSI_LCD_H_RES * EXAMPLE_LVGL_DRAW_BUF_LINES);
|
||||
|
||||
// register display driver to LVGL library
|
||||
lv_disp_drv_init(&disp_drv);
|
||||
disp_drv.hor_res = EXAMPLE_MIPI_DSI_LCD_H_RES;
|
||||
disp_drv.ver_res = EXAMPLE_MIPI_DSI_LCD_V_RES;
|
||||
disp_drv.flush_cb = example_lvgl_flush_cb;
|
||||
disp_drv.draw_buf = &disp_buf;
|
||||
disp_drv.user_data = mipi_dpi_panel;
|
||||
lv_disp_t *disp = lv_disp_drv_register(&disp_drv);
|
||||
|
||||
ESP_LOGI(TAG, "Use esp_timer as LVGL tick timer");
|
||||
const esp_timer_create_args_t lvgl_tick_timer_args = {
|
||||
.callback = &example_increase_lvgl_tick,
|
||||
.name = "lvgl_tick"
|
||||
};
|
||||
esp_timer_handle_t lvgl_tick_timer = NULL;
|
||||
ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer));
|
||||
ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000));
|
||||
|
||||
// LVGL APIs are meant to be called across the threads without protection, so we use a mutex here
|
||||
lvgl_api_mux = xSemaphoreCreateRecursiveMutex();
|
||||
assert(lvgl_api_mux);
|
||||
|
||||
ESP_LOGI(TAG, "Create LVGL task");
|
||||
xTaskCreate(example_lvgl_port_task, "LVGL", EXAMPLE_LVGL_TASK_STACK_SIZE, NULL, EXAMPLE_LVGL_TASK_PRIORITY, NULL);
|
||||
|
||||
ESP_LOGI(TAG, "Display LVGL Meter Widget");
|
||||
// Lock the mutex due to the LVGL APIs are not thread-safe
|
||||
if (example_lvgl_lock(-1)) {
|
||||
example_lvgl_demo_ui(disp);
|
||||
// Release the mutex
|
||||
example_lvgl_unlock();
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
# SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
|
||||
|
||||
@pytest.mark.esp32p4
|
||||
@pytest.mark.generic
|
||||
def test_rgb_lcd_lvgl(dut: Dut) -> None:
|
||||
dut.expect_exact('example: Install MIPI DSI LCD control panel')
|
6
examples/peripherals/lcd/mipi_dsi/sdkconfig.defaults
Normal file
6
examples/peripherals/lcd/mipi_dsi/sdkconfig.defaults
Normal file
@ -0,0 +1,6 @@
|
||||
CONFIG_LV_MEM_CUSTOM=y
|
||||
CONFIG_LV_MEMCPY_MEMSET_STD=y
|
||||
CONFIG_LV_USE_USER_DATA=y
|
||||
CONFIG_LV_USE_CHART=y
|
||||
CONFIG_LV_USE_PERF_MONITOR=y
|
||||
CONFIG_IDF_EXPERIMENTAL_FEATURES=y
|
@ -0,0 +1,3 @@
|
||||
CONFIG_SPIRAM=y
|
||||
CONFIG_SPIRAM_MODE_HEX=y
|
||||
CONFIG_SPIRAM_SPEED_200M=y
|
Loading…
Reference in New Issue
Block a user