正点原子linux应用编程(第二十四章RGBLCD显示实验)

1)实验平台:正点原子Linux开发板

2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南关注官方微信号公众号,获取更多资料:正点原子

正点原子linux应用编程(第二十四章RGBLCD显示实验)(1)

第二十四章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所示:

正点原子linux应用编程(第二十四章RGBLCD显示实验)(2)

图24.1.1.1 LCD图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所示:

正点原子linux应用编程(第二十四章RGBLCD显示实验)(3)

图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所示:

正点原子linux应用编程(第二十四章RGBLCD显示实验)(4)

图24.1.1.3颜色选取

3、LCD屏幕接口

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

正点原子linux应用编程(第二十四章RGBLCD显示实验)(5)

正点原子linux应用编程(第二十四章RGBLCD显示实验)(6)

表24.1.1.1 RGB数据线

表24.1.1.1就是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也可以正常工作。

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所示:

正点原子linux应用编程(第二十四章RGBLCD显示实验)(7)

图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所示:

正点原子linux应用编程(第二十四章RGBLCD显示实验)(8)

表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所示:

正点原子linux应用编程(第二十四章RGBLCD显示实验)(9)

图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所示:

正点原子linux应用编程(第二十四章RGBLCD显示实验)(10)

图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所示:

正点原子linux应用编程(第二十四章RGBLCD显示实验)(11)

图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所示:

正点原子linux应用编程(第二十四章RGBLCD显示实验)(12)

正点原子linux应用编程(第二十四章RGBLCD显示实验)(13)

表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所示:

正点原子linux应用编程(第二十四章RGBLCD显示实验)(14)

图24.1.1.8 LCDIF接口时钟图

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

正点原子linux应用编程(第二十四章RGBLCD显示实验)(15)

表24.1.1.4 LCDIF时钟源选择

在第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所示:

正点原子linux应用编程(第二十四章RGBLCD显示实验)(16)

表24.1.1.4 LCDIF根时钟选择

这里肯定选择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所示:

正点原子linux应用编程(第二十四章RGBLCD显示实验)(17)

图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所示:

正点原子linux应用编程(第二十四章RGBLCD显示实验)(18)

图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所示:

正点原子linux应用编程(第二十四章RGBLCD显示实验)(19)

图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所示:

正点原子linux应用编程(第二十四章RGBLCD显示实验)(20)

图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所示:

正点原子linux应用编程(第二十四章RGBLCD显示实验)(21)

图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所示:

正点原子linux应用编程(第二十四章RGBLCD显示实验)(22)

图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所示:

正点原子linux应用编程(第二十四章RGBLCD显示实验)(23)

图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所示:

正点原子linux应用编程(第二十四章RGBLCD显示实验)(24)

图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所示:

正点原子linux应用编程(第二十四章RGBLCD显示实验)(25)

图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/***************************************************************

4 Copyright © 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 [31] 0 : 停止复位

39 * bit [19] 1 : 旁路计数器模式

40 * bit [17] 1 : LCD工作在dotclk模式

41 * bit [15:14] 00 : 输入数据不交换

42 * bit [13:12] 00 : CSC不交换

43 * bit [11:10] 11 : 24位总线宽度

44 * bit [9:8] 11 : 24位数据宽度,也就是RGB888

45 * bit [5] 1 : elcdif工作在主模式

46 * bit [1] 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 [19:16] : 0X7 ARGB模式下,传输24位数据,A通道不用传输

53 */

54 LCDIF->CTRL1 =0X7<<16;

55

56/*

57 * 初始化ELCDIF的寄存器TRANSFER_COUNT寄存器

58 * bit [31:16] : 高度

59 * bit [15:0] : 宽度

60 */

61 LCDIF->TRANSFER_COUNT =(tftlcd_dev.height <<16)|

(tftlcd_dev.width <<0);

62

63/*

64 * 初始化ELCDIF的VDCTRL0寄存器

65 * bit [29] 0 : VSYNC输出

66 * bit [28] 1 : 使能ENABLE输出

67 * bit [27] 0 : VSYNC低电平有效

68 * bit [26] 0 : HSYNC低电平有效

69 * bit [25] 0 : DOTCLK上升沿有效

70 * bit [24] 1 : ENABLE信号高电平有效

71 * bit [21] 1 : DOTCLK模式下设置为1

72 * bit [20] 1 : DOTCLK模式下设置为1

73 * bit [17:0] : 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[31:18] :hsw

87 * bit[17:0] : 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[27:16] :水平等待时钟数

94 * bit[15:0] : 垂直等待时钟数

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[18] 1 : 当使用VSHYNC、HSYNC、DOTCLK的话此为置1

101 * bit[17:0] : 宽度

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 [15:14]: 0 默认22K上拉

162 *bit [13]: 0 pull功能

163 *bit [12]: 0 pull/keeper使能

164 *bit [11]: 0 关闭开路输出

165 *bit [7:6]: 10 速度100Mhz

166 *bit [5:3]: 111 驱动能力为R0/7

167 *bit [0]: 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[13] : 1 使能VIDEO PLL时钟

226 * bit[20:19] : 2 设置postDivider为1分频

227 * bit[6:0] : 32 设置loopDivider寄存器

228 */

229 CCM_ANALOG->PLL_VIDEO =(2<<19)|(1<<13)|(loopDiv <<0);

230

231/*

232 * MISC2寄存器设置

233 * bit[31:30]: 0 VIDEO的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[i]= 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}

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页