背景#
最近想要给一个嵌入式项目寻找一块 5 寸左右、1080P 分辨率且带触摸的 MIPI 屏幕。在淘宝搜索一圈,发现一款比较合适且便宜的 5.5 寸屏幕模组大约 95 元一片。为了进一步压缩成本,就开始琢磨有没有再便宜一点的方案,于是开始思考能否利用手机的屏幕。在全面屏时代之前,5.5 英寸、1080P 是智能手机的主流配置,应当有相当多的选择,由于存量大,屏幕模组价格可能也具有优势。长久以来都看着旧手机的屏幕眼馋,这些屏幕虽然来自过时的设备,但显示素质往往远超一般的嵌入式屏幕模块,要是能利用起来就好了。
不过也不是每个手机的屏幕都能轻易拿来使用,点亮手机屏幕的核心障碍在于信息缺失。不像单独出售的成品屏幕模组一般会直接提供规格书和初始化资料,手机屏幕往往没有公开的软硬件资料。要驱动手机屏幕,必须解决两大难题:
一是硬件上需要知道屏幕连接器的型号和引脚定义,才能正确连接电源和信号。虽然理论上可以从显示控制 IC 密密麻麻的引脚一点一点追踪线路到连接器,有大佬能够这么操作,但工程量相当浩大。更便捷的途径是获取手机的维修原理图,得益于庞大的手机维修市场,许多机型的原理图都可以在网络上或一些维修工具上找到。从原理图中可以直接查到屏幕连接器的接口定义,甚至连接器型号。
另一方面是软件上需要知道屏幕的初始化序列与时序。这个一般可以从手机的固件中提取,对于高通平台的设备,可以在设备树中获得时序和初始化序列(可看云犀 Box 文章的设备树提取部分)。这个流程不需要真的拥有一个设备,只要有刷机包一般便可以进行提取。而有一部分特殊的设备,受到开源社区的支持,如比较活跃的项目 postmarketOS。对于这样的设备,我们可以直接从开源代码仓库中获取所需屏幕信息。
所以我决定找软柿子下手,看看是否能找到一款拥有合适屏幕,并且同时满足硬件有图纸、软件有源码的设备。经过在 postmarketOS 的支持设备列表中寻找,最终锁定了小米 5X (xiaomi-tissot) 和红米 5Plus (xiaomi-vince),它们的屏幕模组在淘宝的售价已经低到了 40 元附近。我也对两款屏幕做了对比:
小米 5X 的屏幕为 5.5 英寸,1080x1920 (16:9),403 PPI,68% NTSC 色域。其模组采用的是 GFF 全贴合,使用光学胶将触控层向上层玻璃盖板进行贴合,工艺简单但厚度厚且视觉效果稍差,不点亮屏幕时也觉得黑得不纯粹。
红米 5Plus 的屏幕为 5.99 英寸,1080x2160 (18:9),403 PPI,84% NTSC 色域。同样的屏幕还被用在小米 6X、红米 5Plus 上。其模组采用了 Incell 全贴合工艺,触控单元向下和 LCM 融合了,模组更薄且视觉效果很通透。
由于我根本不需要安卓的三个导航键,以差不多的模组尺寸换取更大的显示面积并获得一定程度的 “全面屏” 效果是十分诱人的,红米 5Plus 无疑是更优的选择。但我计划使用的 RV1126 SoC 在数据手册中标称支持的最高分辨率为 1920x1080,能否驱动 2160x1080 的屏幕存在疑问。因此我购买了两种屏幕,计划都进行测试,若红米 5Plus 的屏幕不能完美兼容,则只能退而求其次选择小米 5X 的屏幕作为保底。
硬件#
分析原理图后发现,小米 5X(上)与红米 5Plus(下)的屏幕接口定义十分接近,但是所用连接器则不同。小米 5X 使用的为 BM23PF0.8-40DS-0.35V (880),引脚间距 0.35mm;红米 5Plus 使用的则为 BM20B (0.8)-40DS-0.4V (51) ,间距 0.4mm,两者引脚数相同但间距不同,互不兼容。
根据原理图,我制作了一块转接板,其一方面做信号转接,将屏幕的 MIPI-DSI 信号、I2C 信号及其他控制 IO,转接至与易百纳 EB-RV1126-DC-201 开发板相匹配的排座上。另一方面板载所需的电源电路,利用开发板提供的 3.3V 电源,生成背光所需的高电压、LCD 所需的正负电压,以及开发板未直接提供的 1.8V IO 电压
转接板焊接完毕,两块屏幕接好转接板,至此硬件就准备就绪,可以开始在软件上操作点屏幕了。
软件#
Rockchip 提供的 Rockchip_DRM_Panel_Porting_Guide 说明了点 MIPI-DSI 屏幕的一般操作。它利用了 Linux 内核中的 panel-simple-dsi
驱动,对于一般的屏幕,可以免去单独添加驱动的麻烦,仅通过修改设备树就可异完成驱动。
配置 panel-simple-dsi
需要提供两大核心信息:
- 初始化序列 (panel-init-sequence):包含了一系列初始化命令,其每条命令的规则为:每条命令由头部和数据负载 (Payload) 构成,都以 16 进制表示。头部包含 3 个字节,分别定义了数据类型 (Data Type)、发送后延时 (Delay, ms) 和负载长度 (Payload Length)。从第四个字节开始的数据代表长度为 Payload Length 的负载。其中的 Data Type 有这些种类:
- 0x05: DCS Short WRITE. no parameters;
- 0x15: DCS Short WRITE.1 parameter;
- 0x39: DCS Long Write/write LUT Command ;
- 0x03: Generic Short WRlTE, no parameters;
- 0x13: Generic Short WRITE.1 parameter;
- 0x23: Generic Short WRITE,2 parameters;
- 0x29: Generic Long Write;
- 显示时序 (display-timings):定义了屏幕刷新过程中的各项时间参数,需要完整知道下面的每个部分
- Htotal = Hactive + Hfront-porch+ HSync + Hback-porch
- Vtotal = Vactive + Vfront-porch + VSync + Vback-porch
- pixel-clock = Htotal × Vtotal × 帧率
对于有较好社区支持的小米 5X 和红米 5Plus,可以从 msm8953-mainline
项目的 lk2nd 设备树中找到屏幕兼容的驱动名称,内核代码中找到具体的屏幕驱动。
例如,对于小米 5X,可以在lk2nd/dts/msm8953-xiaomi-vince.dts at main · msm8953-mainline/lk2nd这个设备树里找到使用的屏幕和对应兼容驱动。实际上像下面所列,同一型号手机可能涉及多种供应商的屏幕,屏幕驱动 IC 和触摸方案可能都不相同,因此有多个条目。正常来讲,需要根据屏幕模组的 LCD_ID 引脚,或者通过 MIPI-DSI 读特定寄存器来判断屏幕方案,进行不同的初始化。但本次仅作一块屏幕的测试,就选择了最简单人工的逐一尝试。
panel {
compatible = "xiaomi,vince-panel";
qcom,mdss_dsi_td4310_fhdplus_video_e7 {
compatible = "xiaomi,td4310-fhdplus-e7";
touchscreen-compatible = "syna,rmi4-i2c";
// touchscreen-compatible = "novatek,nt36525-i2c";
};
// ...
qcom,mdss_dsi_nt36672_csot_fhdplus_video_e7 {
compatible = "xiaomi,nt36672-csot-fhdplus-e7";
touchscreen-compatible = "syna,rmi4-i2c";
// touchscreen-compatible = "novatek,nt36525-i2c";
};
};
以其中一款 xiaomi,nt36672-tianma-fhdplus-e7
为例,对应驱动代码则在linux/drivers/gpu/drm/panel/msm8953-generated/panel-xiaomi-nt36672-tianma-fhdplus-e7.c at 6.12/main · msm8953-mainline/linux,要做的就是提取代码中的信息,按正确格式填入 panel-simple-dsi
对应的设备树节点内。填写完成后长这样:
&dsi {
status = "okay";
rockchip,lane-rate = <1000>;
dsi_panel: panel@0 {
status = "okay";
compatible = "simple-panel-dsi";
reg = <0>;
reset-gpios = <&gpio0 RK_PA2 GPIO_ACTIVE_LOW>;
pinctrl-names = "default";
pinctrl-0 = <&dsi_rst_gpio>;
backlight = <&backlight>;
reset-delay-ms = <200>;
enable-delay-ms = <100>;
prepare-delay-ms = <20>;
unprepare-delay-ms = <20>;
disable-delay-ms = <20>;
init-delay-ms = <120>;
dsi,flags = <(MIPI_DSI_MODE_VIDEO |MIPI_DSI_MODE_VIDEO_BURST |MIPI_DSI_MODE_LPM |MIPI_DSI_MODE_EOT_PACKET)>;
dsi,format = <MIPI_DSI_FMT_RGB888>;
dsi,lanes = <4>;
/* Based on panel-xiaomi-nt36672-tianma-fhdplus-e7.c/nt36672_tianmaplus_e7_on() function */
panel-init-sequence = [
// DCS Short Write
05 78 01 11
// Generic Short Write
13 00 02 B0 04
// Generic Short Write
13 00 02 D6 01
// Generic Long Write
29 00 27 C7 00 19 28 3b 4a 55 6d 7d 8a 96 48 54 62 76 7f 8b 99 a4 b2 00 19 28 3b 4a 55 6d 7d 8a 96 48 54 62 76 7f 8b 99 a4 b2
// Generic Long Write
29 00 38 C8 03 00 01 03 ff fe 00 00 fe 01 fd f7 00 00 01 ff fb f2 00 00 01 03 01 ec 00 00 fe 01 fd f5 00 00 01 fe fa fe 00 00 01 03 ff fe 00 00 fe 01 fd ec 00 00 fe 01 fb d3 00
// DCS Long Write
39 00 03 51 ff 00
// DCS Short Write
15 00 02 53 24
// DCS Short Write
15 00 02 55 00
//DCS Short Write
15 00 02 35 00
// DCS Short Write
05 14 01 29
];
/* panel-exit-sequence */
panel-exit-sequence = [
05 14 01 28
05 78 01 10
];
disp_timings1: display-timings {
native-mode = <&dsi_timing0>;
dsi_timing0: timing0 {
clock-frequency = <148500000>;// (480+50+60+10)*(800+20+34+2)*60
hactive = <1080>;
hfront-porch = <108>;
hback-porch = <12>;
hsync-len = <60>;
vactive = <1920>;
vfront-porch = <166>;
vback-porch = <84>;
vsync-len = <33>;
hsync-active = <0>;
vsync-active = <0>;
de-active = <0>;
pixelclk-active = <0>;
swap-rb = <0>;
swap-rg = <0>;
swap-gb = <0>;
};
};
// ...
};
此外,背光升压 IC 需要一个 PWM 信号作为使能和调光控制。硬件上已经连接好,只需多在设备树中配置一个 pwm-backlight
通用驱动节点,上面的 dsi 节点也已经引用了这个背光节点。
backlight: backlight {
compatible = "pwm-backlight";
brightness-levels = <0 1 2 3 ... 255>;
default-brightness-level = <200>;
pwms = <&pwm7 0 25000 0>; // PWM7, 25kHz
};
测试#
烧录好带新设备树的内核镜像,进入终端之后,可以使用modetest -M rockchip -s 83@69:1080x1920
进行测试。
点屏幕最怕一片漆黑,毕竟软件没写对、硬件没接对都会导向同样的一片漆黑。很不幸,初次测试时尝试了多种初始化序列,屏幕还是一片漆黑,毫无反应。Rockchip_DRM_Panel_Porting_Guide 也提供了一种排查方法,可以通过 MIPI-DSI 的读操作来判断通信链路是否正常。将读命令添加到 panel-simple 驱动当中,即可在启动时观察读取是否报错和 mode 是否有变化。测试发现只有持续用力按压排线到 PCB 的排座上时才能正确读取,原来是焊接不牢的低级错误。这类排座连接器间距密,引脚焊盘非常小,可能也有焊盘和阻焊开窗偏小的原因,导致上锡都比较困难,反复尝试才勉强焊接牢靠。排除此问题后,屏幕便非常顺利地直接点亮了。
最终效果#
小米 5X 的屏幕可以正常显示,测试画面和播放视频都正常,达到了预期效果。
不出意外,红米 5Plus 的屏幕由于 RV1126 的限制不能完美显示。若在设备树中直接配置 vactive
到 2160,驱动在 probe
阶段有长宽与硬件最大能力进行比较的逻辑,超过最大能力 1920 便导致无法初始化。即使移除该判断逻辑,绕过检查强行初始化,则显示内容完全错乱。一种勉强可用的方法是将 vactive
缩小到硬件支持的 1920,并把多余 240 放到时序其他部分。这样,屏幕可以正常显示 1080x1920 的区域,但有 1080x240 无法正常显示。
结语#
通过这次经历可以发现,点亮手机屏幕有时也并不困难,前提是有软硬件资料的支撑(无论是网络分享的还是自己逆向的)。在手机屏幕基本都使用通用 MIPI-DSI 协议的情况下,绝大多数拥有维修原理图和开放固件的手机屏幕,都具备被二次利用的巨大潜力,用于业余的嵌入式项目是可行且经济的方案。
一个有趣的例子是手机维修行业的 “万能 “屏幕测试仪,靠更换不同的排线和配置可以兼容海量移动设备屏幕的测试。比较好奇的是他们是如何获取大量设备的时序和初始化序列?是搞清楚驱动 IC 型号然后套用公版数据,还是通过逻辑分析仪抓取并逆向 DSI 的低速初始化序列呢。
最后,本次使用的屏幕毕竟是来自近 8 年前的产品,如今的手机屏幕已有长足进步,即使还是像红米 Note 11T Pro 这样的 LCD 屏,也屏占比更高,有高刷,色域更广,更不用说更强的 OLED 了,希望之后能挑战一些更新款手机的屏幕。此外,另一个值得我研究的是如何在嵌入式 Linux 环境下,利用 ArgyllCMS 等开源工具对屏幕进行校色,从而在项目中也能实现一定程度的专业色彩管理,使呈现在这些屏幕上的内容也能够色彩准确。