裸机开发-LCD 编程

编程首先必须要搭建一个框架,框架可以在一开始时不必尽善尽美,我们可以在一边写的时候来一边完善矿建,但是框架一定是编程的开始
我们需要达到的目的是实现屏幕画点、画线以及写字,所以为了实现这个目的,我们需要对lcd控制器进行设置,而lcd控制器的设置需要依赖于我们lcd硬件信息,所以硬件信息我们需要放在一个文件里,方便lcd控制器配置文件对lcd硬件信息的提取。最后我们还要在最上层写一个应用测试文件

最上层需要写一个测试文件
应用层 实现画点、画线及写字功能
硬件信息层,在这层里包含了lcd硬件信息
lcd控制器层,需要在这一层里根据上一层的信息来配置lcd控制器

然而我们需要对这个框架进行优化,把层次分得更细一些

最上层需要写一个测试文件
应用层 以画线为基础来实现写字
应用层 以画点为工具来实现画线
应用层 实现画点
硬件信息层,在这层里包含了lcd硬件信息
lcd控制器层,需要在这一层里根据上一层的信息来配置lcd控制器

为了让程序具有更好的移植性,需要把lcd硬件信息层和lcd控制器层再作细分
把lcd硬件信息的通用部分和lcd硬件(不同的lcd显示屏)的个性化信息相互剥离
把lcd控制器通用信息和lcd控制器(不同的开发板)的个性化信息相互剥离

我们先从底层开始,创建硬件信息层和lcd控制器层

在lcd.h的文件里是尽量包含lcd的通常信息,后面将构建个性化的lcd屏硬件信息,我们的目的是让上层使用这些信息的程序可以尽量和实际硬件信息相互独立

lcd.h

#ifndef __LCD_H__
#define __LCD_H__

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

typedef struct pins_polarity {
	int de;    /* normal: 高电平时可以传输数据 */
	int pwren; /* normal: 高电平有效 */
	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 {
	char *name;
	/* 引脚极性 */
	pins_polarity pins_pol;

	/*时序*/
	time_sequence time_seq;

	/*分辨率*/
	int xres;
	int yres;
	int	bpp;

	/*framebuffer地址*/

	unsigned int fb_base;
}lcd_params, *p_lcd_params;

#endif

lcd_controller.h

#ifndef __LCD_CONTROLLER_H__
#define __LCD_CONTROLLER_H__

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

#endif

现在我们来初始化lcd控制器,为了更为清楚的理解lcd屏硬件层的机制和策略分离的思想,把初始化lcd控制器放在前面,将初始化lcd硬件信息放在后面
回顾一下之前所写的硬件信息
在这里插入图片描述
这个图是指导,告诉lcd控制器除了需要发送vd(video data)(通过数据总线)传递数据信息以外,还需要传递一下控制信号,比如时序,HSYNC,VSYNC,等等

LCDCON1
CLKVAL 用来决定每一个像素时钟频率
PNRMODE 选择LCD的款式 这里选择TFT
BPPMODE 选择BPP模式,这里选择16bpp
ENVID 使能LCD控制器
LCDCON2
VBPD VFPD VSPW 都是表示垂直方向的时间参数
LINEVAL These bits determine the vertical size of LCD panel.
显示多少行
LCDCON3
主要是水平方向的时间参数
HOZVAL 每行有多少个像素
LCDCON4
HSPW(TFT) 水平方向上的同步信号的脉冲
LCDCON5
两个只读位是无法修改的,所以可以忽略
FRM565 16位bpp里我们选择的是565
INVVCLK INVVLINE INVVFRAME INVVD 包括后面都是代表着极性控制
其中0代表normal,1代表inverted
我们在之前的时序图中已经看到了,在电平的下降沿取数据,而某些LCD可能不一样,我们就需要设置它的极性
LCDSADDR 用来设置framebuffer的基地址
涉及到极性的位非常多,我们不一定需要每一位都需要配置,极性是管脚的性质,所以我们只需要也只可以给有和s3c2440芯片相连的管脚设置极性。如下是开发板电路图
在这里插入图片描述
上面有一个背光电路
lcd本身是不发光的,它发光的原因当然不是前面所说的电子qiang轰击荧光物质发光,那是crt。lcd需要背光源,在这里我们选择比较简单的gpio控制的背光灯

#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.vclk/2-1+0.5;
	int bppmode = plcdparams->bpp == 8  ? 0xb :\
				  plcdparams->bpp == 16 ? 0xc :\
				  0xd;  /* 0xd: 24bpp */
	LCDCON1 = (clkval<<8) | (3<<5) | (bppmode<<1) ;

	/* [31:24] : VBPD    = tvb - 1
	 * [23:14] : LINEVAL = line - 1
	 * [13:6]  : VFPD    = tvf - 1
	 * [5:0]   : VSPW    = tvp - 1
	 */
	LCDCON2 = 	((plcdparams->time_seq.tvb - 1)<<24) | \
	            ((plcdparams->yres - 1)<<14)         | \
				((plcdparams->time_seq.tvf - 1)<<6)  | \
				((plcdparams->time_seq.tvp - 1)<<0);

	/* [25:19] : HBPD	 = thb - 1
	 * [18:8]  : HOZVAL  = 列 - 1
	 * [7:0]   : HFPD	 = thf - 1
	 */
	LCDCON3 =	((plcdparams->time_seq.thb - 1)<<19) | \
				((plcdparams->xres - 1)<<8)		      | \
				((plcdparams->time_seq.thf - 1)<<0);

	/* 
	 * [7:0]   : HSPW	 = thp - 1
	 */
	LCDCON4 =	((plcdparams->time_seq.thp - 1)<<0);

    /* 用来设置引脚极性, 设置16bpp, 设置内存中象素存放的格式
     * [12] : BPP24BL
	 * [11] : FRM565, 1-565
	 * [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
	 */

	pixelplace = plcdparams->bpp == 24 ? (0) : |\
	             plcdparams->bpp == 16 ? (1) : |\
	             (1<<1);  /* 8bpp */
	LCDCON5 = (plcdparams->pins_pol.vclk<<10) |\
	          (plcdparams->pins_pol.rgb<<7)   |\
	          (plcdparams->pins_pol.hsync<<9) |\
	          (plcdparams->pins_pol.vsync<<8) |\
 			  (plcdparams->pins_pol.de<<6)    |\
			  (plcdparams->pins_pol.pwren<<5) |\
			  (1<<11) | pixelplace;

	/* framebuffer地址 */
	/*
	 * [29:21] : LCDBANK, A[30:22] of fb
	 * [20:0]  : LCDBASEU, A[21:1] of fb
	 */
	addr = plcdparams->fb_base & ~(1<<31);
	LCDSADDR1 = (addr >> 1);

	/* 
	 * [20:0] : LCDBASEL, A[21:1] of end addr
	 */
	addr = plcdparams->fb_base + plcdparams->xres*plcdparams->yres*plcdparams->bpp/8;
	addr >>=1;
	addr &= 0x1fffff;
	LCDSADDR2 = addr;//	
}

void s3c2440_lcd_controller_enalbe(void)
{
	/* 背光引脚 : GPB0 */
	GPBDAT |= (1<<0);
	
	/* pwren    : 给LCD提供AVDD  */
	LCDCON5 |= (1<<3);
	
	/* LCDCON1'BIT 0 : 设置LCD控制器是否输出信号 */
	LCDCON1 |= (1<<0);
}

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

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

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

struct lcd_controller s3c2440_lcd_controller = {
	.name	= "s3c2440",
	.init    = s3c2440_lcd_controller_init,
	.enalbe  = s3c2440_lcd_controller_enalbe,
	.disable = s3c2440_lcd_controller_disable,
};

总结一下配置寄存器的思路
可以分为lcd控制器内和外,内即根据REGBANK中的几个寄存器,外即为根据电路图中的管脚
需要配置时序,配置时序是将lcd控制器的时序图和lcd硬件的时序图想对应匹配。
需要配置极性,跟时序相关的极性我们根据时序图来配置,其他极性还包括电源控制和背光控制,这两个需要分别根据电源电路图和背光源电路图来配置
需要配置其他硬件相关的信息,比如bpp模式、分辨率及framebuffer的基地址
还有这里传递信息是依靠gpio来传递数据信息,所以需要对相应的gpio寄存器进行配置

所以我们单独抽象出一个函数在Init中实现gpio管脚的初始化,让gpio管脚处于lcd专用状态,电源控制和背光源控制都是通过gpio实现信息传递。

LCD硬件信息
lcd_4.3.C

#define LCD_FB_BASE 0x33c00000

lcd_params lcd_4_3_params = {
	.name = "lcd_4.3",
	.pins_polarity = {
		. de = NORMAL,    /* normal: 高电平时可以传输数据 */
		. pwren = NORMAL , /* normal: 高电平有效 */
		. vclk = NORMAL ,  /* normal: 在下降沿获取数据 */
		. rgb = NORMAL ,   /* normal: 高电平表示1 */
		. hsync = INVERT , /* normal: 高脉冲 */
		. vsync = INVERT , /* normal: 高脉冲 */
	},

	.time_sequence = {
	/* 垂直方向 */
		. tvp = 10, /* vysnc脉冲宽度 */
		. tvb = 2, /* 上边黑框, Vertical Back porch */
		. tvf = 2, /* 下边黑框, Vertical Front porch */

		/* 水平方向 */
		. thp = 41, /* hsync脉冲宽度 */
		. thb = 2, /* 左边黑框, Horizontal Back porch */
		. thf = 2, /* 右边黑框, Horizontal Front porch */

		. vclk = 9,

	},

	/*分辨率*/
	. xres = 480,
	. yres = 272,
	. bpp = 16,

	/*framebuffer地址*/

	. fb_base = LCD_FB_BASE ,

};

现在需要实现代码分层的思想,我们的目的是在具体的controller层上面再构建一层,让这一层实现对具体硬件的封装,包含对具体的平台进行封装
对lcd控制器的封装策略
1、用数组来保存底层的各种lcd控制器
2、在这一层中实现一个register_lcd_controller来给底层函数注册硬件信息到数组中使用
3、在这一层中实现一个select_lcd_controller来给上层函数做选择使用
4、上层函数会通过名字来匹配lcd_controller
lcd_controller.c

#define LCD_CONTROLLER_NUM 10

static p_lcd_controller p_lcd_controller_array[LCD_CONTROLLER_NUM];
static p_lcd_controller g_p_lcd_controller_selected;


int register_lcd_controller(p_lcd_controller plcdcon)
{
	int i;

	for(i = 0; i < LCD_CONTROLLER_NUM; i++)
	{
		if(!p_lcd_controller_array[i])
		{
			p_lcd_controller_array[i] = plcdcon;
			return i;
		}
	}
		
	return -1;	
}

int select_lcd_controller(char *name)
{
	int i;

	for(i = 0; i < LCD_CONTROLLER_NUM; i++)
	{
		if(p_lcd_controller_array[i] && !strcmp(p_lcd_controller_array[i]->name, name))
			g_p_lcd_controller_selected = p_lcd_controller_array[i];
		return i;
	}

	return -1;
}

对lcd参数做同样的操作
lcd.c

#define LCD_NUM 10

static p_lcd_params p_array_lcd[LCD_NUM];
static p_lcd_params g_p_lcd_selected;

int register_lcd(p_lcd_params plcd)
{
	int i;
	for (i = 0; i < LCD_NUM; i++)
	{
		if (!p_array_lcd[i])
		{
			p_array_lcd[i] = plcd;
			return i;
		}
	}
	return -1;		
}

int select_lcd(char *name)
{
	int i;
	for (i = 0; i < LCD_NUM; i++)
	{
		if (p_array_lcd[i] && !strcmp(p_array_lcd[i]->name, name))
		{
			g_p_lcd_selected = p_array_lcd[i];
			return i;
		}
	}
	return -1;		
}

然而这并不是这两个文件的结束,程序分层的思想要始终牢记在心

s3c2440_lcd_controller.c 最下面增加如下的代码

void s3c2440_lcd_controller_add(void)
{
	register_lcd_controller(&s3c2440_lcd_controller);
}

lcd_controller.c 增加如下的代码

void lcd_controller_enable(void)
{
	if(g_p_lcd_controller_selected)
	{	
		g_p_lcd_controller_selected.enable();
	}
}

void lcd_controller_disable(void)
{
	if(g_p_lcd_controller_selected)
	{	
		g_p_lcd_controller_selected.disable();
	}
}

int lcd_controller_init(p_lcd_params plcdparmas)
{
	/*调用selected后的全局变量g_p_lcd_controller_selected*/
	/*然而在外面调用时,不能确定已经找到了这个全局变量*/
	if(g_p_lcd_controller_selected)
	{
		g_p_lcd_controller.init(plcdparams);
		return 0;
	}
	return -1;
}
void lcd_controller_add(void)
{
	/*	由于这里只一种芯片平台,无法体现出程序分层的优势
	*	如果还有其他的芯片平台,我们只需修改到这一层就可以了
	*/
	s3c2440_lcd_controller_add();
}

lcd_4.3.c 增加如下代码

/*
*	当我们每增加一款新的lcd硬件,我们都需要把它注册到数组中进行统一管理
*/
void lcd_4_3_add(void)
{
	register_lcd(&lcd_4_3_params);
}

lcd.c 需要增加的代码

int lcd_init(void)
{
	/*注册lcd硬件*/
	lcd_4_3_add();
	
	/*注册lcd_controller*/
	lcd_controller_add();
	
	/*找到合适的lcd硬件, 我们是根据名字来匹配*/
	selected_lcd("lcd_4.3");
	
	/*找到合适的lcd_controller, 同样也是按照名字来匹配*/
	selected_lcd_controller("s3c2440");
	
	/*用lcd的参数来对lcd_controller 进行初始化*/
	lcd_controller_init(g_p_lcd_selected);
}

底层已经实现完成,现在来实现应用层
之前在程序分层时候讲过,应用层也要进行分层
实现画点-以画点为基础画线-以画线为基础来实现写字
程序后面做了一些改动,为了让上层的函数可以直接获得lcd的参数,在lcd.c中构造了一个取参数函数给上层用户使用
lcd.h 增加如下代码

typedef struct lcd_params_for_user {
	/*鍒嗚鲸鐜?/
	int xres;
	int yres;
	int bpp;

	/*framebuffer鍦板潃*/

	unsigned int fb_base;	
	
}lcd_params_for_user, *p_lcd_params_for_user;

lcd.c增加如下代码

void get_lcd_params(lcd_params_for_user *lcdparamsforuser)
{
	lcdparamsforuser->xres = g_p_lcd_params_selected->xres;
	lcdparamsforuser->yres = g_p_lcd_params_selected->yres;
	lcdparamsforuser->bpp = g_p_lcd_params_selected->bpp;
	lcdparamsforuser->fb_base = g_p_lcd_params_selected->fb_base;
}

然后我们在上层引用中,可以按照如下的方式来访问这些参数

lcd_params_for_user lcdparamsforuser;
get_lcd_params(&lcdparamsforuser);
int i = lcdparamsforuser.xrey;
...

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值