彻底解决Linux下LED的背光闪烁
前言
因为家里的那台笔记本太重了,前段时间换了台Thinkpad X250方便外出携带使用。X250的屏幕分辨率是1080P,但不知道为什么,每次使用一段时间后眼睛都非常难受,还伴有轻微的头晕。但是接上外接显示器使用时就很正常,所以我一直认为是显示器太小或者分辨率太高的原因。
一次偶然的机会对着屏幕拍照,发现屏幕上竟然有明暗相间的条纹!还以为是显示器坏了,Google了一下资料后才发现这是普遍的LED背光闪烁(LED backlight flicker)。
上图:在低背光频率下,铅笔快速晃动可以看到明显的残影
目前市场上主要的背光技术主要是LED背光和CCFL背光。CCFL即冷阴极荧光灯,类似于家里使用的灯管,因为其具有的荧光图层可以使屏幕不闪烁,成为了大屁股显示器的绝佳代替品。但同事也有功耗较高、易老化导致屏幕变黄等缺点,在LED背光技术出来之后,原来越少厂家使用这种技术了。
LED作为背光源具有超高亮度、寿命长、功耗高的特点,已经成为主流的背光技术,目前市面上的大部分液晶显示器都是采用了这种技术。而LED的主流亮度调节技术采用的则是低成本的PWM调光。
什么是PWM调光呢?拿家里的电灯说明一下,把电灯的开关关掉,家里的亮度是0%; 打开0.5秒,关闭0.5秒,不停地重复这个操作,我们认为亮度是50%; 打开0.6秒,关闭0.4秒,亮度是60%…打开之后不关闭,亮度就是100%了。PWM也是这个原理,通过调整LED背光的占空比来输出不同的亮度。当然调整的屏幕比我们手动的快多了,据说最低是200Hz。
我们家里的冷光灯频率一般是60Hz,肉眼已经感觉不到闪烁了,按理说200Hz已经超越眼睛感受的极限了,应该不会感到难受才对。但是LED有个非常糟糕的优点:极快速的开关响应。也就是说,在关闭状态下瞬间关闭,在开启状态下瞬间开启,这就导致LED背光给肉眼的感受还是闪烁的。而冷光灯上面的荧光涂层具有光线残留的特点,在关闭的情况下还能保留一段时间,所以肉眼感受起来屏幕一直是亮的,不会有闪烁感。
要解决LED背光闪烁的最后途径就是加快PWM频率,200Hz太闪,2000Hz就没什么感觉了。但是加快频率之后有导致了另外一个问题:亮度增加!相对于传统的CCFL背光来说,LED的亮度简直会亮瞎眼睛!
如果有办法在增加频率的情况下保持低亮度就好了!
解决LED背光闪烁
原理
总结一下就是两点:增加频率,降低亮度。因为X250用的是Intel的显卡,有个寄存器(0xc8254
)可以调节PWM的频率。安装intel_gpu_tools之后就可以读写这个寄存器了。这里(Eliminate LED screen flicker with Intel i915),比如我就设置成1500HZ,闪烁问题立马解决!
1 | sudo intel_reg write 0xC8254 0x035400b4 |
上图:在高背光频率下,铅笔快速晃动的图像比较平滑,无残影
增加频率虽然解决了闪烁,但这个亮度却足以亮瞎眼,所以还要想办法降低亮度。我一度认为降低亮度需要通过硬件来降低电压控制,因为通过键盘上的背光按键即使调到最小值亮度还是非常高!后来在 Archlinux上看到亮度其实是可以比按键调的更小的(后来知道系统限制按键最小只能调到48,可以通过udev规则控制),试了下写入1:
1 | sudo tee /sys/class/backlight/intel_backlight/brightness <<< 1 |
奇迹出现了,在保持高PWM屏幕的情况下,亮度降到了一个可用(准确的说应该是眼睛比较舒服)的级别!
自动化脚本
既然已经实现了调高LED背光屏幕,并且保持低亮度,我么还想要一个脚本来自动设置,这样就可以系统的自动背光调节和按键背光控制了。不然LED的亮度是定死的,调整非常不方便。
经过一段时间的折腾,克服了好几个大坑之后,终于完成了一个初级版本,可以自动设置PWM频率、恢复亮度和增加、降低亮度等功能。点此下载:
1 | #!/bin/sh |
脚本调用很简单,smartbl.sh [参数名]
就可以了,无参数自动恢复亮度。 参数列表:
init
初始化,恢复上次的频率和亮度reset
恢复背光频率到出厂设置freq
获取当前频率inc
增加亮度dec
降低亮度min
调整到最低亮度max
调整到最高亮度set [val]
调整到指定亮度fix
调成到默认亮度get
获取当前亮度和频率
**注意:**脚本支持在xog 和console窗口下调整亮度。
取代系统的背光控制系统
脚本调试完毕之后,我们就可以用它来取代系统的亮调节了。系统的亮度调节系统会在开机、休眠恢复、背光按键按下、插拔外接显示器等事件时触发,我们需要将这些事件都关联到我们的脚本,这样就可以取代系统的背光控制了。
禁用系统自带的亮度调节
Archlinux 的背光服务为[email protected]
,它会在开机的时候自动恢复上次的亮度,我们需要将它禁用掉:
1 | service mask systemd-[email protected]backlight:intel_backlight.service |
注意:
取决于你的硬件平台,非Intel显卡的系统并不是backlight:intel_backlight.service
,有可能是backlight:acpi_video0.service
等。
udev规则
我们需要为背光设置一个udev规则,主要有两个作用
- 取消系统对最低亮度的限制
- 背光相关事件(如插拔投影仪)发生时自动设置频率和背光亮度
添加一个ude规则:/etc/udev/rules.d/91-backlight.rules
,内容如下:
1 | KERNEL=="intel_backlight", SUBSYSTEM=="backlight", ENV{ID_BACKLIGHT_CLAMP}="0", RUN+="/usr/bin/sh -c '/opt/bin/for_acpi/smartbl.sh >> /tmp/smartbl.log'" |
设置完成之后运行sudo udevadm control --reload-rules
重新载入所有规则,然后检查一下这条规则是否有用,运行udevadm test /sys/class/backlight/intel_backlight
,输出结果必须包含运行这个脚本的信息:
1 | ... |
注意:
- 需要根据系统来设置
KERNEL
等字段,运行udevadm info -a -p /sys/class/backlight/intel_backlight
查看这些信息,大致输出类此:1
2
3
4
5
6
7
8
9
10
11...
looking at device '/devices/pci0000:00/0000:00:02.0/drm/card0/card0-eDP-1/intel_backlight':
KERNEL=="intel_backlight"
SUBSYSTEM=="backlight"
DRIVER==""
ATTR{actual_brightness}=="7"
ATTR{bl_power}=="0"
ATTR{brightness}=="7"
ATTR{max_brightness}=="852"
ATTR{type}=="raw"
... ENV{ID_BACKLIGHT_CLAMP}="0"
的意思是允许讲最低亮度设置为0(关闭显示器),如果不设置的话每次开机亮度最低设置为28(我的机器),在脚本里面设置为1也没用!- udev不能运行脚本,只能运行一条命令!所以只能把脚本作为参数传入,命令必须使用完整路径,脚本里面的每条命令也需要完整路径!
自启动设置
开始的时候自动设置频率和恢复亮度,添加一个自启动服务即可:/etc/systemd/system/backlight_restoreb.service
,内容如下:
1 | [Unit] |
编辑完成之后运行 sudo systemd enable backlight_restoreb.service
启用即可。
休眠恢复
休眠恢复的服务和开机的差不多,只不过是事件不同,/etc/systemd/system/backlight_restores.service
:
1 | [Unit] |
编辑完成之后同样运行 sudo systemd enable backlight_restores.service
启用服务。这时候可以让电脑休眠,测试一下从休眠状态下恢复之后亮度有没有恢复。
外接显示器断开
外接显示器连接或者断开时(如仅使用第二屏幕之后再断开就会出发背光重置),也有可能出发背光更改事件,这个通过上面的udev规则调用脚本就handle掉了,这里不需要其他处理。
注意:
需要说明的是开机也是会触发udev规则的,但是udev在开机状态下对背光的设置无效,可能是udev规则先于系统的背光设置吧。
背光调整按键
Archlinux的背光按键默认被Power Manager
handle掉,没按一下都有一个巨大的step(大概40),在那么搞得频率下其实五六十的最高亮度就已经很高了,一次按键就可以达到最高亮度!我们可以通过讲按键映射到我们的脚本讲step设为2就可以了,这样背光级数就比较高。
首先取消Power Manager
对按键的Handle:
然后我们需要将按键映射到我们的脚本就行了,我们需要借助acpid 的event handle来处理,编辑/etc/acpi/handler.sh
文件,在最顶上加上亮度加减按键的处理:
1 | case "$1" in |
注意:
- 如果不知道按键名称,可以运行
acpi_listen
命令,然后按下按键查看输出的按键名称。 - 亮度调节按键似乎不受
xmodemap
的控制,在xmodmap
里更改按键映射似乎无效。 - 如果更改
Power Manager
之后亮度调节按键仍然被系统handle掉,可以直接编辑/usr/share/X11/xkb/symbols/inet
这个文件,将所有具有XF86MonBrightnessDown
和XF86MonBrightnessUp
关键字的行注释掉,然后重启。
写在后面
到此终于完美解决了LED背光闪烁的问题,用起来明显感觉眼睛比以前舒服。目前的大部分显示器都有LED背光闪烁的问题,虽然目前没有科学证明其对眼睛的伤害,但是从个人感觉上来说伤害还是有的,特别是对于长时间使用电脑的人来说,还是CCFL背光的显示器比较合适。可惜市场上CCFL背光的显示器已经很少了,LED技术带我们回到了大屁股显示器的闪烁时代,这也和那些厂商急于追逐利益又关系吧,据说明基掌握LED了不闪屏的黑科技,希望这种技术能尽快普及。
参考资料
- Archlinux Backlight: ACPI, Backlight PWM modulation frequency, udev rule
- Eliminate LED screen flicker with Intel i915, 背光频率计算
- Preventing backlight function keys from changing brightness(禁止功能键调整背光)
- intel_backlight service does not set backlight at boot(解决背光不可以设置一个较小的值)
- The Latest on Computer Screens and Eye Fatigue
- 液晶屏没有闪烁吗?小议液晶屏是如何伤害你的视力的, 果壳
彻底解决Linux下LED的背光闪烁
https://www.shuyz.com/posts/fix-led-backlight-flicker-on-linux/