269 lines
8.8 KiB
Markdown
269 lines
8.8 KiB
Markdown
# 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 实现无关;固件完整编译不受影响。
|