kopia lustrzana https://github.com/espressif/esp-idf
309 wiersze
12 KiB
C
309 wiersze
12 KiB
C
/*
|
|
* 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_timer.h"
|
|
#include "esp_lcd_panel_ops.h"
|
|
#include "esp_lcd_mipi_dsi.h"
|
|
#include "esp_lcd_ili9881c.h"
|
|
#include "esp_ldo_regulator.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 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_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
|
|
|
|
#if CONFIG_EXAMPLE_MONITOR_FPS_BY_GPIO
|
|
#define EXAMPLE_PIN_NUM_FPS_MONITOR 20 // Monitor the FPS by toggling the GPIO
|
|
#endif
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//////////////////// 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_display_t *disp);
|
|
|
|
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_handle = lv_display_get_user_data(disp);
|
|
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, px_map);
|
|
}
|
|
|
|
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 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_t *disp = (lv_display_t *)user_ctx;
|
|
lv_display_flush_ready(disp);
|
|
return false;
|
|
}
|
|
|
|
#if CONFIG_EXAMPLE_MONITOR_FPS_BY_GPIO
|
|
static bool example_monitor_fps(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx)
|
|
{
|
|
static int io_level = 0;
|
|
// please note, the real FPS should be 2*frequency of this GPIO toggling
|
|
gpio_set_level(EXAMPLE_PIN_NUM_FPS_MONITOR, io_level);
|
|
io_level = !io_level;
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
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_channel_handle_t ldo_mipi_phy = NULL;
|
|
#ifdef EXAMPLE_MIPI_DSI_PHY_PWR_LDO_CHAN
|
|
esp_ldo_channel_config_t ldo_mipi_phy_config = {
|
|
.chan_id = EXAMPLE_MIPI_DSI_PHY_PWR_LDO_CHAN,
|
|
.voltage_mv = EXAMPLE_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV,
|
|
};
|
|
ESP_ERROR_CHECK(esp_ldo_acquire_channel(&ldo_mipi_phy_config, &ldo_mipi_phy));
|
|
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
|
|
}
|
|
|
|
#if CONFIG_EXAMPLE_MONITOR_FPS_BY_GPIO
|
|
static void example_bsp_init_fps_monitor_io(void)
|
|
{
|
|
gpio_config_t monitor_io_conf = {
|
|
.mode = GPIO_MODE_OUTPUT,
|
|
.pin_bit_mask = 1ULL << EXAMPLE_PIN_NUM_FPS_MONITOR,
|
|
};
|
|
ESP_ERROR_CHECK(gpio_config(&monitor_io_conf));
|
|
}
|
|
#endif
|
|
|
|
void app_main(void)
|
|
{
|
|
#if CONFIG_EXAMPLE_MONITOR_FPS_BY_GPIO
|
|
example_bsp_init_fps_monitor_io();
|
|
#endif
|
|
|
|
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 = 24,
|
|
.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_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,
|
|
},
|
|
#if CONFIG_EXAMPLE_USE_DMA2D_COPY_FRAME
|
|
.flags.use_dma2d = true,
|
|
#endif
|
|
};
|
|
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();
|
|
// create a lvgl display
|
|
lv_display_t *display = lv_display_create(EXAMPLE_MIPI_DSI_LCD_H_RES, EXAMPLE_MIPI_DSI_LCD_V_RES);
|
|
// associate the mipi panel handle to the display
|
|
lv_display_set_user_data(display, mipi_dpi_panel);
|
|
// create draw buffer
|
|
void *buf1 = NULL;
|
|
void *buf2 = NULL;
|
|
ESP_LOGI(TAG, "Allocate separate LVGL draw buffers");
|
|
// Note:
|
|
// Keep the display buffer in **internal** RAM can speed up the UI because LVGL uses it a lot and it should have a fast access time
|
|
// This example allocate the buffer from PSRAM mainly because we want to save the internal RAM
|
|
size_t draw_buffer_sz = EXAMPLE_MIPI_DSI_LCD_H_RES * EXAMPLE_LVGL_DRAW_BUF_LINES * sizeof(lv_color_t);
|
|
buf1 = heap_caps_malloc(draw_buffer_sz, MALLOC_CAP_SPIRAM);
|
|
assert(buf1);
|
|
buf2 = heap_caps_malloc(draw_buffer_sz, MALLOC_CAP_SPIRAM);
|
|
assert(buf2);
|
|
// initialize LVGL draw buffers
|
|
lv_display_set_buffers(display, buf1, buf2, draw_buffer_sz, LV_DISPLAY_RENDER_MODE_PARTIAL);
|
|
// set color depth
|
|
lv_display_set_color_format(display, LV_COLOR_FORMAT_RGB888);
|
|
// set the callback which can copy the rendered image to an area of the display
|
|
lv_display_set_flush_cb(display, example_lvgl_flush_cb);
|
|
|
|
ESP_LOGI(TAG, "Register DPI panel event callback for LVGL flush ready notification");
|
|
esp_lcd_dpi_panel_event_callbacks_t cbs = {
|
|
.on_color_trans_done = example_notify_lvgl_flush_ready,
|
|
#if CONFIG_EXAMPLE_MONITOR_FPS_BY_GPIO
|
|
.on_refresh_done = example_monitor_fps,
|
|
#endif
|
|
};
|
|
ESP_ERROR_CHECK(esp_lcd_dpi_panel_register_event_callbacks(mipi_dpi_panel, &cbs, display));
|
|
|
|
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(display);
|
|
// Release the mutex
|
|
example_lvgl_unlock();
|
|
}
|
|
}
|