Merge branch 'feat/add_lt9611_conponent' into 'master'

feat(lcd): add MIPI-to-HDMI example (lt9611)

Closes IDF-15419

See merge request espressif/esp-idf!52175
This commit is contained in:
morris
2026-09-14 10:57:57 +08:00
20 changed files with 635 additions and 32 deletions

View File

@@ -317,6 +317,15 @@ examples/peripherals/jpeg/jpeg_encode:
- esp_driver_jpeg
- soc
examples/peripherals/lcd/dsi_hdmi_bridge:
disable:
- if: SOC_MIPI_DSI_SUPPORTED != 1
depends_components:
- esp_driver_dma
- esp_hal_lcd
- esp_lcd
- soc
examples/peripherals/lcd/i2c_oled:
disable:
- if: SOC_I2C_SUPPORTED != 1

View File

@@ -0,0 +1,10 @@
cmake_minimum_required(VERSION 3.22)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
idf_build_set_property(MINIMAL_BUILD ON)
project(dsi_hdmi_bridge)
# TODO IDF-15784: remove
# lvgl upstream sets locals without reading them; silence -Wunused-but-set-variable
idf_component_get_property(lvgl_lib lvgl__lvgl COMPONENT_LIB)
target_compile_options(${lvgl_lib} PRIVATE -Wno-unused-but-set-variable)

View File

@@ -0,0 +1,83 @@
| Supported Targets | ESP32-P4 |
| ----------------- | -------- |
# MIPI DSI to HDMI Bridge Example
This example demonstrates how to drive an HDMI display from the MIPI DSI peripheral through an external DSI-to-HDMI bridge.
The example uses the [LT9611](https://components.espressif.com/components/espressif/esp_lcd_lt9611) at 1920x1080@60 Hz in RGB565. It displays a full-screen LVGL dashboard using DMA2D and partial draw buffers in internal RAM.
## How to use the example
### Hardware Required
* An ESP32-P4 development board
* An LT9611 DSI-to-HDMI bridge
* An HDMI display and cable
* A USB cable for power supply and programming
### Hardware Connection
The connection between the ESP board, the LT9611 bridge, and the HDMI display is as follows:
```text
ESP Board LT9611 Bridge HDMI Display
+-----------------------+ +-------------------+ +-------------------+
| 3V3 +------+ VCC | | |
| | | | | |
| DSI_CLK_P +------+ DSI_CLK_P | | |
| DSI_CLK_N +------+ DSI_CLK_N | | |
| DSI_DAT0_P +------+ DSI_DAT0_P | | |
| DSI_DAT0_N +------+ DSI_DAT0_N | | |
| DSI_DAT1_P +------+ DSI_DAT1_P | | |
| DSI_DAT1_N +------+ DSI_DAT1_N | | |
| | | HDMI OUT+----------+HDMI IN |
| SDA +------+ SDA | | |
| SCL +------+ SCL | | |
| Reset +------+ Reset (optional) | | |
| | | | | |
+-----------------------+ +-------------------+ +-------------------+
```
Before building, update `EXAMPLE_PIN_NUM_LCD_RST`, `EXAMPLE_I2C_NUM`, `EXAMPLE_PIN_NUM_SDA`, and `EXAMPLE_PIN_NUM_SCL` in [dsi_hdmi_bridge_example_main.c](main/dsi_hdmi_bridge_example_main.c) according to the board wiring.
The example powers `VDD_MIPI_DPHY` from LDO channel 3 at 2500 mV. Update `EXAMPLE_MIPI_DSI_PHY_PWR_LDO_CHAN` and `EXAMPLE_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV` in the source according to the board power design.
### Build and Flash
Run the following command to build, flash, and monitor the example:
```text
idf.py -p PORT build flash monitor
```
The first build can take additional time while the Component Manager resolves the external bridge and LVGL dependencies.
(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 complete instructions.
### Example Output
```text
I (...) dsi_hdmi_bridge: MIPI D-PHY powered by LDO channel 3 at 2500 mV
I (...) dsi_hdmi_bridge: Initialize LVGL
I (...) dsi_hdmi_bridge: Starting LVGL task
I (...) dsi_hdmi_bridge: Displaying LVGL UI at 1920x1080
```
## Troubleshooting
If the log shows:
```text
E lcd.dsi: can't fetch data from external memory fast enough, underrun happens
```
the DSI bridge cannot fetch the framebuffer from PSRAM fast enough. Reduce PSRAM traffic, for example:
* Disable PSRAM XIP (this example already does so for 1080p60 RGB565)
* Keep LVGL draw buffers in internal RAM instead of PSRAM
* Avoid other large PSRAM allocations or DMA while the display is running
For technical questions, please open an [issue](https://github.com/espressif/esp-idf/issues).

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "dsi_hdmi_bridge_example_main.c" "lvgl_port.c" "lvgl_demo_ui.c"
INCLUDE_DIRS "."
PRIV_REQUIRES esp_driver_i2c esp_lcd esp_timer)

View File

@@ -0,0 +1,104 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include "driver/i2c_master.h"
#include "esp_err.h"
#include "esp_lcd_lt9611.h"
#include "esp_lcd_mipi_dsi.h"
#include "esp_lcd_panel_ops.h"
#include "esp_ldo_regulator.h"
#include "esp_log.h"
#include "lvgl_port.h"
static const char *TAG = "dsi_hdmi_bridge";
// The "VDD_MIPI_DPHY" should be supplied with 2.5V, it can source from the internal LDO regulator or from external LDO chip
#define EXAMPLE_MIPI_DSI_PHY_PWR_LDO_CHAN 3 // LDO_VO3 is connected to VDD_MIPI_DPHY
#define EXAMPLE_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV 2500
#define EXAMPLE_MIPI_DSI_LANE_NUM 2
#define EXAMPLE_PIN_NUM_LCD_RST -1
// Update these values according to the board I2C wiring.
#define EXAMPLE_I2C_PORT_NUM 0
#define EXAMPLE_PIN_NUM_SDA 7
#define EXAMPLE_PIN_NUM_SCL 8
static void example_enable_mipi_dphy_power(void)
{
const esp_ldo_channel_config_t ldo_config = {
.chan_id = EXAMPLE_MIPI_DSI_PHY_PWR_LDO_CHAN,
.voltage_mv = EXAMPLE_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV,
};
esp_ldo_channel_handle_t ldo = NULL;
ESP_ERROR_CHECK(esp_ldo_acquire_channel(&ldo_config, &ldo));
ESP_LOGI(TAG, "MIPI D-PHY powered by LDO channel %d at %d mV",
EXAMPLE_MIPI_DSI_PHY_PWR_LDO_CHAN,
EXAMPLE_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV);
}
static i2c_master_bus_handle_t create_i2c_bus(void)
{
const i2c_master_bus_config_t bus_config = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
.i2c_port = EXAMPLE_I2C_PORT_NUM,
.sda_io_num = EXAMPLE_PIN_NUM_SDA,
.scl_io_num = EXAMPLE_PIN_NUM_SCL,
.flags.enable_internal_pullup = true,
};
i2c_master_bus_handle_t bus = NULL;
ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &bus));
return bus;
}
static esp_lcd_dsi_bus_handle_t create_dsi_bus(void)
{
const esp_lcd_dsi_bus_config_t bus_config = {
.bus_id = 0,
.num_data_lanes = EXAMPLE_MIPI_DSI_LANE_NUM,
.lane_bit_rate_mbps = LT9611_1920x1080_60HZ_RGB16_LANE_BITRATE_MBPS,
};
esp_lcd_dsi_bus_handle_t bus = NULL;
ESP_ERROR_CHECK(esp_lcd_new_dsi_bus(&bus_config, &bus));
return bus;
}
static esp_lcd_panel_handle_t create_lt9611_panel(i2c_master_bus_handle_t i2c_bus,
esp_lcd_dsi_bus_handle_t dsi_bus)
{
const esp_lcd_dpi_panel_config_t dpi_config = LT9611_1920x1080_60HZ_RGB16_DPI_PANEL_CONFIG_WITH_FBS(1);
const lt9611_vendor_config_t vendor_config = {
.mipi_config = {
.dsi_bus = dsi_bus,
.dpi_config = &dpi_config,
.lane_num = EXAMPLE_MIPI_DSI_LANE_NUM,
},
};
const esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = EXAMPLE_PIN_NUM_LCD_RST,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.bits_per_pixel = 16,
.flags.reset_active_high = true,
.vendor_config = (void *) &vendor_config,
};
esp_lcd_panel_handle_t panel = NULL;
ESP_ERROR_CHECK(esp_lcd_new_panel_lt9611(i2c_bus, &panel_config, &panel));
return panel;
}
void app_main(void)
{
example_enable_mipi_dphy_power();
i2c_master_bus_handle_t i2c_bus = create_i2c_bus();
esp_lcd_dsi_bus_handle_t dsi_bus = create_dsi_bus();
esp_lcd_panel_handle_t panel = create_lt9611_panel(i2c_bus, dsi_bus);
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel));
ESP_ERROR_CHECK(esp_lcd_dpi_panel_enable_dma2d(panel));
// Draw the first LVGL frame into the framebuffer before starting DPI scanout.
example_lvgl_init(panel);
ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
}

View File

@@ -0,0 +1,5 @@
description: MIPI DSI to HDMI bridge example
dependencies:
lvgl/lvgl: "9.5.0"
esp_lcd_lt9611: "^0.1.0"

View File

@@ -0,0 +1,182 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include "lvgl.h"
#define EXAMPLE_COLOR_FORMAT "RGB565"
static void strip_default_obj_style(lv_obj_t *obj)
{
lv_obj_remove_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_style_bg_opa(obj, LV_OPA_TRANSP, 0);
lv_obj_set_style_border_width(obj, 0, 0);
lv_obj_set_style_pad_all(obj, 0, 0);
lv_obj_set_style_radius(obj, 0, 0);
lv_obj_set_style_shadow_width(obj, 0, 0);
}
static void gauge_anim_cb(void *var, int32_t value)
{
lv_obj_t *arc = var;
lv_arc_set_value(arc, value);
lv_label_set_text_fmt((lv_obj_t *)lv_obj_get_user_data(arc), "%" LV_PRId32 "%%", value);
}
static void create_header(lv_obj_t *parent, int32_t hor_res, int32_t ver_res, const lv_font_t *font)
{
lv_obj_t *header = lv_obj_create(parent);
strip_default_obj_style(header);
lv_obj_set_size(header, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_bg_opa(header, LV_OPA_COVER, 0);
lv_obj_set_style_bg_color(header, lv_color_hex(0x151A22), 0);
lv_obj_set_style_text_color(header, lv_color_white(), 0);
lv_obj_set_style_pad_hor(header, ver_res / 24, 0);
lv_obj_set_style_pad_ver(header, ver_res / 36, 0);
static int32_t cols[] = {LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_FR(1), LV_GRID_TEMPLATE_LAST};
static int32_t rows[] = {LV_GRID_CONTENT, LV_GRID_TEMPLATE_LAST};
lv_obj_set_grid_dsc_array(header, cols, rows);
lv_obj_t *left = lv_label_create(header);
lv_obj_set_style_text_font(left, font, 0);
lv_obj_set_style_text_color(left, lv_color_white(), 0);
lv_label_set_text(left, "MIPI DSI to HDMI");
lv_obj_set_grid_cell(left, LV_GRID_ALIGN_START, 0, 1, LV_GRID_ALIGN_CENTER, 0, 1);
lv_obj_t *right = lv_label_create(header);
lv_obj_set_style_text_font(right, font, 0);
lv_obj_set_style_text_color(right, lv_color_white(), 0);
lv_label_set_text_fmt(right, "%" LV_PRId32 "x%" LV_PRId32 " %s", hor_res, ver_res, EXAMPLE_COLOR_FORMAT);
lv_obj_set_grid_cell(right, LV_GRID_ALIGN_END, 2, 1, LV_GRID_ALIGN_CENTER, 0, 1);
}
static lv_obj_t *create_gauge_card(lv_obj_t *parent, const char *name, lv_color_t color,
int32_t gauge_size, int32_t arc_width,
const lv_font_t *font_value, const lv_font_t *font_caption)
{
lv_obj_t *card = lv_obj_create(parent);
strip_default_obj_style(card);
lv_obj_set_flex_grow(card, 1);
lv_obj_set_height(card, LV_PCT(100));
lv_obj_set_flex_flow(card, LV_FLEX_FLOW_COLUMN);
lv_obj_set_flex_align(card, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
lv_obj_set_style_bg_opa(card, LV_OPA_COVER, 0);
lv_obj_set_style_bg_color(card, lv_color_hex(0x171E28), 0);
lv_obj_set_style_radius(card, 16, 0);
lv_obj_set_style_pad_all(card, 16, 0);
lv_obj_set_style_pad_row(card, 12, 0);
lv_obj_t *arc = lv_arc_create(card);
lv_obj_set_size(arc, gauge_size, gauge_size);
lv_arc_set_rotation(arc, 270);
lv_arc_set_bg_angles(arc, 0, 360);
lv_arc_set_range(arc, 0, 100);
lv_arc_set_value(arc, 0);
lv_obj_remove_style(arc, NULL, LV_PART_KNOB);
lv_obj_remove_flag(arc, LV_OBJ_FLAG_CLICKABLE);
lv_obj_set_style_arc_width(arc, arc_width, LV_PART_MAIN);
lv_obj_set_style_arc_width(arc, arc_width, LV_PART_INDICATOR);
lv_obj_set_style_arc_color(arc, lv_color_hex(0x2A3140), LV_PART_MAIN);
lv_obj_set_style_arc_color(arc, color, LV_PART_INDICATOR);
lv_obj_set_style_arc_rounded(arc, true, LV_PART_MAIN);
lv_obj_set_style_arc_rounded(arc, true, LV_PART_INDICATOR);
lv_obj_t *value = lv_label_create(arc);
lv_obj_set_style_text_font(value, font_value, 0);
lv_obj_set_style_text_color(value, lv_color_white(), 0);
lv_label_set_text(value, "0%");
lv_obj_center(value);
lv_obj_set_user_data(arc, value);
lv_obj_t *caption = lv_label_create(card);
lv_obj_set_style_text_font(caption, font_caption, 0);
lv_obj_set_style_text_color(caption, lv_color_hex(0xA8B0C0), 0);
lv_label_set_text(caption, name);
return arc;
}
static void create_color_bars(lv_obj_t *parent, int32_t height)
{
static const uint32_t colors[] = {
0xFFFFFF, 0xFFFF00, 0x00FFFF, 0x00FF00,
0xFF00FF, 0xFF0000, 0x0000FF, 0x3C3C3C,
};
lv_obj_t *row = lv_obj_create(parent);
strip_default_obj_style(row);
lv_obj_set_size(row, LV_PCT(100), height);
lv_obj_set_flex_flow(row, LV_FLEX_FLOW_ROW);
lv_obj_set_style_pad_column(row, 0, 0);
for (size_t i = 0; i < sizeof(colors) / sizeof(colors[0]); i++) {
lv_obj_t *bar = lv_obj_create(row);
strip_default_obj_style(bar);
lv_obj_set_flex_grow(bar, 1);
lv_obj_set_height(bar, LV_PCT(100));
lv_obj_set_style_bg_opa(bar, LV_OPA_COVER, 0);
lv_obj_set_style_bg_color(bar, lv_color_hex(colors[i]), 0);
}
}
void example_lvgl_demo_ui(lv_display_t *disp)
{
const int32_t hor_res = lv_display_get_horizontal_resolution(disp);
const int32_t ver_res = lv_display_get_vertical_resolution(disp);
const bool is_1080p = (ver_res >= 1000);
const lv_font_t *font_header = is_1080p ? &lv_font_montserrat_48 : &lv_font_montserrat_28;
const lv_font_t *font_value = &lv_font_montserrat_48;
const lv_font_t *font_caption = is_1080p ? &lv_font_montserrat_28 : &lv_font_montserrat_16;
const int32_t pad = is_1080p ? 32 : 20;
const int32_t gauge_size = ver_res * 42 / 100;
const int32_t arc_width = is_1080p ? 24 : 16;
lv_theme_default_init(disp, lv_palette_main(LV_PALETTE_BLUE), lv_palette_main(LV_PALETTE_RED),
true, font_caption);
lv_obj_t *scr = lv_display_get_screen_active(disp);
lv_obj_remove_flag(scr, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_style_bg_color(scr, lv_color_hex(0x0B0E12), 0);
lv_obj_set_style_text_color(scr, lv_color_white(), 0);
lv_obj_set_style_border_width(scr, 0, 0);
lv_obj_set_style_radius(scr, 0, 0);
lv_obj_set_style_pad_all(scr, 0, 0);
lv_obj_set_style_pad_row(scr, 0, 0);
lv_obj_set_flex_flow(scr, LV_FLEX_FLOW_COLUMN);
create_header(scr, hor_res, ver_res, font_header);
lv_obj_t *gauges = lv_obj_create(scr);
strip_default_obj_style(gauges);
lv_obj_set_width(gauges, LV_PCT(100));
lv_obj_set_flex_grow(gauges, 1);
lv_obj_set_flex_flow(gauges, LV_FLEX_FLOW_ROW);
lv_obj_set_flex_align(gauges, LV_FLEX_ALIGN_SPACE_EVENLY, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
lv_obj_set_style_pad_all(gauges, pad, 0);
lv_obj_set_style_pad_column(gauges, pad, 0);
static const char *names[] = {"Video", "Render", "Memory"};
static const lv_palette_t palette[] = {LV_PALETTE_BLUE, LV_PALETTE_RED, LV_PALETTE_GREEN};
static const uint32_t durations[] = {4100, 2600, 2800};
static const uint32_t playbacks[] = {2700, 3200, 1800};
lv_anim_t animation;
lv_anim_init(&animation);
lv_anim_set_values(&animation, 20, 100);
lv_anim_set_repeat_count(&animation, LV_ANIM_REPEAT_INFINITE);
lv_anim_set_exec_cb(&animation, gauge_anim_cb);
for (int i = 0; i < 3; i++) {
lv_obj_t *arc = create_gauge_card(gauges, names[i], lv_palette_main(palette[i]),
gauge_size, arc_width, font_value, font_caption);
lv_anim_set_var(&animation, arc);
lv_anim_set_duration(&animation, durations[i]);
lv_anim_set_playback_duration(&animation, playbacks[i]);
lv_anim_start(&animation);
}
create_color_bars(scr, ver_res * 12 / 100);
}

View File

@@ -0,0 +1,125 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <assert.h>
#include <unistd.h>
#include <sys/lock.h>
#include <sys/param.h>
#include "sdkconfig.h"
#include "esp_heap_caps.h"
#include "esp_lcd_mipi_dsi.h"
#include "esp_lcd_panel_ops.h"
#include "esp_log.h"
#include "esp_macros.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "lvgl.h"
#include "lvgl_port.h"
static const char *TAG = "dsi_hdmi_bridge";
#define EXAMPLE_H_RES 1920
#define EXAMPLE_V_RES 1080
#define EXAMPLE_BYTES_PER_PIXEL 2
#define EXAMPLE_LVGL_COLOR_FORMAT LV_COLOR_FORMAT_RGB565
#define EXAMPLE_LVGL_DRAW_BUF_LINES 20
#define EXAMPLE_LVGL_TICK_PERIOD_MS 2
#define EXAMPLE_LVGL_TASK_STACK_SIZE (8 * 1024)
#define EXAMPLE_LVGL_TASK_PRIORITY 2
#define EXAMPLE_LVGL_TASK_MAX_DELAY_MS 500
#define EXAMPLE_LVGL_TASK_MIN_DELAY_MS (1000 / CONFIG_FREERTOS_HZ)
// LVGL APIs are called from multiple tasks, so serialize access with a lock.
static _lock_t s_lvgl_api_lock;
extern void example_lvgl_demo_ui(lv_display_t *disp);
static void example_rounder_flush_area_cb(lv_event_t *event)
{
// DMA2D / MSPI may require 16-byte aligned windows (flash encryption, PSRAM ECC, etc.).
lv_area_t *area = lv_event_get_invalidated_area(event);
area->x1 = ESP_ALIGN_DOWN(area->x1, 16);
area->x2 = ESP_ALIGN_UP(area->x2 + 1, 16) - 1;
}
static void example_lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
{
esp_lcd_panel_handle_t panel = lv_display_get_user_data(disp);
// LVGL coordinates are inclusive, while draw_bitmap uses an exclusive end.
ESP_ERROR_CHECK(esp_lcd_panel_draw_bitmap(panel, area->x1, area->y1, area->x2 + 1, area->y2 + 1, px_map));
}
static bool example_notify_lvgl_flush_ready(esp_lcd_panel_handle_t panel,
esp_lcd_dpi_panel_event_data_t *edata,
void *user_ctx)
{
lv_display_flush_ready((lv_display_t *)user_ctx);
return false;
}
static void example_increase_lvgl_tick(void *arg)
{
lv_tick_inc(EXAMPLE_LVGL_TICK_PERIOD_MS);
}
static void example_lvgl_port_task(void *arg)
{
ESP_LOGI(TAG, "Starting LVGL task");
while (true) {
_lock_acquire(&s_lvgl_api_lock);
uint32_t time_till_next_ms = lv_timer_handler();
_lock_release(&s_lvgl_api_lock);
time_till_next_ms = MAX(time_till_next_ms, EXAMPLE_LVGL_TASK_MIN_DELAY_MS);
time_till_next_ms = MIN(time_till_next_ms, EXAMPLE_LVGL_TASK_MAX_DELAY_MS);
usleep(1000 * time_till_next_ms);
}
}
void example_lvgl_init(esp_lcd_panel_handle_t panel)
{
ESP_LOGI(TAG, "Initialize LVGL");
lv_init();
lv_display_t *display = lv_display_create(EXAMPLE_H_RES, EXAMPLE_V_RES);
assert(display);
lv_display_set_user_data(display, panel);
lv_display_set_color_format(display, EXAMPLE_LVGL_COLOR_FORMAT);
const size_t draw_buffer_size =
EXAMPLE_H_RES * EXAMPLE_LVGL_DRAW_BUF_LINES * EXAMPLE_BYTES_PER_PIXEL;
// Keep the small 20-line partial buffers in internal RAM to reduce PSRAM
// contention with the continuous framebuffer scanout.
void *buf1 = heap_caps_aligned_calloc(16, 1, draw_buffer_size,
MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
void *buf2 = heap_caps_aligned_calloc(16, 1, draw_buffer_size,
MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
assert(buf1 && buf2);
lv_display_set_buffers(display, buf1, buf2, draw_buffer_size, LV_DISPLAY_RENDER_MODE_PARTIAL);
lv_display_set_flush_cb(display, example_lvgl_flush_cb);
lv_display_add_event_cb(display, example_rounder_flush_area_cb, LV_EVENT_INVALIDATE_AREA, NULL);
const esp_lcd_dpi_panel_event_callbacks_t callbacks = {
.on_color_trans_done = example_notify_lvgl_flush_ready,
};
ESP_ERROR_CHECK(esp_lcd_dpi_panel_register_event_callbacks(panel, &callbacks, display));
const esp_timer_create_args_t tick_timer_args = {
.callback = example_increase_lvgl_tick,
.name = "lvgl_tick",
};
esp_timer_handle_t tick_timer = NULL;
ESP_ERROR_CHECK(esp_timer_create(&tick_timer_args, &tick_timer));
ESP_ERROR_CHECK(esp_timer_start_periodic(tick_timer, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000));
_lock_acquire(&s_lvgl_api_lock);
example_lvgl_demo_ui(display);
_lock_release(&s_lvgl_api_lock);
BaseType_t task_created = xTaskCreate(example_lvgl_port_task, "LVGL",
EXAMPLE_LVGL_TASK_STACK_SIZE, NULL,
EXAMPLE_LVGL_TASK_PRIORITY, NULL);
assert(task_created == pdPASS);
ESP_LOGI(TAG, "Displaying LVGL UI at %dx%d", EXAMPLE_H_RES, EXAMPLE_V_RES);
}

View File

@@ -0,0 +1,26 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#pragma once
#include "esp_lcd_panel_ops.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Initialize LVGL on top of the HDMI DPI panel
*
* Creates the LVGL display, flush path, tick timer and LVGL task, then starts the demo UI.
*
* @param panel LCD panel handle returned from the HDMI bridge driver
*/
void example_lvgl_init(esp_lcd_panel_handle_t panel);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,8 @@
CONFIG_LV_CONF_SKIP=y
CONFIG_LV_COLOR_DEPTH_16=y
CONFIG_LV_FONT_MONTSERRAT_16=y
CONFIG_LV_FONT_MONTSERRAT_28=y
CONFIG_LV_FONT_MONTSERRAT_48=y
CONFIG_LV_USE_OBSERVER=y
CONFIG_LV_USE_SYSMON=y
CONFIG_LV_USE_PERF_MONITOR=y

View File

@@ -0,0 +1,3 @@
CONFIG_SPIRAM=y
CONFIG_SPIRAM_MODE_HEX=y
CONFIG_SPIRAM_SPEED_250M=y