一、LCD原理介绍
- LCD内部内部结构
(1)lcd由Framebuffer、lcd屏幕、信号线、电子枪、lcd控制器组成。
(2)Framebuffer提供显示数据、lcd屏幕显示、信号线传输Frambuffer中的数据和lcd控制器发出的信号、电子枪根据数据将颜色打在lcd屏幕上。 - LCD电路图
(1)DE:LCD控制信号;
(2)VLINE:HSYNC,换行信号;
(3)R1~B4:RGB565,像素数据线;
(4)YU~XR:中断信号线;
(5)LEDL~LEDR:LCD背光线;
(6)CLK:时钟线;
(7)VFRAME:VSYNC信号; - LCD操作原理
(1)lcd显示的数据存储在FrameBuffer中,FrameBuffer是内存中的一块的区域,可以自己设定;
(2)FrameBuffer中的数据存储方式,需要参考lcd芯片手册的bpp(bit per pixel每个像素占了多少位);
(3)bpp分为24(32)bpp、16bpp、8bpp,lcd上每个像素所显示的颜色分别占用FramBuffer中的4Byte、2Byte、1Byte;
(4)24bpp的R:G:B(红绿蓝)分配为8:8:8,16bpp为5:6:5;8bppFrameBuffer中存储的非像素数据,而是RGB在调色板中的地址;
(5)由于8bpp在FrameBuffer中无法存储颜色数据,因此会在内存特定的区域即调色板中存放16bpp的数据,而存放的地址会放到FrameBuffer中,lcd控制器会自动根据8pp的数据索引到调色板中对应的像素数据;
(6)需要根据lcd芯片手册中的时序、引脚极性、分辨率、bpp,以及设定的FrameBuffer地址,结合2440芯片手册,来设置lcd控制器,使lcd控制器会在适当的时候发出对应的数据信号和控制信号
(7)lcd控制器将像素数据发送给lcd屏幕,lcd根据像素数据,每一个VCLK时钟周期,就在屏幕上从左到右显示一个像素点;
(8)当像素点移到最右端时,lcd控制器从会发出HSYNC信号,像素点就会移到下一行的行首,如此循环;
(9)当移到最下端最后一个像素点时,lcd控制器会发出VHYNC信号,像素点回到第一行的行首,如此循环;
(10)lcd屏幕像素如何显示主要取决于FrameBuffer中数据的存储,因此要实现画点画线画圆显示字符,都要向FrameBuffer中写入不同的数据。
二、编程实验
本次实验尝试使用面向对象的编程思路,本程序可以满足添加多款不同的lcd和lcd控制器而无需改动的需求。
- 抽象出多款lcd参数作为调用模板:引脚极性、时序、分辨率、bpp、FrameBuffer地址
typedef struct lcd_params{
char *name;
pins_polarity pin_pol; //引脚极性
time_sequence time_seq; //信号时序
int xres; //分辨率、bpp
int yres;
int bpp;
unsigned int FB_addr; //FB_addr
}lcd_params, *p_lcd_params;
- 抽象出多款lcd控制器操作作为调用模板:初始化lcd控制器、使能、禁止使能lcd控制器
typedef struct lcd_con {
char *name;
void (*init)(p_lcd_params plcd);
void (*enable)(void);
void (*disable)(void);
}lcd_con, *p_lcd_con;
- 调用lcd参数模板,设置lcd_4_3的参数
lcd_params lcd_4_3 = {
.name = "lcd_4_3",
.pin_pol = {
.vclk = normal,
.hsync = invert,
.vsync = invert,
.VD = normal,
.DE = normal,
.INVPWREN = normal,
},
.time_seq = {
.thf = 2,
.thb = 2,
.thp = 41,
.tvf = 2,
.tvb = 2,
.tvp = 10,
.vclk = 9,
},
.xres = 480,
.yres = 272,
.bpp = 16,
.FB_addr = 0x33a00000,
};
- 调用lcd控制器模板设置2440的lcd控制器
/*初始化函数*/
void lcd_con_2440_init(p_lcd_params plcd)
{
LCDCON1 = (CLKVAL << 8) | (PNRMODE << 5) | (BPPMODE << 1);
LCDCON2 = (VBPD << 24) | (LINEVAL << 14) | (VFPD << 6) | (VSPW << 0);
LCDCON3 = (HBPD << 19) | (HOZVAL << 8) | (HFPD << 0);
LCDCON4 = (HSPW << 0);
LCDCON5 = (FRM565 << 11) | (INVVCLK << 10) | (INVVLINE << 9)|\
(INVVFRAME << 8) | (INVVD << 7) | (INVVDEN << 6) |\
(INVPWREN << 5) | (BPP_Display << 0);
LCDSADDR1 = (plcd->FB_addr & ~(1 << 31)) >> 1;
LCDSADDR2 = (FB_end_addr >> 1) & 0x1fffff;
}
/*使能函数*/
void lcd_con_2440_enable(void)
{
LCDCON1 |= (1 << 0);
LCDCON5 |= (1 << 3);
GPBDAT |= (1 << 0);
}
/*关闭使能函数*/
void lcd_con_2440_disable(void)
{
LCDCON1 &= ~(1 << 0); //Disable the video output and the LCD control signal.
LCDCON5 &= ~(1 << 3); //Disable PWREN signal
GPGDAT &= ~(1 << 0); //KEYBOARD
}
lcd_con lcd_con_2440 = {
.name = "lcd_con_2440",
.init = lcd_con_2440_init,
.enable = lcd_con_2440_enable,
.disable = lcd_con_2440_disable,
};
- 将所有的lcd控制器集合成一个数组,将lcd参数传给对应的lcd控制器
//==============================================
p_lcd_con lcd_con_arry[10]; //lcd_con数组
p_lcd_con g_lcd_con_selected; //被选的lcd_con
/*注册lcd控制器*/
int lcd_con_register(p_lcd_con clcd)
{
p_con_arry[i] = clcd;
}
/*选择lcd控制器*/
int lcd_con_selected(char *name)
{
g_lcd_con_selected = lcd_con_arry[i];
}
//================================================
/*传递lcd参数给lcd控制器*/
void lcd_con_init(p_lcd_params plcd)
{
g_lcd_con_selected->init(plcd);
}
void lcd_con_enable(void)
{
g_lcd_con_selected->enable();
}
void lcd_con_disable(void)
{
g_lcd_con_selected->disable();
}
- 将所有的lcd控制器集合成一个数组,选择某款lcd和lcd控制器,将lcd的参数传给lcd控制器
//=============================================
/*注册lcd*/
p_lcd_params lcd_params_arry[10];
p_lcd_params g_lcd_params_selected;
int lcd_params_register(p_lcd_params plcd)
{
lcd_params_arry[i] = plcd;
}
/*选择lcd*/
int lcd_params_select(char *name)
{
g_lcd_params_selected = lcd_params_arry[i];
}
//==========================================
/*选择lcd及lcd控制器,进行操作*/
void lcd_init(void)
{
lcd_params_4_3_registe(); //注册lcd
lcd_con_regist_add(); //注册lcd控制器
lcd_params_select("lcd_4_3"); //选择lcd
lcd_con_selected("lcd_con_2440"); //选择lcd控制器
lcd_con_init(g_lcd_params_selected); //初始化lcd
}
- 对lcd进行测试:向FrameBuffer中写数据
(1)获取lcd的参数:根据分辨率,来设置显示多少数据及每个像素显示什么颜色;根据bpp,来设置向FrameBuffer写入数据的格式;以及FrameBuffer地址。
void lcd_test(void)
{
int xres, yres, bpp;
unsigned int FB_addr;
unsigned short *p_16;
unsigned int *p_32;
int i, j;
lcd_init(); //初始化lcd
lcd_enable(); //使能lcd
lcd_params_get(&xres, &yres, &bpp, &FB_addr); //获取lcd参数
/*显示整屏颜色*/
if (bpp == 16)
{
p_16 = (unsigned short *)FB_addr;
for (j = 0; j < yres; j++)
{
for (i = 0; i < xres; i++)
{
*p_16++ = 0x1f;
}
}
if (bpp == 32)
{
for (j = 0; j < yres; j++)
{
for (i = 0; i < xres; i++)
{
*p_32 = 0x0000ff00;
}
}
}
/*画圆*/
draw_circle(xres/2, yres/2, yres/4, 0xaabbcc);
/*画线*/
draw_line(xres, 0, xres, yres, 0xff0000);
- 屏幕显示字符
(1)根据字符的ascii码,获得该字符在字符数组的位置,每个字符在数组中由8bit16Byte组成,每bit代表一个行像素,每byte代表一个列像素,所以每个字符由816个像素组成
(2)将每个字符在数组的数据读出,逐个分析数据每行的8个bit,若1则对应的1个像素显示,若0则不显示,8bit结束后换下一个Byte,像素也换到下一行,以此来显示字符。
/*显示字符*/
void lcd_font_ascii(int x, int y, char c, int color)
{
pc = (unsigned char *)fontdata_8x16[c * 16]; //得到字符在数组的位置
int bit = 7;
for (j = y; j < y + 16; j++) //逐个Byte分析
{
for (i = x; i < x + 8; i++) //逐个bit分析
{
if (*pc & (1 << bit)) //若bit - 1,则显示
{
lcd_FB_input(i, j, color);
}
bit--;
}
}
}
(3)显示字符串就是在显示完一个字符后,在上一个字符的起始像素位置上行像素+8,如果到达屏幕最右端,则行像素 = 0,列像素+16;
(4)如果遇到的字符为换行字符,则行像素=0,列像素+16。
void lcd_str_ascii(int x, int y, char *str, int color)
{
int i = 0, j;
while(str[i]) //当字符串存在时
{
if (str[i] == '\r') //如果字符为回到行首
{
x = 0;
}
else if(str[i] == '\n') //如果字符为换行字符
{
y = y + 16;
}
else
{
lcd_font_ascii(x, y, str[i], color); //打印当前字符
x = x+8; //原来起始地址 +8
if( x > xres) //屏幕最右端
{
x = 0;
y = y+16;
}
}
}
}