正点原子 发表于 2020-6-6 11:40:17

【正点原子Linux连载】第二十四章RGBLCD显示实验--摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南

本帖最后由 正点原子 于 2020-10-24 16:06 编辑

1)实验平台:正点原子阿尔法Linux开发板
2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-300792-1-1.html
4)本章实例源码下载:
5)对正点原子Linux感兴趣的同学可以加群讨论:876919289
6)关注正点原子公众号,获取最新资料





第二十四章RGBLCD显示实验


      LCD液晶屏是常用到的外设,通过LCD可以显示绚丽的图形、界面等,提高人机交互的效率。I.MX6U提供了一个eLCDIF接口用于连接RGB接口的液晶屏。本章我们就学习如何驱动RGB接口液晶屏,并且在屏幕上显示字符。

24.1 LCD和eLCDIF简介
24.1.1 LCD简介
LCD全称是Liquid Crystal Display,也就是液晶显示器,是现在最常用到的显示器,手机、电脑、各种人机交互设备等基本都用到了LCD,最常见就是手机和电脑显示器了。由于笔者不是LCD从业人员,对于LCD的具体原理不了解,百度百科对于LCD的原理解释如下:
LCD 的构造是在两片平行的玻璃基板当中放置液晶盒,下基板玻璃上设置TFT(薄膜晶体管),上基板玻璃上设置彩色滤光片,通过TFT上的信号与电压改变来控制液晶分子的转动方向,从而达到控制每个像素点偏振光出射与否而达到显示目的。
我们现在要在I.MX6U-ALPHA开发板上使用LCD,所以不需要去研究LCD的具体实现原理,我们只需要从使用的角度去关注LCD的几个重要点:
1、分辨率
提起LCD显示器,我们都会听到720P、1080P、2K或4K这样的字眼,这个就是LCD显示器分辨率。LCD显示器都是由一个一个的像素点组成,像素点就类似一个灯(在OLED显示器中,像素点就是一个小灯),这个小灯是RGB灯,也就是由R(红色)、G(绿色)和B(蓝色)这三种颜色组成的,而RGB就是光的三原色。1080P的意思就是一个LCD屏幕上的像素数量是1920*1080个,也就是这个屏幕一列1080个像素点,一共1920列,如图24.1.1.1所示:

图24.1.1.1 LCD像素点排布
      在图24.1.1.1就是1080P 显示器的像素示意图,X轴就是LCD显示器的横轴,Y轴就是显示器的竖轴。图中的小方块就是像素点,一共有1920*1080=2073600个像素点。左上角的A点是第一个像素点,右下角的C点就是最后一个像素点。2K就是2560*1440个像素点,4K是3840*2160个像素点。很明显,在LCD尺寸不变的情况下,分辨率也高越清晰。同样的,分辨率不变的情况下,LCD尺寸越小越清晰。比如我们常用的24寸显示器基本都是1080P的,而我们现在使用的5寸的手机基本也是1080P的,但是手机显示细腻程度就要比24寸的显示器要好很多!
      由此可见,LCD显示器的分辨率是一个很重要的参数,但是并不是分辨率越高的LCD就越好。衡量一款LCD的好坏,分辨率只是其中的一个参数,还有色彩还原程度、色彩偏离、亮度、可视角度、屏幕刷新率等其他参数。
2、像素格式
上面讲了,一个像素点就相当于一个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个字节的内存,这四个字节每个位的分配如图24.1.1.2所示:

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

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



表24.1.1.1 RGB数据线
      表24.1.1.1就是RGBLCD的信号线,R、G和B这24根是数据线,DE、VSYNC、HSYNC和PCLK这四根是控制信号线。RGB LCD一般有两种驱动模式:DE模式和HV模式,这两个模式的区别是DE模式需要用到DE信号线,而HV模式不需要用到DE信号线,在DE模式下是可以不需要HSYNC信号线的,即使不接HSYNC信号线LCD也可以正常工作。
      ALIENTEK一共有三款RGB LCD屏幕,型号分别为:ATK-4342(4.3寸,480*272)、ATK-7084(7寸,800*480)和ATK-7016(7寸,1024*600),本教程就以ATK-7016这款屏幕为例讲解,ATK-7016的屏幕接口原理图如图24.1.1.4所示:

图24.1.1.4 RGB LCD液晶屏屏幕接口
      图中J1就是对外接口,是一个40PIN的FPC座(0.5mm间距),通过FPC线,可以连接到I.MX6U-ALPHA开发板上面,从而实现和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面板的分辨率和相关参数,以提高程序兼容性。这几个位的设置关系如表24.1.1.2所示:



表24.1.1.2 ALIENTEK RGBLCD模块ID对应关系
      ATK-7016模块,就设置M2:M0=010即可。这样,我们在程序里面,读取LCD_R7/G7/B7,得到M0:M2的值,从而判断RGBLCD模块的型号,并执行不同的配置,即可实现不同LCD模块的兼容。
4、LCD时间参数
      如果将LCD显示一帧图像的过程想象成绘画,那么在显示的过程中就是用一根“笔”在不同的像素点画上不同的颜色。这根笔按照从左至右、从上到下的顺序扫描每个像素点,并且在像素画上对应的颜色,当画到最后一个像素点的时候一幅图像就绘制好了。假如一个LCD的分辨率为1024*600,那么其扫描如图24.1.1.5所示:

图24.1.1.5 LCD一帧图像扫描图
      结合图24.1.1.4我们来看一下LCD是怎么扫描显示一帧图像的。一帧图像也是由一行一行组成的。HSYNC是水平同步信号,也叫做行同步信号,当产生此信号的话就表示开始显示新的一行了,所以此信号都是在图24.1.1.5的最左边。当VSYNC信号是垂直同步信号,也叫做帧同步信号,当产生此信号的话就表示开始显示新的一帧图像了,所以此信号在图24.1.1.4的左上角。
      在图24.1.1.5可以看到有一圈“黑边”,真正有效的显示区域是中间的白色部分。那这一圈“黑边”是什么东西呢?这就要从显示器的“祖先”CRT显示器开始说起了,CRT显示器就是以前很常见的那种大屁股显示器,在2019年应该很少见了,如果在农村应该还是可以见到的。CRT显示器屁股后面是个电子枪,这个电子枪就是我们上面说的“画笔”,电子枪打出的电子撞击到屏幕上的荧光物质使其发光。只要控制电子枪从左到右扫打万一行(也就是扫描一行),然后从上到下扫描完所有行,这样一帧图像就显示出来了。也就是说,显示一帧图像电子枪是按照‘Z’形在运动,当扫描速度很快的时候看起来就是一幅完成的画面了。
      当显示完一行以后会发出HSYNC信号,此时电子枪就会关闭,然后迅速的移动到屏幕的左边,当HSYNC信号结束以后就可以显示新的一行数据了,电子枪就会重新打开。在HSYNC信号结束到电子枪重新打开之间会插入一段延时,这段延时就图24.1.1.5中的HBP。当显示完一行以后就会关闭电子枪等待HSYNC信号产生,关闭电子枪到HSYNC信号产生之间会插入一段延时,这段延时就是图24.1.1.5中的HFP信号。同理,当显示完一帧图像以后电子枪也会关闭,然后等到VSYNC信号产生,期间也会加入一段延时,这段延时就是图24.1.1.5中的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数据手册了。
5、RGB LCD屏幕时序
      上面讲了行显示和帧显示,我们来看一下行显示对应的时序图,如图24.1.1.6所示:

图24.1.1.6 行显示时序
      图24.1.1.6就是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的帧显示时序如图24.1.1.7所示:

图24.1.1.7 帧显示时序图
      图24.1.1.7就是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。ALIENTEK三款RGB LCD屏幕的参数如表24.1.1.3所示:



表24.1.1.3 RGB LCD屏幕时间参数
      6、像素时钟
      像素时钟就是RGB LCD的时钟信号,以ATK7016这款屏幕为例,显示一帧图像所需要的时钟数就是:
= (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。
      I.MX6U的eLCDIF接口时钟图如图24.1.1.8所示:

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


表24.1.1.4LCDIF时钟源选择
      在第16章讲解I.MX6U时钟系统的时候说过有个专用的PLL5给VIDEO使用,所以LCDIF1_PRE_CLK_SEL设置为2。
      ②、此部分是LCDIF时钟的预分频器,由寄存器CCM_CSCDR2的位LCDIF1_PRED来决定预分频值。可设置值为0~7,分别对应1~8分频。
      ③、此部分进一步分频,由寄存器CBCMR的位LCDIF1_PODF来决定分频值。可设置值为0~7,分别对应1~8分频。
      ④、此部分是一个选择器,选择LCDIF最终的根时钟,由寄存器CSCDR2的位LCDIF1_CLK_SEL决定,LCDIF1_CLK_SEL选择设置如表24.1.1.5所示:

表24.1.1.4LCDIF根时钟选择
这里肯定选择PLL5出来的那一路时钟作为LCDIF的根时钟,因此LCDIF1_CLK_SEL设置为0。LCDIF既然选择了PLL5作为时钟源,那么还需要初始化PLL5,LCDIF的时钟是由PLL5和图24.1.1.8中的②、③这两个分频值决定的,所以需要对这三个进行合理的设置以搭配出所需的时钟值,我们就以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,此寄存器结构如图24.1.1.9所示:

图24.1.1.9寄存器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_ANALOG_MISC2的位VIDEO_DIV(bit31:30)与寄存器CCM_PLL_VIDEO的位POST_DIV_SLECT(bit20:19)共同决定了postDivider,通过这两个的配合可以获得2、4、8、16分频。本章将VIDEO_DIV设置为0,也就是1分频,因此postDivider就是1,loopDivider设置为32,PLL5的时钟频率就是:
PLL5_CLK = OSC24M * loopDivider / postDivider
                                                      =24M * 32/1
                                                      =768MHz。
      PLL5此时为768MHz,在经过图24.1.1.8中的②和③进一步分频,设置②中为3分频,也就是寄存器CCM_CSCDR2的位LCDIF1_PRED(bit14:12)为2。设置③中为5分频,就是寄存器CCM_CBCMR的位LCDIF1_PODF(bit25:23)为4。设置好以后最终进入到LCDIF的时钟频率就是:768/3/5=51.2MHz,这就是我们需要的像素时钟频率。
7、显存
在讲像素格式的时候就已经说过了,如果采用ARGB8888格式的话一个像素需要4个字节的内存来存放像素数据,那么1024*600分辨率就需要1024*600*4=2457600B≈2.4MB内存。但是RGB LCD内部是没有内存的,所以就需要在开发板上的DDR3中分出一段内存作为RGB LCD屏幕的显存,我们如果要在屏幕上显示什么图像的话直接操作这部分显存即可。
24.1.2eLCDIF接口
      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.6 MPU 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接口时序如图24.1.2.1所示:

图24.1.2.1 DOTCLK接口时序
      图24.1.2.1是不是和图24.1.1.6、图24.1.1.7很类似,因为DOTCLK接口就是连接RGB屏幕的,本教程使用的就是DOTCLK接口。
      eLCDIF要驱动起来RGB LCD屏幕,重点是配置好上一小节讲解的那些时间参数即可,这个通过配置相应的寄存器就可以了,所以我们接下来看一下eLCDIF接口的几个重要的寄存器,首先看一下LCDIF_CTRL寄存器,此寄存器结构如图24.1.2.1所示:

图24.1.2.1寄存器LCDIF_CTRL结构
      寄存器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的使能位。
      接下来看一下寄存器LCDIF_CTRL1,此寄存器我们只用到位BYTE_PACKING_FORMAT(bit19:16),此位用来决定在32位的数据中哪些字节的数据有效,默认值为0XF,也就是所有的字节有效,当为0的话表示所有的字节都无效。如果显示的数据是24位(ARGB格式,但是A通道不传输)的话就设置此位为0X7。
      接下来看一下寄存器LCDIF_TRANSFER_COUNT,这个寄存器用来设置所连接的RGB LCD屏幕分辨率大小,此寄存器结构如图24.1.2.2所示:

图24.1.2.2寄存器LCDIF_TRANSFER_COUNT结构
      寄存器LCDIF_TRANSFER_COUNT分为两部分,高16位和低16位,高16位是V_COUNT,是LCD的垂直分辨率。低16位是H_COUNT,是LCD的水平分辨率。如果LCD分辨率为1024*600的话,那么V_COUNT就是600,H_COUNT就是1024。
      接下来看一下寄存器LCDIF_VDCTRL0,这个寄存器是VSYNC和DOTCLK模式控制寄存器0,寄存器结构如图24.1.2.3所示:

图24.1.2.3寄存器LCDIF_VDCTRL0结构
寄存器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参数设置位。
接下来看一下寄存器LCDIF_VDCTRL1,这个寄存器是VSYNC和DOTCLK模式控制寄存器1,此寄存器只有一个功能,用来设置VSYNC总周期,就是:屏幕高度+VSPW+VBP+VFP。
接下来看一下寄存器LCDIF_VDCTRL2,这个寄存器分为高16位和低16位两部分,高16位是HSYNC_PULSE_WIDTH,用来设置HSYNC信号宽度,也就是HSPW。低16位是HSYNC_PERIOD,设置HSYNC总周期,就是:屏幕宽度+HSPW+HBP+HFP。
接下来看一下寄存器LCDIF_VDCTRL3,此寄存器结构如图24.1.2.4所示:

图24.1.2.4寄存器LCDIF_VDCTRL3结构
寄存器LCDIF_VDCTRL3用到的重要位如下:
      HORIZONTAL_WAIT_CNT(bit27:16):此位用于DOTCLK模式,用于设置HSYNC信号产生到有效数据产生之间的时间,也就是HSPW+HBP。
      VERTICAL_WAIR_CNT(bit15:0):和HORIZONTAL_WAIT_CNT一样,只是此位用于VSYNC信号,也就是VSPW+VBP。
      接下来看一下寄存器LCDIF_VDCTRL4,此寄存器结构如图24.1.2.5所示:

图24.1.2.5寄存器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接口的寄存器就介绍到这里,关于这些寄存器详细的描述,请参考《I.MX6ULL参考手册》第2165页的34.6小节。本章我们使用I.MX6U的eLCDIF接口来驱动ALIENTEK的ATK7016这款屏幕,配置步骤如下:
      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函数,比如画点、画线、画圆函数,字符串显示函数等。
24.2硬件原理分析
      本试验用到的资源如下:
、指示灯LED0。
、RGB LCD接口。
③、DDR3
④、eLCDIF
      RGB LCD接口在I.MX6U-ALPHA开发板底板上,原理图如图24.2.1所示:

图24.2.1 RGB LCD接口原理图
      图24.2.1中三个SGM3157的目的是在未使用RGBLCD的时候将LCD_DATA7、LCD_DATA15和LCD_DATA23这三个线隔离开来,因为ALIENTEK的屏幕的LCD_R7/G7/B7着几个线用来设置LCD的ID,所以这几根线上有上拉/下拉电阻。但是I.MX6U的BOOT设置也用到了LCD_DATA7、LCD_DATA15和LCD_DATA23这三个引脚,所以接上屏幕以后屏幕上的ID电阻就会影响到BOOT设置,会导致代码无法运行,所以先将其隔离开来,如果要使用RGB LCD屏幕的时候再通过LCD_DE将其“连接”起来。我们需要40P的FPC线将ATK7016屏幕和I.MX6U-ALPHA开发板连接起来,如图24.2.2.2所示:

图24.2.2屏幕和开发板连接图
24.3实验程序编写
本实验对应的例程路径为:开发板光盘-> 1、裸机例程->15_lcd。
      本章实验在上一章例程的基础上完成,更改工程名字为“lcd”,然后在bsp文件夹下创建名为“lcd”的文件夹,在bsp/lcd中新建bsp_lcd.c、bsp_lcd.h、bsp_lcdapi.c、bsp_lcdapi.h和font.h这五个文件。bsp_lcd.c和bsp_lcd.h是LCD的驱动文件,bsp_lcdapi.c和bsp_lcdapi.h是LCD的API操作函数文件,font.h是字符集点阵数据数组文件。在bsp_lcd.h中输入如下内容:
示例代码24.3.1 bsp_lcd.h文件代码
1#ifndef _BSP_LCD_H
2#define _BSP_LCD_H
3/***************************************************************
4Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
5文件名    : bsp_lcd.h
6作者      : 左忠凯
7版本      : V1.0
8描述      : LCD驱动文件头文件。
9其他      : 无
10论坛      : www.openedv.com
11日志      : 初版V1.0 2019/1/3 左忠凯创建
12 ***************************************************************/
13 #include "imx6ul.h"
14
15/* 颜色宏定义 */
16 #define LCD_BLUE                     0x000000FF
17 #define LCD_GREEN                  0x0000FF00
18 #define LCD_RED                      0x00FF0000
19/*省略掉其它宏定义,完整的请参考实验例程*/
20 #define LCD_ORANGE                  0x00FFA500
21 #define LCD_TRANSPARENT         0x00000000
22
23 #define LCD_FRAMEBUF_ADDR      (0x89000000)      /* LCD显存地址 */
24
25/* LCD控制参数结构体 */
26struct tftlcd_typedef{
27                unsignedshort height;      /* LCD屏幕高度                              */
28      unsignedshort width;      /* LCD屏幕宽度                              */
29      unsignedchar pixsize;      /* LCD每个像素所占字节大小      */
30      unsignedshort vspw;                        /* VSYNC信号宽度                        */
31      unsignedshort vbpd;                        /*帧同步信号后肩                        */
32      unsignedshort vfpd;                        /* 帧同步信号前肩                        */
33      unsignedshort hspw;                        /* HSYNC信号宽度                        */
34      unsignedshort hbpd;                        /* 水平同步信号后见肩                */
35      unsignedshort hfpd;                        /* 水平同步信号前肩                        */
36      unsignedint framebuffer;      /* LCD显存首地址      */
37      unsignedint forecolor;      /* 前景色                                        */
38      unsignedint backcolor;      /* 背景色                                        */
39};
40
41externstruct tftlcd_typedef tftlcd_dev;
42
43/* 函数声明 */
44void lcd_init(void);
45void lcdgpio_init(void);
46void lcdclk_init(unsignedchar loopDiv,unsignedchar prediv,
unsignedchar div);
47void lcd_reset(void);
48void lcd_noreset(void);
49void lcd_enable(void);
50void video_pllinit(unsignedchar loopdivi,unsignedchar postdivi);
51 inline void lcd_drawpoint(unsignedshort x,unsignedshort y,
unsignedint color);
52 inline unsignedint lcd_readpoint(unsignedshort x,
unsignedshort y);
53void lcd_clear(unsignedint color);
54void lcd_fill(unsignedshort x0,unsignedshort y0,
unsignedshort x1,unsignedshort y1,
unsignedint color);
55 #endif
      在文件bsp_lcd.h中一开始定义了一些常用的颜色宏定义,颜色格式都是ARGB8888的。第23行的宏LCD_FRAMEBUF_ADDR是显存首地址,此处将显存首地址放到了0X89000000地址处。这个要根据所使用的LCD屏幕大小和DDR内存大小来确定的,前面我们说了ATK7016这款RGB屏幕所需的显存大小为2.4MB,而I.MX6U-ALPHA开发板配置的DDR有256和512MB两种类型,内存地址范围分别为0X80000000~0X90000000和0X80000000~0XA0000000。所以LCD显存首地址选择为0X89000000不管是256MB还是512MB的DDR都可以使用。
      第26行的结构体tftlcd_typedef是RGB LCD的控制参数结构体,里面包含了跟LCD配置有关的一些成员变量。最后就是一些变量和函数是声明。
      在bsp_lcd.c中输入如下内容:
示例代码24.3.2 bsp_lcd.c文件代码
/***************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名   : bsp_lcd.c
作者   : 左忠凯
版本   : V1.0
描述   : LCD驱动文件。
其他   : 无
论坛   : www.openedv.com
日志   : 初版V1.0 2019/1/3 左忠凯创建
***************************************************************/
1   #include "bsp_lcd.h"
2   #include "bsp_gpio.h"
3   #include "bsp_delay.h"
4   #include "stdio.h"
5
6/* 液晶屏参数结构体 */
7struct tftlcd_typedef tftlcd_dev;
8
9/*
10   * @description      : 始化LCD
11   * @param               : 无
12   * @return            : 无
13   */
14void lcd_init(void)
15{
16      lcdgpio_init();                /* 初始化IO                */
17      lcdclk_init(32,3,5);                /* 初始化LCD时钟      */
18
19      lcd_reset();                /* 复位LCD               */
20      delayms(10);                /* 延时10ms                */
21      lcd_noreset();                /* 结束复位      */
22
23/* RGB LCD参数结构体初始化 */
24      tftlcd_dev.height =600;      /* 屏幕高度                        */
25      tftlcd_dev.width =1024;      /* 屏幕宽度                        */
26      tftlcd_dev.pixsize =4;      /* ARGB8888模式,每个像素4字节      */
27      tftlcd_dev.vspw =3;      /* VSYNC信号宽度                */
28      tftlcd_dev.vbpd =20;      /* 帧同步信号后肩                */
29      tftlcd_dev.vfpd =12;      /* 帧同步信号前肩                */
30      tftlcd_dev.hspw =20;      /* HSYNC信号宽度                */
31      tftlcd_dev.hbpd =140;      /* 水平同步信号后见肩                        */
32      tftlcd_dev.hfpd =160;      /* 水平同步信号前肩                        */
33      tftlcd_dev.framebuffer = LCD_FRAMEBUF_ADDR;/* 帧缓冲地址                */
34      tftlcd_dev.backcolor = LCD_WHITE;/* 背景色为白色                        */
35      tftlcd_dev.forecolor = LCD_BLACK;/* 前景色为黑色                        */
36
37/* 初始化ELCDIF的CTRL寄存器
38       * bit 0 : 停止复位
39       * bit 1 : 旁路计数器模式
40       * bit 1 : LCD工作在dotclk模式
41       * bit 00 : 输入数据不交换
42       * bit 00 : CSC不交换
43       * bit 11 : 24位总线宽度
44       * bit    11 : 24位数据宽度,也就是RGB888
45       * bit    1: elcdif工作在主模式
46       * bit    0: 所有的24位均有效
47       */
48       LCDIF->CTRL |=(1<<19)|(1<<17)|(0<<14)|(0<<12)|
49(3<<10)|(3<<8)|(1<<5)|(0<<1);
50/*
51       * 初始化ELCDIF的寄存器CTRL1
52       * bit : 0X7 ARGB模式下,传输24位数据,A通道不用传输
53       */
54       LCDIF->CTRL1 =0X7<<16;
55
56/*
57      * 初始化ELCDIF的寄存器TRANSFER_COUNT寄存器
58      * bit : 高度
59      * bit    : 宽度
60      */
61      LCDIF->TRANSFER_COUNT=(tftlcd_dev.height <<16)|
(tftlcd_dev.width <<0);
62
63/*
64       * 初始化ELCDIF的VDCTRL0寄存器
65       * bit 0 : VSYNC输出
66       * bit 1 : 使能ENABLE输出
67       * bit 0 : VSYNC低电平有效
68       * bit 0 : HSYNC低电平有效
69       * bit 0 : DOTCLK上升沿有效
70       * bit 1 : ENABLE信号高电平有效
71       * bit 1 : DOTCLK模式下设置为1
72       * bit 1 : DOTCLK模式下设置为1
73       * bit : vspw参数
74       */
75      LCDIF->VDCTRL0 =0;/* 先清零 */
76      LCDIF->VDCTRL0 =(0<<29)|(1<<28)|(0<<27)|
77(0<<26)|(0<<25)|(1<<24)|
78(1<<21)|(1<<20)|(tftlcd_dev.vspw <<0);
79/*
80       * 初始化ELCDIF的VDCTRL1寄存器,设置VSYNC总周期
81       */
82      LCDIF->VDCTRL1 = tftlcd_dev.height + tftlcd_dev.vspw +
tftlcd_dev.vfpd + tftlcd_dev.vbpd;
83
84/*
85      * 初始化ELCDIF的VDCTRL2寄存器,设置HSYNC周期
86      * bit :hsw
87      * bit: HSYNC总周期
88      */
89      LCDIF->VDCTRL2 =(tftlcd_dev.hspw <<18)|(tftlcd_dev.width +
tftlcd_dev.hspw + tftlcd_dev.hfpd + tftlcd_dev.hbpd);
90
91/*
92       * 初始化ELCDIF的VDCTRL3寄存器,设置HSYNC周期
93       * bit :水平等待时钟数
94       * bit: 垂直等待时钟数
95       */
96      LCDIF->VDCTRL3 =((tftlcd_dev.hbpd + tftlcd_dev.hspw)<<16)|
(tftlcd_dev.vbpd + tftlcd_dev.vspw);
97
98/*
99       * 初始化ELCDIF的VDCTRL4寄存器,设置HSYNC周期
100      * bit 1 : 当使用VSHYNC、HSYNC、DOTCLK的话此为置1
101      * bit: 宽度
102      */
103
104   LCDIF->VDCTRL4 =(1<<18)|(tftlcd_dev.width);
105
106/*
107      * 初始化ELCDIF的CUR_BUF和NEXT_BUF寄存器
108      * 设置当前显存地址和下一帧的显存地址
109      */
110   LCDIF->CUR_BUF =(unsignedint)tftlcd_dev.framebuffer;
111   LCDIF->NEXT_BUF =(unsignedint)tftlcd_dev.framebuffer;
112
113   lcd_enable();      /* 使能LCD            */
114   delayms(10);
115   lcd_clear(LCD_WHITE);      /* 清屏      */
116
117}
118
119/*
120* @description         : LCD GPIO初始化
121* @param               : 无
122* @return            : 无
123*/
124void lcdgpio_init(void)
125{
126   gpio_pin_config_t gpio_config;
127
128/* 1、IO初始化复用功能 */
129   IOMUXC_SetPinMux(IOMUXC_LCD_DATA00_LCDIF_DATA00,0);
130   IOMUXC_SetPinMux(IOMUXC_LCD_DATA01_LCDIF_DATA01,0);
131   IOMUXC_SetPinMux(IOMUXC_LCD_DATA02_LCDIF_DATA02,0);
132   IOMUXC_SetPinMux(IOMUXC_LCD_DATA03_LCDIF_DATA03,0);
……
154   IOMUXC_SetPinMux(IOMUXC_LCD_ENABLE_LCDIF_ENABLE,0);
155   IOMUXC_SetPinMux(IOMUXC_LCD_HSYNC_LCDIF_HSYNC,0);
156   IOMUXC_SetPinMux(IOMUXC_LCD_VSYNC_LCDIF_VSYNC,0);
157   IOMUXC_SetPinMux(IOMUXC_GPIO1_IO08_GPIO1_IO08,0);/* 背光引脚*/
158
159/* 2、配置LCD IO属性
160      *bit 16:0 HYS关闭
161      *bit : 0 默认22K上拉
162      *bit : 0 pull功能
163      *bit : 0 pull/keeper使能
164      *bit : 0 关闭开路输出
165      *bit : 10 速度100Mhz
166      *bit : 111 驱动能力为R0/7
167      *bit : 1 高转换率
168      */
169   IOMUXC_SetPinConfig(IOMUXC_LCD_DATA00_LCDIF_DATA00,0xB9);
170   IOMUXC_SetPinConfig(IOMUXC_LCD_DATA01_LCDIF_DATA01,0xB9);
……
193   IOMUXC_SetPinConfig(IOMUXC_LCD_CLK_LCDIF_CLK,0xB9);
194   IOMUXC_SetPinConfig(IOMUXC_LCD_ENABLE_LCDIF_ENABLE,0xB9);
195   IOMUXC_SetPinConfig(IOMUXC_LCD_HSYNC_LCDIF_HSYNC,0xB9);
196   IOMUXC_SetPinConfig(IOMUXC_LCD_VSYNC_LCDIF_VSYNC,0xB9);
197   IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO08_GPIO1_IO08,0xB9);
198
199/* GPIO初始化 */
200   gpio_config.direction = kGPIO_DigitalOutput;      /* 输出                */
201   gpio_config.outputLogic =1;      /* 默认关闭背光      */
202   gpio_init(GPIO1,8,&gpio_config);      /* 背光默认打开      */
203   gpio_pinwrite(GPIO1,8,1);      /* 打开背光      */
204}
205
206/*
207* @description             : LCD时钟初始化, LCD时钟计算公式如下:
208*                              LCD CLK = 24 * loopDiv / prediv / div
209* @param - loopDiv         : loopDivider值
210* @param - loopDiv         : lcdifprediv值
211* @param - div             : lcdifdiv值
212* @return                  : 无
213*/
214void lcdclk_init(unsignedchar loopDiv,unsignedchar prediv,
unsignedchar div)
215{
216/* 先初始化video pll
217      * VIDEO PLL = OSC24M * (loopDivider + (denominator /
numerator)) / postDivider
218      *不使用小数分频器,因此denominator和numerator设置为0
219      */
220   CCM_ANALOG->PLL_VIDEO_NUM =0;/* 不使用小数分频器 */
221   CCM_ANALOG->PLL_VIDEO_DENOM =0;
222
223/*
224      * PLL_VIDEO寄存器设置
225      * bit                : 1   使能VIDEO PLL时钟
226      * bit      : 2   设置postDivider为1分频
227      * bit         : 32设置loopDivider寄存器
228      */
229   CCM_ANALOG->PLL_VIDEO =(2<<19)|(1<<13)|(loopDiv <<0);
230
231/*
232      * MISC2寄存器设置
233      * bit: 0VIDEO的post-div设置,1分频
234      */
235   CCM_ANALOG->MISC2 &=~(3<<30);
236   CCM_ANALOG->MISC2 =0<<30;
237
238/* LCD时钟源来源与PLL5,也就是VIDEO PLL*/
239   CCM->CSCDR2 &=~(7<<15);
240   CCM->CSCDR2 |=(2<<15);/* 设置LCDIF_PRE_CLK使用PLL5 */
241
242/* 设置LCDIF_PRE分频 */
243   CCM->CSCDR2 &=~(7<<12);
244   CCM->CSCDR2 |=(prediv -1)<<12;      /* 设置分频*/
245
246/* 设置LCDIF分频 */
247   CCM->CBCMR &=~(7<<23);
248   CCM->CBCMR |=(div -1)<<23;
249
250/* 设置LCD时钟源为LCDIF_PRE时钟 */
251   CCM->CSCDR2 &=~(7<<9);      /* 清除原来的设置            */
252   CCM->CSCDR2 |=(0<<9);      /* LCDIF_PRE时钟源选择LCDIF_PRE时钟 */
253}
254
255/*
256* @description         : 复位ELCDIF接口
257* @param               : 无
258* @return            : 无
259*/
260void lcd_reset(void)
261{
262   LCDIF->CTRL=1<<31;      /* 强制复位                */
263}
264
265/*
266* @description         : 结束复位ELCDIF接口
267* @param               : 无
268* @return            : 无
269*/
270void lcd_noreset(void)
271{
272   LCDIF->CTRL=0<<31;      /* 取消强制复位      */
273}
274
275/*
276* @description         : 使能ELCDIF接口
277* @param               : 无
278* @return            : 无
279*/
280void lcd_enable(void)
281{
282   LCDIF->CTRL |=1<<0;/* 使能ELCDIF */
283}
284
285/*
286* @description            : 画点函数
287* @param - x               : x轴坐标
288* @param - y               : y轴坐标
289* @param - color          : 颜色值
290* @return                  : 无
291*/
292 inline void lcd_drawpoint(unsignedshort x,unsignedshort y,
unsignedint color)
293{
294*(unsignedint*)((unsignedint)tftlcd_dev.framebuffer +
295                      tftlcd_dev.pixsize *(tftlcd_dev.width *
y + x)) = color;
296}
297
298
299/*
300* @description      : 读取指定点的颜色值
301* @param - x             : x轴坐标
302* @param - y            : y轴坐标
303* @return               : 读取到的指定点的颜色值
304*/
305 inline unsignedint lcd_readpoint(unsignedshort x,
unsignedshort y)
306{
307return*(unsignedint*)((unsignedint)tftlcd_dev.framebuffer +
308            tftlcd_dev.pixsize *(tftlcd_dev.width * y + x));
309}
310
311/*
312* @description                : 清屏
313* @param - color         : 颜色值
314* @return                  : 读取到的指定点的颜色值
315*/
316void lcd_clear(unsignedint color)
317{
318unsignedint num;
319unsignedint i =0;
320
321unsignedint*startaddr=(unsignedint*)tftlcd_dev.framebuffer;
322   num=(unsignedint)tftlcd_dev.width * tftlcd_dev.height;
323for(i =0; i < num; i++)
324{
325         startaddr= color;
326}
327}
328
329/*
330* @description               : 以指定的颜色填充一块矩形
331* @param - x0            : 矩形起始点坐标X轴
332* @param - y0            : 矩形起始点坐标Y轴
333* @param - x1            : 矩形终止点坐标X轴
334* @param - y1            : 矩形终止点坐标Y轴
335* @param - color         : 要填充的颜色
336* @return                  : 读取到的指定点的颜色值
337*/
338void lcd_fill(unsignedshort x0,unsignedshort y0,
339unsignedshort x1,unsignedshort y1,
unsignedint color)
340{
341unsignedshort x, y;
342
343if(x0 <0) x0 =0;
344if(y0 <0) y0 =0;
345if(x1 >= tftlcd_dev.width) x1 = tftlcd_dev.width -1;
346if(y1 >= tftlcd_dev.height) y1 = tftlcd_dev.height -1;
347
348for(y = y0; y <= y1; y++)
349{
350for(x = x0; x <= x1; x++)
351             lcd_drawpoint(x, y, color);
352}
353}
      文件bsp_lcd.c里面一共有10个函数,第一个函数是lcd_init,这个是LCD初始化函数,此函数先调用LCD的IO初始化函数、时钟初始化函数、复位函数等,然后会按照我们前面讲解的步骤初始化eLCDIF相关的寄存器,最后使能eLCDIF。第二个函数是lcdgpio_init,这个是LCD的IO初始化函数。第三个函数lcdclk_init是LCD的时钟初始化函数。第四个函数lcd_reset和第五个函数lcd_noreset分别为复位LCD的停止LCD复位函数。第六个函数lcd_enable是eLCDIF使能函数,用于使能eLCDIF。第七个和第八个是画点和读点函数,分别为lcd_drawpoint和lcd_readpoint,通过这两个函数就可以在LCD的指定像素点上显示指定的颜色,或者读取指定像素点的颜色。第九个函数lcd_clear是清屏函数,使用指定的颜色清除整个屏幕。最后一个函数lcd_fill是填充函数,使用此函数的时候需要指定矩形的起始坐标、终止坐标和填充颜色,这样就可以填充出一个矩形区域。
      在bsp_lcdapi.h中输入如下所示内容:
示例代码24.3.3 bsp_lcdapi.h文件代码
1#ifndef BSP_LCDAPI_H
2#define BSP_LCDAPI_H
3/***************************************************************
4Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
5文件名    : bsp_lcdapi.h
6作者      : 左忠凯
7版本      : V1.0
8描述      : LCD显示API函数。
9其他      : 无
10论坛      : www.openedv.com
11日志      : 初版V1.0 2019/3/18 左忠凯创建
12 ***************************************************************/
13 #include "imx6ul.h"
14 #include "bsp_lcd.h"
15
16/* 函数声明 */
17void lcd_drawline(unsignedshort x1,unsignedshort y1,unsigned
short x2,unsignedshort y2);
18void lcd_draw_rectangle(unsignedshort x1,unsignedshort y1,
unsignedshort x2,unsignedshort y2);
19void lcd_draw_circle(unsignedshort x0,unsignedshort y0,
unsignedchar r);
20void lcd_showchar(unsignedshort x,unsignedshort y,
unsignedchar num,unsignedchar size,
unsignedchar mode);
21unsignedint lcd_pow(unsignedchar m,unsignedchar n);
22void lcd_shownum(unsignedshort x,unsignedshort y,
unsignedint num,unsignedchar len,
unsignedchar size);
23void lcd_showxnum(unsignedshort x,unsignedshort y,
unsignedint num,unsignedchar len,
unsignedchar size,unsignedchar mode);
24void lcd_show_string(unsignedshort x,unsignedshort y,
unsignedshort width,unsignedshort height,
unsignedchar size, char*p);
25 #endif

      文件bsp_lcdapi.h内容很简单,就是函数声明。在bsp_lcdapi.c中输入如下内容:
示例代码24.3.4 bsp_lcdapi.c文件代码
/***************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名   : bsp_lcdapi.c
作者   : 左忠凯
版本   : V1.0
描述   : LCD API函数文件。
其他   : 无
论坛   : www.openedv.com
日志   : 初版V1.0 2019/3/18 左忠凯创建
***************************************************************/
1   #include "bsp_lcdapi.h"
2   #include "font.h"
3
4/*
5    * @description               : 画线函数
6    * @param - x1            : 线起始点坐标X轴
7    * @param - y1            : 线起始点坐标Y轴
8    * @param - x2            : 线终止点坐标X轴
9    * @param - y2            : 线终止点坐标Y轴
10   * @return                  : 无
11   */
12void lcd_drawline(unsignedshort x1,unsignedshort y1,
unsignedshort x2,unsignedshort y2)
13{
14      u16 t;
15int xerr =0, yerr =0, delta_x, delta_y, distance;
16int incx, incy, uRow, uCol;
17      delta_x = x2 - x1;      /* 计算坐标增量      */
18      delta_y = y2 - y1;
19      uRow = x1;
20      uCol = y1;
21if(delta_x >0) incx =1;      /* 设置单步方向                */
22elseif(delta_x==0) incx =0;/* 垂直线                        */
23else
24{
25          incx =-1;
26          delta_x =-delta_x;
27}
28
29if(delta_y>0) incy=1;
30elseif(delta_y ==0)   incy=0;      /* 水平线                        */
31else
32{
33          incy =-1;
34          delta_y =-delta_y;
35}
36if( delta_x > delta_y)distance = delta_x;/*选取基本增量坐标轴*/
37else distance = delta_y;
38for(t =0; t <= distance+1; t++)                        /* 画线输出 */
39{
40          lcd_drawpoint(uRow, uCol, tftlcd_dev.forecolor);/* 画点 */
41          xerr += delta_x ;
42          yerr += delta_y ;
43if(xerr > distance)
44{
45            xerr -= distance;
46            uRow += incx;
47}
48if(yerr > distance)
49{
50            yerr -= distance;
51            uCol += incy;
52}
53}
54}
55
56/*
57   * @description         : 画矩形函数
58   * @param - x1          : 矩形坐上角坐标X轴
59   * @param - y1          : 矩形坐上角坐标Y轴
60   * @param - x2          : 矩形右下角坐标X轴
61   * @param - y2          : 矩形右下角坐标Y轴
62   * @return            : 无
63   */
64void lcd_draw_rectangle(unsignedshort x1,unsignedshort y1,
unsignedshort x2,unsignedshort y2)
65{
66      lcd_drawline(x1, y1, x2, y1);
67      lcd_drawline(x1, y1, x1, y2);
68      lcd_drawline(x1, y2, x2, y2);
69      lcd_drawline(x2, y1, x2, y2);
70}
71
72/*
73   * @description         : 在指定位置画一个指定大小的圆
74   * @param - x0          : 圆心坐标X轴
75   * @param - y0          : 圆心坐标Y轴
76   * @param - y2          : 圆形半径
77   * @return             : 无
78   */
79void lcd_draw_circle(unsignedshort x0,unsignedshort y0,
unsignedchar r)
80{
81int mx = x0, my = y0;
82int x =0, y = r;
83
84int d =1- r;
85while(y > x)/* y>x即第一象限的第1区八分圆 */
86{
87          lcd_drawpoint(x+ mx, y+ my, tftlcd_dev.forecolor);
88          lcd_drawpoint(y+ mx, x+ my, tftlcd_dev.forecolor);
89          lcd_drawpoint(-x + mx, y+ my, tftlcd_dev.forecolor);
90          lcd_drawpoint(-y + mx, x+ my, tftlcd_dev.forecolor);
91
92          lcd_drawpoint(-x + mx,-y + my, tftlcd_dev.forecolor);
93          lcd_drawpoint(-y + mx,-x + my, tftlcd_dev.forecolor);
94          lcd_drawpoint(x+ mx,-y + my, tftlcd_dev.forecolor);
95          lcd_drawpoint(y+ mx,-x + my, tftlcd_dev.forecolor);
96if( d <0)
97{
98            d = d +2* x +3;
99}
100else
101{
102             d= d +2*(x - y)+5;
103             y--;
104}
105         x++;
106}
107}
108
109/*
110* @description         : 在指定位置显示一个字符
111* @param - x         : 起始坐标X轴
112* @param - y         : 起始坐标Y轴
113* @param - num         : 显示字符
114* @param – size      : 字体大小, 可选12/16/24/32
115* @param – mode      : 叠加方式(1)还是非叠加方式(0)
116* @return            : 无
117*/
118void lcd_showchar(unsignedshort x,unsignedshort y,
119unsignedchar num,unsignedchar size,
120unsignedchar mode)
121{
122unsignedchartemp, t1, t;
123unsignedshort y0 = y;
                /* 得到字体一个字符对应点阵集所占的字节数   */
124unsignedchar csize =(size /8+((size %8)?1:0))*
(size /2);
125   num = num -' ';/* 得到偏移后的值(ASCII字库是从空格开始取模,
所以-' '就是对应字符的字库)*/
126for(t =0; t < csize; t++)
127{
128if(size ==12) temp = asc2_1206;/* 调用1206字体 */
129elseif(size ==16)temp = asc2_1608;/* 调用1608字体 */
130elseif(size ==24)temp = asc2_2412;/* 调用2412字体 */
131elseif(size ==32)temp = asc2_3216;/* 调用3216字体 */
132elsereturn;/* 没有的字库   */
133for(t1 =0; t1 <8; t1++)
134{
135if(temp &0x80)lcd_drawpoint(x, y, tftlcd_dev.forecolor);
136elseif(mode==0)lcd_drawpoint(x, y,
tftlcd_dev.backcolor);
137             temp <<=1;
138             y++;
139if(y >= tftlcd_dev.height)return;/* 超区域了 */
140if((y - y0)== size)
141{
142               y = y0;
143               x++;
144if(x >= tftlcd_dev.width)return;/* 超区域了 */
145break;
146}
147}
148}
149}
150
151/*
152* @description : 计算m的n次方
153* @param - m   : 要计算的值
154* @param - n   : n次方
155* @return      : m^n次方.
156*/
157unsignedint lcd_pow(unsignedchar m,unsignedchar n)
158{
159unsignedint result =1;
160while(n--) result *= m;
161return result;
162}
163
164/*
165* @description         : 显示指定的数字,高位为0的话不显示
166* @param - x         : 起始坐标点X轴。
167* @param - y         : 起始坐标点Y轴。
168* @param - num         : 数值(0~999999999)。
169* @param - len         : 数字位数。
170* @param – size      : 字体大小
171* @return            : 无
172*/
173void lcd_shownum(unsignedshort x,
174unsignedshort y,
175unsignedint num,
176unsignedchar len,
177unsignedchar size)
178{
179unsignedchart, temp;
180unsignedcharenshow =0;
181for(t =0; t < len; t++)
182{
183         temp =(num / lcd_pow(10, len - t -1))%10;
184if(enshow ==0&& t <(len -1))
185{
186if(temp ==0)
187{
188               lcd_showchar(x +(size /2)* t, y,' ', size,0);
189continue;
190}else enshow =1;
191}
192         lcd_showchar(x +(size /2)* t, y, temp +'0', size,0);
193}
194}
195
196/*
197* @description         : 显示指定的数字,高位为0,还是显示
198* @param - x               : 起始坐标点X轴。
199* @param - y               : 起始坐标点Y轴。
200* @param - num             : 数值(0~999999999)。
201* @param - len             : 数字位数。
202* @param - size            : 字体大小
203* @param - mode            : :0,不填充;1,填充0.
204*                              :保留
205*                              :0,非叠加显示;1,叠加显示.
206* @return                  : 无
207*/
208void lcd_showxnum(unsignedshort x,unsignedshort y,
209unsignedint num,unsignedchar len,
210unsignedchar size,unsignedchar mode)
211{
212unsignedchar t, temp;
213unsignedchar enshow =0;
214for(t =0; t < len; t++)
215{
216         temp =(num / lcd_pow(10, len - t-1))%10;
217if(enshow ==0&& t <(len -1))
218{
219if(temp ==0)
220{
221if(mode &0X80) lcd_showchar(x +(size /2)* t, y, \
'0', size, mode &0X01);
222elselcd_showchar(x +(size /2)* t, y ,' ', size,
mode &0X01);
223continue;
224}else enshow=1;
225
226}
227         lcd_showchar( x +(size /2)* t, y, temp +'0', size ,
mode &0X01);
228}
229}
230
231/*
232* @description                : 显示一串字符串
233* @param - x               : 起始坐标点X轴。
234* @param - y               : 起始坐标点Y轴。
235* @param - width         : 字符串显示区域长度
236* @param - height          : 字符串显示区域高度
237* @param - size    : 字体大小
238* @param - p               : 要显示的字符串首地址
239* @return                  : 无
240*/
241void lcd_show_string(unsignedshort x,unsignedshort y,
242unsignedshort width,unsignedshort height,
243unsignedchar size,char*p)
244{
245unsignedchar x0 = x;
246   width += x;
247   height += y;
248while((*p <='~')&&(*p >=' '))/* 判断是不是非法字符! */
249{
250if(x >= width){x = x0; y += size;}
251if(y >= height)break;      /* 退出 */
252         lcd_showchar(x, y,*p , size,0);
253         x += size /2;
254         p++;
255}
256}
      文件bsp_lcdapi.h里面都是一些LCD的API操作函数,比如画线、画矩形、画圆、显示数字、显示字符和字符串等函数。这些函数都是从STM32例程里面移植过来的,如果学习过ALIENTEK的STM32教程的话就会很熟悉,都是一些纯软件的东西。
lcd_showchar函数是字符显示函数,要理解这个函数就得先了解一下字符(ASCII字符集)在LCD上的显示原理。要显示字符,我们先要有字符的点阵数据,ASCII常用的字符集总共有95个,从空格符开始,分别为:!"#$%&'()*+,-0123456789:;<=>?@ABCDEFGHIJKLMNOPQR
STUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~.
我们先要得到这个字符集的点阵数据,这里我们介绍一个款很好的字符提取软件:PCtoLCD2002完美版。该软件可以提供各种字符,包括汉字(字体和大小都可以自己设置)阵提取,且取模方式可以设置好几种,常用的取模方式,该软件都支持。该软件还支持图形模式,也就是用户可以自己定义图片的大小,然后画图,根据所画的图形再生成点阵数据,这功能在制作图标或图片的时候很有用。
该软件的界面如图24.3.1所示:

图24.3.1 PCtoLCD2002软件界面
然后我们点击字模选项按钮进入字模选项设置界面。设置界面中点阵格式和取模方式等参数配置如图24.3.2所示:

图24.3.2 设置取模方式
上图设置的取模方式,在右上角的取模说明里面有,即:从第一列开始向下每取8个点作为一个字节,如果最后不足8个点就补满8位。取模顺序是从高到低,即第一个点作为最高位。如*-------取为10000000。其实就是按如图24.3.3所示的这种方式:

图24.3.3 取模方式图解
从上到下,从左到右,高位在前。我们按这样的取模方式,然后把ASCII字符集按12*6大小、16*8、24*12和32*16大小取模出来(对应汉字大小为12*12、16*16、24*24和32*32,字符的只有汉字的一半大!)。将取出的点阵数组保存在font.h里面,每个12*6的字符占用12个字节,每个16*8的字符占用16个字节,每个24*12的字符占用36个字节,每个32*16的字符占用64个字节。font.h中的字符集点阵数据数组asc2_1206、asc2_1608、asc2_2412和asc2_3216就对应着这四个大小字符集,具体见font.h部分代码(该部分我们不再这里列出来了,请大家参考光盘里面的代码)。
      最后在main.c中输入如下所示内容:
示例代码24.3.5main.c文件代码
/**************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名   : mian.c
作者   : 左忠凯
版本   : V1.0
描述   : I.MX6U开发板裸机实验16 LCD液晶屏实验
其他   : 本实验学习如何在I.MX6U上驱动RGB LCD液晶屏幕,I.MX6U有个
            ELCDIF接口,通过此接口可以连接一个RGB LCD液晶屏。
论坛   : www.openedv.com
日志   : 初版V1.0 2019/1/15 左忠凯创建
**************************************************************/
1#include "bsp_clk.h"
2#include "bsp_delay.h"
3#include "bsp_led.h"
4#include "bsp_beep.h"
5#include "bsp_key.h"
6#include "bsp_int.h"
7#include "bsp_uart.h"
8#include "stdio.h"
9#include "bsp_lcd.h"
10 #include "bsp_lcdapi.h"
11
12
13/* 背景颜色数组 */
14unsignedint backcolor={
15LCD_BLUE,       LCD_GREEN,      LCD_RED,    LCD_CYAN,   LCD_YELLOW,
16LCD_LIGHTBLUE,LCD_DARKBLUE,   LCD_WHITE,LCD_BLACK,LCD_ORANGE
17
18};
19
20/*
21* @description         : main函数
22* @param                : 无
23* @return               : 无
24*/
25int main(void)
26{
27      unsignedchar index =0;
28      unsignedchar state = OFF;
29
30      int_init();      /* 初始化中断(一定要最先调用!)         */
31      imx6u_clkinit();      /* 初始化系统时钟      */
32                delay_init();      /* 初始化延时                        */
33      clk_enable();      /* 使能所有的时钟      */
34      led_init();      /* 初始化led                           */
35      beep_init();      /* 初始化beep                            */
36      uart_init();      /* 初始化串口,波特率115200         */
37      lcd_init();      /* 初始化LCD                           */
38
39                tftlcd_dev.forecolor = LCD_RED;
40      lcd_show_string(10,10,400,32,32,(char*)"ALPHA-IMX6UL
ELCD TEST");
41      lcd_draw_rectangle(10,52,1014,290);      /* 绘制矩形框      */
42      lcd_drawline(10,52,1014,290);      /* 绘制线条      */
43      lcd_drawline(10,290,1014,52);      /* 绘制线条      */
44      lcd_draw_Circle(512,171,119);      /* 绘制圆形      */
45
46      while(1)
47      {
48      index++;
49      if(index ==10) index =0;
50                lcd_fill(0,300,1023,599, backcolor);
51      lcd_show_string(800,10,240,32,32,(char*)"INDEX=");
52      lcd_shownum(896,10, index,2,32);/* 显示数字,叠加显示*/
53
54      state =!state;
55      led_switch(LED0,state);
56      delayms(1000);                                                /* 延时一秒 */
57      }
58      return0;
59}
      第37行调用函数lcd_init初始化LCD。
      第39行设置前景色,也就是画笔颜色为红色。
      第40~44行都是调用bsp_lcdapi.c中的API函数在LCD上绘制各种图形和显示字符串。
      第46行的while循环中每隔1S中就调用函数lcd_fill填充指定的区域,并且显示index值。
      main函数很简单,重点就是初始化LCD,然后调用LCD的API函数进行一些常用的操作,比如画线、画矩形、显示字符串和数字等等。
24.4 编译下载验证
24.4.1 编写Makefile和链接脚本
修改Makefile中的TARGET为lcd,然后在在INCDIRS和SRCDIRS中加入“bsp/lcd”,修改后的Makefile如下:
示例代码19.4.1 Makefile代码
1CROSS_COMPILE         ?= arm-linux-gnueabihf-
2TARGET               ?=lcd
3
4/* 省略掉其它代码...... */
5
6INCDIRS      :=      imx6ul \
7                stdio/include \
8                bsp/clk \
9                bsp/led \
10                bsp/delay\
11                bsp/beep \
12                bsp/gpio \
13                bsp/key \
14                bsp/exit \
15                bsp/int \
16                bsp/epittimer \
17                bsp/keyfilter \
18                bsp/uart \
19                bsp/lcd
20
21 SRCDIRS      :=      project \
22                stdio/lib \
23               bsp/clk \
24                bsp/led \
25                bsp/delay \
26                bsp/beep \
27                bsp/gpio \
28                bsp/key \
29                bsp/exit \
30                bsp/int \
31                bsp/epittimer \
32                bsp/keyfilter \
33                bsp/uart \
34                bsp/lcd
35
36/* 省略掉其它代码...... */
37
38 clean:
39rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS)$(SOBJS)
第2行修改变量TARGET为“lcd”,也就是目标名称为“lcd”。
      第19行在变量INCDIRS中添加RGB LCD驱动头文件(.h)路径。
      第34行在变量SRCDIRS中添加RGB LCD驱动驱动文件(.c)路径。
      链接脚本保持不变。
24.4.2编译下载
      使用Make命令编译代码,编译成功以后使用软件imxdownload将编译完成的lcd.bin文件下载到SD卡中,命令如下:
chmod 777 imxdownload                        //给予imxdownload可执行权限,一次即可
./imxdownload lcd.bin /dev/sdd                //烧写到SD卡中
      烧写成功以后将SD卡插到开发板的SD卡槽中,然后复位开发板。程序开始运行,LED0每隔1S闪烁依次,屏幕下半部分会每1S刷新依次,并且在屏幕的右上角显示索引值,LCD屏幕显示如图24.4.3所示

图24.4.3 LCD显示画面


页: [1]
查看完整版本: 【正点原子Linux连载】第二十四章RGBLCD显示实验--摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南