嵌入式驱动开发详解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 驱动的框架。

  • 设备树配置

    c 复制代码
    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
    	>;
    };
    c 复制代码
    &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>;
    			};
    		};
    	};
    };
    c 复制代码
    &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相关系统设置

  1. 将前面设备树小节所修改的文件编译生成新的设备树文件,

  2. Linux 内核启动的时候可以选择显示小企鹅 logo,只要这个小企鹅 logo 显示没问题那么我 们的 LCD 驱动基本就工作正常了,我们可以通过图形化界面使能 Linux logo 显示。

  3. 设置 LCD 作为终端控制台
    重启开发板,进入 Linux 命令行,重新设置 bootargs 参数的 console 内容,命令如下所示:

    c 复制代码
    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'

    打开开发板根文件系统中的/etc/inittab 文件,在里面加入下面这一行:

    c 复制代码
    tty1::askfirst:-/bin/sh

    修改完成后重启开发板即可。

后续

本文对LCD驱动进行了基本的讲解,主要是讲解了LCD的基本工作原理,以及如何修改LCD的设备树使其适应不同的LCD,对LCD的framebuffer没有深入讲解,需要深入研究的时候会继续更新此文。

参考文献

  1. 个人专栏系列文章
  2. 正点原子嵌入式驱动开发指南
  3. 对代码有兴趣的同学可以查看链接https://github.com/NUAATRY/imx6ull_dev
相关推荐
Tester_孙大壮33 分钟前
第17章:Python TDD回顾与总结货币类开发
驱动开发
Tester_孙大壮1 小时前
第15章:Python TDD应对货币类开发变化(二)
驱动开发
Tester_孙大壮4 小时前
第12章:Python TDD完善货币加法运算(一)
驱动开发
Tester_孙大壮5 小时前
第10章:Python TDD优化货币类方法与引入工厂方法
驱动开发
sukalot6 小时前
Windows蓝牙驱动开发-蓝牙 IOCTL
windows·驱动开发
sukalot11 小时前
Windows 蓝牙驱动开发-BLE低功耗
windows·驱动开发
sukalot2 天前
windows蓝牙驱动开发-BLE音频(一)
windows·驱动开发
sukalot4 天前
Windows 蓝牙驱动开发-安装蓝牙设备
windows·驱动开发
sukalot6 天前
Windows 蓝牙驱动开发-蓝牙设备栈
windows·驱动开发