1.准备框架
为了让程序更加好扩展,体现出”高内聚、低耦合"的特点,能够兼容各种不同型号的lcd,假如有两款尺寸大小的lcd,如何快速的在两个lcd上切换?
首先我们抽象出lcd_3.5.c和lcd_4.3.c的共同点,比如都有初始化函数init(),我们可以新建一个lcd.c,然后定义一个结构体:
struct lcd_opr{ void (*init)(void); };
用户不接触lcd_3.5.c和lcd_4.3.c,只需要在lcd.c里通过指针访问对应的结构体的函数,也就调用了不同init(),如下图所示:
我们的目的是在LCD显示屏上画线、画圆(geomentry.c)和写字(font.c)其核心是画点(farmebuffer.c),这些都属于纯软件。此外还需要一个lcd_test.c测试程序提供操作菜单,调用画线、画圆和写字操作。
往下操作的是LCD相关的内容,不同的LCD,其配置的参数也会不一样,通过lcd_3.5.c或lcd_4.3.c来设置属性参数。
根据LCD的特性,来设置LCD控制器,首先编写lcd_controller.c,它向上要接收不同LCD的参数,向下要使用这些参数设置对应具体的某一款LCD控制器。
对于我们开发板,就是s3c2440_lcd_controller.c,假如希望在其它开发板上也实现LCD显示,只需添加相应的代码文件即可。文件自上而下的框架如下:
1)构造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;
2)构造LCD行为方法
我们知道在c++中是面向对象编程的,那么一个对象就有它的属性和方法,LCD属性我们上面已经定义好了,那么方法我们可以定义一个lcd_controller.c用来控制管理LCD,定义个一个lcd_controller.h, struct lcd_controller结构体放置lcd对象的一些成员函数,即对象的方法,或者称之为对象的行为:
typedef struct lcd_controller {
char *name;
void (*init)(p_lcd_params plcdparams);
void (*enable)(void);
void (*disable)(void);
void (*init_palette)(void);
}lcd_controller, *p_lcd_controller;
那么lcd_controller.c相当于一个管理者,会去选择具体型号的LCD对象去执行具体的成员函数,比如管理s3c2440_lcd_controller.c,它向上接受传入的LCD参数,向下传给具体的LCD控制器。
void lcd_controller_init(p_lcd_params plcdparams)
{
/* 调用2440的LCD控制器的初始化函数,lcd_controller是一个被选中的对象,即s3c2440_lcd_controller*/
lcd_controller.init(plcdparams);
}
这样在s3c2440_lcd_controller.c再构造一个具体的lcd对象:
struct lcd_controller s3c2440_lcd_controller = {
.name = xxx,
.init = xxx,
.enalbe = xxx,
.disable = xxx,
};
lcd_controller.c代码框架如下:
#include "lcd_controller.h"
#define LCD_CONTROLLER_NUM 10
static p_lcd_controller p_array_lcd_controller[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_array_lcd_controller[i])
{
p_array_lcd_controller[i] = plcdcon;
return i;
}
}
return -1;
}
int select_lcd_controller(char *name)
{
int i;
for (i = 0; i < LCD_CONTROLLER_NUM; i++)
{
if (p_array_lcd_controller[i] && !strcmp(p_array_lcd_controller[i]->name, name))
{
g_p_lcd_controller_selected = p_array_lcd_controller[i];
return i;
}
}
return -1;
}
/* 向上: 接收不同LCD的参数
* 向下: 使用这些参数设置对应的LCD控制器
*/
int lcd_controller_init(p_lcd_params plcdparams)
{
/* 调用所选择的LCD控制器的初始化函数 */
if (g_p_lcd_controller_selected)
{
g_p_lcd_controller_selected->init(plcdparams);
return 0;
}
return -1;
}
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();
}
}
下面详细分析lcd_controller.c框架的含义以及作用:
1.开始定义了一个p_array_lcd_controller数组和g_p_lcd_controller_selected,p_array_lcd_controller数组表示lcd控制器的集合,g_p_lcd_controller_selected表示被选中的那一个lcd_controller;
2.当我们初始化时要先调用register_lcd_controller,select_lcd_controller选中具体的lcd_controller;
3.然后才能调用lcd_controller_init初始化具体的lcd_controller,去控制具体型号的lcd。
同理,也通过lcd.c去管理lcd_4.3.c,思路如下:
a. 有一个数组存放各类lcd的参数;
b. 有一个register_lcd给下面的lcd程序来设置数组;
c. 有一个select_lcd,供上层选择某款LCD;
参考前面的lcd_controller.c编辑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;
}
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;
}
2. LCD初始化
1)初始化lcd控制器
①初始化引脚: 我们配置LCD的背光引脚成输出模式:
GPBCON &= ~0x3; GPBCON |= 0x01;
然后再配置LCD的控制引脚和数据引脚,LCD控制引脚和数据引脚分别复用了GPC和GPD,如下图所示:
设置GPC, GPD均为0xaaaa,aaaa。
/* LCD专用引脚 */ GPCCON = 0xaaaaaaaa; GPDCON = 0xaaaaaaaa;
设置GPG4成PWREN引脚
GPGCON |= (3<<8);
②初始化LCD控制寄存器、地址寄存器:
按照上一节s3c2440裸机-LCD编程(二、LCD控制器)中的介绍去设置LCDCON1,LCDCON2,LCDCON3...LCDSADDR1等寄存器,代码如下:
void s3c2440_lcd_controller_init(p_lcd_params plcdparams)
{
/* [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) : |