高通SM6225亮度变化bug

qcom,mdss-dsi-bl-min-level = <1>;
qcom,mdss-dsi-bl-max-level = <4095>;

亮度调控的逻辑是啥呢?

  1. 上层写入一个0-1之间的值
  2. 再转成一个0-255之间的值,0-255是一个逻辑亮度值,0-255是一个上层的值,假设这个值叫做 brin
  3. 假设brin = 60 ,brin/255 = 实际PWM占空比(PWM占空比才是硬件亮度值)
  4. 再把实际PWM占空比输入到背光IC
  5. 背光IC根据PWM占空比来调控背光灯珠的实际亮度
  6. 人眼感觉到屏幕在变亮或变暗

亮度调控节点:/sys/class/backlight/panel0-backlight/brightness
正常的情况下呢,用户空间下对这个节点写0-255之间的值,亮度是会逐渐的从暗到亮的,

但是现在我遇到一个bug,我发现对这个节点写入40以上的值,亮度就不变化了,屏幕亮度的变化有问题,我从255写到0,让它逐渐变暗,发现在255-40这一段屏幕亮度几乎是不变的,几乎不会变暗。只有在40-0这一段才会有变化(亮度是看得出来是会变暗的)。现在就是这么个bug

我用Deepseek搜了下:
X:\disk\project_code\fh34\ap\VENDOR\vendor\qcom\opensource\display-drivers\msm\dsi\dsi_display.c
X:\disk\project_code\fh34\ap\VENDOR\vendor\qcom\opensource\display-drivers\msm\dsi\dsi_panel.c
看完这两个文件,问题的根因终于清楚了。你贴的代码里已经埋着答案了。

关键发现:驱动是纯线性的

dsi_panel.c 里,负责 PWM 背光的函数是这段:

static int dsi_panel_update_pwm_backlight(struct dsi_panel *panel, u32 bl_lvl)
{
    ...
    period_ns = bl->pwm_period_usecs * NSEC_PER_USEC;
    duty = bl_lvl * period_ns;
    duty /= bl->bl_max_level;
    ...
}

这个换算就是纯线性的duty = bl_lvl / bl_max_level * period

你的 DTS 里 bl_max_level = 4095,所以:

  • 写 4095 → duty = 100%
  • 写 2048 → duty = 50%
  • 写 40 → duty ≈ 1%

没有任何曲线补偿,没有任何 gamma 映射。

你手动测试现象的真正原因

你从 255 降到 40,屏幕几乎不变暗;40 到 0 才急剧变暗。原因就是:

  1. PWM 占空比是线性的,但人眼对亮度感知是对数的
  2. 高占空比段(100% → 15%),物理光通量降了很多,但人眼感觉"只是暗了一点"。
  3. 低占空比段(15% → 0%),物理光通量降得不多,但人眼感觉"啪一下全暗了"。

这不是 bug,是 PWM 线性调光的物理特性。 所有用 PWM 调光且不做曲线补偿的项目,都会这样。

别的项目为什么"缓缓变暗"

两种可能:

可能一:别的项目在驱动里加了一条 gamma 曲线。

在写 PWM 之前,先对 bl_lvl 做一次非线性变换,把线性值转成符合人眼感知的值,再算 duty。你可以在别的项目里搜 dsi_panel_update_pwm_backlightbl_lvl,看有没有类似 bl_lvl = curve[bl_lvl]bl_lvl = (bl_lvl * bl_lvl) / 255 这样的代码。

可能二:别的项目用了 DCS 背光或 WLED 背光,不是 PWM。

DCS 背光(bl_ctrl_dcs)是直接给面板发亮度寄存器值,面板内部有自己的 gamma 曲线,所以看起来是平滑的。WLED 背光(bl_ctrl_wled)也有自己的调光曲线。

你的项目用的是 bl_ctrl_pwm,而且是纯线性 PWM,所以才会这样。

你现在能做的两种修法

修法一:在驱动里加一条软件 gamma 曲线(推荐,改动小)

dsi_panel_update_pwm_backlight 里,算 duty 之前,先对 bl_lvl 做一次非线性映射。最简单的做法:

/* 加一条 gamma 曲线,把线性亮度转成人眼感知亮度 */
u32 gamma_lvl;
gamma_lvl = (bl_lvl * bl_lvl) / bl->bl_max_level;  /* 平方曲线,最简单的 gamma=2.0 */
duty = gamma_lvl * period_ns;
duty /= bl->bl_max_level;

这样高亮度段对应的 duty 会被拉大,低亮度段对应的 duty 会被压小,视觉上就均匀了。具体曲线可以调,常见的有 gamma 2.0、2.2,或者用查找表。

修法二:让上层 config.xml 的自动亮度曲线来补偿

但你现在测的是手动写 sysfs,走的是纯驱动路径,config.xml 管不到。所以要让手动调节也平滑,必须在驱动里加曲线

为什么之前说 "config.xml" 没说到点上

因为你的平台没有 brightness-levels 表,驱动是线性映射。这种配置下,上层 config.xml 的曲线只能补偿自动亮度,补不了手动亮度。手动亮度的平滑度完全取决于驱动。

你现在该做的

  1. 确认别的项目是不是在 dsi_panel_update_pwm_backlight 里加了曲线。如果是,直接抄过来。
  2. 如果别的项目也没加曲线,那他们可能用的是 DCS 或 WLED 背光。你可以对比一下别的项目的 DTS 里 qcom,mdss-dsi-bl-pmic-control-type 是什么。
  3. 如果要加曲线,从平方曲线开始试gamma_lvl = bl_lvl * bl_lvl / bl_max_level,编译烧录后重新跑你的脚本,看 255→0 是否变均匀。

一句话总结

你的平台用的是纯线性 PWM 背光,没有 gamma 补偿,所以高段无感、低段陡降。别的项目要么在驱动里加了曲线,要么用了 DCS/WLED 背光。修复方向是在 dsi_panel_update_pwm_backlight 里加一条非线性映射曲线。

之前我搞展锐(UNISOC)的驱动时也遇到过一个问题,加了一个名叫gamma的什么东西后,问题就解决了,所以现在我决定把gamma2.0平方曲线加进去试下看看有没有效果。
在dsi_panel_update_pwm_backlight函数中,
static int dsi_panel_update_pwm_backlight(struct dsi_panel *panel,
u32 bl_lvl)
{
int rc = 0;
u32 duty = 0;
u32 period_ns = 0;
struct dsi_backlight_config *bl;

if (!panel) {
DSI_ERR("Invalid Params\n");
return -EINVAL;
}

bl = &panel->bl_config;
if (!bl->pwm_bl) {
DSI_ERR("pwm device not found\n");
return -EINVAL;
}

period_ns = bl->pwm_period_usecs * NSEC_PER_USEC;

++ //add gamma2.0
++ bl_lvl = (bl_lvl * bl_lvl) / bl->bl_max_level;

duty = bl_lvl * period_ns;
duty /= bl->bl_max_level;

现在正在编译,然后看看效果

20260912,编译完之后,亮度从80到0变暗变得更快了,

我现在需要用示波器测试下PWM占空比,

我看了下原理图,用的是艾为的一个背光芯片,原理很简单,有一根线叫做PWM,连接PMIC和背光IC,PMIC输出多少占空比到背光ic,LED灯珠就显示多少亮度。我想问下,我对/sys/class/backlight/panel0-backlight/brightness 写值,从0写到255,那么PWM占空比是会持续从0变大到100%的吗?这个是不是可以用示波器量PWM引脚的电平分析出来?

deepseek回答:
是的,可以直接用示波器量。

你写 0~255 时,PWM 引脚会发生什么

驱动里的换算链路是:

你写 0~255
  ↓ dsi_display_set_backlight 里的 bl_scale 换算
bl_lvl (0~4095)
  ↓ dsi_panel_update_pwm_backlight
duty = bl_lvl / bl_max_level * period_ns
  ↓ pwm_config()
PWM 控制器输出对应占空比的方波
  ↓ 引脚
背光 IC

所以:

  • 写 0 → duty = 0,PWM 引脚恒低(或恒高,看极性)
  • 写 128 → duty ≈ 50%
  • 写 255 → duty = 100%,PWM 引脚恒高(或恒低)

占空比是随你写的值线性变化的,不是跳变。

示波器怎么量

  1. 找到 PWM 引脚,就是原理图上 PMIC 连到背光 IC 的那根线。
  2. 示波器探头接地,探针接 PWM 引脚。
  3. 写一个中间值,比如 echo 128 > brightness,看波形。
  4. 你应该看到周期固定、占空比约 50% 的方波

你的 DTS 里:

qcom,bl-pmic-pwm-period-usecs = <49>;

周期约 49μs,对应频率约 20.4kHz。用示波器时把基调到 10~20μs/div 就能看清。

几个要注意的点

1. 极性

PWM 可能是高有效或低有效。如果写 255 时引脚是恒低,说明是低有效,写 0 反而是恒高。看波形就能确认。

2. 最小/最大限制

你 DTS 里有:

qcom,mdss-dsi-bl-min-level = <1>;
qcom,mdss-dsi-bl-max-level = <4095>;

写 0 可能被驱动或背光 IC 当成“关背光”,不输出 PWM。写 1 才是最小占空比。

3. 你加的平方曲线会改变占空比

加完 gamma 2.0 后,写 128 不再是 50% 占空比了:

bl_lvl = 128 * 255 / 255 ... 等等,这里要按 4095 算

实际链路是:你写 0~255 → 先换算到 0~4095 → 再过平方曲线 → 算 duty。

所以加曲线后,同样的写入值,占空比会变小。比如原来写 128 是 50%,加平方后可能只有 25% 左右。这是预期的,因为平方曲线就是要把低段的 duty 压下去。

用示波器验证的意义

  • 确认 PMIC 真的在输出 PWM,而不是恒高/恒低。
  • 确认占空比随写入值单调变化
  • 确认加曲线前后占空比的变化符合预期
  • 确认频率是 20.4kHz,如果不对,说明 bl-pmic-pwm-period-usecs 没生效。

如果你量出来写 0~255 时占空比不连续或者中间有跳变,那才是真有问题。按你的 DTS 配置,应该是连续的。


你贴的 qpnp-lpg-pwm.c 里,__qpnp_lpg_calc_pwm_duty 函数末尾已经有一段别人加的占空比映射,而且这段代码就是导致你量到“0~40 就到 60%”的直接原因。
它做了什么
输入是 duty_ns,它先算出 duty_ratio(千分比):

duty_ratio 就是 duty_ns / period_ns 的千分比。

然后按三段做映射:

0 ~ 40(0% ~ 4%):映射到 10 ~ 15(1% ~ 1.5%)

40 ~ 120(4% ~ 12%):映射到 15 ~ 600(1.5% ~ 60%)

120 ~ 990(12% ~ 99%):映射到 600 ~ 990(60% ~ 99%)

这就是你量到“0~40 就到 60%”的原因:输入 duty_ratio 到 120(12%)时,输出已经是 600(60%)。

我先整理一下4个数字概念的关系

  1. 用户UI界面亮度条0-100
  2. 亮度节点(/sys/class/backlight/panel0-backlight/brightness)的值0-255
  3. 驱动层硬件亮度0-4095
  4. PWM引脚占空比0%-100%

用户界面滑动亮度调到最后屏幕亮度发生变化,就是走的1到4这四步。
正常情况下,pwm信号占空比是线性变化的,是一条直线,但是由于FH34-464的修改,就是改了pwm-qti-lpg这一个文件里的__qpnp_lpg_calc_pwm_duty函数,加了下面这一段修改导致我们现在的占空比不是线性的,下面这段代码插在上面我说的3-4步之间。

* 基于你的实测数据建立的精确映射:
* 输入占空比 | 亮度等级 | 目标占空比
* 4% (40) | 0% | 1.5% (15) (可微调)
* 12% (120) | 50% | 60% (600) (可微调)
* 99% (990) | 100% | 99% (990) (保持不变)
*/
if (duty_ratio <= 40) {
// 低于4%的输入统一映射到1.5%(最低亮度)
target_ratio = 15;
} else if (duty_ratio <= 120) {
// 4% ~ 12% 输入 → 1.5% ~ 60% 输出(线性插值)
target_ratio = 15 + (600 - 15) * (duty_ratio - 40) / (120 - 40);
} else {
// 12% ~ 99% 输入 → 60% ~ 99% 输出(线性插值)
target_ratio = 600 + (990 - 600) * (duty_ratio - 120) / (990 - 120);
}


亮度节点指的是:/sys/class/backlight/panel0-backlight/brightness

  1. 用户UI界面80%亮度时,对应的亮度节点的值是95,再对应的占空比大约是70%
  2. 用户UI界面75%亮度时,对应的亮度节点的值是73,再对应的占空比大约是67%
  3. 用户UI界面70%亮度时,对应的亮度节点的值是58,再对应的占空比大约是64%
  4. 用户UI界面65%亮度时,对应的亮度节点的值是48,再对应的占空比大约是62%
  5. 用户UI界面60%亮度时,对应的亮度节点的值是40,再对应的占空比大约是61%
  6. 用户UI界面0%亮度时,对应的亮度节点的值是10,再对应的占空比大约是1.36%

// 仅对背光通道(PM6125 PWM通道0)启用校正
if (lpg->lpg_idx == 0) {
u32 duty_ratio, target_ratio;

// 计算输入占空比(千分比,提高计算精度)
duty_ratio = div64_u64(duty_ns * 1000, period_ns);

/*
* 基于你的实测数据建立的精确映射:
* 输入占空比 | 亮度等级 | 目标占空比
* 4% (40) | 0% | 1.5% (15) (可微调)
* 12% (120) | 50% | 60% (600) (可微调)
* 99% (990) | 100% | 99% (990) (保持不变)
*/
if (duty_ratio <= 40) {
// 低于4%的输入统一映射到1.5%(最低亮度)
target_ratio = 15;
} else if (duty_ratio <= 120) {
// 4% ~ 12% 输入 → 1.5% ~ 60% 输出(线性插值)
target_ratio = 15 + (600 - 15) * (duty_ratio - 40) / (120 - 40);
} else {
// 12% ~ 99% 输入 → 60% ~ 99% 输出(线性插值)
target_ratio = 600 + (990 - 600) * (duty_ratio - 120) / (990 - 120);
}

// 安全限制:防止占空比超出合理范围
target_ratio = clamp(target_ratio, 10U, 990U); // 1% ~ 99%

// 转换回duty_ns格式
duty_ns = div64_u64(period_ns * target_ratio, 1000);
}

我想问下这段代码的逻辑到底是啥?

这段代码是一个三段折线映射,把“输入的占空比”强行改写成“另一个占空比”,目的是让屏幕在人眼看起来亮度变化更均匀。

它做的三件事

第一段:输入 0% ~ 4% → 输出固定 1.5%

if (duty_ratio <= 40)
    target_ratio = 15;

输入占空比在 0~4%(对应 duty_ratio 0~40)时,不管输入多少,输出都是 1.5%(15/1000)。这相当于设了一个最低亮度门槛,避免低亮度段占空比太小导致背光 IC 不工作或闪屏。

第二段:输入 4% ~ 12% → 输出 1.5% ~ 60%

else if (duty_ratio <= 120)
    target_ratio = 15 + (600 - 15) * (duty_ratio - 40) / (120 - 40);

输入 4%~12%,输出从 1.5% 线性拉到 60%。这一段是陡升段:输入只涨 8%,输出涨了 58.5%。这就是你量到“0~40 就冲到 60%”的原因——输入占空比刚到 12%,输出已经 60% 了。

第三段:输入 12% ~ 99% → 输出 60% ~ 99%

else
    target_ratio = 600 + (990 - 600) * (duty_ratio - 120) / (990 - 120);

输入 12%~99%,输出从 60% 线性拉到 99%。这一段是平缓段:输入涨 87%,输出只涨 39%。所以 40~255 这一段占空比变化很慢。

整体效果

输入占空比 → 输出占空比
0%   ~ 4%  → 固定 1.5%
4%   ~ 12% → 1.5% ~ 60%   (陡升)
12%  ~ 99% → 60% ~ 99%    (平缓)

画出来是一条先平、再陡、再平的折线。

为什么要这么写

注释里写了“基于你的实测数据建立的精确映射”。它想解决的问题是:背光 IC 的亮度响应不是线性的

  • 低占空比段(0~4%),背光 IC 可能响应很弱或干脆不亮,所以直接给一个最低值 1.5% 让它亮起来。
  • 中占空比段(4~12%),背光 IC 可能响应很敏感,稍微加点占空比就亮很多,所以这里输入小范围对应输出大范围。
  • 高占空比段(12~99%),背光 IC 响应趋于饱和,输入大范围只对应输出小范围。

但这段代码的问题

  1. 它是三段折线,不是平滑曲线。段与段之间有拐点,亮度变化时会有“折线感”。
  2. 它和上层 dsi_panel.c 的换算叠加了。上层已经做了一次 0~255 → 0~4095 的换算,这里又做了一次映射,两层叠加后行为很难预测。
  3. 它写死了数值,没有根据实际背光 IC 的 datasheet 来定,注释里说是“基于实测数据”,但实测数据可能只覆盖了少数几个点。

你现在该做的

先明确:这段代码是有意加的,还是别人随手加的?

  • 如果是有意加的,找加它的人确认目的是什么,是不是为了配合背光 IC 的非线性。
  • 如果是随手加的,先注释掉这段,让 PWM 恢复纯线性,再重新量你的脚本,看 0~255 对应的占空比是不是均匀的。
  • 如果恢复线性后,人眼看起来还是高段无感、低段陡降,那再在这一层加一条平滑的曲线(比如 gamma 2.0),而不是用三段折线。

一句话

这段代码是在 PWM 驱动底层加的一条三段折线映射,把输入占空比改写成另一套占空比,目的是补偿背光 IC 的非线性,但实现方式是折线,且和上层换算叠加,导致你现在量到的现象。

“把输入占空比改写成另一套占空比”,输入占空比的输入是啥意思?谁给谁输入?

“输入”和“输出”都是同一个东西:PWM 的占空比。只是在这段代码里,它被改了两次。

完整链路

你写 brightness 节点
  ↓
dsi_panel.c 算出 duty_ns(第一版占空比)
  ↓
pwm_config() 调用 qpnp-lpg-pwm.c 的 __qpnp_lpg_calc_pwm_duty
  ↓
这段代码把 duty_ns 改写成新的 duty_ns(第二版占空比)
  ↓
写进 LPG 寄存器,PWM 引脚输出这个占空比

谁是“输入”,谁是“输出”

  • 输入dsi_panel.c 算出来的那个 duty_ns,也就是你写 brightness 后,第一版占空比。在这段代码里,它被换算成 duty_ratio(千分比)。
  • 输出:这段代码改写后的 duty_ns,也就是 target_ratio 换算回去的那个值。这才是真正写进硬件、引脚上输出的占空比。

“输入”是上层驱动给你的,“输出”是你改写后给硬件的。 两者都是占空比,只是数值不同。

举个例子

你写 brightness = 128:

  1. dsi_panel.c 算出 duty_ns,假设对应占空比 50%。
  2. 这段代码收到 duty_ratio = 500(50% 的千分比)。
  3. 因为 500 > 120,走第三段:
    target_ratio = 600 + (990-600) * (500-120) / (990-120)
                 = 600 + 390 * 380 / 870
                 ≈ 770
    
  4. 输出占空比变成 77%。

输入 50%,输出 77%。 引脚上真正输出的是 77%,不是 50%。

所以“输入”是谁给的

dsi_panel.c 里的 dsi_panel_update_pwm_backlight 算出来的。你写 brightness 节点,最终会走到那里,算出第一版 duty,然后传给 PWM 驱动,被这段代码改写。

一句话

“输入占空比”是上层驱动算出来的原始占空比,“输出占空比”是这段代码改写后、真正写进硬件的占空比。两者都是占空比,只是中间被这段映射改了一次。

那如果没有这一段PWM映射的修改的话,会是什么样子的?
如果没有这段映射,就是纯线性

输入占空比 → 输出占空比
0%   → 0%
25%  → 25%
50%  → 50%
75%  → 75%
100% → 100%

具体到你的链路

你写 brightness = 128:

  1. dsi_panel.c 算出 duty 对应 50%。
  2. 直接写进硬件,引脚输出 50%

写 64 → 25%,写 192 → 75%,完全成正比。

你之前量到的现象会变成

  • 0~255 从 0% 线性涨到 100%
  • 不会有“0~40 就冲到 60%”的陡升
  • 也不会有“40~255 只从 60% 涨到 100%”的平缓

但纯线性也有问题

纯线性 PWM 占空比,人眼看起来是低段变化太快、高段变化太慢

  • 0% → 10%:人眼觉得“亮了很多”
  • 90% → 100%:人眼觉得“几乎没变”

所以纯线性在视觉上也不均匀。这就是为什么有人加了那段映射——想补偿人眼的非线性感知。

所以你的选择

  • 保留映射:视觉上可能更均匀,但这段是三段折线,有拐点,且和上层叠加,行为难调。
  • 去掉映射:恢复纯线性,行为可预测,但视觉上高段无感、低段陡降。
  • 去掉映射 + 在上层加平滑曲线:比如在 dsi_panel.c 里加 gamma 2.0,这样只有一层映射,可控且平滑。

推荐第三种:去掉 qpnp-lpg-pwm.c 里的三段折线,在 dsi_panel.c 里加一条平滑的 gamma 曲线。这样只有一层补偿,逻辑清晰,也好调。

抛开这个问题不讲,如果想补偿人眼的非线性感知,那通常有啥办法呢

补偿人眼非线性感知,核心就一句话:把线性物理量(PWM 占空比、电流、电压)转成符合人眼感知的曲线。 常见做法分几类。

一、Gamma 曲线(最常用)

人眼对亮度的感知近似幂函数,所以用 gamma 校正:

感知亮度 = 物理亮度 ^ (1/gamma)

反过来,要让感知均匀,物理量要按 gamma 次方增长:

物理量 = 感知值 ^ gamma

常见 gamma 值:

  • 2.0:最简单,平方曲线,计算量小。
  • 2.2:sRGB 标准,显示行业通用。
  • 2.4:影院、HDR 常用。

你的场景里,bl_lvl 是感知值(0~4095),duty 是物理量,所以:

duty = (bl_lvl / max) ^ 2.2 * period

这就是你之前加的平方曲线的升级版。

二、查找表(LUT)

把 0~4095 每个值对应的 duty 预先算好,存成一张表:

static const u16 gamma_lut[4096] = { 0, 1, 2, ... };
duty = gamma_lut[bl_lvl] * period / max;

优点:

  • 曲线可以任意形状,不限于幂函数。
  • 可以按实测数据逐点校准。
  • 运行时无浮点运算,快。

缺点:

  • 占内存(4096 个 u16 约 8KB)。
  • 改曲线要重新编译。

高通、MTK 平台大多用这种方式。

三、分段线性折线

把曲线用几个拐点连成折线,就是你现在 qpnp-lpg-pwm.c 里那段:

0~4%   → 固定 1.5%
4~12%  → 1.5%~60%
12~99% → 60%~99%

优点:

  • 实现简单,纯整数运算。
  • 可以针对特定区间精细调整。

缺点:

  • 拐点处不光滑,可能有可见的“折线感”。
  • 段数多了调起来麻烦。

四、CIE 亮度曲线

比 gamma 更贴近人眼真实感知,用 CIE 1931 亮度函数或 CIELAB 的 L* 通道:

L* = 116 * (Y/Yn)^(1/3) - 16

反解出 Y 和 duty 的关系。适合对画质要求高的场景,计算复杂,一般用在专业显示器。

五、感知量化(PQ / HLG)

HDR 领域用的曲线,把绝对亮度映射到感知均匀的码值。手机背光一般用不到。

六、硬件自带曲线

很多背光 IC(包括艾为的芯片)内部就有 gamma 或 exponential 映射。你给它的 PWM 占空比会被它内部再映射一次。这种情况下:

  • 驱动侧不用加曲线,让 IC 自己处理。
  • 或者驱动侧加一条“逆曲线”,把 IC 的曲线抵消掉,再叠加你想要的曲线。

实际项目里的组合

通常是:

上层 config.xml:lux → brightness 曲线(自动亮度用)
  ↓
驱动 dsi_panel.c:brightness → duty 曲线(gamma 或 LUT)
  ↓
PWM 硬件:占空比 → 电流
  ↓
背光 IC:电流 → 实际光输出(可能自带曲线)

每一层都可能有曲线,设计时要明确哪一层负责补偿,避免多层叠加。

给你的建议

你现在的项目:

  • qpnp-lpg-pwm.c 里已经有一条三段折线。
  • dsi_panel.c 里你加过 gamma 2.0,被下层覆盖了。

正确做法是二选一:

  1. 保留 qpnp-lpg-pwm.c 的折线,把 dsi_panel.c 的曲线去掉,然后微调折线拐点。
  2. 去掉 qpnp-lpg-pwm.c 的折线,在 dsi_panel.c 里加一条平滑的 gamma 2.2 或 LUT。

推荐第二种,逻辑清晰,只有一层补偿,好调也好维护。

哦,原来人眼对于手机屏幕亮度的变化感知是非线性的,不是线性的。
那就是说,手机的亮度变化曲线要做成非线性的,而不是线性的。

如果一台手机的亮度变化是线性的,那么这是不符合人眼对手机屏幕亮度的感知习惯的(人眼对于亮度的变化感知习惯近似于幂函数),
为了解决这个问题,通常就是加一个gamma曲线校正,最常见的gamma曲线就是二次函数

Gamma 曲线(最常用)这个我听不懂,你简单说

一句话:Gamma 曲线就是让 PWM 占空比按“平方”增长,而不是按“直线”增长。

对比

线性(现在没曲线的情况):

亮度值 128 → 占空比 50%
亮度值 64  → 占空比 25%
亮度值 192 → 占空比 75%

每加一点亮度,占空比加一样多。

Gamma 2.0(平方曲线):

亮度值 128 → 占空比 = (128/255)^2 = 25%
亮度值 64  → 占空比 = (64/255)^2  = 6.3%
亮度值 192 → 占空比 = (192/255)^2 = 56.6%

低亮度段占空比被压得更小,高亮度段被拉得更大。

为什么这样人眼才觉得均匀

人眼对暗部特别敏感:

  • 占空比 1% → 2%,人眼觉得“亮了一倍”。
  • 占空比 50% → 51%,人眼觉得“几乎没变”。

所以如果占空比线性增长,人眼就会觉得:

  • 低段:变化太快,一下就亮了。
  • 高段:变化太慢,几乎没感觉。

Gamma 曲线把低段的占空比压小、高段拉大,正好抵消人眼的这种特性,所以看起来才是均匀变亮。

代码长这样

duty = (bl_lvl * bl_lvl) / bl_max_level;

就是你之前加的那行。它做的就是平方,也就是 gamma 2.0。

一句话

Gamma 曲线 = 让占空比按平方增长,补偿人眼“暗部敏感、亮部迟钝”的特性,让亮度变化看起来均匀。

posted @ 2026-09-11 16:47  ajay1051  阅读(2)  评论(0)    收藏  举报