Files
lucky-cat/docs/backlight-pwm-brightness-control.zh-CN.md
T

269 lines
8.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# LCD 背光 PWM 亮度控制实现说明
## 1. 问题背景与原因
原有 LCD 背光使用 GPIO7 作为普通数字输出,初始化时仅根据背光有效电平写入高电平或低电平:
```c
gpio_set_level(CONFIG_TQ_SCREEN_BACKLIGHT_GPIO,
CONFIG_TQ_SCREEN_BACKLIGHT_ACTIVE_HIGH ? 1 : 0);
```
这种实现只能控制背光完全开启或完全关闭,不能设置中间亮度。设备进入轻待机后,即使减少 LCD 图片刷新,背光仍保持全亮,屏幕主要功耗无法有效降低。
项目配置中原先只有背光 GPIO 和有效电平配置,也没有可以通过 `sdkconfig` 设置默认亮度的选项。
## 2. 解决方式
本次将 GPIO7 背光控制从普通 GPIO 输出改为 ESP32-S3 LEDC PWM 输出,并增加以下能力:
1. PWM 频率固定为 20 kHz,避免肉眼可见闪烁,并降低进入可闻频段的风险。
2. 使用 10 bit PWM 分辨率,内部占空比范围为 0~1023。
3. 支持 0~100 的亮度百分比。
4. 兼容高电平有效和低电平有效两种背光电路。
5. 增加 Kconfig 配置,可以通过 `sdkconfig` 设置开机默认亮度。
6. 增加运行时亮度接口,为后续轻待机降亮度和唤醒恢复亮度提供基础能力。
7. 使用独立互斥锁串行化运行时亮度更新,避免并发修改 LEDC 占空比。
## 3. 配置方式
新增配置项:
```ini
CONFIG_TQ_SCREEN_BACKLIGHT_BRIGHTNESS_PERCENT=100
```
取值范围:
| 配置值 | 效果 |
| --- | --- |
| `0` | 背光关闭 |
| `30` | 约 30% PWM 占空比 |
| `50` | 约 50% PWM 占空比 |
| `100` | 背光全亮 |
Kconfig 定义如下:
```kconfig
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.
```
可以通过以下菜单修改:
```text
Example Configuration
-> TQ Screen
-> Default backlight brightness (percent)
```
也可以直接修改 `sdkconfig` 中的配置值。
## 4. 具体代码
### 4.1 PWM 参数
```c
#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
```
### 4.2 百分比转换为 PWM 占空比
```c
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;
}
```
计算中加入 50 后再除以 100,用于百分比转换时四舍五入。低电平有效的硬件会自动反转占空比。
### 4.3 LEDC PWM 初始化
```c
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");
return ESP_OK;
}
```
LCD GPIO 初始化不再把背光引脚注册为普通 GPIO 输出,而是在 DC 和复位引脚初始化后调用 PWM 初始化:
```c
static esp_err_t lcd_direct_gpio_init(void)
{
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);
}
gpio_config_t gpio_config_data = {
.pin_bit_mask = pin_mask,
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_RETURN_ON_ERROR(gpio_config(&gpio_config_data), TAG, "gpio config failed");
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 lcd_direct_backlight_init();
}
```
### 4.4 运行时亮度接口
平台公共接口:
```c
esp_err_t platform_lcd_set_backlight_brightness(uint8_t brightness_percent);
```
底层实现:
```c
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);
return err;
}
```
后续轻待机可以调用:
```c
platform_lcd_set_backlight_brightness(35);
```
唤醒后恢复默认亮度可以调用:
```c
platform_lcd_set_backlight_brightness(
CONFIG_TQ_SCREEN_BACKLIGHT_BRIGHTNESS_PERCENT);
```
## 5. 解决成果
1. LCD 背光从开关控制升级为 0~100% PWM 亮度控制。
2. 默认亮度可以通过 `sdkconfig` 配置,不需要修改驱动源码。
3. 默认值保持 100%,升级后现有设备的默认显示亮度不变。
4. 已提供运行时接口,可以直接支持后续轻待机和唤醒状态切换。
5. 高电平有效和低电平有效配置均得到支持。
6. 使用 ESP-IDF 5.5.4 完整编译成功。
7. 生成固件大小为 `0x211bf0` 字节,最小应用分区仍有约 41% 空余空间。
对应代码提交:
```text
988dc92 feat(platform): add PWM backlight brightness control
```
## 6. 后续影响与注意事项
### 6.1 LEDC 资源占用
当前实现固定使用:
```text
LEDC low-speed timer 0
LEDC channel 0
```
项目后续增加蜂鸣器、其他 LED 调光或舵机 PWM 时,不能重复占用该定时器和通道,或者需要增加统一的 LEDC 资源分配策略。
### 6.2 亮度百分比不是视觉线性
当前配置值与 PWM 占空比线性对应,但人眼对亮度的感知不是线性的。例如 50% 占空比不一定看起来正好是全亮的一半。后续如果需要更自然的渐亮、渐暗效果,可以增加 Gamma 校正表。
### 6.3 需要实机确认调光范围
软件编译已经通过,但仍需要在真实屏幕上确认:
1. 20 kHz 下是否存在异常噪声。
2. 低亮度时背光是否稳定。
3. 10%、20%、30%、50% 等档位的视觉效果和实际电流。
4. 背光接口所在硬件是否对高频 PWM 有额外限制。
### 6.4 对 LCD 刷新无影响
PWM只控制背光供电占空比,不改变 ST7789 的 SPI 时钟、图片刷新频率、颜色数据和显存内容。调整亮度不会中断当前表情动画。
### 6.5 对轻待机功能的影响
本次只提供背光控制基础能力,尚未实现5分钟无交互计时和自动降亮度。后续轻待机逻辑需要在业务状态层调用运行时接口,并处理以下状态:
1. 无交互5分钟后降低亮度。
2. 唤醒词、PTT按键或抽签开关触发时恢复亮度。
3. OTA、配网、低电量提示等特殊状态是否覆盖待机亮度。
4. 多个状态同时请求亮度时的优先级。
### 6.6 现有架构检查告警
项目架构检查仍会报告电池任务中存在 `portMAX_DELAY` 无界等待。该告警在本次改动之前已经存在,与背光 PWM 实现无关;固件完整编译不受影响。