s3c2440裸机-LCD编程(三、框架准备和LCD初始化)

2023-08-02  

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) : |

             (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;

}

③如何使能、禁用LCD:


根据上面LCD背光电路的发现背光引脚是GPB0,那么配置GPBDAT[0]置1,使能背光引脚,设置LCDCON5和

LCDCON1使能power enable和LCD输出,反之。代码如下:


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);

}

这样我们的s3c2440的lcd控制器初始化就编写完了,那么用户只要调用s3c2440_lcd_controller_init去设置LCD的属性即可。下面开始介绍如何设置LCD属性,让LCD控制器能够适应具体型号的LCD。


struct lcd_controller s3c2440_lcd_controller = {

.name    = "s3c2440",

.init    = s3c2440_lcd_controller_init,

.enable  = s3c2440_lcd_controller_enalbe,

.disable = s3c2440_lcd_controller_disable,

};

2)初始化lcd设备

参考AT043TN24 LCD数据手册上的参数性能,见下表:

配置lcd_params属性如下:

#define LCD_FB_BASE 0x33c00000

lcd_params lcd_4_3_params = {

.name = "lcd_4.3"

.pins_polarity = {

.de    = 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,  /* MHz */

},

.xres = 480,

.yres = 272,

.bpp  = 16,

.fb_base = LCD_FB_BASE,

};

.de表示数据输出使能引脚,高电平有效,所以配置成NORMAL;


.pwren表示LCD_PWREN引脚,高电平有效;


.vclk表示LCD的时钟,从手册的LCD时序图中可以看到下降沿有效,所以配置NORMAL;


.rgb表示颜色数据的引脚极性,高电平表示1,配置成NORMAL;


.hsync表示行同步信号,normal表示高脉冲,参考手册发现该信号低脉冲有效,所以配置成INVERT;


什么是高低脉冲?


高脉冲:即从逻辑0变化bai到逻辑du1再变化到逻辑0,如此便是一个高脉zhi冲。在单片机中定义高脉冲就是让某个I/O先输出逻辑0,接着保持一定的时间(延时),再输出逻辑1,同样保持一定的时间(延时),最后再转变输出为逻辑0+延时。

文章来源于:电子工程世界    原文链接
本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。