嵌入式驱动开发详解12(LCD驱动)
前言
LCD 是现在最常用到的显示器,手机、 电脑、各种人机交互设备等基本都用到了 LCD,最常见就是手机和电脑显示器了。通过 LCD 可以显示绚丽的图形、界面等,提高人机交互的效率。
LCD
LCD 的构造是在两片平行的玻璃基板当中放置液晶盒,下基板玻璃上设置 TFT(薄膜晶体 管),上基板玻璃上设置彩色滤光片,通过 TFT 上的信号与电压改变来控制液晶分子的转动方 向,从而达到控制每个像素点偏振光出射与否而达到显示目的。
LCD 简介
-
分辨率:提起 LCD 显示器,我们都会听到 720P、1080P、2K 或 4K 这样的字眼,这个就是 LCD 显 示器分辨率。LCD 显示器都是由一个一个的像素点组成,像素点就类似一个灯(在 OLED 显示器中,像素点就是一个小灯),这个小灯是 RGB 灯,也就是由 R(红色)、G(绿色)和 B(蓝色)这三 种颜色组成的,而 RGB 就是光的三原色。1080P 的意思就是一个 LCD 屏幕上的像素数量是 1920*1080 个,也就是这个屏幕一列 1080 个像素点,一共 1920 列。
但是并不是分辨率越高的 LCD 就 越好。衡量一款 LCD 的好坏,分辨率只是其中的一个参数,还有色彩还原程度、色彩偏离、亮 度、可视角度、屏幕刷新率等其他参数。
-
像素格式:一个像素点就相当于一个 RGB 小灯,通过控制 R、G、B 这三种颜色的亮度就 可以显示出各种各样的色彩。一般一个 R、 G、B 这三部分分别使用 8bit 的数据,那么一个像素点就是 8bit*3=24bit,也就是说一个像素点 3 个字节,这种像素格式称为 RGB888。如果再加入 8bit 的 Alpha(透明)通道的话一个像素点就是 32bit,也就是 4 个字节,这种像素格式称为 ARGB8888。如果学习过 STM32 的话应该还听 过 RGB565 这种像素格式。
-
屏幕接口:LCD 屏幕或者说显示器有很多种接口,比如在显示器上常见的 VGA、HDMI、DP 等等,
R[7:0]、G[7:0]和 B[7:0]这 24 根是数据线,DE、VSYNC、 HSYNC 和 PCLK 这四根是控制信号线。RGB LCD 一般有两种驱动模式:DE 模式和 HV 模式, 这两个模式的区别是 DE 模式需要用到 DE 信号线,而 HV 模式不需要用到 DE 信号线,在 DE 模式下是可以不需要 HSYNC 信号线的,即使不接 HSYNC 信号线 LCD 也可以正常工作。
以 ATK-7016 这款屏幕为例讲解, ATK-7016 (7 寸,1024*600)的屏幕接口原理图如图:
右侧的几个电阻,并不是都焊接的, 而是可以用户自己选择。默认情况,R1 和 R6 焊接,设置 LCD_LR 和 LCD_UD,控制 LCD 的 扫描方向,是从左到右,从上到下(横屏看)。而 LCD_R7/G7/B7 则用来设置 LCD 的 ID,由于 RGBLCD 没有读写寄存器,也就没有所谓的 ID,这里我们通过在模块上面,控制 R7/G7/B7 的 上/下拉,来自定义 LCD 模块的 ID,帮助 MCU 判断当前 LCD 面板的分辨率和相关参数,以提 高程序兼容性。其中LCD_BL是控制背光灯的,TP开头的是控制触摸屏的。
LCD屏幕时序
-
LCD时间参数:。HSYNC 是水平同步信号,也叫做行同步信号,当产生此信号的话就表示开始显示新的 一行了,所以此信号都是在图的最左边。当 VSYNC 信号是垂直同步信号,也叫做帧同步信号,当产生此信号的话就表示开始显示新的一帧图像了,所以此信号在图的左上角。
以看到有一圈“黑边”,真正有效的显示区域是中间的白色部分。RGB LCD 屏幕内部是有一个 IC 的,发送一行或者一帧数据给 IC,IC 是需要反应时间的。通过这段反应 时间可以让 IC 识别到一行数据扫描完了,要换行了,或者一帧图像扫描完了,要开始下一帧图 像显示了。因此,在 LCD 屏幕中继续存在 HBP、HFP、VPB 和 VFP 这四个参数的主要目的是 为了锁定有效的像素数据。 当显示完一行以后会发出 HSYNC 信号,此时电子枪就会关闭,然后迅速的移动到屏幕的左边,当 HSYNC 信号结束以后就可以显示新的一行数据了,电子枪就会重新打开。在 HSYNC 信号结束到电子枪重新打开之间会插入一段延时,这段延时就图中的 HBP。当显示完一行以后就会关闭电子枪等待 HSYNC 信号产生,关闭电子枪到 HSYNC 信号产生之间会插入一段延时,这段延时就是图中的 HFP 信号。同理,当显示完一帧图像以后电子枪也会关闭,然后等到 VSYNC 信号产生,期间也会加入一段延时,这段延时就是图中的 VFP。 VSYNC 信号产生,电子枪移动到左上角,当 VSYNC 信号结束以后电子枪重新打开,中间也会加入一段延时,这段延时就是图中的 VBP。 -
LCD屏幕时序:
上面讲了行显示和帧显示,我们来看一下行显示对应的时序图
HSYNC:行同步信号,当此信号有效的话就表示开始显示新的一行数据;HSPW:有些地方也叫做 thp,是 HSYNC 信号宽度,也就是 HSYNC 信号持续时间。HSYNC 信号不是一个脉冲,而是需要持续一段时间才是有效的;HBP和HFP:前面已经说了;HOZVAL:有些地方叫做 thd,显示一行数据所需的时间,假如屏幕分辨率为 1024*600, 那么 HOZVAL 就是 1024;所以显示一行所需要的时间就是:HSPW + HBP + HOZVAL + HFP。一帧图像就是由很多个行组成的,RGB LCD 的帧显示时序如图
以上单位都是1行的时间,因此行显示一致,显示一帧所需要的时间就是:VSPW+VBP+LINE+VFP 个行时间。因此显示一帧数据所需要的时间为:T = (VSPW+VBP+LINE+VFP) * (HSPW + HBP + HOZVAL + HFP)
单片机eLCDIF接口
eLCDIF 是 I.MX6U 自带的液晶屏幕接口,用于连接 RGB LCD 接口的屏幕
①、支持 RGB LCD 的 DE 模式。
②、支持 VSYNC 模式以实现高速数据传输。
③、支持 ITU-R BT.656 格式的 4:2:2 的 YCbCr 数字视频,并且将其转换为模拟 TV 信号。
④、支持 8/16/18/24/32 位 LCD。
具体的逻辑配置不做详细描述,需要裸机驱动的话可以参考芯片手册。
Linux 下 LCD 驱动简析
Framebuffer 设备
裸机的时候 LCD 驱动是怎么编写的,裸机 LCD 驱动编写流程如下:
①、初始化 I.MX6U 的 eLCDIF 控制器,重点是 LCD 屏幕宽(width)、高(height)、hspw、 hbp、hfp、vspw、vbp 和 vfp 等信息。
②、初始化 LCD 像素时钟。
③、设置 RGBLCD 显存。
④、应用程序直接通过操作显存来操作 LCD,实现在 LCD 上显示字符、图片等信息。
在 Linux 中应用程序最终也是通过操作 RGB LCD 的显存来实现在 LCD 上显示字符、图片 等信息。在裸机中我们可以随意的分配显存,但是在 Linux 系统中内存的管理很严格,显存是 需要申请的,不是你想用就能用的。而且因为虚拟内存的存在,驱动程序设置的显存和应用程 序访问的显存要是同一片物理内存。 为了解决上述问题,Framebuffer 诞生了, Framebuffer 翻译过来就是帧缓冲,简称 fb,因 此大家在以后的 Linux 学习中见到“Framebuffer”或者“fb”的话第一反应应该想到 RGBLCD 或者显示设备。fb 是一种机制,将系统中所有跟显示有关的硬件以及软件集合起来,虚拟出一 个 fb 设备,当我们编写好 LCD 驱动以后会生成一个名为/dev/fbX(X=0~n)的设备,应用程序通 过访问/dev/fbX 这个设备就可以访问 LCD。关于 fb 的详细处理过程就不去深究了,我们的重点是驱动 LCD。
设备树配置
-
NXP 官方的设备树已经添加了 LCD 设备节点,只是此节点的 LCD 屏幕信息是针对 NXP 官方 EVK 开发板所使用的 4.3 寸 480*272 编写的,我们需要将其改为我们所使用的屏幕参数。
-
我们简单看一下 NXP 官方编写的 Linux 下的 LCD 驱动,打开 imx6ull.dtsi,然后找到 lcdif 节点内容,可以看出 lcdif 节点 的 compatible 属性值为“fsl,imx6ul-lcdif”和“ fsl,imx28-lcdif”,因此在 Linux 源码中搜索这两个 字符串即可找到 I.MX6ULL 的 LCD 驱动文件,这个文件为 drivers/video/fbdev/mxsfb.c。
-
可以看出该文件是一个标准的 platform 驱动,当驱动和设备匹配以后 mxsfb_probe 函数就会执行 。
-
Linux 内核将所有的 Framebuffer 抽象为一个叫做 fb_info 的结构 体,fb_info 结构体包含了 Framebuffer 设备的完整属性和操作集合,因此每一个 Framebuffer 设 备都必须有一个 fb_info。换言之就是,LCD 的驱动就是构建 fb_info,并且向系统注册 fb_info 的过程。
-
mxsfb_probe 函数实现非常负责,如果不从事LCD驱动相关工作的话没必要了解那么深入,这里我们就简单了解 一下 Linux 下 Framebuffer 驱动的框架。
-
设备树配置
pinctrl_lcdif_dat: lcdifdatgrp { fsl,pins = < MX6UL_PAD_LCD_DATA00__LCDIF_DATA00 0x49 MX6UL_PAD_LCD_DATA01__LCDIF_DATA01 0x49 MX6UL_PAD_LCD_DATA02__LCDIF_DATA02 0x49 MX6UL_PAD_LCD_DATA03__LCDIF_DATA03 0x49 MX6UL_PAD_LCD_DATA04__LCDIF_DATA04 0x49 MX6UL_PAD_LCD_DATA05__LCDIF_DATA05 0x49 MX6UL_PAD_LCD_DATA06__LCDIF_DATA06 0x49 MX6UL_PAD_LCD_DATA07__LCDIF_DATA07 0x49 MX6UL_PAD_LCD_DATA08__LCDIF_DATA08 0x49 MX6UL_PAD_LCD_DATA09__LCDIF_DATA09 0x49 MX6UL_PAD_LCD_DATA10__LCDIF_DATA10 0x49 MX6UL_PAD_LCD_DATA11__LCDIF_DATA11 0x49 MX6UL_PAD_LCD_DATA12__LCDIF_DATA12 0x49 MX6UL_PAD_LCD_DATA13__LCDIF_DATA13 0x49 MX6UL_PAD_LCD_DATA14__LCDIF_DATA14 0x49 MX6UL_PAD_LCD_DATA15__LCDIF_DATA15 0x49 MX6UL_PAD_LCD_DATA16__LCDIF_DATA16 0x49 MX6UL_PAD_LCD_DATA17__LCDIF_DATA17 0x49 MX6UL_PAD_LCD_DATA18__LCDIF_DATA18 0x49 MX6UL_PAD_LCD_DATA19__LCDIF_DATA19 0x49 MX6UL_PAD_LCD_DATA20__LCDIF_DATA20 0x49 MX6UL_PAD_LCD_DATA21__LCDIF_DATA21 0x49 MX6UL_PAD_LCD_DATA22__LCDIF_DATA22 0x49 MX6UL_PAD_LCD_DATA23__LCDIF_DATA23 0x49 >; }; pinctrl_lcdif_ctrl: lcdifctrlgrp { fsl,pins = < MX6UL_PAD_LCD_CLK__LCDIF_CLK 0x49 MX6UL_PAD_LCD_ENABLE__LCDIF_ENABLE 0x49 MX6UL_PAD_LCD_HSYNC__LCDIF_HSYNC 0x49 MX6UL_PAD_LCD_VSYNC__LCDIF_VSYNC 0x49 >; }; pinctrl_pwm1: pwm1grp { fsl,pins = < MX6UL_PAD_GPIO1_IO08__PWM1_OUT 0x110b0 >; };
&lcdif { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_lcdif_dat &pinctrl_lcdif_ctrl>; /* &pinctrl_lcdif_reset>; */ display = <&display0>; status = "okay"; display0: display { bits-per-pixel = <24>; bus-width = <24>; display-timings { native-mode = <&timing0>; timing0: timing0 { clock-frequency = <51200000>; hactive = <1024>; vactive = <600>; hfront-porch = <160>; hback-porch = <140>; hsync-len = <20>; vback-porch = <20>; vfront-porch = <12>; vsync-len = <3>; hsync-active = <0>; vsync-active = <0>; de-active = <1>; pixelclk-active = <0>; }; }; }; };
&pwm1 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_pwm1>; status = "okay"; }; backlight { compatible = "pwm-backlight"; pwms = <&pwm1 0 5000000>; brightness-levels = <0 4 8 16 32 64 128 255>; default-brightness-level = <6>; status = "okay"; };
以上便是对LCD设备树的展示,需要配置好LCD所需要用到的数据引脚,控制引脚以及背光引脚,随后需要对lcd屏幕的相关参数进行设置,并配置好背光引脚,但是linux系统并不知道pwm1_out就是对应LCD的背光引脚,这里我们需要参考设备树绑定文档Documentation/devicetree/indings/video/backlight/pwm-backlight.txt 这个文档,此文档详细讲解了 backlight 节点该如何去创建。
LCD相关系统设置
- 将前面设备树小节所修改的文件编译生成新的设备树文件,
- Linux 内核启动的时候可以选择显示小企鹅 logo,只要这个小企鹅 logo 显示没问题那么我 们的 LCD 驱动基本就工作正常了,我们可以通过图形化界面使能 Linux logo 显示。
- 设置 LCD 作为终端控制台
重启开发板,进入 Linux 命令行,重新设置 bootargs 参数的 console 内容,命令如下所示:
打开开发板根文件系统中的/etc/inittab 文件,在里面加入下面这一行:setenv bootargs 'console=tty1 console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.1.250: /home/zuozhongkai/linux/nfs/rootfs ip=192.168.1.251:192.168.1.250:192.168.1.1:255.255.255.0::eth0: off'
修改完成后重启开发板即可。tty1::askfirst:-/bin/sh
后续
本文对LCD驱动进行了基本的讲解,主要是讲解了LCD的基本工作原理,以及如何修改LCD的设备树使其适应不同的LCD,对LCD的framebuffer没有深入讲解,需要深入研究的时候会继续更新此文。
参考文献
- 个人专栏系列文章
- 正点原子嵌入式驱动开发指南
- 对代码有兴趣的同学可以查看链接https://github.com/NUAATRY/imx6ull_dev
原文地址:https://blog.csdn.net/weixin_63699001/article/details/144231125
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!