diff --git a/components/platform/CMakeLists.txt b/components/platform/CMakeLists.txt index e52e033..e7ea45e 100644 --- a/components/platform/CMakeLists.txt +++ b/components/platform/CMakeLists.txt @@ -36,6 +36,7 @@ set(platform_priv_requires esp_driver_gpio esp_driver_i2c esp_driver_i2s + esp_driver_ledc esp_driver_rmt esp_driver_spi esp_event diff --git a/components/platform/include/platform.h b/components/platform/include/platform.h index e7455d0..0681fdf 100644 --- a/components/platform/include/platform.h +++ b/components/platform/include/platform.h @@ -452,6 +452,15 @@ esp_err_t platform_multimodal_audio_register_downstream_playback_callbacks( esp_err_t platform_init_lcd_device(void); +/** + * @brief Set LCD backlight brightness using PWM. + * + * @param brightness_percent Brightness from 0 (off) to 100 (full). + * @return ESP_OK on success, ESP_ERR_INVALID_ARG outside the supported range, + * or ESP_ERR_INVALID_STATE before LCD initialization. + */ +esp_err_t platform_lcd_set_backlight_brightness(uint8_t brightness_percent); + /** * @brief Start LCD render loop. * diff --git a/components/platform/internal/lcd_direct.h b/components/platform/internal/lcd_direct.h index b6cfae7..adba895 100644 --- a/components/platform/internal/lcd_direct.h +++ b/components/platform/internal/lcd_direct.h @@ -10,6 +10,7 @@ extern "C" { #endif esp_err_t lcd_direct_init_device(void); +esp_err_t lcd_direct_set_backlight_brightness(uint8_t brightness_percent); void lcd_direct_start_render_loop(void); esp_err_t lcd_direct_stop_render_loop(int timeout_ms); esp_err_t lcd_direct_set_status_overlay(const platform_lcd_status_overlay_t *overlay); diff --git a/components/platform/src/lcd_direct.c b/components/platform/src/lcd_direct.c index 380f966..fe700c2 100644 --- a/components/platform/src/lcd_direct.c +++ b/components/platform/src/lcd_direct.c @@ -17,6 +17,7 @@ #include #include "driver/gpio.h" +#include "driver/ledc.h" #include "driver/spi_master.h" #include "esp_attr.h" #include "esp_check.h" @@ -58,6 +59,12 @@ #define LCD_DIRECT_LAST_FRAME_EXTRA_HOLD_MS 200U #define LCD_DIRECT_BASIC_FACE_FRAME_HOLD_MS 100U #define LCD_DIRECT_BASIC_FACE_LAST_FRAME_HOLD_MS 100U +#define LCD_DIRECT_BACKLIGHT_PWM_FREQUENCY_HZ 20000U +#define LCD_DIRECT_BACKLIGHT_PWM_DUTY_RESOLUTION LEDC_TIMER_10_BIT +#define LCD_DIRECT_BACKLIGHT_PWM_DUTY_MAX ((1U << 10U) - 1U) +#define LCD_DIRECT_BACKLIGHT_PWM_TIMER LEDC_TIMER_0 +#define LCD_DIRECT_BACKLIGHT_PWM_CHANNEL LEDC_CHANNEL_0 +#define LCD_DIRECT_BACKLIGHT_LOCK_TIMEOUT_MS 100U #define LCD_CMD_MADCTL 0x36 #define LCD_CMD_RAMCTRL 0xB0 @@ -133,6 +140,7 @@ static const lcd_direct_init_cmd_t s_st7789_init_cmds[] = { static esp_lcd_panel_io_handle_t s_panel_io; static esp_lcd_panel_handle_t s_panel; static SemaphoreHandle_t s_lock; +static SemaphoreHandle_t s_backlight_lock; static SemaphoreHandle_t s_completion_sem; static SemaphoreHandle_t s_preheat_done_sem; static SemaphoreHandle_t s_transfer_done_sem; @@ -465,10 +473,49 @@ static esp_err_t lcd_direct_get_png_image(const char *path, return ESP_OK; } +static uint32_t lcd_direct_backlight_duty(uint8_t brightness_percent) +{ + uint32_t duty = ((uint32_t)brightness_percent * LCD_DIRECT_BACKLIGHT_PWM_DUTY_MAX + 50U) / 100U; +#if !CONFIG_TQ_SCREEN_BACKLIGHT_ACTIVE_HIGH + duty = LCD_DIRECT_BACKLIGHT_PWM_DUTY_MAX - duty; +#endif + return duty; +} + +static esp_err_t lcd_direct_backlight_init(void) +{ + const ledc_timer_config_t timer_config = { + .speed_mode = LEDC_LOW_SPEED_MODE, + .duty_resolution = LCD_DIRECT_BACKLIGHT_PWM_DUTY_RESOLUTION, + .timer_num = LCD_DIRECT_BACKLIGHT_PWM_TIMER, + .freq_hz = LCD_DIRECT_BACKLIGHT_PWM_FREQUENCY_HZ, + .clk_cfg = LEDC_AUTO_CLK, + }; + ESP_RETURN_ON_ERROR(ledc_timer_config(&timer_config), TAG, "backlight PWM timer config failed"); + + const ledc_channel_config_t channel_config = { + .gpio_num = CONFIG_TQ_SCREEN_BACKLIGHT_GPIO, + .speed_mode = LEDC_LOW_SPEED_MODE, + .channel = LCD_DIRECT_BACKLIGHT_PWM_CHANNEL, + .intr_type = LEDC_INTR_DISABLE, + .timer_sel = LCD_DIRECT_BACKLIGHT_PWM_TIMER, + .duty = lcd_direct_backlight_duty(CONFIG_TQ_SCREEN_BACKLIGHT_BRIGHTNESS_PERCENT), + .hpoint = 0, + }; + ESP_RETURN_ON_ERROR(ledc_channel_config(&channel_config), TAG, "backlight PWM channel config failed"); + + ESP_LOGI(TAG, + "Backlight PWM initialized gpio=%d frequency=%uHz brightness=%d%% active_high=%d", + CONFIG_TQ_SCREEN_BACKLIGHT_GPIO, + (unsigned)LCD_DIRECT_BACKLIGHT_PWM_FREQUENCY_HZ, + CONFIG_TQ_SCREEN_BACKLIGHT_BRIGHTNESS_PERCENT, + CONFIG_TQ_SCREEN_BACKLIGHT_ACTIVE_HIGH ? 1 : 0); + return ESP_OK; +} + static esp_err_t lcd_direct_gpio_init(void) { - uint64_t pin_mask = (1ULL << CONFIG_TQ_SCREEN_BACKLIGHT_GPIO) | - (1ULL << CONFIG_TQ_SCREEN_DC_GPIO); + uint64_t pin_mask = (1ULL << CONFIG_TQ_SCREEN_DC_GPIO); if (CONFIG_TQ_SCREEN_RESET_PIN >= 0) { pin_mask |= (1ULL << CONFIG_TQ_SCREEN_RESET_PIN); } @@ -482,13 +529,11 @@ static esp_err_t lcd_direct_gpio_init(void) }; ESP_RETURN_ON_ERROR(gpio_config(&gpio_config_data), TAG, "gpio config failed"); - gpio_set_level(CONFIG_TQ_SCREEN_BACKLIGHT_GPIO, - CONFIG_TQ_SCREEN_BACKLIGHT_ACTIVE_HIGH ? 1 : 0); gpio_set_level(CONFIG_TQ_SCREEN_DC_GPIO, 0); if (CONFIG_TQ_SCREEN_RESET_PIN >= 0) { gpio_set_level(CONFIG_TQ_SCREEN_RESET_PIN, 1); } - return ESP_OK; + return lcd_direct_backlight_init(); } static esp_err_t lcd_direct_panel_init(void) @@ -1301,6 +1346,11 @@ esp_err_t lcd_direct_init_device(void) s_lock = xSemaphoreCreateMutex(); ESP_RETURN_ON_FALSE(s_lock != NULL, ESP_ERR_NO_MEM, TAG, "lock create failed"); + s_backlight_lock = xSemaphoreCreateMutex(); + ESP_RETURN_ON_FALSE(s_backlight_lock != NULL, + ESP_ERR_NO_MEM, + TAG, + "backlight lock create failed"); s_completion_sem = xSemaphoreCreateBinary(); ESP_RETURN_ON_FALSE(s_completion_sem != NULL, ESP_ERR_NO_MEM, TAG, "completion sem create failed"); s_preheat_done_sem = xSemaphoreCreateBinary(); @@ -1331,6 +1381,38 @@ esp_err_t lcd_direct_init_device(void) return ESP_OK; } +esp_err_t lcd_direct_set_backlight_brightness(uint8_t brightness_percent) +{ + ESP_RETURN_ON_FALSE(brightness_percent <= 100U, + ESP_ERR_INVALID_ARG, + TAG, + "backlight brightness must be between 0 and 100 percent"); + ESP_RETURN_ON_FALSE(s_initialized && s_backlight_lock != NULL, + ESP_ERR_INVALID_STATE, + TAG, + "LCD backlight is not initialized"); + ESP_RETURN_ON_FALSE(xSemaphoreTake(s_backlight_lock, + pdMS_TO_TICKS(LCD_DIRECT_BACKLIGHT_LOCK_TIMEOUT_MS)) == pdTRUE, + ESP_ERR_TIMEOUT, + TAG, + "backlight lock timed out"); + + esp_err_t err = ledc_set_duty(LEDC_LOW_SPEED_MODE, + LCD_DIRECT_BACKLIGHT_PWM_CHANNEL, + lcd_direct_backlight_duty(brightness_percent)); + if (err == ESP_OK) { + err = ledc_update_duty(LEDC_LOW_SPEED_MODE, LCD_DIRECT_BACKLIGHT_PWM_CHANNEL); + } + xSemaphoreGive(s_backlight_lock); + + if (err == ESP_OK) { + ESP_LOGI(TAG, "Backlight brightness set to %u%%", (unsigned)brightness_percent); + } else { + ESP_LOGE(TAG, "Backlight brightness update failed: %s", esp_err_to_name(err)); + } + return err; +} + void lcd_direct_start_render_loop(void) { if (!s_initialized) { diff --git a/components/platform/src/runtime/platform_lcd_runtime.c b/components/platform/src/runtime/platform_lcd_runtime.c index 4c64cfb..34b3113 100644 --- a/components/platform/src/runtime/platform_lcd_runtime.c +++ b/components/platform/src/runtime/platform_lcd_runtime.c @@ -12,6 +12,11 @@ esp_err_t platform_init_lcd_device(void) return lcd_direct_init_device(); } +esp_err_t platform_lcd_set_backlight_brightness(uint8_t brightness_percent) +{ + return lcd_direct_set_backlight_brightness(brightness_percent); +} + void platform_start_lcd_render_loop(void) { lcd_direct_start_render_loop(); diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index 35342ad..13d15d8 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -578,6 +578,15 @@ menu "Example Configuration" bool "Backlight active high" default y + config TQ_SCREEN_BACKLIGHT_BRIGHTNESS_PERCENT + int "Default backlight brightness (percent)" + default 100 + range 0 100 + depends on TQ_SCREEN_ENABLE + help + Initial LCD backlight PWM duty after screen initialization. + Set 0 to turn the backlight off or 100 for full brightness. + config TQ_SCREEN_MIRROR_X bool "Mirror panel in X direction" default y diff --git a/sdkconfig.defaults b/sdkconfig.defaults index d9658d3..0d101b0 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -277,6 +277,7 @@ CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=y CONFIG_TQ_SCREEN_INVERT_COLOR=y CONFIG_TQ_SCREEN_SWAP_COLOR_BYTES=y CONFIG_TQ_SCREEN_BACKLIGHT_ACTIVE_HIGH=y +CONFIG_TQ_SCREEN_BACKLIGHT_BRIGHTNESS_PERCENT=100 CONFIG_TQ_SCREEN_MIRROR_X=y CONFIG_TQ_SCREEN_SWAP_XY=y CONFIG_TQ_SCREEN_X_GAP=0