第十五章 LCD编程
15.1 LCD硬件原理
15.2.1 LCD硬件工作原理简介
假设上图是一个LCD屏幕,屏幕中一个一个密密麻麻的黑点称之为像素点,每一行有若干个点,试想下有一个电子枪,电子枪位于某一个像素点的背后,然后向这个像素发射红,绿,蓝三种原色,这三种颜色不同比例的组合成任意一种颜色。电子枪在像素点的背后,一边移动一边发出各种颜色的光,电子枪从左往右移动,到右边边缘之后就跳到下一行的行首,继续从左往右移动,如此往复,一直移动到屏幕右下角的像素点,最后就跳回原点。
问题1:电子枪如何移动?
答: 有一条像素时钟信号线(DCLK),连接屏幕,每来一个像素时钟信号(DCLK),电子枪就移动一个像素。
问题2:电子枪打出的颜色该如何确定?
答:有三组红,绿,蓝信号线(RGB),连接屏幕,由这三组信号线(RGB)确定颜色
问题3:电子枪移动到LCD屏幕右边边缘时,如何得知需要跳到下一行的行首?
答:有一条水平同步信号线(HSYNC),连接屏幕,当接收到水平同步信号(HSYNC),电子枪就跳到下一行的最左边
问题4:电子枪如何得知需要跳到原点?
答:有一条垂直同步信号线(VSYNC),连接屏幕,当接收到垂直同步信号线(VSYNC),电子枪就由屏幕右下脚跳到左上角(原点)
问题5:电子枪如何得知三组信号线(RGB)确定的颜色就是它是需要的呢?
答:有一条RGB数据使能信号线(DE),连接屏幕,当接收到数据使能信号线(DE),电子枪就知道这时由这三组信号线(RGB)确定的颜色是有效的,可以发射到该像素点。
下图是开发板,LCD控制器,LCD屏幕的框图
之前提到的像素时钟(DCLK), 三组红,绿,蓝信号线(RGB),水平同步信号线(HSYNC),垂直同步信号线(VSYNC),RGB数据使能信号线(DE)都是从LCD控制器发出的,只要开发板支持LCD显示,他肯定就会有一个LCD控制器。
问题6:RGB三组信号线上的数据从何而来?
上图是RGB数据来源框图,内存中划出一部分区域,这块区域成为Framebuffer,在这个Framebuffer里面我们会构造好每一个颜色所对应的像素,Framebuffer中的值会被LCD控制器读出来,通过RGB三组线传给电子枪,电子枪再它转换成红绿蓝三种颜色打到屏幕上,在屏幕上的每一个像素,在我们的Frambuffer里面都有一个对应存储空间,里面存有屏幕上对应像素的颜色。
我们的LCD控制器会周而复始的从Framebuffer中取出一个像素的颜色值,发给电子枪,同时需要和DCLK,VSYNC,HSYNC,DE这些信号配合好。
15.2.2 RGB接口的LCD硬件连接信号
本次实验编程的屏幕属于RGB接口的显示屏,RGB接口的显示屏至少具备以下信号:
(1)像素时钟信号(DCLK)
像素时钟信号,用于同步LCD上的DE,VS,HS,RGB信号线。
(2)RGB数据信号(R[0:7] ,G[0:7],B[0:7])
三组信号线组成,分别代表R(红色),G(绿色),B(蓝色),这三组信号中的每一组都会有8根信号,三组共同组成24根线来控制颜色数据。
(3)RGB数据使能信号(DE)
RGB接口的 LCD 有两种驱动模式DE 模式和 HV 模式, 在HV模式下,需要用到HS与VS同伴RGB数据,在DE模式下,则只需要DE信号同伴RGB数据,但是一般做LCD显示程序,都会兼容两种模式,所以一般都要将数据使能信号(DE),垂直同步信号(HS),水平同步信号(VS)一起使用。
(4)水平同步信号,
电路中常用HS或HSYNC表示,详细说明下一小节会说明。
(5)垂直同步信号(帧同步或场同步)
电路中常用VS或VSYNC表示,相信说明下一小节会说明。
(6)LCD背光电源控制信号
一般是由普通GPIO控制(利用高低电平控制背光),背光就是在在LCD显示屏的背部一大串的灯珠,用它们来照亮屏幕。
例如100ASK_IMX6ULL开发板的LCD接口定义,就包含了上面所述的几种信号类型:
15.2.3 TFT材质液晶屏接口简介(7寸1024600TN-RGB)
嵌入式一般都采用TFT材质的液晶屏,如遇到别的材质的屏幕,操作方法也是雷同,可能稍微有些差异,针对差异去做修改即可,7寸1024600TN-RGB液晶屏幕接口引脚如下图,一些关键的引脚做了注释。
①信号时序与信号的极性
接下来我们查看下100ASK_7.0寸LCD手册时序图
从最小的像素开始分析,电子枪每次在CLK下降沿,如上图所示,该LCD在像素时钟下降沿采集数据,从数据线上得到数据,发射到显示屏上,然后移动到下一个位置。Dn0-Dn7上的数据来源就是前面介绍的FrameBuffer。就这样从一行的最左边,一直移动到一行的最右边,完成了一行的显示,假设为x。
当打完一行的最后一个数据后,就会收到Hsync行同步信号,如上图可知该LCD的HSD有效脉冲为低脉冲,根据时序图,一个HSD周期可以大致分为五部分组成:thp、thb、thd、thf。thpw称为脉冲宽度,这个时间不能太短,太短电子枪可能识别不到。电子枪正确识别到thpw后,会从最右端移动最左端,这个移动的时间就是thb,称之为移动时间。thfp表示显示完最右像素,再过多久HSD才来,thd为数据有效区,th为打完一行所需要的时间。
同理,当电子枪一行一行的从上面移动到最下面时,VSD垂直同步信号,如上图可知该LCD的VSD有效脉冲为低脉冲。然后就让电子枪移动回最上边。VSD中的tvpw是脉冲宽度,tvb是移动时间,tvfp表示显示完最下一行像素,再过多久VSD才来,tvd为数据有效区,tv为打完一帧所需要的时间。假设一共有y行,则LCD的分辨率就是x*y。
RGB数据有效信号(DEN),高电平表示数据有效。
根据以上信息大致了解几个关键信号的时序和极性,后面章节会详细介绍。
再根据上图,我们就可以确定像素时钟是51.2Mhz。
②RGB数据的存放形式
前面的LCD硬件接口,R0-R7、G0-G7、B0-B7,每个像素是占据3*8=24位的,即硬件上LCD的BPP是确定的。虽然硬件引脚连接是固定的,但我们使用的时候,可以根据实际情况进行取舍,比如我们的IMX6ULL开发板,可以让他支持不同的像素格式,ARGB888,ARGB555,RGB565等等,
本实验支持ARGB888和ARGB555。
ARGB888:每个像素就占据32位数据,其中最高字节A表示灰度透明度其余RGB数据8+8+8=24BPP。
ARGB555:每个像素就占据16位数据,其中最高位A表示灰度透明度其余RGB数据5+5+5=15BPP。
15.2 IMX6ULL LCD控制器操作及寄存器
15.2.1 LCD控制器模块介绍
IMX6ULL的LCD控制器名称为elcdif(增强型LCD接口)主要特性如下:
a.支持MPU模式,针对显示屏内部有显存的显示屏;
b.支持DOTCLK模式,针对RGB接口使用,本实验就是此模式;
c.VSYNC模式,针对高速数据传输(行场信号);
d. 8/16/18/24/32 bit 的bpp数据都支持,取决于IO的复用设置及寄存器配置;
e.MPU模式,VSYNC模式,DOTCLK模式,都具有配置的时序参数;
上图是IMX6ULL的LCD控制器框图,AXI是一种总线协议,通过此总线将显存中的RGB数据写入到FIFO,经过FIFO过度,到达LCD接口,LCD控制器分两个时钟域,一个是外设总线时钟域,一个是LCD像素时钟域,前者是用于让LCD控制器正常工作的时钟,后者是控制电子枪移动速度的时钟。Read_Data操作工作在MPU模式,我们采用的是DCLK模式,因此不予考虑。
以上只是介绍了部分,如需要更加详细的了解需要查看IMX6ull芯片手册《Chapter 34
Enhanced LCD Interface (eLCDIF)》
15.2.2 LCD控制器寄存器简介
[外链图片转存中…(img-Qpqmd64m-1642060379778)]
上图是我们将要使用到的寄存器,接着将会大致讲解下使用到的寄存器,更加详细说明会在后续的LCD控制编程实验中提及。
15.2.2.1 ①LCDIF_CTRL寄存器:
SFTRST:软复位,用于修改像素时钟后,进行复位同步时钟;
BYPASS_COUNT:DOTCLK和DVI modes都需要设置为1;
DOTCLK_MODE:设置为1,进入DOTCLK模式;
LCD_DATABUS_WIDTH:RGB数据总线,跟进数据总线宽度设置;
WORD_LENGTH:输入的RBG数据格式,即多少位表示一个像素;
MASTER:LCD控制器主机模式设置;
DATA_FORMAT_16_BIT:当设置为16BPP的时候需要设置该位
15.2.2.2 ②LCDIF_CTRL1寄存器:
BYTE_PACKING_FORMAT:用于表示4字节RGB数据中,那几个字节是属于有有效数据,因为其中有一个字节表示A(灰度,透明度)
15.2.2.3 ③LCDIF_TRANSFER_COUNT寄存器:
V_COUNT:表示垂直方向上的像素个数,即分辨率中的x;
H_COUNT:表示水平方向上的像素个数,即分辨率中的y;
15.2.2.4 ④LCDIF_VDCTRL0寄存器
VSYNC_PULSE_WIDTH:垂直同步信号的宽度;
VSYNC_PULSE_WIDTH_UNIT:根据不同模式下的计算时钟方式来决定垂直同步信号宽度;
VSYNC_PERIOD_UNIT:根据不同模式下的计算时钟方式来发垂直同步信号;
ENABLE_POL:DE数据有效信号的极性,即有效电平极性;
DOTCLK_POL:像素时钟信号的极性,即有效电平极性;
HSYNC_POL:水平同步信号的极性,即有效电平极性;
VSYNC_POL:垂直同步信号的极性,即有效电平极性;
ENABLE_PRESENT:在DOTCLK模式下,是否会硬件产生ENABLE使能数据信号;
VSYNC_OEB:VSYNC设置为输出还是输入模式,我们选择输出模式;
15.2.2.5 ⑤LCDIF_VDCTRL1寄存器
VSYNC_PERIOD:两个垂直同步信号之间的总数,即垂直方向同步信号的总周期;
15.2.2.6 ⑥LCDIF_VDCTRL2寄存器
HSYNC_PULSE_WIDTH:水平同步信号脉冲宽度;
HSYNC_PERIOD:两个水平同步信号之间的总数,即水平方向同步信号的总周期
15.2.2.7 ⑦LCDIF_VDCTRL3寄存器
HRIZONTAL_WAIT_CNT:水平方向上的等待像素个数;
VERTICAL_WAIT_CNT:垂直方向上的等待像素个数;
15.2.2.8 ⑧LCDIF_VDCTRL4寄存器
SYNC_SIGNALS_ON:工作在DOTCLK模式下,需要设置为1;
DOTCLK_H_VALID_DATA_CNT:DOTCLK模式下,水平方向上的有效像素点个数,即分辨率的y;
15.2.2.9 ⑨LCDIF_CUR_BUF寄存器
ADDR:通过LCD控制器,发送的当前帧地址;
15.2.2.10 ⑩LCDIF_NEXT_BUF寄存器
ADDR:通过LCD控制器,发送的下一帧地址;
15.3 编程_框架与准备
本节文档对应的视频是《第003节_LCD编程_框架与准备_P》。
15.3.3 功能目的
我们最终的目的是在LCD显示屏上画线、画圆和写字,此外还需要一个测试程序提供操作菜单,调用画线、画圆和写字操作,这些终究其核心是画点,我们需要实现画点才能实现其他功能,但是画点前也要让我们的LCD控制器正常工作起来才能实现它,最终总结:先让LCD控制器正常工作(配置寄存器),再编写画点的函数。
15.3.4 编程框架
接着我们就需要实现画点,在实现画点之前想两个问题:
①有两款尺寸大小的LCD显示屏,如何快速的在两个lcd上切换?
②有两款不同的CPU都需要显示同一款LCD显示屏,如何快速的在两个cpu上切换?
为了让程序更加好扩展,下面介绍“面向对象编程”的概念
我们发现LCD显示屏虽然不同尺寸,参数不同,但是它们终究是LCD显示屏,我们可以把他们归一类,当需要使用某款LCD显示屏的参数时就提供该款的参数,其他的LCD显示屏参数不管他不就可以了吗?
同理不同的CPU虽然LCD控制器地址不同,操作也不同,但是它们终究是LCD控制器,我们可以把他们归一类,当确定使用某个LCD控制器的时候就用这个LCD控制器的操作,其他的LCD控制器不管他。
下图是LCD编程的框架,尽可能的“高内聚低耦合”,即类的内聚性是否高,耦合度是否低。目的是使程序模块的可重用性、移植性大大增强。通常程序结构中各模块的内聚程度越高,模块间的耦合程度就越低
根据不同的LCD控制器特性,来设置不同的LCD控制器,对于我们开发板,就是imx6ull_con.c,假如希望在其它 开发板上也实现LCD显示,只需添加相应的代码文件xxx_con即可。
根据不同的LCD屏幕特性,来编写不同的LCD屏幕参数,对于我们的开发板,就是lcd_7_0.c,假如希望这个开发板支持别的LCD屏幕,只需添加相应的代码文件lcd_xxx.c即可。
15.4 编程_抽象出重要结构体
本节代码在裸机Git仓库 NoosProgramProject/(15_LCD编程/01_simple_test/lcd_manager.h) 与**裸机Git仓库 NoosProgramProject/(15_LCD编程/01_simple_test/lcd_controller_manager.h)**头文件中,本节文档对应的视频是《第004节_LCD编程_抽象出重要结构体_P》。
15.4.1 抽象出LCD屏幕的结构体
建立一个lcd_manager.h,将任意LCD都共有的参数(引脚的极性、时序、数据的格式bpp、分辨率等)使用面向对象的思维方式,将这些封装成结构体放在lcd_manager.h中
enum {
NORMAL = 0,
INVERT = 1,
};
/* NORMAL : 正常
* INVERT : 取反
*/
typedef struct pins_polarity {
int de; /* normal: 高电平使能数据 */
int vclk; /* normal: 在下降沿获取数据 */
int hsync; /* normal:高脉冲 */
int vsync; /* normal:高脉冲 */
}pins_polarity, *p_pins_polarity;
typedef struct time_sequence {
/* 垂直方向 */
int tvp; /* vysnc脉冲宽度 */
int tvb; /*上边黑框 , Vertical Back porch */
int tvf; /*下边黑框, Vertical Front porch */
/* 水平方向 */
int thp; /* hsync脉冲宽度 */
int thb; /* 左边黑框 ,Horizontal Back porch */
int thf; /* 右边黑框,Horizontal Front porch */
int vclk;
}time_sequence, *p_time_sequence;
typedef struct lcd_params {
char *name;
/*引脚极性参数*/
pins_polarity pins_pol;
/*时序参数*/
time_sequence time_seq;
/*分辨率*/
int xres;
int yres;
int bpp;
/*显存*/
unsigned int fb_base;
}lcd_params, *p_lcd_params;
以后就使用lcd_params结构体来表示lcd参数 ,通过register_lcd函数注册某款LCD屏幕参数到一个lcd_params结构体数组,然后通过select_lcd函数在lcd_params结构体数组中选中指定的LCD屏幕参数保存起来,提供给其他函数用。
15.4.2 抽象出LCD控制器的结构体
建立一个lcd_controller_manager.h,将任意LCD控制器都共有的函数(初始化函数,使能函数等)使用面向对象的思维方式,将这些封装成结构体放在lcd_controller_manager.h中
typedef struct lcd_controller{
char* name;
void (*init)(p_lcd_params plcdparams);
void(*enable)(void);
void(*disable)(void);
}lcd_controller, *p_lcd_controller;
以后就使用lcd_controller结构体来表示lcd控制器。
建立一个lcd_controller_manager.c,提供一系列的LCD控制器的管理函数,用这些管理函数,通过register_lcd_controller函数注册新的LCD控制器到lcd_controller的结构体数组中,然后通过select_lcd_controller函数在lcd_controller结构体数组中选中指定的LCD控制器,提供给其他函数用,最终用户再调用光宇LCD控制器的操作时,就会通过选中的lcd_controller的结构体访问到对应的某款LCD控制器的函数。
15.5 编程_LCD控制器
本节代码在**裸机Git仓库 NoosProgramProject/(15_LCD编程/01_simple_test/imx6ull_con.c)**源文件中, 本节文档对应的视频是《第005节_LCD编程_LCD控制器_P》。
15.5.1 LCD控制器相关引脚复用配置
根据前面硬件接口章节15.1.3和上图,我们知道要设置这30个引脚,设置引脚按两步走,第一步设置引脚复用功能,第二部设置引脚的硬件属性。查看相应的寄存器得知,复用功能寄存器均设置为0即可,接着硬件属性根据章节《4-1.3 GPIO操作方法》的内容,均设置为0xB9即可。
15.5.2 LCD控制器像素时钟配置
根据IMX6ULL芯片手册的Chapter 18 Clock Controller Module (CCM),我们就可以设置像素时钟为我们需要的51.2Mhz
15.5.2.1 ①确定PLL
由上图可知LCD控制器的时钟来源是PLL5(video pll)
[外链图片转存中…(img-1hb2DTWp-1642060379786)]
根据上图可得知VIDEO pll的公式,但是为了方便计算,我们不需要后面的小数运算即Video PLL output frequency(PLL5)= Fref * (DIV_SELECT + 0)
15.5.2.2 ②确定PLL后的分频系数
根据上图可知PLL5出来后经过两级分频,即PLL5_MAIN_CLK = PLL5 / POST_DIV_SELECT / VIDEO_DIV
15.5.2.3 ③PLL分频后进入LCDIF控制器前的分频系数
根据上图可知PLL5分频后到LCDIF控制器也有两级分频,即LCDIF1_CLK_ROOT = PLL5_MAIN_CLK /LCDIF1_PRED / LCDIF1_PODF,根据上面三个内容,我们可以采用以下这个配置来达到像素时钟51.2Mhz,DIV_SELECT = 32;NUM = 0;DENOM = 0;POST_DIV_SELECT = 1,VIDEO_DIV = 1LCDIF1_PRED = 3;LCDIF1_PODF = 5
带入时钟公式得24*(32+0)/1/1/3/5 ≈ 51.2Mhz。下面开始编程:
15.5.3 LCD控制器时钟编程
15.5.3.1 ①取消小数分配器
CCM_ANALOG->PLL_VIDEO_NUM = 0;
CCM_ANALOG->PLL_VIDEO_DENOM = 0;
清零表示取消小数分配器
15.5.3.2 ②设置CCM_ANALOG_PLL_VIDEOn寄存器
CCM_ANALOG->PLL_VIDEO = (2
设置PLL5使能,倍频为32倍,1分频(即不分频),选择外部24M晶振为时钟源,至此PLL5分频后为24 * 32 / 1
15.5.3.3 ③设置CCM_ANALOG_MISC2n
默认就为1分频,所以无需设置,至此PLL5分频后为24 * 32 / 1 / 1 = 768Mhz
15.5.3.4 ④设置CCM_CSCDR2
设置选中对应的时钟源,预分频系数为3,得768 / 3 = 256Mhz
CCM->CSCDR2 &= ~(7 CSCDR2 |= (2 CSCDR2 &= ~(7 CSCDR2 |= (2 CSCDR2 &= ~(7
15.5.3.5 ⑤设置CCM_CBCMR
CCM->CBCMR &= ~(7 CBCMR |= 4
设置预分频系数为5,最终得到51.2Mhz
15.5.3.6 ⑥重新同步时钟
/* 重新设置时钟后,需要软复位LCD控制器,让LCD控制器像素时钟同步*/
LCDIF->CTRL = 1CTRL = 0
15.5.4 LCD控制器像素格式配置
15.5.4.1 ①设置LCDIF_CTRLn寄存器
[外链图片转存中…(img-89nWwH5E-1642060379788)]
LCDIF->CTRL |= (1 bpp == 16)
{
LCDIF->CTRL |= 1
15.5.4.2 ②设置LCDIF_CTRL1n寄存器
if(plcdparams->bpp == 24 || plcdparams->bpp == 32)
{
LCDIF->CTRL1 &= ~(0xf CTRL1 |= (0x7
表示ARGB传输格式模式下,传输24位无压缩数据,A通道不用传输),当我们选用16bpp即ARGB555时,不需要设置此位。
15.5.5 LCD控制器时序配置及极性配置
15.5.5.1 ①设置LCDIF_TRANSFER_COUNT寄存器
```c
LCDIF->TRANSFER_COUNT = (plcdparams->yres xres “`
15.5.5.2 ②设置LCDIF_VDCTRL0n寄存器
[外链图片转存中…(img-CyWxG1LO-1642060379789)]
LCDIF->VDCTRL0 = (1 pins_pol.vsync pins_pol.hsync pins_pol.vclk pins_pol.de time_seq.tvp
15.5.5.3 ③配置LCDIF_VDCTRL1寄存器
LCDIF->VDCTRL1 = plcdparams->time_seq.tvb + plcdparams->time_seq.tvp + plcdparams->yres + plcdparams->time_seq.tvf;
设置垂直方向的总周期:上黑框tvb+垂直同步脉冲tvp+垂直有效高度yres+下黑框tvf
15.5.5.4 ④配置LCDIF_VDCTRL2寄存器
LCDIF->VDCTRL2 = (plcdparams->time_seq.thp time_seq.thb + plcdparams->time_seq.thp + plcdparams->xres + plcdparams->time_seq.thf);
[18:31] : 水平同步信号脉冲宽度
[17: 0] : 水平方向总周期
设置水平方向的总周期:左黑框thb+水平同步脉冲thp+水平有效高度xres+右黑框thf
15.5.5.5 ⑤配置LCDIF_VDCTRL3寄存器
LCDIF->VDCTRL3 = ((plcdparams->time_seq.thb + plcdparams->time_seq.thp) time_seq.tvb + plcdparams->time_seq.tvp);
设置ELCDIF的VDCTRL3寄存器
[27:16] :水平方向上的等待时钟数 =thb + thp
[15:0] : 垂直方向上的等待时钟数 = tvb + tvp
15.5.5.6 ⑥设置LCDIF_VDCTRL4寄存器
LCDIF->VDCTRL4 = (1xres);
设置ELCDIF的VDCTRL4寄存器
[18] : 使用VSHYNC、HSYNC、DOTCLK模式此为置1
17:0: 水平方向的宽度
15.5.6 设置显存
LCDIF->CUR_BUF = plcdparams->fb_base;
LCDIF->NEXT_BUF = plcdparams->fb_base;
CUR_BUF : 当前显存地址
NEXT_BUF : 下一帧显存地址
方便运算,都设置为同一个显存地址
15.5.7 编程_LCD设置
本节代码在**裸机Git仓库 NoosProgramProject/(15_LCD编程/01_simple_test/lcd_7_0.c)**源文件中 ,本节文档对应的视频是《第006节_LCD编程_LCD设置_P》
15.5.8 添加LCD屏幕名称
.name = "lcd_7.0",
先给我们本次实验的LCD屏幕参数一个名称,可以根据名称去选择我们需要的LCD屏幕参数。
15.5.9 极性设置
LCDIF_VDCTRL0n寄存器极性设置位
VSYNC_POL与HSYNC_POL:0表示低电平有效,1表示高电平有效
DOTCLK_POL:0表示上升沿有效,1表示下降沿有效
ENABLE_POL:0表示低电平有效,1表示高电平有效
接着根据15-1.4关键特性章节中,我们已经知道像素时钟DCLK是下降沿时获取数据,水平同步信号HSD和垂直同步信号VSD都是低电平有效,数据使能信号DEN是高电平表示数据有效,接着我们只要在lcd_7_0.c文件中设定成对应的标记用来识别即可,1)数据使能信号de设置为1, 2)像素时钟vclk极性设置为1 ,3)水平同步信号HSD和垂直同步信号VSD都设置为0。
enum {
NORMAL = 0,
INVERT = 1,
};
根据枚举内容,填入对应的参数即可
.pins_pol = {
.de = INVERT, /* normal: 低电平表示使能输出 */
.vclk = INVERT, /* normal: 在上升降沿获取数据*/
.hsync = NORMAL, /* normal: 低脉冲*/
.vsync = NORMAL, /* normal: 低脉冲*/
},
15.5.10 时序设置
观察上图中红色框框内容,接着我们只需要把上面数值往lcd_7_0.c中定义的lcd_7_0_params中填入对应的值即可
1)Thpw:结构体中的tvb = 20
2)Thb:结构体中的thb = 140
3)Thfp:结构体中的thf = 160
4)THA:结构体中的tvp = 1024
5)Tvpw:结构体中的tvp = 3
6)Tvb:结构体中的thp = 20
7)Tvfp:结构体中的tvf = 12
8)Tvd:结构体中的yres = 600
.time_seq = {
/* 垂直方向 */
.tvp= 3, /* vysnc脉冲宽度 */
.tvb= 20, /* 上边黑框, Vertical Back porch */
.tvf= 12, /* 下边黑框, Vertical Front porch */
/* 水平方向 */
.thp= 20, /* hsync脉冲宽度 */
.thb= 140, /* 左边黑框, Horizontal Back porch */
.thf= 160, /* 右边黑框, Horizontal Front porch */
.vclk= 51.2, /* MHz */
},
.xres = 1024,
.yres = 600
15.5.11 显存地址设置
根据上图imx_6ull可以挂接最大2GB的内存,可实际我们使用的100ask_imx_6ull开发板只挂接了512M的内存,因此我们可选的内存地址范围是0x800000000xa0000000。我们的裸机实验的链接地址是0x80100000,我们裸机实验程序不大,最多算它0x900000(实际没有这么多),那么我们显存可以用范围就变成0x810000000xa0000000,再计算我们需要的显存大小10246004=2457600≈2.4MB。
总结:0x81000000~0xa0000000范围内,任意满足2.4MB空间的内存都可以。
本次实验选取的显存是0x99000000。
15.6 编程_简单测试
简单测试编程实现LCD全屏顺序显示红,绿,蓝三种颜色。
本节代码在**裸机Git仓库 NoosProgramProject/(15_LCD编程/01_simple_test)**工程文件中,同时本节文档对应的视频是《第007节_LCD编程_简单测试_P》。
15.6.1 初始化LCD
/*添加LCD屏幕参数*/
lcd_7_0_add();
/*添加LCD控制器*/
lcd_contoller_add();
使用lcd_manager.c和lcd_controller_manager.c中的管理函数,添加LCD屏幕参数和LCD控制器到各自的结构体数组中。
/*选择Imx6ull的LCD控制器*/
select_lcd_controller("Imx6ull");
/*选择LCD屏幕参数*/
select_lcd("lcd_7.0");
使用lcd_manager.c和lcd_controller_manager.c中的管理函数,从各自的结构体数组中选择指定名称的的LCD屏幕参数和LCD控制器。
lcd_controller_init(g_p_lcd_selected);
最后通过lcd_controller_init函数将选择的LCD参数传入到被选中的LCD控制器中,调用该控制器的初始化函数进行初始化。
15.6.2 使能LCD
g_p_lcd_controller_selected变量就是被选LCD控制器的函数指针集合,接着调用对应的函数指针即可
void lcd_controller_enable(void)
{
if (g_p_lcd_controller_selected)
{
g_p_lcd_controller_selected->enable();
}
}
g_p_lcd_controller_selected变量就是被选LCD控制器的函数指针集合,现在我们已经成功的选中LCD控制器,接着调用lcd_controller_enable函数,它将负责调用g_p_lcd_controller_selected结构体中的使能函数。即是被选中的LCD控制器使能函数。
static void Imx6ull_lcd_controller_enable(void)
{
LCDIF->CTRL |= 1
由于我们选定是imx6ull的LCD控制器,所以g_p_lcd_controller_selected->enable()调用的就是我们使用的Imx6ull_lcd_controller_enable函数。
15.6.3 获取LCD参数
在执行我们的简单测试之前,还需要让我们的测试程序知道现在用的lcd关键参数,实现各种颜色的显示。
void get_lcd_params(unsigned int *fb_base, int *xres, int *yres, int *bpp)
{
*fb_base = g_p_lcd_selected->fb_base;
*xres = g_p_lcd_selected->xres;
*yres = g_p_lcd_selected->yres;
*bpp = g_p_lcd_selected->bpp;
}
获取显存地址fb_base,获取分辨率xres和yres,获取bpp即每个像素点所占的位数。
15.6.4 往framebuffer中写数据
为了支持16bpp或32bpp,我们需要分两个分支实现往framebuffer中写数据
p = (unsigned short *)fb_base;
for (x = 0; x
当获取的lcd参数bpp等于16时走上面分支
当bpp为16时,我们lcd控制设定的颜色格式是ARGB555,也就是ARRRRRGGGGGBBBBB,红绿蓝各占5位,最高一位为灰度。
p2 = (unsigned int *)fb_base;
for (x = 0; x
当获取的lcd参数bpp等于32时走上面分支,当bpp为32时,我们lcd控制设定的颜色格式是ARGB888,也就是AAAAAAAARRRRRRRRGGGGGGGGBBBBBBBB,红绿蓝各占8位,最高字节表示灰度。
15.7 编程_画点线圆
本节代码在**裸机Git仓库 NoosProgramProject/(15_LCD编程/02_dot_line_circle)**工程文件中,同时本节文档对应的视频是《第008节_LCD编程_画点线圆_P》。
15.7.1 实现画点
①一个点(x,y)在FB中的位置如图
可以得出其计算公式:(x,y)像素起始地址=fb_base+(xres*bpp/8)y + xbpp/8
②新建立一个frambuffer.c文件,由于新建立的frambuffer.c没有LCD的资源参数,所以需要编写一个调用
void fb_get_lcd_params(void)
{
get_lcd_params(&fb_base, &xres, &yres, &bpp);
}
③在获取LCD资源参数之后,当bpp是32位的时候,可以直接往对应显存位置填颜色值,但是当bpp等于16位时候,我们就需要一些转换,让32位颜色数据变成16位颜色数据,由于16bpp我们使用的是ARG555,因此实际每一个颜色只有5位。我们需要先将32位中的rgb分离出来,再通过移位指令移到ARG555格式对应的红绿蓝位置。
int r = (rgb >> 16)& 0xff;
int g = (rgb >> 8) & 0xff;
int b = rgb & 0xff;
/* argb555 */
r = r >> 3;
g = g >> 3;
b = b >> 3;
return ((r
④编写画点函数,提供x坐标,y坐标,和颜色,实现往显存对应的位置填写颜色数据即可,对于16PP,每个像素只占据16位(2字节),因此采用unsigned short类型;对于32PP,每个像素只占据32位(4字节),因此采用unsigned int类型;
void fb_put_pixel(int x, int y, unsigned int color)
{
unsigned short *pw; /* 16bpp */
unsigned int *pdw; /* 32bpp */
unsigned int pixel_base = fb_base + (xres * bpp / 8) * y + x * bpp / 8;
switch (bpp)
{
case 16:
pw = (unsigned short *) pixel_base;
*pw = convert32bppto16bpp(color);
break;
case 32:
pdw = (unsigned int *) pixel_base;
*pdw = color;
break;
}
}
15.7.2 实现画线
画线的具体原理不是我们的主要内容,我们直接百度“C语言 LCD 画线”可以得到相关的实现代码,比如这
新建一个geometry.c,复制博客中代码,替换里面的描点显示函数即可
最后在主函数测试程序里,加上画线的测试代码:
fb_get_lcd_params();
delay(100000);
draw_line(0, 0, xres - 1, 0, 0xff0000);
delay(100000);
draw_line(xres - 1, 0, xres - 1, yres - 1, 0xffff00);
delay(100000);
draw_line(0, yres - 1, xres - 1, yres - 1, 0xff00aa);
delay(100000);
draw_line(0, 0, 0, yres - 1, 0xff00ef);
delay(100000);
draw_line(0, 0, xres - 1, yres - 1, 0xff4500);
delay(100000);
draw_line(xres - 1, 0, 0, yres - 1, 0xff0780);
我们为划线函数提供对应的参数起点坐标,末点坐标,颜色就可以实现。
15.7.3 实现画圆
画圆的具体原理不是我们的主要内容,我们直接百度“C语言 LCD 画圆”可以得到相关的实现代码,
在geometry.c中添加,博客中代码,替换里面的描点显示函数即可
我们只需提供圆心的坐标,圆的半径,和圆的颜色即可实现。最后在主函数测试程序里,加上画圆画线的测试代码:
draw_circle(xres/2, yres/2, yres/4, 0xff00);
15.8 编程_显示文字
本节代码在**裸机Git仓库 NoosProgramProject/(15_LCD编程/03_font_test)**工程文件中,同时本节文档对应的视频是《第009节_LCD编程_显示文字_P》。
15.8.1 获取LCD参数
新建一个font.c,由于新建文件中没有LCD参数,因此也需要获取LCD参数,根据这些LCD参数显示我们需要显示的字符。
void font_init(void)
{
get_lcd_params(&fb_base, &xres, &yres, &bpp);
}
15.8.2 编写单个字符显示函数
文字也是由点构成的,一个个点组成的点阵,宏观的来看,就是文字。可以参考Linux内核源码中的相关操作,在内核中搜索“font”,打开font_8x16.c,可以看到里面的A字符内容如下:
/* 65 0x41 'A' */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x10, /* 00010000 */
0x38, /* 00111000 */
0x6c, /* 01101100 */
0xc6, /* 11000110 */
0xc6, /* 11000110 */
0xfe, /* 11111110 */
0xc6, /* 11000110 */
0xc6, /* 11000110 */
0xc6, /* 11000110 */
0xc6, /* 11000110 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
0x00, /* 00000000 */
根据这些数据,在一个8*16的区域里,将为1的点显示出来,为0的则不显示,最终将呈现一个字母“A”。根据字母的点阵在LCD上描画文字,需要的步骤如下:
a. 根据带显示的字符的ascii码在fontdata_8x16中得到点阵数据
b. 根据点阵来设置对应象素的颜色
c. 根据点阵的某位决定是否描颜色
void fb_print_char(int x, int y, char c, unsigned int color)
{
int i, j;
/* 根据c的ascii码在fontdata_8x16中得到点阵数据 */
const unsigned char *dots = &fontdata_8x16[c * 16];
unsigned char data;
int bit;
/* 根据点阵来设置对应象素的颜色 */
for (j = y; j
在font_8x16.c里面,每个字符占据16位,因此想要根据ascii码找到对应的点阵数据,需要对应的乘16,再取地址,得到该字符的首地址
再根据每个点阵数据每位是否为1,来调用描点函数fb_put_pixel()。这样,依次显示16个点阵数据,获得字符图。
15.8.3 编写实现字符串显示函数
显示字符串,那么就需要在每显示完一个字符后,x方向加8个像素,同时考虑是否超出屏幕(xres)显示范围进行换行处理(y+16)
void fb_print_string(int x, int y, char* str, unsigned int color)
{
int i = 0, j;
while (str[i])
{
if (str[i] == 'n')
y = y+16;
else if (str[i] == 'r')
x = 0;
else
{
fb_print_char(x, y, str[i], color);
x = x+8;
if (x >= xres) /* 换行 */
{
x = 0;
y = y+16;
}
}
i++;
}
}
最后在在主函数里,加上显示字符串的函数,传入希望显示的字符串即可。
fb_print_string(10, 10, "www.100ask.netnr100ask.taobao.com", 0xff00);
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net
相关推荐: 【IntelliJ IDEA】idea修改文件的file is read-only
今天碰到一个问题,不知怎么弄的,IDEA文件变成只读了,不能编辑修改。 如图文件的图标有这样的变化: 当我想要编辑修改这个文件的时候,出现下图所示的提示: 此处的弹框是问你“是否清除只读状态”,此时点击OK可以将这个文件的只读状态改为可编辑状态。 以上是第一种…