注:以下内容学习于韦东山老师arm裸机第一期视频教程
参考文章http://www.cnblogs.com/shangdawei/p/4760933.html
一.LCD硬件原理
1.1 LCD可以看作是一个电子枪一边移动一边发出颜色
1.1.1 电子枪如何移动?
LCD上有一条CLK信号线,每来一个CLK电子枪就会移动一个像素
1.1.2 颜色如何确定?
颜色有RGB三组信号线来确定
1.1.3 电子枪如何得知应跳到下一行?
HSYNC信号线
1.1.4 电子枪扫描完最后一行后如何得知回到原点?
VSYNC信号线
1.1.5 RGB线上的数据从哪里来?
来自于一段内存(frame buffer)
1.2 JZ2440LCD接口原理图如下
其中VLINE信号线代表了HSYNC信号
VFRAME信号线代表了VSYNC信号
RGB信号线来传输数据(R0-R5,G0-G6,B0-B5,565格式,即一个像素使用16位来表示,16bpp(bit per pixel))
注意LCD上有8根R,8根G,8根B数据线,可以表示24bpp,但是只接到开发板上16条线
1.3 LCD显示配置图如下
HFP->一行中最后一个信号发出之后需要经过多久才会接受HSYNC信号,即代表右边的黑框
HBP->HSYNC信号接受之后需要经过多久才会发出一行中第一个像素的信号,即代表左边的黑框
VFP->最后一行中的最后一个信号发出之后需要经过多久才会接受VSYNC信号,即代表下边的黑框
VBP->VSYNC信号接受之后需要经过多久才会发出第一行的第一个像素的信号,即上边的黑框
二.LCD控制器
2.1 LCD控制器的作用
2.1.1 从内存(frame buffer)中取出像素的数据,(我们需要将framebuffer的地址告诉控制器,bpp,分辨率)
2.1.2 配合其他信号一起将数据发送给LCD(将LCD的时序设置给LCD控制器,设置引脚的极性)
2.2 LCD控制器结构图如下
LCDDMA会从内存中取出数据,然后发送到VD[23:0]引脚上去
我们同时需要设置寄存器来控制LCD控制器来让他发出合适的时序
因此,我们只需要将数据填写到frame buffer中去即可,硬件会自动帮助我们将数据刷到LCD中去
2.3 frambuffer数据格式的设置
如下图:
对于16bpp的数据格式
当BSWP = 0, HWSWP = 0,0地址的高16位存放第一个数据,低16位存放第二个数据,4地址的高16位存放第三个数据,低16位存放第四个数据
当BSWP = 0, HWSWP = 1,0地址的低16位存放第一个数据,高16位存放第二个数据,4地址的低16位存放第三个数据,高16位存放第四个数据
显然第二种情况更容易操作,我们使用第2种情况
2.4 设置数据的传输引脚(16位数据通过VD[23:0]的哪几条来传输数据)
如下图:
对于16位数据,565或者5551
在画原理图时需要根据这个图来进行线的连接
对于565格式:8条红数据,8条绿数据,8条蓝数据,但是分别只用了高5条,高6条,高5条
2.5 2440时序图如下:
我们需要根据LCD的数据手册来设置这个时序图
三.调色板的概念
当使用16条数据线与LCD连接的时候,每个像素在frame buffer应该占据16位
如果想要节省空间,每个像素只占据8位如何做?
8bit的数据需要得到16bit的数据,因此引入调色板,调色板一共有256项,里面存放16bit的数据
LCD控制器会根据得到的8bit数据作为索引在调色板中取出16bit的数据,然后再发给LCD
注意:调色板也在内存中,或者在LCD控制器中
如下图:
第一个数据存放在0X4D000400地址中,第二个数据存放在0X4D000404地址中
四.临时调色板
当想要将LCD设置为一个颜色
对于16bpp,需要去填充frame buffer里面的数据设置为一种值
对于8bpp,可以设置frame buffer的值设置为同一个,也可以设置调色板中的值都设置为颜色值
对于2440提供了一种临时调色板,如下图:
可以设置TPAL寄存器的bit[23:0]来设置某一种颜色值,一旦设置bit24使能之后,对于LCD控制器无论是多少bpp,都会从调色板中取出数据发送给LCD
五.LCD编程(以面向对象的思想)
5.1 明确目的
a.在LCD上画线,画圆(核心都是画点)---->genmetry.c(纯软件,可以在LCD3.5寸,4.3寸上运行)
b.画点---->framebuffer.c
c.写字---->font.c
d.LCD相关--->LCD3.5.c LCD4.3.c--------->抽象出一个结构体(lcd.c来管理各种型号的lcd)
e.LCD控制器--->S3C2440_LCD_CONTROLLER.c----->抽象出结构体(lcd_conrtoller.c来管理各种控制器)
f.提供测试菜单--->lcd_test.c
测试菜单会调用lcd.c中的函数来初始化LCD,初始化控制器,开启电源等,然后调用genmetry.c,font.c中的函数来进行测试
5.2 重要结构体的抽象
a. LCD参数结构体的抽象(这些值提供给LCD控制器用来初始化LCD控制器)
#ifndef _LCD_H
#define _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;
#endif /* _LCD_H *
b. LCD控制器结构体的抽象
/* lcd_constroller.h */
#ifndef _LCD_CONTROLLER_H
#define _LCD_CONTROLLER_H
#include "lcd.h"
typedef struct lcd_controller {
void (*init)(p_lcd_params plcdparams);
void (*enable)(void);
void (*disable)(void);
void (*palette_init)(void)
}lcd_controller, *p_lcd_controller;
#endif /* _LCD_CONTROLLER_H */
lcd控制器结构体,提供LCD控制器初始化函数,使能函数,禁止使能函数和调色板初始化函数.
LCD控制器初始化函数根据上层LCD传入的参数来进行初始化
5.3 LCD程序的完善
/* framebuffer.c, 提供画点函数 */
#include "lcd.h"
/* 实现画点 */
/* 获得LCD参数 */
static unsigned int fb_base;
static int xres, yres, bpp;
void fb_get_lcd_params(void)
{
get_lcd_params(&fb_base, &xres, &yres, &bpp);
}
/* rgb: 0x00RRGGBB */
unsigned short convert32bppto16bpp(unsigned int rgb)
{
int r = (rgb >> 16)& 0xff;
int g = (rgb >> 8) & 0xff;
int b = rgb & 0xff;
/* rgb565 */
r = r >> 3;
g = g >> 2;
b = b >> 3;
return ((r<<11) | (g<<5) | (b));
}
/* color : 32bit, 0x00RRGGBB
*
*/
void fb_put_pixel(int x, int y, unsigned int color)
{
unsigned char *pc; /* 8bpp */
unsigned short *pw; /* 16bpp */
unsigned int *pdw; /* 32bpp */
unsigned int pixel_base = fb_base + (xres * bpp / 8) * y + x * bpp / 8;
switch (bpp)
{
case 8:
pc = (unsigned char *) pixel_base;
*pc = color;
break;
case 16:
pw = (unsigned short *) pixel_base;
*pw = convert32bppto16bpp(color);
break;
case 32:
pdw = (unsigned int *) pixel_base;
*pdw = color;
break;
}
}
/* lcd.c,对各种型号的LCD进行管理,同时向最上层提供初始化函数 */
#include "lcd.h"
#include "lcd_controller.h"
#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;
}
void lcd_enable(void)
{
lcd_controller_enable();
}
void lcd_disable(void)
{
lcd_controller_disable();
}
int lcd_init(void)
{
/* 注册LCD */
lcd_4_3_add();
/* 注册LCD控制器 */
lcd_contoller_add();
/* 选择某款LCD */
select_lcd("lcd_4.3");
/* 选择某款LCD控制器 */
select_lcd_controller("s3c2440");
/* 使用LCD的参数, 初始化LCD控制器 */
lcd_controller_init(g_p_lcd_selected);
}
/* lcd4.3.c (根据芯片手册构造了lcd_4_3_params结构体)*/
#include "lcd.h"
#define LCD_FB_BASE 0x33c00000
lcd_params lcd_4_3_params = {
.name = "lcd_4.3",
.pins_pol = {
.de = NORMAL, /* normal: 高电平时可以传输数据 */
.pwren = NORMAL, /* normal: 高电平有效 */
.vclk = NORMAL, /* normal: 在下降沿获取数据 */
.rgb = NORMAL, /* normal: 高电平表示1 */
.hsync = INVERT, /* normal: 高脉冲 */
.vsync = INVERT, /* normal: 高脉冲 */
},
.time_seq = {
/* 垂直方向 */
.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 = 8, /* 16, no 24bpp */
.fb_base = LCD_FB_BASE,
};
void lcd_4_3_add(void)
{
register_lcd(&lcd_4_3_params);
}
/* lcd_constroller.c(对底层的各种LCD控制器进行管理的函数,同时向上层提供选择控制器的函数) */
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);
g_p_lcd_controller_selected->init_palette();
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();
}
}
void lcd_contoller_add(void)
{
s3c2440_lcd_contoller_add();
}
/* s3c2440_lcd_controller.c,会构造一个s3c2440_lcd_controller结构体,根据LCD参数来初始化 */
/* 最后会向上提供一个注册函数,即将所构造的结构体放入一个lcd_controller结构体数组中,调用的时候通过名字来选择不同的控制器 */
#include "lcd.h"
#include "lcd_controller.h"
#include "../s3c2440_soc.h"
#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 = (float)HCLK/plcdparams->time_seq.vclk/2-1+0.5;
//int clkval = 5;
int bppmode = plcdparams->bpp == 8 ? 0xb :\
plcdparams->bpp == 16 ? 0xc :\
0xd; /* 0xd: 24,32bpp */
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 == 32 ? (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);
}
/* 设置调色板之前, 先关闭lcd_controller */
void s3c2440_lcd_controller_init_palette(void)
{
volatile unsigned int *palette_base = (volatile unsigned int *)0x4D000400;
int i;
int bit = LCDCON1 & (1<<0);
/* LCDCON1'BIT 0 : 设置LCD控制器是否输出信号 */
if (bit)
LCDCON1 &= ~(1<<0);
for (i = 0; i < 256; i++)
{
/* 低16位 : rgb565 */
*palette_base++ = i;
}
if (bit)
LCDCON1 |= (1<<0);
}
struct lcd_controller s3c2440_lcd_controller = {
.name = "s3c2440",
.init = s3c2440_lcd_controller_init,
.enable = s3c2440_lcd_controller_enalbe,
.disable = s3c2440_lcd_controller_disable,
.init_palette = s3c2440_lcd_controller_init_palette,
};
void s3c2440_lcd_contoller_add(void)
{
register_lcd_controller(&s3c2440_lcd_controller);
}
其余的font.c, genmetry.c都是基于画点函数的一些算法,这里就不一一列出了.