<Linux开发>驱动开发 -之- Linux LCD 驱动

news2024/11/15 19:33:15

<Linux开发>驱动开发 -之- Linux LCD 驱动

交叉编译环境搭建:
<Linux开发> linux开发工具-之-交叉编译环境搭建

uboot移植可参考以下:
<Linux开发> -之-系统移植 uboot移植过程详细记录(第一部分)
<Linux开发> -之-系统移植 uboot移植过程详细记录(第二部分)
<Linux开发> -之-系统移植 uboot移植过程详细记录(第三部分)(uboot移植完结)

Linux内核及设备树移植可参考以下:
<Linux开发>系统移植 -之- linux内核移植过程详细记录(第一部分)
<Linux开发>系统移植 -之- linux内核移植过程详细记录(第二部分完结)

Linux文件系统构建移植参考以下:
<Linux开发>系统移植 -之- linux构建BusyBox根文件系统及移植过程详细记录
<Linux开发>系统移植 -之-使用buildroot构建BusyBox根文件系统

Linux驱动开发参考以下:
<Linux开发>驱动开发 -之-pinctrl子系统
<Linux开发>驱动开发 -之-gpio子系统
<Linux开发>驱动开发 -之-基于pinctrl/gpio子系统的LED驱动
<Linux开发>驱动开发 -之-基于pinctrl/gpio子系统的beep驱动
<Linux开发>驱动开发 -之-资源的并发与竞争处理
<Linux开发>驱动开发 -之-内核定时器与中断
<Linux开发>驱动开发 -之-阻塞、非阻塞IO和异步通知
<Linux开发>驱动开发 -之-Linux MISC 驱动
<Linux开发>驱动开发 -之-Linux INPUT 子系统

一 前言

LCD 液晶屏是常用到的外设,通过 LCD 可以显示绚丽的图形、界面等,提高人机交互的效率。本系列文章所用的开发板I.MX6U 提供了一个 eLCDIF 接口用于连接 RGB 接口的液晶屏。

二 LCD

2.1 LCD简介

LCD 全称是 Liquid Crystal Display,也就是液晶显示器,是现在最常用到的显示器,手机、电脑、各种人机交互设备等基本都用到了 LCD,最常见就是手机和电脑显示器了。由于笔者不是 LCD 从业人员,对于 LCD 的具体原理不了解,百度百科对于 LCD 的原理解释如下:
LCD 的构造是在两片平行的玻璃基板当中放置液晶盒,下基板玻璃上设置 TFT(薄膜晶体管),上基板玻璃上设置彩色滤光片,通过 TFT 上的信号与电压改变来控制液晶分子的转动方向,从而达到控制每个像素点偏振光出射与否而达到显示目的。

我们现在要在 I.MX6U开发板上使用 LCD,所以不需要去研究 LCD 的具体实现原理,我们只需要从使用的角度去关注 LCD 的几个重要点:
① 分辨率
② 像素格式
③ LCD 屏幕接口
④ LCD 时间参数
⑤ RGB LCD 屏幕时序
⑥ 像素时钟
⑦ 显存

2.2 分辨率

提起 LCD 显示器,我们都会听到 720P、1080P、2K 或 4K 这样的字眼,这个就是 LCD 显示器分辨率。LCD 显示器都是由一个一个的像素点组成,像素点就类似一个灯(在 OLED 显示器中,像素点就是一个小灯),这个小灯是 RGB 灯,也就是由 R(红色)、G(绿色)和 B(蓝色)这三种颜色组成的,而 RGB 就是光的三原色。1080P 的意思就是一个 LCD 屏幕上的像素数量是1920*1080 个,也就是这个屏幕一列 1080 个像素点,一共 1920 列,如下图所示:
在这里插入图片描述
图2.2

上图就是 1080P 显示器的像素示意图,X 轴就是 LCD 显示器的横轴,Y 轴就是显示器的竖轴。图中的小方块就是像素点,一共有 19201080=2073600 个像素点。左上角的 A 点是第一个像素点,右下角的 C 点就是最后一个像素点。2K 就是 25601440 个像素点,4K 是3840*2160 个像素点。很明显,在 LCD 尺寸不变的情况下,分辨率越高越清晰。同样的,分辨率不变的情况下,LCD 尺寸越小越清晰。比如我们常用的 24 寸显示器基本都是 1080P 的,而我们现在使用的 5 寸的手机基本也是 1080P 的,但是手机显示细腻程度就要比 24 寸的显示器要好很多!

由此可见,LCD 显示器的分辨率是一个很重要的参数,但是并不是分辨率越高的 LCD 就越好。衡量一款 LCD 的好坏,分辨率只是其中的一个参数,还有色彩还原程度、色彩偏离、亮度、可视角度、屏幕刷新率等其他参数。

2.3 像素格式

上面讲了,一个像素点就相当于一个 RGB 小灯,通过控制 R、G、B 这三种颜色的亮度就可以显示出各种各样的色彩。那该如何控制 R、G、B 这三种颜色的显示亮度呢?一般一个 R、G、B 这三部分分别使用 8bit 的数据,那么一个像素点就是 8bit*3=24bit,也就是说一个像素点3 个字节,这种像素格式称为 RGB888。如果再加入 8bit 的 Alpha(透明)通道的话一个像素点就是 32bit,也就是 4 个字节,这种像素格式称为 ARGB8888。如果学习过 STM32 的话应该还听过 RGB565 这种像素格式,在本章实验中我们使用 ARGB8888 这种像素格式,一个像素占用 4个字节的内存,这四个字节每个位的分配如下图所示:
在这里插入图片描述
图 2.3.1

在上图中,一个像素点是 4 个字节,其中 bit31-bit24 是 Alpha 通道,bit23-bit16 是RED 通道,bit15-bit14 是 GREEN 通道,bit7-bit0 是 BLUE 通道。所以红色对应的值就是0X00FF0000,蓝色对应的值就是 0X000000FF,绿色对应的值为 0X0000FF00。通过调节 R、G、B的比例可以产生其它的颜色,比如0X00FFFF00就是黄色,0X00000000就是黑色,0X00FFFFFF就是白色。大家可以打开电脑的“画图”工具,在里面使用调色板即可获取到想要的颜色对应的数值,如下图所示:
在这里插入图片描述
图 2.3.2

2.4 LCD 屏幕接口

LCD 屏幕或者说显示器有很多种接口,比如在显示器上常见的 VGA、HDMI、DP、MIPI、LVDS 等等,但是I.MX6U开发板不支持这些接口。I.MX6U-ALPHA支持RGB接口的LCD,RGBLCD接口的信号线如下表所示:
表 2.4.1

信号线描述
R[7:0]8 根红色数据线。
G[7:0]8 根绿色数据线。
B[7:0]8 根蓝色数据线。
DE数据使能线
VSYNC垂直同步信号线
HSYNC水平同步信号线
PCLK像素时钟信号线

上表就是RGBLCD的信号线,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(7 寸,1024*600)这款 RGB LCD 屏幕,ATK-7016 的屏幕接口原理图如下图所示:
在这里插入图片描述
在这里插入图片描述
图 2.4.1

图中 J1 就是对外接口,是一个 40PIN 的 FPC 座(0.5mm 间距),通过 FPC 线,可以连接到 I.MX6U开发板上面,从而实现和 I.MX6U 的连接。该接口十分完善,采用 RGB888格式,并支持 DE&HV 模式,还支持触摸屏和背光控制。右侧的几个电阻,并不是都焊接的,而是可以用户自己选择。默认情况,R1 和 R6 焊接,设置 LCD_LR 和 LCD_UD,控制 LCD 的
扫描方向,是从左到右,从上到下(横屏看)。而 LCD_R7/G7/B7 则用来设置 LCD 的 ID,由于RGBLCD 没有读写寄存器,也就没有所谓的 ID,这里我们通过在模块上面,控制 R7/G7/B7 的上/下拉,来自定义 LCD 模块的 ID,帮助 MCU 判断当前 LCD 面板的分辨率和相关参数,以提高程序兼容性。这几个位的设置关系如下表所示:

M0 LCD_R7M1 LCD_G7M2 LCD_B7LCD ID描述
00040342ATK-4342 RGBLCD 模块,分辨率:480*272
0017084ATK-7084 RGBLCD 模块,分辨率:800*480
0107016ATK-7016,RGBLCD 模块,分辨率:1024*600
1004384ATK-4384,RGBLCD 模块,分辨率:800*480
XXXNCNC

ATK-7016 模块,就设置 M2:M0=010 即可。这样,我们在程序里面,读取 LCD_R7/G7/B7,得到 M0:M2 的值,从而判断 RGBLCD 模块的型号,并执行不同的配置,即可实现不同 LCD 模块的兼容。

2.5 LCD 时间参数

如果将 LCD 显示一帧图像的过程想象成绘画,那么在显示的过程中就是用一根“笔”在不同的像素点画上不同的颜色。这根笔按照从左至右、从上到下的顺序扫描每个像素点,并且在像素画上对应的颜色,当画到最后一个像素点的时候一幅图像就绘制好了。假如一个 LCD 的分辨率为 1024*600,那么其扫描如下图所示:
在这里插入图片描述
图 2.5.1

结合图 2.5.1 我们来看一下 LCD 是怎么扫描显示一帧图像的。一帧图像也是由一行一行组成的。
HSYNC 是水平同步信号,也叫做行同步信号,当产生此信号时就表示开始显示新的一行了,所以此信号都是在图 2.5.1 的最左边。
VSYNC 信号是垂直同步信号,也叫做帧同步信号,当产生此信号时就表示开始显示新的一帧图像了,所以此信号在图2.5.1 的左上角。

在图2.5.1 可以看到有一圈“黑边”,真正有效的显示区域是中间的白色部分。那这一圈“黑边”是什么东西呢?这就要从显示器的“祖先”CRT 显示器开始说起了,CRT 显示器就是以前很常见的那种大屁股显示器,如今基本都淘汰了。CRT 显示器屁股后面是个电子枪,这个电子枪就是我们上面说的“画笔”,电子枪打出的电子撞击到屏幕上的荧光物质使其发光。只要控制电子枪从左到右扫完一行(也就是扫描一行),然后从上到下扫描完所有行,这样一帧图像就显示出来了。也就是说,显示一帧图像电子枪是按照‘Z’形在运动,当扫描速度很快的时候看起来就是一幅完成的画面了。

当显示完一行以后会发出 HSYNC 信号,此时电子枪就会关闭,然后迅速的移动到屏幕的左边,当 HSYNC 信号结束以后就可以显示新的一行数据了,电子枪就会重新打开。在 HSYNC信号结束到电子枪重新打开之间会插入一段延时,这段延时就图 2.5.1 中的 HBP。当显示完一行以后就会关闭电子枪等待 HSYNC 信号产生,关闭电子枪到 HSYNC 信号产生之间会插入一段延时,这段延时就是图 2.5.1 中的 HFP 信号。同理,当显示完一帧图像以后电子枪也会关闭,然后等到 VSYNC 信号产生,期间也会加入一段延时,这段延时就是图 2.5.1 中的 VFP。VSYNC 信号产生,电子枪移动到左上角,当 VSYNC 信号结束以后电子枪重新打开,中间也会加入一段延时,这段延时就是图 24.1.1.5 中的 VBP。

HBP、HFP、VBP 和 VFP 就是导致图 24.1.1.5 中黑边的原因,但是这是 CRT 显示器存在黑边的原因,现在是 LCD 显示器,不需要电子枪了,那么为何还会有黑边呢?这是因为 RGB LCD屏幕内部是有一个 IC 芯片的,发送一行或者一帧数据给 IC芯片,IC芯片 是需要反应时间的。通过这段反应时间可以让 IC芯片 识别到一行数据扫描完了,要换行了,或者一帧图像扫描完了,要开始下一帧图像显示了。因此,在 LCD 屏幕中继续存在 HBP、HFP、VPB 和 VFP 这四个参数的主要目的是为了锁定有效的像素数据。这四个时间是 LCD 重要的时间参数,后面编写 LCD 驱动的时候要用到的,至于这四个时间参数具体值是多少,那要需要去查看所使用的 LCD 数据手册了。

2.6 RGB LCD 屏幕时序

上面讲了行显示和帧显示,我们来看一下行显示对应的时序图,如下图所示:
在这里插入图片描述
图 2.6.1

图2.6.1 就是 RGB LCD 的行显示时序,我们来分析一下其中重要的几个参数:
HSYNC:行同步信号,当此信号有效的话就表示开始显示新的一行数据,查阅所使用的LCD 数据手册可以知道此信号是低电平有效还是高电平有效,假设此时是低电平有效。

HSPW:有些地方也叫做 thp,是 HSYNC 信号宽度,也就是 HSYNC 信号持续时间。HSYNC信号不是一个脉冲,而是需要持续一段时间才是有效的,单位为 CLK。

HBP:有些地方叫做 thb,前面已经讲过了,术语叫做行同步信号后肩,单位是 CLK。

HOZVAL:有些地方叫做 thd,显示一行数据所需的时间,假如屏幕分辨率为 1024*600,那么 HOZVAL 就是 1024,单位为 CLK。

HFP:有些地方叫做 thf,前面已经讲过了,术语叫做行同步信号前肩,单位是 CLK。

当 HSYNC 信号发出以后,需要等待 HSPW+HBP 个 CLK 时间才会接收到真正有效的像素数据。当显示完一行数据以后需要等待 HFP 个 CLK 时间才能发出下一个 HSYNC 信号,所以显示一行所需要的时间就是:HSPW + HBP + HOZVAL + HFP。

一帧图像就是由很多个行组成的,RGB LCD 的帧显示时序如下图所示:
在这里插入图片描述
图 2.6.2

图 2.6.2 就是 RGB LCD 的帧显示时序,我们来分析一下其中重要的几个参数:

VSYNC:帧同步信号,当此信号有效的话就表示开始显示新的一帧数据,查阅所使用的LCD 数据手册可以知道此信号是低电平有效还是高电平有效,假设此时是低电平有效。

VSPW:有些地方也叫做 tvp,是 VSYNC 信号宽度,也就是 VSYNC 信号持续时间,单位为 1 行的时间。

VBP:有些地方叫做 tvb,前面已经讲过了,术语叫做帧同步信号后肩,单位为 1 行的时间。

LINE:有些地方叫做 tvd,显示一帧有效数据所需的时间,假如屏幕分辨率为 1024*600,那么 LINE 就是 600 行的时间。

VFP:有些地方叫做 tvf,前面已经讲过了,术语叫做帧同步信号前肩,单位为 1 行的时间。

显示一帧所需要的时间就是:VSPW+VBP+LINE+VFP 个行时间,最终的计算公式:
T = (VSPW+VBP+LINE+VFP) * (HSPW + HBP + HOZVAL + HFP)

因此我们在配置一款 RGB LCD 的时候需要知道这几个参数:HOZVAL(屏幕有效宽度)、LINE(屏幕有效高度)、HBP、HSPW、HFP、VSPW、VBP 和 VFP。本文所用 RGB LCD屏幕的参数如下表所示:

参数单位
水平显示区域 HOZVAL1024tCLK
HSPW(thp)20tCLK
HBP(thb)140tCLK
HFP(thf)160tCLK
垂直显示区域 LINE600th
VSPW(tvp)3th
VBP(tvb)20th
VFP(tvf)12th
像素时钟51.2MHz

2.7 像素时钟

像素时钟就是 RGB LCD 的时钟信号,以 本次使用的LCD款屏幕为例,显示一帧图像所需要的时钟数就是:
= (VSPW+VBP+LINE+VFP) * (HSPW + HBP + HOZVAL + HFP)
= (3 + 20 + 600 + 12) * (20 + 140 + 1024 + 160)
= 635 * 1344
= 853440。

显示一帧图像需要853440个时钟数,那么显示60帧就是:853440 * 60 = 51206400≈51.2M,所以像素时钟就是 51.2MHz。

查阅IMX6UL参考手册.pdf,I.MX6U 的 eLCDIF 接口时钟图如下图所示:
在这里插入图片描述
图 2.7.1
在这里插入图片描述
在这里插入图片描述

①:此部分是一个选择器,用于选择哪个 PLL 可以作为 LCDIF 时钟源,由寄存器CCM_CSCDR2 的位 LCDIF1_PRE_CLK_SEL(bit17:15)来决定,LCDIF1_PRE_CLK_SEL 选择设置如下表所示:

时钟源
0PLL2 作为 LCDIF 的时钟源
1PLL3_PFD3 作为 LCDIF 的时钟源
2PLL5 作为 LCDIF 的时钟源
3PLL2_PFD0 作为 LCDIF 的时钟源
4PLL2_PFD1 作为 LCDIF 的时钟源
5PLL3_PFD1 作为 LCDIF 的时钟源

②、此部分是 LCDIF 时钟的预分频器,由寄存器 CCM_CSCDR2 的位 LCDIF1_PRED 来决定预分频值。可设置值为 0-7,分别对应 1-8 分频。
③、此部分进一步分频,由寄存器 CBCMR 的位 LCDIF1_PODF 来决定分频值。可设置值为 0-7,分别对应 1-8 分频。
④、此部分是一个选择器,选择 LCDIF 最终的根时钟,由寄存器 CSCDR2 的位LCDIF1_CLK_SEL 决定,LCDIF1_CLK_SEL 选择设置如下表所示:

时钟源
0前面复用器出来的时钟,也就是前面 PLL5 出来的时钟作为 LCDIF 的根时钟。
1ipp_di0_clk 作为 LCDIF 的根时钟
2ipp_di1_clk 作为 LCDIF 的根时钟
3ldb_di0_clk 作为 LCDIF 的根时钟
4ldb_di1_clk 作为 LCDIF 的根时钟

这里肯定选择 PLL5 出来的那一路时钟作为 LCDIF 的根时钟,因此 LCDIF1_CLK_SEL 设置为 0。LCDIF 既然选择了 PLL5 作为时钟源,那么还需要初始化 PLL5,LCDIF 的时钟是由PLL5 和图 2.7.1 中的②、③这两个分频值决定的,所以需要对这三个进行合理的设置以搭配出所需的时钟值,我们就以 ATK7016 屏幕所需的 51.2MHz 为例,看看如何进行配置。

PLL5 频率设置涉及到四个寄存器:CCM_PLL_VIDEO、CCM_PLL_VIDEO_NUM、CCM_PLL_VIDEO_DENOM 、 CCM_MISC2 。
其 中 CCM_PLL_VIDEO_NUM 和CCM_PLL_VIDEO_DENOM 这两个寄存器是用于小数分频的,我们这里为了简单不使用小数分频,因此这两个寄存器设置为 0。
PLL5 的时钟计算公式如下:
PLL5_CLK = OSC24M * (loopDivider + (denominator / numerator)) / postDivider
不使用小数分频的话 PLL5 时钟计算公式就可以简化为 :
PLL5_CLK = OSC24M * loopDivider / postDivider
OSC24M 就是 24MHz 的有源晶振,现在的问题就是设置 loopDivider 和 postDivider。
先来看一下寄存器 CCM_PLL_VIDEO,此寄存器结构如下图所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
寄存器 CCM_PLL_VIDEO 用到的重要的位如下:

POST_DIV_SLECT(bit20:19):此位和寄存器 CCM_ANALOG_CCMSC2 的 VIDEO_DIV 位共同决定了 postDivider,为 0 的话是 4 分频,为 1 的话是 2 分频,为 2 的话是 1 分频。本章设置为 2,也就是 1 分频。

ENABLE(bit13):PLL5(PLL_VIDEO)使能位,为 1 的话使能 PLL5,为 0 的话关闭 PLL5。

DIV_SELECT(bit6:0):loopDivider 值,范围为 27~54,本章设置为 32。

寄存器 CCM_MISC2 ,此寄存器结构如下图所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

寄存器 CCM_ANALOG_MISC2 的位 VIDEO_DIV(bit31:30)与寄存器 CCM_PLL_VIDEO 的位 POST_DIV_SLECT(bit20:19)共同决定了 postDivider,通过这两个的配合可以获得 2、4、8、16 分频。本章将 CCM_ANALOG_MISC2::VIDEO_DIV 设置为 0,也就是 1 分频,因此 postDivider 就是 1,CCM_PLL_VIDEO :: DIV_SELECT(bit6:0):loopDivider设置为 32,PLL5 的时钟频率就是:
PLL5_CLK = OSC24M * loopDivider / postDivider
= 24M * 32 / 1
= 768MHz。

PLL5 此时为 768MHz,在经过图 2.7.1 中的②和③进一步分频,设置②中为 3 分频,也就是寄存器 CCM_CSCDR2 的位 LCDIF1_PRED(bit14:12)为 2。设置③中为 5 分频,就是寄存器CCM_CBCMR 的位 LCDIF1_PODF(bit25:23)为 4。设置好以后最终进入到 LCDIF 的时钟频率就是:768/3/5 =51.2MHz,这就是我们需要的像素时钟频率。

2.8 显存

在讲像素格式的时候就已经说过了,如果采用 ARGB8888 格式的话一个像素需要 4 个字节的内存来存放像素数据,那么 1024600 分辨率就需要 1024600*4=2457600B≈2.4MB 内存。但是 RGB LCD 内部是没有内存的,所以就需要在开发板上的 DDR3 中分出一段内存作为 RGB LCD 屏幕的显存,我们如果要在屏幕上显示什么图像的话直接操作这部分显存即可。

三 eLCDIF 简介

3.1 eLCDIF 接口介绍

eLCDIF 是 I.MX6U 自带的液晶屏幕接口,用于连接 RGB LCD 接口的屏幕,eLCDIF 接口特性如下:
①、支持 RGB LCD 的 DE 模式。
②、支持 VSYNC 模式以实现高速数据传输。
③、支持 ITU-R BT.656 格式的 4:2:2 的 YCbCr 数字视频,并且将其转换为模拟 TV 信号。
④、支持 8/16/18/24/32 位 LCD。

eLCDIF 支持三种接口:MPU 接口、VSYNC 接口和 DOTCLK 接口,这三种接口区别如下:

(1) MPU 接口
MPU 接口用于在 I.MX6U 和 LCD 屏幕直接传输数据和命令,这个接口用于 6080/8080 接口的 LCD 屏幕,比如我们学习 STM32 的时候常用到的 MCU 屏幕。如果寄存器 LCDIF_CTRL的位 DOTCLK_MODE、DVI_MODE 和 VSYNC_MODE 都为 0 的话就表示 LCDIF 工作在 MPU接口模式。关于 MPU 接口的详细信息以及时序参考《I.MX6ULL 参考手册》第 2150 页的“34.4.6MPU Interface”小节,本教程不使用 MPU 接口。

(2) VSYNC 接口
VSYNC 接口时序和 MPU 接口时序基本一样,只是多了 VSYNC 信号来作为帧同步,当LCDIF_CTRL 的位 VSYNC_MODE 为 1 的时候此接口使能。关于 VSYNC 接口的详细信息请参考《I.MX6ULL 参考手册》第 2152 页的“34.4.7 VSYNC Interface”小节,本教程不使用 VSYNC接口。

(3) DOTCLK 接口
DOTCLK 接口就是用来连接 RGB LCD 接口屏幕的, 它包括 VSYNC、HSYNC、DOTCLK和 ENABLE(可选的)这四个信号,这样的接口通常被称为 RGB 接口。DOTCLK 接口时序如下图所示:
在这里插入图片描述
图 3.1

图3.1 是不是和图 2.6.1、图 2.6.2 很类似,因为 DOTCLK 接口就是连接 RGB 屏幕的,本教程使用的就是 DOTCLK 接口。

3.2 LCDIF_CTRL 寄存器

eLCDIF 要驱动起来 RGB LCD 屏幕,重点是配置好上一小节讲解的那些时间参数即可,这个通过配置相应的寄存器就可以了,所以我们接下来看一下 eLCDIF 接口的几个重要的寄存器,首先看一下 LCDIF_CTRL 寄存器,此寄存器结构如下图所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
图 3.2

寄存器 LCDIF_CTRL 用到的重要位如下:

SFTRST(bit31):eLCDIF 软复位控制位,当此位为 1 的话就会强制复位 LCD。

CLKGATE(bit30):正常运行模式下,此位必须为 0!如果此位为 1 的话时钟就不会进入到LCDIF。

BYPASS_COUNT(bit19):如果要工作在 DOTCLK 模式的话就此位必须为 1。

VSYNC_MODE(bit18):此位为 1 的话 LCDIF 工作在 VSYNC 接口模式。

DOTCLK_MODE(bit17):此位为 1 的话 LCDIF 工作在 DOTCLK 接口模式。

INPUT_DATA_SWIZZLE(bit15:14):输入数据字节交换设置,此位为 0 的话不交换字节也就是小端模式;为 1 的话交换所有字节,也就是大端模式;为 2 的话半字交换;为 3 的话在每个半字内进行字节交换。本章我们设置为 0,也就是不使用字节交换。

CSC_DATA_SWIZZLE(bit13:12) : CSC 数 据 字 节 交 换 设 置 , 交 换 方 式 和INPUT_DATA_SWIZZLE 一样,本章设置为 0,不使用字节交换。

LCD_DATABUS_WIDTH(bit11:10):LCD 数据总线宽度,为 0 的话总线宽度为 16 位;为1 的话总线宽度为 8 位;为 2 的话总线宽度为 18 位;为 3 的话总线宽度为 24 位。本章我们使用 24 位总线宽度。

WORD_LENGTH(bit9:8):输入的数据格式,也就是像素数据宽度,为 0 的话每个像素 16位;为 1 的话每个像素 8 位;为 2 的话每个像素 18 位;为 3 的话每个像素 24 位。

MASTER(bit5):为 1 的话设置 eLCDIF 工作在主模式。

DATA_FORMAT_16_BIT(bit3):当此位为 1 并且 WORD_LENGTH 为 0 的时候像素格式为 ARGB555,当此位为 0 并且 WORD_LENGTH 为 0 的时候像素格式为 RGB565。

DATA_FORMAT_18_BIT(bit2):只有当 WORD_LENGTH 为 2 的时候此位才有效,此位为 0 的话低 18 位有效,像素格式为 RGB666,高 14 位数据无效。当此位为 1 的话高 18 位有效,像素格式依旧是 RGB666,但是低 14 位数据无效。

DATA_FORMAT_24_BIT(bit1):只有当 WORD_LENGTH 为 3 的时候此位才有效,为 0 的时候表示全部的 24 位数据都有效。为 1 的话实际输入的数据有效位只有 18 位,虽然输入的是24 位数据,但是每个颜色通道的高 2 位数据会被丢弃掉。

RUN(bit0):eLCDIF 接口运行控制位,当此位为 1 的话 eLCDIF 接口就开始传输数据,也就是 eLCDIF 的使能位。

3.3 LCDIF_CTRL1 寄存器

接 下 来 看 一 下 寄 存 器 LCDIF_CTRL1 , 此 寄 存 器 我 们 只 用 到 位
BYTE_PACKING_FORMAT(bit19:16),此位用来决定在 32 位的数据中哪些字节的数据有效,默认值为 0XF,也就是所有的字节有效,当为 0 的话表示所有的字节都无效。如果显示的数据是24 位(ARGB 格式,但是 A 通道不传输)的话就设置此位为 0X7。
在这里插入图片描述
在这里插入图片描述
图 3.3

3.4 LCDIF_TRANSFER_COUNT寄存器

接下来看一下寄存器 LCDIF_TRANSFER_COUNT,这个寄存器用来设置所连接的 RGB LCD 屏幕分辨率大小,此寄存器结构如图 24.1.2.2 所示:
在这里插入图片描述
图 3.4

寄存器LCDIF_TRANSFER_COUNT 分为两部分,高16位和低16位,高16位是V_COUNT,是 LCD 的垂直分辨率。低 16 位是 H_COUNT,是 LCD 的水平分辨率。如果 LCD 分辨率为1024*600 的话,那么 V_COUNT 就是 600,H_COUNT 就是 1024。

3.5 LCDIF_VDCTRL0寄存器

接下来看一下寄存器 LCDIF_VDCTRL0,这个寄存器是 VSYNC 和 DOTCLK 模式控制寄存器 0,寄存器结构如下图所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
图 3.5

寄存器 LCDIF_VDCTRL0 用到的重要位如下:

VSYNC_OEB(bit29):VSYNC 信号方向控制位,为 0 的话 VSYNC 是输出,为 1 的话VSYNC 是输入。

ENABLE_PRESENT(bit28):EBABLE 数据线使能位,也就是 DE 数据线。为 1 的话使能ENABLE 数据线,为 0 的话关闭 ENABLE 数据线。

VSYNC_POL(bit27):VSYNC 数据线极性设置位,为 0 的话 VSYNC 低电平有效,为 1 的话 VSYNC 高电平有效,要根据所使用的 LCD 数据手册来设置。

HSYNC_POL(bit26):HSYNC 数据线极性设置位,为 0 的话 HSYNC 低电平有效,为 1 的话 HSYNC 高电平有效,要根据所使用的 LCD 数据手册来设置。

DOTCLK_POL(bit25):DOTCLK 数据线(像素时钟线 CLK) 极性设置位,为 0 的话下降沿锁存数据,上升沿捕获数据,为 1 的话相反,要根据所使用的 LCD 数据手册来设置。

ENABLE_POL(bit24):EANBLE 数据线极性设置位,为 0 的话低电平有效,为 1 的话高电平有效。

VSYNC_PERIOD_UNIT(bit21):VSYNC 信号周期单位,为 0 的话 VSYNC 周期单位为像素时钟。为 1 的话 VSYNC 周期单位是水平行,如果使用 DOTCLK 模式话就要设置为 1。

VSYNC_PULSE_WIDTH_UNIT(bit20) : VSYNC 信 号 脉 冲 宽 度 单 位 , 和VSYNC_PERIOD_UNUT 一样,如果使用 DOTCLK 模式的话要设置为 1。

VSYNC_PULSE_WIDTH(bit17:0):VSPW 参数设置位。

3.6 LCDIF_VDCTRL1寄存器

接下来看一下寄存器 LCDIF_VDCTRL1,这个寄存器是 VSYNC 和 DOTCLK 模式控制寄存器 1,此寄存器只有一个功能,用来设置 VSYNC 总周期,就是:屏幕高度+VSPW+VBP+VFP。
在这里插入图片描述
图 3.6

3.7 LCDIF_VDCTRL2寄存器

接下来看一下寄存器 LCDIF_VDCTRL2,这个寄存器分为高 16 位和低 16 位两部分,高 16位是 HSYNC_PULSE_WIDTH,用来设置 HSYNC 信号宽度,也就是 HSPW。低 16 位是HSYNC_PERIOD,设置 HSYNC 总周期,就是:屏幕宽度+HSPW+HBP+HFP。
在这里插入图片描述
图 3.7

3.8 LCDIF_VDCTRL3寄存器

接下来看一下寄存器 LCDIF_VDCTRL3,此寄存器结构如下图所示:
在这里插入图片描述
图 3.8

寄存器 LCDIF_VDCTRL3 用到的重要位如下:

HORIZONTAL_WAIT_CNT(bit27:16):此位用于 DOTCLK 模式,用于设置 HSYNC 信号产生到有效数据产生之间的时间,也就是 HSPW+HBP。

VERTICAL_WAIR_CNT(bit15:0):和 HORIZONTAL_WAIT_CNT 一样,只是此位用于VSYNC 信号,也就是 VSPW+VBP。

3.9 LCDIF_VDCTRL4寄存器

接下来看一下寄存器 LCDIF_VDCTRL4,此寄存器结构如下图所示:
在这里插入图片描述
寄存器 LCDIF_VDCTRL4 用到的重要位如下:

SYNC_SIGNALS_ON(bit18):同步信号使能位,设置为 1 的话使能 VSYNC、HSYNC、DOTCLK 这些信号。

DOTCLK_H_VALID_DATA_CNT(bit15:0):设置 LCD 的宽度,也就是水平像素数量。最后在看一下寄存器 LCDIF_CUR_BUF 和 LCDIF_NEXT_BUF,这两个寄存器分别为当前帧和下一帧缓冲区,也就是 LCD 显存。一般这两个寄存器保存同一个地址,也就是划分给 LCD的显存首地址。

四 eLCDIF使用总结

关于 eLCDIF 接口的寄存器就介绍到这里,关于这些寄存器详细的描述,请参考《I.MX6ULL参考手册》第 2165 页的 34.6 小节。一般 I.MX6U 的 eLCDIF 接口来驱动屏幕的配置步骤如下:

1、初始化 LCD 所使用的 IO
首先肯定是初始化 LCD 所示使用的 IO,将其复用为 eLCDIF 接口 IO。
2、设置 LCD 的像素时钟
查阅所使用的 LCD 屏幕数据手册,或者自己计算出的时钟像素,然后设置 CCM 相应的寄存器。
3、配置 eLCDIF 接口
设置 LCDIF 的寄存器 CTRL、CTRL1、TRANSFER_COUNT、VDCTRL0~4、CUR_BUF 和NEXT_BUF。根据 LCD 的数据手册设置相应的参数。
4、编写 API 函数
驱动 LCD 屏幕的目的就是显示内容,所以需要编写一些基本的 API 函数,比如画点、画线、画圆函数,字符串显示函数等。

五 LCD背光

不管是使用显示器还是手机,其屏幕背光都是可以调节的,通过调节背光就可以控制屏幕的亮度。在户外阳光强烈的时候可以通过调高背光来看清屏幕,在光线比较暗的地方可以调低背光,防止伤眼睛并且省电。目前市面上的LCD 大都支持背光调节,我们来看下如何调节 LCD 背光。

5.1 LCD 背光调节简介

一般LCD 都有一个背光控制引脚,给这个背光控制引脚输入高电平就会
点亮背光,输入低电平就会关闭背光。假如我们不断的打开和关闭背光,当速度足够快的时候就不会感觉到背光关闭这个过程了。这个正好可以使用 PWM 来完成,PWM 全称是 Pulse Width Modulation,也就是脉冲宽度调制,PWM 信号如下图所示:
在这里插入图片描述
图 5.1

PWM 信号有两个关键的术语:频率和占空比,频率就是开关速度,把一次开关算作一个周期,那么频率就是 1 秒内进行了多少次开关。占空比就是一个周期内高电平时间和低电平时间的比例,一个周期内高电平时间越长占空比就越大,反之占空比就越小。占空比用百分之表示,如果一个周期内全是低电平那么占空比就是 0%,如果一个周期内全是高电平那么占空比就是100%。

我们给 LCD 的背光引脚输入一个 PWM 信号,这样就可以通过调整占空比的方式来调整LCD 背光亮度了。提高占空比就会提高背光亮度,降低占空比就会降低背光亮度。重点就在于PWM 信号的产生和占空比的控制,很幸运的是,I.MX6U 提供了 PWM 外设,因此我们可以配置 PWM 外设来产生 PWM 信号。

5.2 I.MX6ULL的PWM

打开《I.MX6ULL 参考手册》的第 40 章“Chapter 40 Pulse Width Modulation(PWM)”,I.MX6U一共有 8 路 PWM 信号,每个 PWM 包含一个 16 位的计数器和一个 4 x 16 的数据 FIFO,I.MX6U的 PWM 外设结构如图 29.1.2 所示:
在这里插入图片描述
图 5.2

上图中的各部分功能如下:

①、此部分是一个选择器,用于选择 PWM 信号的时钟源,一共有三种时钟源:ipg_clk、ipg_clk_highfreq 和 ipg_clk_32k。
②、这是一个 12 位的分频器,可以对①中选择的时钟源进行分频。
③、这是 PWM 的 16 位计数器寄存器,保存着 PWM 的计数值。
④、这是 PWM 的 16 位周期寄存器,此寄存器用来控制 PWM 的频率。
⑤、这是 PWM 的 16 位采样寄存器,此寄存器用来控制 PWM 的占空比。
⑥、此部分是 PWM 的中断信号,PWM 是提供中断功能的,如果使能了相应的中断的话就会产生中断。
⑦、此部分是 PWM 对应的输出 IO,产生的 PWM 信号就会从对应的 IO 中输出,I.MX6U 开发板的 LCD 背光控制引脚连接在 I.MX6U 的 GPIO1_IO8 上,GPIO1_IO8 可以复用为 PWM1_OUT。

可以通过配置相应的寄存器来设置 PWM 信号的频率和占空比,PWM 的 16 位计数器是个向上计数器,此计数器会从 0X0000 开始计数,直到计数值等于寄存器 PWMx_PWMPR(x=1~8) + 1,然后计数器就会重新从 0X0000 开始计数,如此往复。所以寄存器 PWMx_PWMPR 可以设置 PWM 的频率。
在一个周期内,PWM 从 0X0000 开始计数的时候,PWM 引脚先输出高电平(默认情况下,可以通过配置输出低电平)。采样 FIFO 中保存的采样值会在每个时钟和计数器值进行比较,当采样值和计数器相等的话 PWM 引脚就会改为输出低电平(默认情况下,同样可以通过配置输出高电平)。计数器会持续计数,直到和周期寄存器 PWMx_PWMPR(x=1~8) + 1 的值相等,这样一个周期就完成了。所以,采样 FIFO 控制着占空比,而采样 FIFO 里面的值来源于采样寄存器PWMx_PWMSAR,因此相当于 PWMx_PWMSAR 控制着占空比。至此,PWM 信号的频率和占空比设置我们就知道该如何去做了。

PWM 开启以后会按照默认值运行,并产生 PWM 波形,而这个默认的 PWM 一般并不是我们需要的波形。如果这个 PWM 波形控制着设备的话就会导致设备因为接收到错误的 PWM 信号而运行错误,严重情况下可能会损坏设备,甚至人身安全。因此,在开启 PWM 之前最好设置好 PWMx_PWMPR 和 PWMx_PWMSAR 这两个寄存器,也就是设置好 PWM 的频率和占空比。

当我们向 PWMx_PWMSAR 寄存器写入采样值的时候,如果 FIFO 没满的话其值会被存储到 FIFO 中。如果 FIFO 满的时候写入采样值就会导致寄存器 PWMx_PWMSR 的位 FWE(bit6)置1,表示 FIFO 写错误,FIFO 里面的值也并不会改变。FIFO 可以在任何时候写入,但是只有在PWM 使能的情况下读取。寄存器 PWMx_SR 的位 FIFOAV(bit2:0)记录着当前 FIFO 中有多少个数据。从采样寄存器 PWMx_PWMSAR 读取一次数据,FIFO 里面的数据就会减一,每产生一个周期的 PWM 信号,FIFO 里面的数据就会减一,相当于被用掉了。PWM 有个 FIFO 空中断,当FIFO 为空的时候就会触发此中断,可以在此中断处理函数中向 FIFO 写入数据。

查阅手册可pwm有关的寄存器目录如下:
在这里插入图片描述

5.3 PWM1_PWMCR寄存器

我们使用的是 PWM1,首先看一下寄存器 PWM1_PWMCR 寄存器,此寄存器结构如下图所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
图 5.3

寄存器 PWM1_PWMCR 用到的重要位如下:

FWM(bit27:26):FIFO 水位线,用来设置 FIFO 空余位置为多少的时候表示 FIFO 为空。设置为 0 的时候表示 FIFO 空余位置大于等于 1 的时候 FIFO 为空;设置为 1 的时候表示 FIFO 空余位置大于等于 2 的时候 FIFO 为空;设置为 2 的时候表示 FIFO 空余位置大于等于 3 的时候

FIFO 为空;设置为 3 的时候表示 FIFO 空余位置大于等于 4 的时候 FIFO 为空。

STOPEN(bit25):此位用来设置停止模式下 PWM 是否工作,为 0 的话表示在停止模式下PWM 继续工作,为 1 的话表示停止模式下关闭 PWM。

DOZEN(bit24):此位用来设置休眠模式下 PWM 是否工作,为 0 的话表示在休眠模式下PWM 继续工作,为 1 的话表示休眠模式下关闭 PWM。

WAITEN(bit23):此位用来设置等待模式下 PWM 是否工作,为 0 的话表示在等待模式下PWM 继续工作,为 1 的话表示等待模式下关闭 PWM。

DEGEN(bit22):此位用来设置调试模式下 PWM 是否工作,为 0 的话表示在调试模式下PWM 继续工作,为 1 的话表示调试模式下关闭 PWM。

BCTR(bit21):字节交换控制位,用来控制 16 位的数据进入 FIFO 的字节顺序。为 0 的时候不进行字节交换,为 1 的时候进行字节交换。

HCRT(bit20):半字交换控制位,用来决定从 32 位 IP 总线接口传输来的哪个半字数据写入采样寄存器的低 16 位中。

POUTC(bit19:18):PWM 输出控制控制位,用来设置 PWM 输出模式,为 0 的时候表示PWM 先输出高电平,当计数器值和采样值相等的话就输出低电平。为 1 的时候相反,当为 2 或者 3 的时候 PWM 信号不输出。本章我们设置为 0,也就是一开始输出高电平,当计数器值和采样值相等的话就改为低电平,这样采样值越大高电平时间就越长,占空比就越大。

CLKSRC(bit17:16):PWM 时钟源选择,为 0 的话关闭;为 1 的话选择 ipg_clk 为时钟源;为 2 的话选择 ipg_clk_highfreq 为时钟源;为 3 的话选择 ipg_clk_32k 为时钟源。本章我们设置为 1,也就是选择 ipg_clk 为 PWM 的时钟源,因此 PWM 时钟源频率为 66MHz。

PRESCALER(bit15:4):分频值,可设置为 0-4095,对应着 1-4096 分频。

SWR(bit3):软件复位,向此位写 1 就复位 PWM,此位是自清零的,当复位完成以后此位会自动清零。

REPEAT(bit2:1):重复采样设置,此位用来设置 FIFO 中的每个数据能用几次。可设置 0~3,分别表示 FIFO 中的每个数据能用 1~4 次。本章我们设置为 0,即 FIFO 中的每个数据只能用一次。

EN(bit0):PWM 使能位,为 1 的时候使能 PWM,为 0 的时候关闭 PWM。

5.4 PWM1_PWMIR寄存器

接下来看一下寄存器 PWM1_PWMIR 寄存器,这个是 PWM 的中断控制寄存器,此寄存器结构如下图所示:
在这里插入图片描述
在这里插入图片描述
图 5.4
寄存器 PWM1_PWMIR 只有三个位,这三个位的含义如下:

CIE(bit2):比较中断使能位,为 1 的时候使能比较中断,为 0 的时候关闭比较中断。

RIE(bit1):翻转中断使能位,当计数器值等于采样值并回滚到 0X0000 的时候就会产生此中断,为 1 的时候使能翻转中断,为 0 的时候关闭翻转中断。

FIE(bit0):FIFO 空中断,为 1 的时候使能,为 0 的时候关闭。

5.5 PWM1_PWMSR寄存器

再来看一下状态寄存器 PWM1_PWMSR,此寄存器结构如下图所示:
在这里插入图片描述
在这里插入图片描述
图 5.5

寄存器 PWM1_PWMSR 各个位的含义如下:
FWE(bit6):FIFO 写错误事件,为 1 的时候表示发生了 FIFO 写错误。

CMP(bit5):FIFO 比较事件发标志位,为 1 的时候表示发生 FIFO 比较事件。

ROV(bit4):翻转事件标志位,为 1 的话表示翻转事件发生。

FE(bit3):FIFO 空标志位,为 1 的时候表示 FIFO 位空。

FIFOAV(bit2:1):此位记录 FIFO 中的有效数据个数,有效值为 0-4,分别表示 FIFO 中有0~4 个有效数据。

5.6 PWM1_PWMPR寄存器

接下来是寄存器 PWM1_PWMPR 寄存器,这个是 PWM 周期寄存器,可以通过此寄存器来设置 PWM 的频率,此寄存器结构如下图所示:
在这里插入图片描述
图 5.6

从上图可以看出,寄存器 PWM1_PWMPR 只有低 16 位有效,当 PWM 计数器的值等于 PERIOD+1 的时候就会从 0X0000 重新开始计数,开启另一个周期。PWM 的频率计算公式
如下:

PWMO(Hz) = PCLK(Hz) / (PERIOD + 2)

其中 PCLK 是最终进入 PWM 的时钟频率,假如 PCLK 的频率为 1MHz,现在我们要产生一个频率为 1KHz 的 PWM 信号,那么就可以设置 PERIOD = 1000000 / 1000 – 2 = 998。

5.7 PWM1_PWMSAR寄存器

最后来看一下寄存器 PWM1_PWMSAR,这是采样寄存器,用于设置占空比的,此寄存器结构如下图所示:
在这里插入图片描述
此寄存器也是只有低 16 位有效,为采样值。通过这个采样值即可调整占空比,当计数器的值小于 SAMPLE 的时候输出高电平(或低电平)。当计数器值大于等于 SAMPLE,小于寄存器PWM1_PWMPR 的 PERIO 的时候输出低电平(或高电平)。同样在上面的例子中,假如我们要设置 PWM 信号的占空比为 50%,那么就可以将 SAMPLE 设置为(PERIOD + 2) / 2 = 1000 / 2=500。
关于 PWM 有关的寄存器就介绍到这里,关于这些寄存器详细的描述,请参考《I.MX6ULL参考手册》第 2480 页的 40.7 小节。

5.8 PWM小节

根据板子连接使用 I.MX6U 的 PWM1,PWM1 的输出引脚为GPIO1_IO8,一般配置步骤如下:

(1) 配置引脚 GPIO1_IO8
配置 GPIO1_IO08 的复用功能,将其复用为 PWM1_OUT 信号线。

(2) 初始化 PWM1
初始化 PWM1,配置所需的 PWM 信号的频率和默认占空比。

(3) 设置中断
因为 FIFO 中的采样值每个周期都会少一个,所以需要不断的向 FIFO 中写入采样值,防止其为空。我们可以使能 FIFO 空中断,这样当 FIFO 为空的时候就会触发相应的中断,然后在中断处理函数中向 FIFO 写入采样值。

(4) 使能 PWM1
配置好 PWM1 以后就可以开启了。

六 Linux 下 LCD 驱动

6.1 linux的fb 设备

前面分析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。NXP 官方的 Linux 内核默认已经开启了 LCD 驱动,因此我们是可以看到/dev/fb0 这样一个设备,如下图所示:
在这里插入图片描述
图6.1

上图中的/dev/fb0 就是 LCD 对应的设备文件,/dev/fb0 是个字符设备,因此肯定有file_operations 操作集,fb 的 file_operations 操作集定义在 drivers/video/fbdev/core/fbmem.c 文件中,如下所示:

路径:drivers/video/fbdev/core/fbmem.c
static const struct file_operations fb_fops = {
	.owner =	THIS_MODULE,
	.read =		fb_read,
	.write =	fb_write,
	.unlocked_ioctl = fb_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl = fb_compat_ioctl,
#endif
	.mmap =		fb_mmap,
	.open =		fb_open,
	.release =	fb_release,
#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
	.get_unmapped_area = get_fb_unmapped_area,
#endif
#ifdef CONFIG_FB_DEFERRED_IO
	.fsync =	fb_deferred_io_fsync,
#endif
	.llseek =	default_llseek,
};

对于fb相关,不做深入分析,有兴趣的读者可自行研究。

6.2 I.MX6ULL官方源码驱动分析

根据第三、四大节内容可知,LCD 裸机例程主要分两部分:
①、获取 LCD 的屏幕参数。
②、根据屏幕参数信息来初始化 eLCDIF 接口控制器。
不同分辨率的 LCD 屏幕其 eLCDIF 控制器驱动代码都是一样的,只需要修改好对应的屏幕参数即可。屏幕参数信息属于屏幕设备信息内容,这些肯定是要放到设备树中的,因此我们本节实验的主要工作就是修改设备树,NXP 官方的设备树已经添加了 LCD 设备节点,只是此节点的 LCD 屏幕信息是针对 NXP 官方 EVK 开发板所使用的 4.3 寸 480*272 编写的,我们需要将其改为我们所使用的屏幕参数。

6.2.1 NXP官方LCD设备树节点

们简单看一下 NXP 官方编写的 Linux 下的 LCD 驱动,打开 imx6ull.dtsi,然后找到 lcdif节点内容,如下所示:

路径:arch/arm/boot/dts/imx6ull.dtsi
lcdif: lcdif@021c8000 {
				compatible = "fsl,imx6ul-lcdif", "fsl,imx28-lcdif";
				reg = <0x021c8000 0x4000>;
				interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>;
				clocks = <&clks IMX6UL_CLK_LCDIF_PIX>,
					 <&clks IMX6UL_CLK_LCDIF_APB>,
					 <&clks IMX6UL_CLK_DUMMY>;
				clock-names = "pix", "axi", "disp_axi";
				status = "disabled";
			};

lcdif 节点信息是所有使用 I.MX6ULL 芯片的板子所共有的,并不是完整的 lcdif 节点信息。像屏幕参数这些需要根据不同的硬件平台去添加,比如向imx6ull-water-emmc.dts 中的 lcdif 节点添加其他的属性信息。

从示例代码 可以看出 lcdif 节点的 compatible 属性值为“fsl,imx6ul-lcdif”和“fsl,imx28-lcdif”,因此在 Linux 源码中搜索这两个字符串即可找到 I.MX6ULL 的 LCD 驱动文件。

6.2.2 NXP官方LCD驱动

根据设备树compatible 属性值搜索到I.MX6ULL 的 LCD 驱动文件,在此文件中找到如下内容:

路径:drivers/video/fbdev/mxsfb.c
tatic const struct of_device_id mxsfb_dt_ids[] = {
	{ .compatible = "fsl,imx23-lcdif", .data = &mxsfb_devtype[0], },
	{ .compatible = "fsl,imx28-lcdif", .data = &mxsfb_devtype[1], },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mxsfb_dt_ids);

.....
static struct platform_driver mxsfb_driver = {
	.probe = mxsfb_probe,
	.remove = mxsfb_remove,
	.shutdown = mxsfb_shutdown,
	.id_table = mxsfb_devtype,
	.driver = {
		   .name = DRIVER_NAME,
		   .of_match_table = mxsfb_dt_ids,
		   .pm = &mxsfb_pm_ops,
	},
};

module_platform_driver(mxsfb_driver);

从上述代码 可以看出,这是一个标准的 platform 驱动,当驱动和设备匹配以后mxsfb_probe 函数就会执行。在看 mxsfb_probe 函数之前我们先简单了解一下 Linux 下Framebuffer 驱动的编写流程,Linux 内核将所有的 Framebuffer 抽象为一个叫做 fb_info 的结构体,fb_info 结构体包含了 Framebuffer 设备的完整属性和操作集合,因此每一个 Framebuffer 设备都必须有一个 fb_info。换言之就是,LCD 的驱动就是构建 fb_info,并且向系统注册 fb_info的过程。fb_info 结构体定义在 include/linux/fb.h 文件里面,内容如下:

路径:include/linux/fb.h
struct fb_info {
	atomic_t count;
	int node;
	int flags;
	struct mutex lock;		/* Lock for open/release/ioctl funcs */	/* 互斥锁 */
	struct mutex mm_lock;		/* Lock for fb_mmap and smem_* fields *//* 互斥锁,用于 fb_mmap 和 smem_*域*/
	struct fb_var_screeninfo var;	/* Current var *//* 当前可变参数 */
	struct fb_fix_screeninfo fix;	/* Current fix */ /* 当前固定参数 */
	struct fb_monspecs monspecs;	/* Current Monitor specs */ /* 当前显示器特性 */
	struct work_struct queue;	/* Framebuffer event queue */ /* 帧缓冲事件队列 */
	struct fb_pixmap pixmap;	/* Image hardware mapper */ /* 图像硬件映射 */
	struct fb_pixmap sprite;	/* Cursor hardware mapper */ /* 光标硬件映射 */
	struct fb_cmap cmap;		/* Current cmap */	/* 当前调色板 */
	struct list_head modelist;      /* mode list */	/* 当前模式列表 */
	struct fb_videomode *mode;	/* current mode */	/* 当前视频模式 */

#ifdef CONFIG_FB_BACKLIGHT  /* 如果 LCD 支持背光的话 */
	/* assigned backlight device */
	/* set before framebuffer registration, 
	   remove after unregister */
	struct backlight_device *bl_dev;	/* 背光设备 */

	/* Backlight level curve */
	struct mutex bl_curve_mutex;	
	u8 bl_curve[FB_BACKLIGHT_LEVELS];
#endif
#ifdef CONFIG_FB_DEFERRED_IO
	struct delayed_work deferred_work;
	struct fb_deferred_io *fbdefio;
#endif

	struct fb_ops *fbops;		/* 帧缓冲操作函数集 */
	struct device *device;		/* This is the parent */ /* 父设备 */
	struct device *dev;		/* This is this fb device */ /* 当前 fb 设备 */
	int class_flag;                    /* private sysfs flags */ /* 私有 sysfs 标志 */
#ifdef CONFIG_FB_TILEBLITTING
	struct fb_tile_ops *tileops;    /* Tile Blitting */
#endif
	char __iomem *screen_base;	/* Virtual address */	/* 虚拟内存基地址(屏幕显存) */
	unsigned long screen_size;	/* Amount of ioremapped VRAM or 0 */  /* 虚拟内存大小(屏幕显存大小) */
	void *pseudo_palette;		/* Fake palette of 16 colors */  /* 伪 16 位调色板 */
#define FBINFO_STATE_RUNNING	0
#define FBINFO_STATE_SUSPENDED	1
	u32 state;			/* Hardware state i.e suspend */
	void *fbcon_par;                /* fbcon use-only private area */
	/* From here on everything is device dependent */
	void *par;
	/* we need the PCI or similar aperture base/size not
	   smem_start/size as smem_start may just be an object
	   allocated inside the aperture so may not actually overlap */
	struct apertures_struct {
		unsigned int count;
		struct aperture {
			resource_size_t base;
			resource_size_t size;
		} ranges[0];
	} *apertures;

	bool skip_vt_switch; /* no VT switch on suspend/resume required */
};

fb_info 结构体的成员变量很多,我们重点关注 var、fix、fbops、screen_base、screen_size和 pseudo_palette。mxsfb_probe 函数的主要工作内容为:
①、申请 fb_info。
②、初始化 fb_info 结构体中的各个成员变量。
③、初始化 eLCDIF 控制器。
④、使用 register_framebuffer 函数向 Linux 内核注册初始化好的 fb_info。

register_framebuffer函数原型如下:

int register_framebuffer(struct fb_info *fb_info)

函数参数和返回值含义如下:
fb_info:需要上报的 fb_info。
返回值:0,成功;负值,失败。

6.2.3 NXP官方LCD驱动xxx_probe函数

简单看一下 mxsfb_probe 函数,函数内容如下:

路径:drivers/video/fbdev/mxsfb.c
static int mxsfb_probe(struct platform_device *pdev)
{
	const struct of_device_id *of_id =
			of_match_device(mxsfb_dt_ids, &pdev->dev);
	struct resource *res;
	struct mxsfb_info *host;
	struct fb_info *fb_info;
	struct pinctrl *pinctrl;
	int irq = platform_get_irq(pdev, 0);
	int gpio, ret;

	if (of_id)
		pdev->id_entry = of_id->data;

	gpio = of_get_named_gpio(pdev->dev.of_node, "enable-gpio", 0);
	if (gpio == -EPROBE_DEFER)
		return -EPROBE_DEFER;

	if (gpio_is_valid(gpio)) {
		ret = devm_gpio_request_one(&pdev->dev, gpio, GPIOF_OUT_INIT_LOW, "lcd_pwr_en");
		if (ret) {
			dev_err(&pdev->dev, "faild to request gpio %d, ret = %d\n", gpio, ret);
			return ret;
		}
	}

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res) {
		dev_err(&pdev->dev, "Cannot get memory IO resource\n");
		return -ENODEV;
	}

	host = devm_kzalloc(&pdev->dev, sizeof(struct mxsfb_info), GFP_KERNEL);
	if (!host) {
		dev_err(&pdev->dev, "Failed to allocate IO resource\n");
		return -ENOMEM;
	}

	fb_info = framebuffer_alloc(sizeof(struct fb_info), &pdev->dev);
	if (!fb_info) {
		dev_err(&pdev->dev, "Failed to allocate fbdev\n");
		devm_kfree(&pdev->dev, host);
		return -ENOMEM;
	}
	host->fb_info = fb_info;
	fb_info->par = host;

	ret = devm_request_irq(&pdev->dev, irq, mxsfb_irq_handler, 0,
			  dev_name(&pdev->dev), host);
	if (ret) {
		dev_err(&pdev->dev, "request_irq (%d) failed with error %d\n",
				irq, ret);
		ret = -ENODEV;
		goto fb_release;
	}

	host->base = devm_ioremap_resource(&pdev->dev, res);
	if (IS_ERR(host->base)) {
		dev_err(&pdev->dev, "ioremap failed\n");
		ret = PTR_ERR(host->base);
		goto fb_release;
	}

	host->pdev = pdev;
	platform_set_drvdata(pdev, host);

	host->devdata = &mxsfb_devdata[pdev->id_entry->driver_data];

	host->clk_pix = devm_clk_get(&host->pdev->dev, "pix");
	if (IS_ERR(host->clk_pix)) {
		host->clk_pix = NULL;
		ret = PTR_ERR(host->clk_pix);
		goto fb_release;
	}

	host->clk_axi = devm_clk_get(&host->pdev->dev, "axi");
	if (IS_ERR(host->clk_axi)) {
		host->clk_axi = NULL;
		ret = PTR_ERR(host->clk_axi);
		goto fb_release;
	}

	host->clk_disp_axi = devm_clk_get(&host->pdev->dev, "disp_axi");
	if (IS_ERR(host->clk_disp_axi)) {
		host->clk_disp_axi = NULL;
		ret = PTR_ERR(host->clk_disp_axi);
		goto fb_release;
	}

	host->reg_lcd = devm_regulator_get(&pdev->dev, "lcd");
	if (IS_ERR(host->reg_lcd))
		host->reg_lcd = NULL;

	fb_info->pseudo_palette = devm_kzalloc(&pdev->dev, sizeof(u32) * 16,
					       GFP_KERNEL);
	if (!fb_info->pseudo_palette) {
		ret = -ENOMEM;
		goto fb_release;
	}

	INIT_LIST_HEAD(&fb_info->modelist);

	pm_runtime_enable(&host->pdev->dev);

	ret = mxsfb_init_fbinfo(host);
	if (ret != 0)
		goto fb_pm_runtime_disable;

	mxsfb_dispdrv_init(pdev, fb_info);

	if (!host->dispdrv) {
		pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
		if (IS_ERR(pinctrl)) {
			ret = PTR_ERR(pinctrl);
			goto fb_pm_runtime_disable;
		}
	}

	if (!host->enabled) {
		writel(0, host->base + LCDC_CTRL);
		mxsfb_set_par(fb_info);
		mxsfb_enable_controller(fb_info);
		pm_runtime_get_sync(&host->pdev->dev);
	}

	ret = register_framebuffer(fb_info);
	if (ret != 0) {
		dev_err(&pdev->dev, "Failed to register framebuffer\n");
		goto fb_destroy;
	}

	console_lock();
	ret = fb_blank(fb_info, FB_BLANK_UNBLANK);
	console_unlock();
	if (ret < 0) {
		dev_err(&pdev->dev, "Failed to unblank framebuffer\n");
		goto fb_unregister;
	}

	dev_info(&pdev->dev, "initialized\n");

	return 0;

fb_unregister:
	unregister_framebuffer(fb_info);
fb_destroy:
	if (host->enabled)
		clk_disable_unprepare(host->clk_pix);
	fb_destroy_modelist(&fb_info->modelist);
fb_pm_runtime_disable:
	pm_runtime_disable(&host->pdev->dev);
	devm_kfree(&pdev->dev, fb_info->pseudo_palette);
fb_release:
	framebuffer_release(fb_info);
	devm_kfree(&pdev->dev, host);

	return ret;
}

第 7行,host 结构体指针变量,表示 I.MX6ULL 的 LCD 的主控接口,mxsfb_info 结构体是 NXP 定义的针对 I.MX 系列 SOC 的 Framebuffer 设备结构体。也就是我们前面一直说的设备结构体,此结构体包含了 I.MX 系列 SOC 的 Framebuffer 设备详细信息,比如时钟、eLCDIF控制器寄存器基地址、fb_info 等。

第 28行,从设备树中获取 eLCDIF 接口控制器的寄存器首地址,设备树中 lcdif 节点已经设置了 eLCDIF 寄存器首地址为 0X021C8000,因此 res=0X021C8000。

第 34行,给 host 申请内存,host 为 mxsfb_info 类型结构体指针。

第 40行,给 fb_info 申请内存,也就是申请 fb_info。

第 46~47行,设置 host 的 fb_info 成员变量为 fb_info,设置 fb_info 的 par 成员变量为host。通过这一步就将前面申请的 host 和 fb_info 联系在了一起。

第 49行,申请中断,中断服务函数为 mxsfb_irq_handler。

第 58行,对从设备树中获取到的寄存器首地址(res)进行内存映射,得到虚拟地址,并保存到 host 的 base 成员变量。因此通过访问 host 的 base 成员即可访问 I.MX6ULL 的整个 eLCDIF寄存器。其实在 mxsfb.c 中已经定义了 eLCDIF 各个寄存器相比于基地址的偏移值,如下所示:

路径:drivers/video/fbdev/mxsfb.c
#define LCDC_CTRL			0x00
#define LCDC_CTRL1			0x10
#define LCDC_V4_CTRL2			0x20
#define LCDC_V3_TRANSFER_COUNT		0x20
#define LCDC_V4_TRANSFER_COUNT		0x30
#define LCDC_V4_CUR_BUF			0x40
#define LCDC_V4_NEXT_BUF		0x50
#define LCDC_V3_CUR_BUF			0x30
#define LCDC_V3_NEXT_BUF		0x40
#define LCDC_TIMING			0x60
#define LCDC_VDCTRL0			0x70
#define LCDC_VDCTRL1			0x80
#define LCDC_VDCTRL2			0x90
#define LCDC_VDCTRL3			0xa0
#define LCDC_VDCTRL4			0xb0
#define LCDC_DVICTRL0			0xc0
#define LCDC_DVICTRL1			0xd0
#define LCDC_DVICTRL2			0xe0
#define LCDC_DVICTRL3			0xf0
#define LCDC_DVICTRL4			0x100
#define LCDC_V4_DATA			0x180
#define LCDC_V3_DATA			0x1b0
#define LCDC_V4_DEBUG0			0x1d0
#define LCDC_V3_DEBUG0			0x1f0

大家可以对比着《I.MX6ULL 参考手册》中的 eLCDIF 章节检查一下代码中的这些寄存器有没有错误。

继续回到mxsfb_probe 函数

第95行,给 fb_info中的 pseudo_palette申请内存。

第 106行,调用 mxsfb_init_fbinfo 函数初始化 fb_info,重点是 fb_info 的 var、fix、fbops,screen_base 和 screen_size。其中 fbops 是 Framebuffer 设备的操作集,NXP 提供的 fbops 为mxsfb_ops,内容如下:

路径:drivers/video/fbdev/mxsfb.c
static struct fb_ops mxsfb_ops = {
	.owner = THIS_MODULE,
	.fb_check_var = mxsfb_check_var,
	.fb_set_par = mxsfb_set_par,
	.fb_setcolreg = mxsfb_setcolreg,
	.fb_ioctl = mxsfb_ioctl,
	.fb_blank = mxsfb_blank,
	.fb_pan_display = mxsfb_pan_display,
	.fb_mmap = mxsfb_mmap,
	.fb_fillrect = cfb_fillrect,
	.fb_copyarea = cfb_copyarea,
	.fb_imageblit = cfb_imageblit,
};

mxsfb_init_fbinfo 函数通过调用 mxsfb_init_fbinfo_dt 函数从设备树中获取到 LCD 的各个参数信息。最后,mxsfb_init_fbinfo函数会调用 mxsfb_map_videomem 函数申请 LCD 的帧缓冲内存(也就是显存)。

第 122~123行,设置 eLCDIF 控制器的相应寄存器。

第 127行,最后调用 register_framebuffer 函数向 Linux 内核注册 fb_info。

mxsfb.c 文件很大,还有一些其他的重要函数,比如 mxsfb_remove、mxsfb_shutdown 等,这里我们就简单的介绍了一下 mxsfb_probe 函数,至于其他的函数大家自行查阅。

七 LCD驱动适配

前面已经说了,6ULL 的 eLCDIF 接口驱动程序 NXP 已经编写好了,因此 LCD 驱动部分不需要去修改。我们需要做的就是按照所使用的 LCD 来修改设备树。重点要注意三个地方:
①、LCD 所使用的 IO 配置。
②、LCD 屏幕节点修改,修改相应的属性值,换成我们所使用的 LCD 屏幕参数。
③、LCD 背光节点信息修改,要根据实际所使用的背光 IO 来修改相应的设备节点信息。

接下来我们依次来看一下上面这两个节点改如何去修改:

7.1 LCD 屏幕 IO 配置

首先要检查一下设备树中 LCD 所使用的 IO 配置,这个其实 NXP 都已经给我们写好了,不需要修改,不过我们还是要看一下。打开 imx6ull-water-emmc.dts 文件,在 iomuxc 节点中找到如下内容:

路径:arch/arm/boot/dts/imx6ull-water-emmc.dts
		pinctrl_lcdif_dat: lcdifdatgrp {
			fsl,pins = <
				MX6UL_PAD_LCD_DATA00__LCDIF_DATA00  0x79
				MX6UL_PAD_LCD_DATA01__LCDIF_DATA01  0x79
				MX6UL_PAD_LCD_DATA02__LCDIF_DATA02  0x79
				MX6UL_PAD_LCD_DATA03__LCDIF_DATA03  0x79
				MX6UL_PAD_LCD_DATA04__LCDIF_DATA04  0x79
				MX6UL_PAD_LCD_DATA05__LCDIF_DATA05  0x79
				MX6UL_PAD_LCD_DATA06__LCDIF_DATA06  0x79
				MX6UL_PAD_LCD_DATA07__LCDIF_DATA07  0x79
				MX6UL_PAD_LCD_DATA08__LCDIF_DATA08  0x79
				MX6UL_PAD_LCD_DATA09__LCDIF_DATA09  0x79
				MX6UL_PAD_LCD_DATA10__LCDIF_DATA10  0x79
				MX6UL_PAD_LCD_DATA11__LCDIF_DATA11  0x79
				MX6UL_PAD_LCD_DATA12__LCDIF_DATA12  0x79
				MX6UL_PAD_LCD_DATA13__LCDIF_DATA13  0x79
				MX6UL_PAD_LCD_DATA14__LCDIF_DATA14  0x79
				MX6UL_PAD_LCD_DATA15__LCDIF_DATA15  0x79
				MX6UL_PAD_LCD_DATA16__LCDIF_DATA16  0x79
				MX6UL_PAD_LCD_DATA17__LCDIF_DATA17  0x79
				MX6UL_PAD_LCD_DATA18__LCDIF_DATA18  0x79
				MX6UL_PAD_LCD_DATA19__LCDIF_DATA19  0x79
				MX6UL_PAD_LCD_DATA20__LCDIF_DATA20  0x79
				MX6UL_PAD_LCD_DATA21__LCDIF_DATA21  0x79
				MX6UL_PAD_LCD_DATA22__LCDIF_DATA22  0x79
				MX6UL_PAD_LCD_DATA23__LCDIF_DATA23  0x79
			>;
		};

		pinctrl_lcdif_ctrl: lcdifctrlgrp {
			fsl,pins = <
				MX6UL_PAD_LCD_CLK__LCDIF_CLK	    0x79
				MX6UL_PAD_LCD_ENABLE__LCDIF_ENABLE  0x79
				MX6UL_PAD_LCD_HSYNC__LCDIF_HSYNC    0x79
				MX6UL_PAD_LCD_VSYNC__LCDIF_VSYNC    0x79
			>;
		};

		pinctrl_pwm1: pwm1grp {
			fsl,pins = <
				MX6UL_PAD_GPIO1_IO08__PWM1_OUT   0x110b0
			>;
		};

第 3~28 行,子节点 pinctrl_lcdif_dat,为 RGB LCD 的 24 根数据线配置项。

第 31~37 行,子节点 pinctrl_lcdif_ctrl,RGB LCD 的 4 根控制线配置项,包括 CLK、ENABLE、VSYNC 和 HSYNC。

第 40~44 行,子节点 pinctrl_pwm1,LCD 背光 PWM 引脚配置项。这个引脚要根据实际情况设置,这里我们建议大家在以后的学习或工作中,LCD 的背光 IO 尽量和半导体厂商的官方开发板一致。

注意上述代码中默认将 LCD 的电气属性都设置为 0X79,这里将其都改为 0X49,也就是将 LCD 相关 IO 的驱动能力改为 R0/1,也就是降低 LCD 相关 IO 的驱动能力。因为前面已经说了,笔者所用 开发板上的 LCD 接口用了三个 SGM3157 模拟开关,为了防止模拟开关影响到网络,因此这里需要降低 LCD 数据线的驱动能力,如果你所使用的板子没有用到模拟开关那么就不需要将 0X79 改为 0X49。
修改后的设备树如下:

		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
			>;
		};

7.2 LCD 屏幕参数节点信息修改

继续在imx6ull-water-emmc.dts 文件中找到 lcdif 节点,节点内容如下所示:

&lcdif {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_lcdif_dat		/* 使用到的 IO */
		     &pinctrl_lcdif_ctrl
		     &pinctrl_lcdif_reset>;
	display = <&display0>;
	status = "okay";

	display0: display {			/* LCD 属性信息 */
		bits-per-pixel = <16>;	/* 一个像素占用几个 bit */
		bus-width = <24>;		/* 总线宽度 */

		display-timings {			/* 时序信息 */
			native-mode = <&timing0>;
			timing0: timing0 {
			clock-frequency = <9200000>;	/* LCD 像素时钟,单位 Hz */
			hactive = <480>;				/* LCD X 轴像素个数 */
			vactive = <272>;				/* LCD Y 轴像素个数 */
			hfront-porch = <8>;				/* LCD hfp 参数 */
			hback-porch = <4>;				/* LCD hbp 参数 */
			hsync-len = <41>;				/* LCD hspw 参数 */
			vback-porch = <2>;				/* LCD vbp 参数 */
			vfront-porch = <4>;				/* LCD vfp 参数 */
			vsync-len = <10>;				/* LCD vspw 参数 */

			hsync-active = <0>;				/* hsync 数据线极性 */
			vsync-active = <0>;				/* vsync 数据线极性 */
			de-active = <1>;				/* de 数据线极性 */
			pixelclk-active = <0>;			/* clk 数据线先极性 */
			};
		};
	};
};

上述就是向 imx6ull.dtsi 文件中的 lcdif 节点追加的内容,我们依次来看一下上述代码中的这些属性都是些什么含义。

第 3 行,pinctrl-0 属性,LCD 所使用的 IO 信息,这里用到了 pinctrl_lcdif_dat、pinctrl_lcdif_ctrl和 pinctrl_lcdif_reset 这三个 IO 相关的节点,前两个在7.1小节中已经讲解了。pinctrl_lcdif_reset 是 LCD 复位 IO 信息节点, 作者用的I.MX6U 开发板的 LCD 没有用到复位 IO,因此 pinctrl_lcdif_reset 可以删除掉。

第 6 行,display 属性,指定 LCD 属性信息所在的子节点,这里为 display0,下面就是 display0子节点内容。

第 9~32 行,display0 子节点,描述 LCD 的参数信息,第 10 行的 bits-per-pixel 属性用于指明一个像素占用的 bit 数,默认为 16bit。本教程我们将 LCD 配置为 RGB888 模式,因此一个像素点占用 24bit,bits-per-pixel 属性要改为 24。第 11 行的 bus-width 属性用于设置数据线宽度,因为要配置为 RGB888 模式,因此 bus-width 也要设置为 24。

第 13~30 行,这几行非常重要!因为这几行设置了 LCD 的时序参数信息,NXP 官方的 EVK开发板使用了一个 4.3 寸的 480*272 屏幕,因此这里默认是按照 NXP 官方的那个屏幕参数设置的。每一个属性的含义后面的注释已经写的很详细了,大家自己去看就行了,这些时序参数就是我们重点要修改的,需要根据自己所使用的屏幕去修改,如2.6节分析的参数值。

修改lcdif 节点后如下:

&lcdif {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_lcdif_dat
		     &pinctrl_lcdif_ctrl>;
	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>;
			};
		};
	};
};

第 3 行,设置 LCD 屏幕所使用的 IO,删除掉原来的 pinctrl_lcdif_reset,因为没有用到屏
幕复位 IO,其他的 IO 不变。
第 9 行,使用 RGB888 模式,所以一个像素点是 24bit。
第 15~23 行,ATK7016 屏幕时序参数,根据自己所使用的屏幕修改即可(笔者这里参考2.4小节所分析的参数设定)。

7.3 LCD 屏幕背光节点信息

作者的LCD 接口背光控制 IO 连接到了 I.MX6U 的 GPIO1_IO08 引脚上,GPIO1_IO08复用为 PWM1_OUT,通过 PWM 信号来控制 LCD 屏幕背光的亮度,这个我们已经在第五节分析过了。作者的I.MX6U-开发板的 LCD 背光引脚和 NXP 官方 EVK 开发板的背光引脚一样,因此背光的设备树节点是不需要修改的,但是考虑到读者可能使用别的开发板或者屏幕,LCD 背光引脚和 NXP 官方 EVK 开发板可能不同,因此我们还是来看一下如何在设备树中添加背光节点信息。

首先是 GPIO1_IO08 这个 IO 的配置,在imx6ull-water-emmc.dts 中找到如下内容:

pinctrl_pwm1: pwm1grp {
			fsl,pins = <
				MX6UL_PAD_GPIO1_IO08__PWM1_OUT   0x110b0
			>;
		};

pinctrl_pwm1 节点就是 GPIO1_IO08 的配置节点,从第 3 行可以看出,设置 GPIO1_IO08这个 IO 复用为 PWM1_OUT,并且设置电气属性值为 0x110b0。LCD 背光要用到 PWM1,因此也要设置 PWM1 节点,在 imx6ull.dtsi 文件中找到如下内容:
路径:arch/arm/boot/dts/imx6ull.dtsi

pwm1: pwm@02080000 {
				compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm";
				reg = <0x02080000 0x4000>;
				interrupts = <GIC_SPI 83 IRQ_TYPE_LEVEL_HIGH>;
				clocks = <&clks IMX6UL_CLK_PWM1>,
					 <&clks IMX6UL_CLK_PWM1>;
				clock-names = "ipg", "per";
				#pwm-cells = <2>;
			};

imx6ull.dtsi 文件中的 pwm1 节点信息大家不要修改,如果要修改 pwm1 节点内容的话请在imx6ull-water-emmc.dts 文件中修改。在整个 Linux 源码文件中搜索 compatible 属性的这两个值即可找到 imx6ull 的 pwm 驱动文件,imx6ull 的 PWM 驱动文件为 drivers/pwm/pwm-imx.c,这里我们就不详细的去分析这个文件了。继续在imx6ull-water-emmc.dts 文件中找到向 pwm1追加的内容,如下所示:

&pwm1 {
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_pwm1>;
	status = "okay";
};

第 3 行,设置 pwm1 所使用的 IO 为 pinctrl_pwm1,也就是pinctrl定义的GPIO1_IO08 这个 IO。

第 4 行,将 status 设置为 okay。

如果背光用的其他 pwm 通道,比如 pwm2,那么就需要仿照上述pinctrl代码的内容,向pwm2 节点追加相应的内容。pwm 和相关的 IO 已经准备好了,但是 Linux 系统怎么知道PWM1_OUT 就是控制 LCD背光的呢?因此我们还需要一个节点来将 LCD 背光和 PWM1_OUT连接起来。

这个节点就是 backlight , backlight 节点描述可以参考
Documentation/devicetree/indings/video/backlight/pwm-backlight.txt 这个文档,此文档详细讲解了backlight 节点该如何去创建,这里大概总结一下:
①、节点名称要为“backlight”。

②、节点的 compatible 属性值要为“pwm-backlight”,因此可以通过在 Linux 内核中搜索“ pwm-backlight ” 来 查 找 PWM 背 光 控 制 驱 动 程 序 , 这 个 驱 动 程 序 文 件 为drivers/video/backlight/pwm_bl.c,感兴趣的可以去看一下这个驱动程序。

③、pwms属性用于描述背光所使用的PWM以及PWM频率,比如本章我们要使用的pwm1,pwm 频率设置为 200Hz(NXP 官方推荐设置)。

④、brightness-levels 属性描述亮度级别,范围为 0~255,0 表示 PWM 占空比为 0%,也就是亮度最低,255 表示 100%占空比,也就是亮度最高。至于设置几级亮度,大家可以自行填写此属性。

⑤、default-brightness-level 属性为默认亮度级别。

根据上述 5 点设置 backlight 节点,这个 NXP 已经给我们设置好了,大家在 imx6ull-water-emmc.dts 文件中找到如下内容:

backlight {
		compatible = "pwm-backlight";
		pwms = <&pwm1 0 5000000>;
		brightness-levels = <0 4 8 16 32 64 128 255>;
		default-brightness-level = <6>;
		status = "okay";
	};

第 3 行,设置背光使用 pwm1,PWM 频率为 200Hz。

第 4 行,设置背 8 级背光(0~7),分别为 0、4、8、16、32、64、128、255,对应占空比为0%、1.57%、3.13%、6.27%、12.55%、25.1%、50.19%、100%,如果嫌少的话可以自行添加一些其他的背光等级值。

第 5 行,设置默认背光等级为 6,也就是 50.19%的亮度。

关于背光的设备树节点信息就讲到这里,整个的 LCD 设备树节点内容我们就讲完了,按照这些节点内容配置自己的开发板即可。

八 LCD驱动适配验证

8.1 编译DTS/kernel

根据第七节的修改后重新编译DTS,用新编译的DTB文件启动。
linux内核有一个自带的开机Logo,为了验证LCD是否正常显示,我们开启自带的Logo,配置如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
上图中这三个选项分别对应黑白、16 位、24 位色彩格式的 logo,我们把这三个都选中,都编译进 Linux 内核里面。设置好以后保存退出,重新编译 Linux 内核,编译完成以后使用新编译出来的 imx6ull-water-emmc.dtb 和 zImage 镜像启动系统,如果 LCD 驱动工作正常的话就会在 LCD 屏幕左上角出现一个彩色的小企鹅 logo,屏幕背景色为黑色,如下图所示:
在这里插入图片描述

8.2 设置 LCD 作为终端控制台

我们一直使用SecureCRT作为Linux开发板终端,开发板通过串口和 SecureCRT进行通信。现在我们已经驱动起来 LCD 了,所以可以设置 LCD 作为终端,也就是开发板使用自己的显示设备作为自己的终端,然后接上键盘就可以直接在开发板上敲命令了,将 LCD 设置为终端控制台的方法如下:
(1)设置 uboot 中的 bootargs
重启开发板,进入 Linux 命令行,重新设置 bootargs 参数的 console 内容,命令如下所示:

setenv bootargs 'console=tty1 console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.0.116:/home/water/imax/nfs/buildrootfs,v3 proto=tcp rw ip=192.168.0.117:192.168.0.116:192.168.0.1:255.255.255.0::eth0:off'
saveenv //保存环境变量

在这里插入图片描述
这里我们设置了两遍 console,第一次设置 console=tty1,也就是设置 LCD 屏幕为控制台,第二遍又设置console=ttymxc0,115200,也就是设置串口也作为控制台。相当于我们打开了两个 console,一个是 LCD,一个是串口,大家重启开发板就会发现 LCD 和串口都会显示 Linux 启动 log 信息。但是此时我们还不能使用 LCD 作为终端进行交互,因为我们的设置还未完成。
在这里插入图片描述

(2)修改/etc/inittab 文件

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

tty1::respawn:/sbin/getty -L  tty1 0 vt100 # GENERIC_SERIAL

在这里插入图片描述
可能有的教程是加tty1::askfirst:-/bin/sh,其实加入的内容与console保持一直即可。
重启设备后如下:
在这里插入图片描述
前面学习<Linux开发>驱动开发 -之-Linux INPUT 子系统时已经将开发板上的key0设置会回车键, 所以按下key0可看到LCD显示屏有换行的变化。

也可以接上一个 USB 键盘,Linux 内核默认已经使能了 USB 键盘驱动
了,因此可以直接使用 USB 键盘。
在这里插入图片描述

8.3 LCD 背光调节

背光设备树节点设置了 8 个等级的背光调节,可以设置为 0~7,可以通过设置背光等级来实现 LCD 背光亮度的调节,进入如下目录:

/sys/devices/platform/backlight/backlight/backlight

在这里插入图片描述
在这里插入图片描述
当前屏幕亮度等级为 6,根据前面的分析可以,这个是 50%亮度。屏幕最大亮度等级为 7。如果我们要修改屏幕亮度,只需要向 brightness 写入需要设置的屏幕亮度等级即可。比如设置屏幕亮度等级为 7,那么可以使用如下命令:

echo 7 > brightness

输入上述命令以后就会发现屏幕亮度增大了,如果设置 brightness 为 0 的话就会关闭 LCD背光,屏幕就会熄灭。

8.4 LCD 自动关闭解决方法

默认情况下 10 分钟以后 LCD 就会熄屏,这个并不是代码有问题,而是 Linux 内核设置的,就和我们用手机或者电脑一样,一段时间不操作的话屏幕就会熄灭,以节省电能。解决这个问题有多种方法,我们依次来看一下:

(1)键盘唤醒
按下开发板上的 KEY 按键即可唤醒屏幕。如果开发板上没有按键的话可以外接 USB 键盘,然后按下 USB 键盘上的回车键唤醒屏幕。

(2)关闭 10 分钟熄屏功能
在 Linux 源码中找到 drivers/tty/vt/vt.c 这个文件,在此文件中找到 blankinterval 变量,如下所示:

路径:drivers/tty/vt/vt.c
static int vesa_blank_mode; /* 0:none 1:suspendV 2:suspendH 3:powerdown */
static int vesa_off_interval;
static int blankinterval = 10*60;
core_param(consoleblank, blankinterval, int, 0444);

blankinterval 变量控制着 LCD 关闭时间,默认是 10*60,也就是 10 分钟。将 blankinterval的值改为 0 即可关闭 10 分钟熄屏的功能,修改完成以后需要重新编译 Linux 内核,得到新的zImage,然后用新的 zImage 启动开发板。

(3) 编写一个 APP 来关闭熄屏功能
在 ubuntu 中新建一个名为 lcd_always_on.c 的文件,然后在里面输入如下所示内容:

#include <fcntl.h>
#include <stdio.h>
#include <sys/ioctl.h>

int main(int argc, char *argv[])
{
	int fd;
	fd = open("/dev/tty1", O_RDWR);
	write(fd, "\033[9;0]", 8);
	close(fd);
	return 0;	
}

使用如下命令编译 lcd_always_on.c 这个文件:

arm-linux-gnueabihf-gcc lcd_always_on.c -o lcd_always_on

编译生成 lcd_always_on 以后将此可执行文件拷贝到开发板根文件系统的/usr/bin 目录中,然后给予可执行权限。设置 lcd_always_on 这个软件为开机自启动,打开/etc/init.d/rcS,在此文件最后面加入如下内容:

cd /usr/bin
./lcd_always_on
cd ..

修改完成以后保存/etc/init.d/rcS 文件,然后重启开发板即可。

九 总结

本文主要是了解IMX6ULL平台下的LCD使用,第二节主要是对LCD的有个认识,第三节是对IMX6ULL平台下的eLCDIF 外设的使用进行介绍。不同平台LCD的使用接口不同,而且不同LCD也有不同的通信接口方式。本文主要是对IMX平台LCD进行介绍。后续笔者也会出其它平台/其它接口的LCD相关文章吗,欢迎关注。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/677310.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网络基础一

网络发展 独立模式&#xff1a;计算机之间相互独立。 网络互联&#xff1a;多台计算机连接在一起&#xff0c;完成数据共享。 局域网LAN&#xff1a;计算机数量更多了&#xff0c;通过交换机和路由器连接在一起&#xff1b; 广域网WAN&#xff1a;将远隔千里的计算机都连在…

[BPU部署教程] 万字长文!通透解读模型部署端到端大流程——以终为始,以行为知

去年6月份拿到开发板到现在&#xff0c;转眼已经过去大半年了&#xff0c;这个博客11月初就在写&#xff0c;断断续续写到现在。C部署需要考虑的问题很多&#xff0c;如果只给个简单部署教程的话&#xff0c;就算整理出来&#xff0c;感觉帮助也不大&#xff0c;各位开发时候我…

YOLOv5改进系列(11)——添加损失函数之EIoU、AlphaIoU、SIoU、WIoU

【YOLOv5改进系列】前期回顾: YOLOv5改进系列(0)——重要性能指标与训练结果评价及分析 YOLOv5改进系列(1)——添加SE注意力机制

模版方法模式在 JDK 及 spring 源码中的应用

模版方法模式 模板方法模式是一种行为设计模式&#xff0c; 它在超类中定义了一个算法的框架&#xff0c; 允许子类在不修改结构的情况下重写算法的特定步骤。 更多有关于模版方法模式的介绍详见&#xff1a;https://refactoringguru.cn/design-patterns/template-method 模版…

津津乐道设计模式 - 委派模式详解(以家庭弟位让你彻底明白)

&#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Micro麦可乐的博客 &#x1f425;《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程&#xff0c;入门到实战 &#x1f33a;《RabbitMQ》…

STM32 Proteus仿真DHT11温度湿度光敏光强DS1302闹钟-0044

STM32 Proteus仿真DHT11温度湿度光敏光强DS1302闹钟-0044 Proteus仿真小实验&#xff1a; STM32 Proteus仿真DHT11温度湿度光敏光强DS1302闹钟-0044 功能&#xff1a; 硬件组成&#xff1a; STM32F103C6T6单片机 DHT11温度湿度光敏电阻采集光强 多个按键模拟红外遥控1个LED…

node.js--vue仓库进销存管理信息系统whkb8

随着社会的发展&#xff0c;系统的管理形势越来越严峻。越来越多的用户利用互联网获得信息&#xff0c;但各种信息鱼龙混杂&#xff0c;信息真假难以辨别。为了方便用户更好的获得仓库管理信息&#xff0c;因此&#xff0c;设计一种安全高效的仓库管理信息系统极为重要。 为设计…

【从零开始学习JAVA | 第十五篇】 多态

前言&#xff1a; 本篇我们来解释一下什么是多态关系&#xff0c;多态关系属于面向对象三大特征的最后一个&#xff0c;可以说面向对象的重点就在多态&#xff0c;因此我们要学好面向对象编程思想&#xff0c;就要学好多态。 多态&#xff1a; Java中的多态是指同一类对象在不同…

nssctf之SSRF刷题记录

[NISACTF 2022]easyssrf 题目讲的主要是ssrf以及php伪协议的能力&#xff0c;题目详情如下 一般来说&#xff0c;当一个网站出现curl类的功能时就可能会出现ssrf之类的漏洞&#xff0c;常见的ssrf协议如下 file:/// dict:// sftp:// ldap:// tftp:// gopher://file:// 这种…

基于python的matplotlib、numpy库实现的图形绘制(数据可视化)

一、sin&#xff0c;cos函数 1.题目要求 编写程序&#xff0c;绘制正弦曲线和余弦曲线。 提示&#xff1a;利用numpy的linspace()、sin()或cos()函数生成样本数据、正弦或余弦值。 2.函数讲解及代码 import matplotlib.pyplot as plt import numpy as np#linspace函数是用…

【MySQL数据库 | 第二十篇】explain执行计划

目录 前言&#xff1a; explain&#xff1a; 语法&#xff1a; 总结&#xff1a; 前言&#xff1a; 上一篇我们介绍了从时间角度分析MySQL语句执行效率的三大工具&#xff1a;SQL执行频率&#xff0c;慢日志查询&#xff0c;profile。但是这三个方法也只是在时间角度粗略的…

kubernetes入门案例

kubernetes入门案例 本文我们通过一个 Java Web 应用例子来介绍 kubernetes 的使用&#xff0c;可以让新手快速上手和实践。 此 Java Web 应用的结构比较简单&#xff0c;是一个运行在 Tomcat 里的 Web App&#xff0c;JSP 页面通过 JDBC 直接访问 MySQL 数据库并展示数据。…

青梅产业成立“国家队”,溜溜梅迎来树立品牌良机?

扩大内需在今年政府工作报告中被频频提及。国务院发展研究中心原副主任王一鸣认为&#xff0c;激活潜在消费需求&#xff0c;将释放中国超大规模市场的经济增长潜力。 如今&#xff0c;休闲零食市场也正在经历这样一场激活潜在需求的变革。无论是洽洽食品、三只松鼠、良品铺子…

Kubernetes(k8s)容器编排概述

目录 1 k8s 是什么2 K8s的由来2.1 K8s发展历程2.2 发展时间线 3 为什么使用k8s3.1 什么是容器3.2 什么是 Kubernetes3.3 K8s 的著名优势特色3.3.1 一个平台搞定所有3.3.2 云环境无缝迁移3.3.3 高效的利用资源3.3.4 开箱即用的自动缩放能力3.3.5 使 CI/CD 更加简单3.3.6 可靠性 …

香橙派 1. 上手,配置wifi以及vnc

0. 环境 香橙派4以及电源 读卡器 32GB TF卡 1. 重新烧写固件 Orangepi4_2.1.2_ubuntu_bionic_desktop_linux4.4.179.img 用sd card formatter 格式化TF卡 安装Win32DiskImager&#xff0c;打开&#xff0c;选择IMG&#xff0c;确认U盘&#xff0c;点击写入。 2. 插上TTL 注意…

Mybatis相关知识(2)

Mybatis相关知识 今天接着上期mybatis相关知识进行讲解&#xff0c;今天主要是讲解mybatis和数据库相关的映射&#xff0c;标签和SQL编写等。 会结合实际业务和代码进行讲解。 1 占位符和传参的相关问题 先来看两条xml的SQL。第一条SQL从id名称可知&#xff0c;是根据id删除数…

cookie、session、token学习笔记

一.cookie 1.什么是cookie&#xff1f; Cookie用于存储web页面的用户信息。 Cookie是一些数据&#xff0c;存储于你电脑的文本文件中。 当web服务器向浏览器发送web页面时&#xff0c;在连接关闭后&#xff0c;服务端不会记录用户的信息。 而Cookie的作用就是用于解决“如…

【unity每日一记】 三大金星之(音频Audio + 碰撞和触发+光源组件)

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;uni…

Kubernetes(k8s)部署模式发展

目录 1 简介2 物理单机(~2000)2.1 主要代表 3 虚拟化&#xff1a;初期&#xff08;2001~2009&#xff09;3.1 VMware3.2 laaS 4 虚拟化&#xff1a;成熟期&#xff08;2010~至今&#xff09;4.1 OpenStack4.2 虚拟化四巨头 5 容器化:&#xff08;2013-至今&#xff09;5.1 Dock…

【从零开始学习JAVA | 第十三篇】继承

目录 前言&#xff1a; 引入&#xff1a; 继承&#xff1a; 小拓展&#xff1a; 优点&#xff1a; 成员方法的继承问题&#xff1a; 总结&#xff1a; 前言&#xff1a; 继承是面向对象三大特性之一&#xff0c;它是在封装之后我们讲解的一个重要的性质&#xff0c;继承…