[外链图片转存中…(img-lRChXkF3-1642060379771)]
假设上图是一个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接口定义,就包含了上面所述的几种信号类型:
嵌入式一般都采用TFT材质的液晶屏,如遇到别的材质的屏幕,操作方法也是雷同,可能稍微有些差异,针对差异去做修改即可,7寸1024600TN-RGB液晶屏幕接口引脚如下图,一些关键的引脚做了注释。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vNh3YKOO-1642060379773)(https://cdn.jsdelivr.net/gh/DongshanPI/HomeSite-Photos@main/IMX6ULL-BareMetal/LCD_program_bare_metal_image-20220112145308136.png)]
15.2.4 LCD关键特性①信号时序与信号的极性
接下来我们查看下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。
[外链图片转存中…(img-EtHcBoO3-1642060379775)]
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
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?