09-LCD

声明:

本文是本人在韦东山笔记的基础上加了一些注释,方便理解。原文地址:

在复习本章时的感悟:
本章除了说lcd的原理,以及对lcd参数和2440lcd控制器初始化之外呢,更重要的是韦东山老师展示了面向对象编
程的思维方式,即模块化的编程。这需要你最好去纸上把框架写出来,然后根据框架去编程。

第01节 LCD硬件原理

这节课简单介绍下LCD的操作原理。
关于显示原理,可以参考这篇博客:http://www.cnblogs.com/shangdawei/p/4760933.html
如下图的LCD示意图,里面的每个点就是一个像素点。
下面这个动图很形象,说明了LCD的电子枪投射原理。
在这里插入图片描述
下面这个图是说,电子枪有RGB三种颜色,按一定比例混合然后投射到显示屏上的某个像素上,某个像素就呈现某种颜色。每个像素用R,G,B三个分量表示,虽说像素上是RGB三种颜色,但是对我们人类来说离远了看就是混合好的某种颜色。
在这里插入图片描述 在这里插入图片描述
想象有一个电子枪,一边移动,一边发出各种颜色的光。这里有很多细节问题,我们一个一个的梳理。

  1. 电子枪是如何移动的?
    答:有一条CLK时钟线与LCD相连,每发出一次CLK(高或低电平),电子枪就移动一个像素。

  2. 颜色如何确定?
    答:由连接LCD的三组线:R(Red)、G(Green)、B(Blue)确定。

  3. 电子枪如何得知应跳到下一行?
    答:有一条HSYNC信号线与LCD相连,每发出一次脉冲(高低电平),电子枪就跳到下一行。

  4. 电子枪如何得知应跳到原点(即上图中,电子枪移动到右下角的点之后跳回到左上角点)?
    答:有一条VSYNC信号线与LCD相连,每发出一次脉冲(高低电平),电子枪就跳到原点。

  5. RGB线上的数据从何而来?
    答:内存里面划分一块显存(FrameBuffer),里面存放了要显示的数据,LCD控制器从里面将数据读出来,通过RGB三组线传给电子枪,电子枪再依次打到显示屏上。

  6. 前面的信号由谁发给LCD?
    答:有S3C2440里面的LCD控制器来控制发出信号。

对于第4点和第五点,有一个形象的图片,如下图:
在这里插入图片描述
通过JZ2440原理图对上面进行验证,下图的LCD控制器接口原理图:
①是时钟信号,每来一个CLK,电子枪就移动一个像素;
②是用来传输颜色数据;
③是垂直方向同步信号,FRAME(帧);
④是水平方向同步信号,LINE(行);
在这里插入图片描述
再来看看LCD的芯片手册:
先是VLED+、VLED-背光灯电源。
VDD、VDD是LCD电源。
R0-R7、G0-G7、B0-B7是红绿蓝颜色信号。
PCLK是像素时钟信号。
DISP是像素开关。
HSYNC、VSYNC分别是水平方向、垂直方向信号。
DE数据使能。
X1、Y1、X2、Y2是触摸屏信号。
在这里插入图片描述
从上图我们可以看出LCD有很多信号,这些信号要根据时序图传输才能正确显示。参考JZ2440_4.3寸LCD手册_AT043TN24的时序如下:
在这里插入图片描述
在这里插入图片描述
对上面2个图片(时序图)分析:
(1)从最小的像素开始分析,电子枪每次在CLK下降沿(本开发板是下降沿)从数据线Dn0-Dn7上得到数据,发射到显示屏上,然后移动到下一个位置。Dn0-Dn7上的数据来源就是前面介绍的FrameBuffer。就这样从一行的最左边,一直移动到一行的最右边,完成了一行的显示,假设为x。

(2)当打完一行的最后一个数据后,就会收到Hsync行同步信号,根据时序图,一个Hsync周期可以大致分为五部分组成:thp、thb、1/tc、thd、thf。thp称为脉冲宽度,这个时间不能太短,太短电子枪可能识别不到。电子枪正确识别到thp后,会从最右端移动最左端,这个移动的时间就是thb,称之为移动时间。thf表示显示完最右像素,再过多久Hsync才来。

(3)同理,当电子枪一行一行的从上面移动到最下面时,Vsync垂直同步信号就让电子枪移动回最上边。Vsync中的tvp是脉冲宽度,tvb是移动时间,tvf表示显示完最下一行像素,再过多久Vsync才来。 假设一共有y行,则LCD的分辨率就是x*y。

LCD显示配置示意图如下图:
当发出一个HSYNC信号后,电子枪就会从最右边花费HBP时长移动到最左边,等到了最右边后,等待HFP时长HSYNC信号才回来。因此,HBP和HFP分别决定了左边和右边的黑框。
同理,当发出一个VSYNC信号后,电子枪就会从最下边花费VBP时长移动到最上边,等到了最下边后,等待VFP时长VSYNC信号才回来。因此,VBP和VFP分别决定了上边和下边的黑框。 中间灰色区域才是有效显示区域。
在这里插入图片描述

好,再来解决最后一个问题:每个像素再FrameBuffer中,占据多少位BPP(Bits Per Pixels)?
前面的LCD引脚功能图里,R0-R7、G0-G7、B0-B7,每个像素是占据3*8=24位的,即硬件上LCD的BPP是确定的。
虽然LCD上的引脚是固定的,但我们使用的时候,可以根据实际情况进行取舍,比如我们的JZ2440使用的是16BPP,因此LCD只需要R0-R4、G0-G5、B0-B4与SOC相连,5+6+5=16BPP,每个像素就只占据16位数据。(你看你的板子原理图LCD部分,R接了5根线,G接了6根线,B接了5根线)。

本节,综上所述,我们写程序的思路如下:

  1. 查看LCD芯片手册,查看相关的时间参数、分辨率、引脚极性;
  2. 根据以上信息(第一点的信息)设置LCD控制器寄存器,让其发出正确信号;
  3. 在内存里面分配一个FrameBuffer,在里面用若干位表示一个像素,再把首地址告诉LCD控制器;

之后LCD控制器就能周而复始取出FrameBuffer里面的像素数据,配合其它控制信号,发送给电子枪,电子枪再让在LCD上显示出来。
以后我们想显示图像,只需要编写程序向FrameBuffer填入相应数据即可,硬件会自动的完成显示操作。

第02节 S3C2440_LCD控制器

这节课讲LCD控制器,是理论部分。

LCD控制器主要功能和需要的设置:

  1. 取数据:从内存(FrameBuffer)取出某个像素的数据;之后需要把FrameBuffer地址、BPP(Bits Per Pixels,即像素深度是指存储每个像素所用的位数)、分辨率告诉LCD控制器;
  2. 发数据:配合其它信号把FrameBuffer数据发给LCD;需要设置LCD控制器时序、设置引脚极性(设置引脚极性是指有的LCD外设是下降沿有效,有的是上升沿);

这里主要的难点就是如何配合其它信号(即下图中VIDEOMUX引脚的控制信号),需要我们阅读LCD芯片手册,知道其时序要求,然后设置相应的LCD控制器。先看下S3C2440芯片手册上的LCD控制器框图:
通过设置REGBANK(寄存器组)(这个寄存器是设置下图最右面的VIDEOMUX引脚的控制信号),LCDCDMA会自动自动(无需CPU参与)把内存上FrameBuffer里的数据,通过VIDPRCS发送到引脚VD[23:0]上,再配合VIDEOMUX引脚的控制信号以发出正确的时序,从而正确的显示出来。
在这里插入图片描述
S3C2440芯片手册介绍了LCD控制器支持TFT和STN两种LCD,我们常用的都是TFT材质的,因此主要看TFT相关的部分。

讲一下调色板的概念:
画油画的时候,通常先在调色板里配好想要的颜色,再用画笔沾到画布上作画。LCD控制器里也借用了这个概念,从FrameBuffer获得数据,这个数据作为索引从调色板获得对应数据,再发给电子枪显示出来。
在这里插入图片描述
如图,假如是16BPP的数据,LCD控制器从FB取出16bit数据,显示到LCD上。

如果想节约内存,对颜色要求也没那么高,就可以采用调色板的方式,调色板里存放了256个16bit的数据,FB只存放每个像素的索引,根据索引去调色板找到对应的数据传给LCD控制器,再通过电子枪显示出来。
注:这个调色板在LCD控制器中,是一小片内存,存着256个16bit的数据(颜色)。
在这里插入图片描述
假设现在想要LCD只显示一种颜色怎么办?

如果是16BPP/24BPP需要修改FB里面的数据,填充同一个值。
如果是8BPP可以修改FB为同一种颜色,也可以设置调色板为同一种颜色。
对于2440来说还有一种办法:对于S3C22440有个临时调色板的特性(即有个TPAL寄存器,一旦使能了临时调色板(TPAL寄存器),并设置为了某种颜色,则不管FB里面是什么数据,都只调用临时调色板的数据,从而只显示一种颜色。

第03节 编程_框架与准备

这节课讲编程的框架。具体的编程_LCD控制器、LCD设置、画点线圆、显示文字、使用调色板,这些是下面的节再细讲。这节课是框架。

本节主要有两个目的:
a. 讲解后续程序的框架;
b. 准备一个支持NAND、NOR启动的程序;

3.1 基本框架

框架如下:
(1)我们的目的是在LCD显示屏上画线、画圆(画线、画圆功能写成geomentry.c文件)和写字(携程font.c文件)。这三个功能的核心是画点(写成farmebuffer.c文件),以上这些都属于纯软件。
此外我们还需要一个lcd_test.c测试程序,去提供操作菜单,以调用画线、画圆和写字功能。
(2)再往下操作的是LCD相关的内容,不同的LCD外设(有3.5寸的有4.3寸的),其配置的参数也会不一样,通过lcd_3.5.c或lcd_4.3.c来设置。
(3)根据LCD的特性,来设置LCD控制器,对于我们开发板,就是s3c2440_lcd_controller.c,假如希望在其它其它开发板上也实现LCD显示,只需添加相应的代码文件即可。
框架图如下,尽可能的“高内聚低耦合”。:
在这里插入图片描述
在这里插入图片描述
lcd.c文件管理lcd_3.5文件和lcd_4.3文件。
lcd_controller.c文件管理s3c2440_lcd_controller.c和ti_s3c2440_lcd_controller.c文件。

3.1 细化框架_结合面向对象程序设计的思想

为了让程序更加好扩展(即让程序模块化),下面介绍“面向对象编程”的概念。

假如我们写好程序后,有两款尺寸大小的lcd,如何快速的在两个lcd上切换?
首先我们抽象出lcd_3.5.c和lcd_4.3.c的共同点,比如都有初始化函数init(),我们可以新建一个lcd.c,然后定义一个结构体:

struct lcd_opr{
   
    void (*init)(void);
};

你要知道不同尺寸lcd的初始化里面的参数是不一样的。但共同点是无论什么尺寸的lcd你都得初始化吧。因此写一个指针,指向特定的lcd初始化函数。
这样一来,用户不用接触lcd_3.5.c和lcd_4.3.c,只需要在lcd.c里通过指针访问对应的结构体的函数,也就调用了不同init()。
在这里插入图片描述
注意:现在的LCD相关代码比较大,超过了4K,因此需要将代码写为满足nor flash和nand flash。nand flash将全部代码重定位。具体代码你去看源码吧。并且这个以前也讲过了。
本节源码见:
F:\韦东山\005_ARM裸机1期加强版(又叫新1期,151节,23节免费,已完结)\源码文档图片\源码\源码_20180321_添加传感器\020_lcd_017\001_nand_nor_boot

第04节 编程_抽象出重要结构体

开始正式编写程序,根据前面的框架,新建如下文件:
font.c、framebuffer.c、geometry.c、lcd.c、lcd_4.3.c、lcd_controller.c、s3c2440_lcd_controller.c、lcd_test.c

首先编写lcd_controller.c,它向上要接收不同LCD的参数,向下要使用这些参数设置对应的LCD控制器。

前面我们列举了LCD的参数,例如引脚的极性、时序、数据的格式bpp、分辨率等,使用面向对象的思维方式,将这些封装成结构体放在lcd.h中:

enum {
   
	NORMAL = 0,
	INVERT = 1,
};

/* NORMAL : 正常极性
 * INVERT : 反转极性
 */
typedef struct pins_polarity {
   
	int vclk;  /* normal: 在下降沿获取数据 */
	int rgb;   /* normal: 高电平表示1 */
	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 {
   
	/* 引脚极性 */
	pins_polarity pins_pol;
	
	/* 时序 */
	time_sequence time_seq;
	
	/* 分辨率, bpp */
	int xres;
	int yres;
	int bpp;
	
	/* framebuffer的地址 */
	unsigned int fb_base;
}lcd_params, *p_lcd_params;

以后就使用lcd_params结构体来表示lcd参数。

对于有多个lcd的情况,如一个4.3的和一个3.5的。则再定义个一个结构体,包含指针初始化函数和使能函数,放在lcd_controller.h里面:

下面这个结构体,是用typedef来写的,方便你有多个lcd,在你的特定尺寸lcd外设进行定义定义
结构体变量,即s3c2440_lcd_controller.c文件中的
struct lcd_controller s3c2440_lcd_controller = {
   };

typedef struct lcd_controller {
   
	void (*init)(p_lcd_params plcdparams);
	void (*enable)(void);
	void (*disable)(void);
}lcd_controller, *p_lcd_controller;


struct 结构体名{
   
成员表列
}变量名表列;

在s3c2440_lcd_controller.c(文件里面包含#include “lcd_controller.h”,以进行结构体变量定义),还需构造一个当前soc的lcd控制器结构体:

/*定义一个s3c2440_lcd_controller名称的lcd_controller结构体并进行初始化*/
struct lcd_controller s3c2440_lcd_controller = {
   

/*xxx是指某个函数名,现在还没写*/
	.init    = xxx,
	.enalbe  = xxx,
	.disable = xxx,
};

最后在lcd_controller.c里传入lcd参数,再通过指针函数初始化对应的lcd控制器:

void lcd_controller_init(p_lcd_params plcdparams)/*函数参数参入的是来自lcd.c中的参数*/
{
   
	/* 调用 2440(即特定lcd型号的) 的LCD控制器的初始化函数 */
	lcd_controller.init(plcdparams);
	/*注意:应该是s3c2440_lcd_controller.init(plcdparams);
	韦东山老师写错了。
	你要知道,这节课为韦东山的代码还没有编译呢。
*/
}

第05节 编程_LCD控制器

这节课继续上一节的代码。
首先完善s3c2440_lcd_controller.c:

struct lcd_controller s3c2440_lcd_controller = {
   
	.init    = s3c2440_lcd_controller_init,
	.enalbe  = s3c2440_lcd_controller_enalbe,
	.disable = xs3c2440_lcd_controller_disable,
};

然后对结构体s3c2440_lcd_controller 中的每个函数进行功能实现。

5.1 s3c2440_lcd_controller_init()

5.1.1 LCDCON1

首先是s3c2440_lcd_controller_init,依次设置LCD控制器寄存器,先是LCD寄存器1:
在这里插入图片描述
[27:18]为只读数据位,不需要设置;

[17:8]用于设置CLKVAL(像素时钟频率),我们使用的是TFT屏,因此采用的公式是VCLK = HCLK / [(CLKVAL+1) x 2],其中HCLK为100M。LCD手册里面Clock cycle的要求范围为5-12MHz即可,即假设VCLK=9,根据公式9=100/[(CLKVAL+1)x2],算出CLKVAL≈4.5=5。
VCLK为plcdparams->time_seq.vclk,则clkval = HCLK/plcdparams->time_seq.vclk/2-1+0.5;

[7]不用管,默认即可;

[6:5]TFT lcd配置为0b11;

[4:1]设置bpp模式,根据传入的plcdparams->bpp配置为相应的数值;

[0]LCD输出使能,先暂时关闭不输出;

5.1.2 LCDCON2

在这里插入图片描述
对比2440LCD部分时序图和LCD时序图,得出两者之间关系,以后就可通过plcdparams传参数进来设置相关寄存器。(tvb、line、tvf、tvp都是你传参进来的)(时序字母的具体含义,去看时序图)

[31:24] : VBPD = tvb - 1

[23:14] : LINEVAL = line - 1

[13:6] : VFPD = tvf - 1

[5:0] : VSPW = tvp - 1

5.1.3 LCDCON3

在这里插入图片描述
(thb、列、thf都是你传参进来的)
[25:19] : HBPD = thb - 1

[18:8] : HOZVAL = 列 - 1

[7:0] : HFPD = thf - 1

5.1.4 LCDCON4

在这里插入图片描述
(thp是你传参进来的)
[7:0] : HSPW = thp - 1

5.1.5 LCDCON5

设置
在这里插入图片描述
这个寄存器用来设置极性、 设置16bpp、设置内存中象素存放的格式。

[12] : BPP24BL

[11] : FRM565, 置一代表5:6:5

[10] : INVVCLK, 0 = The video data is fetched at VCLK falling edge

[9] : HSYNC是否反转

[8] : VSYNC是否反转

[7] : INVVD, rgb是否反转

[6] : INVVDEN

[5] : INVPWREN

[4] : INVLEND

[3] : PWREN, LCD_PWREN output signal enable/disable

[2] : ENLEND

[1] : BSWP

[0] : HWSWP

5.1.6 LCDSADDR1

用来设置framebuffer地址
在这里插入图片描述
[29:21] : LCDBANK, A[30:22] of fb

[20:0] : LCDBASEU, A[21:1] of fb

即用寄存器的[29:0]表示起始地址的[30:1]
即用寄存器的[29:0]表示起始地址的[30:1]

5.1.6 LCDSADDR2

在这里插入图片描述
[20:0] : LCDBASEL, A[21:1] of end addr
即framebuffer的结束地址。

5.2 相关引脚设置

看2440原理图,去设置相关引脚,包括背光控制引脚、LCD专用引脚、电源控制引脚:

void jz2440_lcd_pin_init(void)
{
   
	/* 初始化引脚 : 背光引脚 */
	/*看2440原理图(看韦东山的),lcd芯片有led+和led—引脚,2440原理图中有个lcd背光电路,
	真正控制背光的是KEYBOARD引脚,其接到GPB0上,故控制GPB0从而控制背光灯*/
	GPBCON &= ~0x3;
	GPBCON |= 0x01;

	/* LCD专用引脚 */
	/*看2440原理图,发现LCD接于2440的各个引脚要么是GPC,要么是GPD,(对,还有一个GPG引脚,咱们
	在下面说)。因此这里把GPCCON和GPDCON使能*/
	/*看2440芯片手册,GPCCON的GPC0 - GPC15置为10,含义应该是LCD专属的*/
	/*全置为10即为0xaaaaaaaa*/
	GPCCON = 0xaaaaaaaa;
	GPDCON = 0xaaaaaaaa;

	/* PWREN  看原理图,是接于2440的GPG4引脚
	2440芯片手册搜索GPG4,发现其置为11意为LCD_PWREN
	*/
	GPGCON |= (3<<8);/*意思是说,让GPG4置为LCD_PWREN而不是INOUT或者OUTPUT,LCD_PWREN具体的是否
	*使能,你要看LCDCON5的bit3*/
}

5.3 s3c2440_lcd_controller_enalbe

void s3c2440_lcd_controller_enalbe(void)
{
   
	/* 背光引脚 : GPB0 */
	GPBDAT |= (1<<0);
	
		/* pwren	: 给LCD提供AVDD(即提供逻辑电源(可以理解为供电))  */
	/*LCDCON的bit5是PWREN, 作用为LCD_PWREN(GPG4中的) output signal enable/disable*/
	LCDCON5 |= (1<<3);//让LCD_PWREN使能
	
	/* LCDCON1的BIT 0 : 作用为:设置LCD 控制器控制器控制器 是否输出信号 */
	LCDCON1 |= (1<<0);
}

5.4 s3c2440_lcd_controller_disable

void s3c2440_lcd_controller_disable(void)
{
   
	/* 背光引脚 : GPB0 */
	GPBDAT &= ~(1<<0);

	/* pwren    : 不不给LCD提供AVDD  */
	LCDCON5 &= ~(1<<3);

	/* LCDCON1'BIT 0 : 设置LCD控制器是否输出信号 */
	LCDCON1 &= ~(1<<0);
}

最后,下面是本节的全部代码:

#define HCLK 100

void jz2440_lcd_pin_init(void)
{
   
	/* 初始化引脚 : 背光引脚 */
	GPBCON &= ~0x3;
	GPBCON |= 0x01;

	/* LCD专用引脚 */
	GPCCON = 0xaaaaaaaa;
	GPDCON = 0xaaaaaaaa;

	/* PWREN */
	GPGCON |= (3<<8);
}


/* 根据传入的LCD参数设置LCD控制器 */
void s3c2440_lcd_controller_init(p_lcd_params plcdparams)
{
   
	int pixelplace;
	unsigned int addr;

	jz2440_lcd_pin_init();
	
	/* [17:8]: clkval, vclk = HCLK / [(CLKVAL+1) x 2]
	 *                   9   = 100M /[(CLKVAL+1) x 2], clkval = 4.5 = 5
	 *                 CLKVAL = 100/vclk/2-1
	 * [6:5]: 0b11, tft lcd
	 * [4:1]: bpp mode
	 * [0]  : LCD video output and the logic enable/disable
	 */
	int clkval = (double)HCLK/plcdparams->time_seq.
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值