LINUX_LCD编程 TFT LCD
1:LCD屏幕
1.1:LCDTTL时序信号
1.2:LCD数据显示
一帧数据由多行组成,一行数据由多个像素点组成,每个像素由若干个数据位组成。对于单色显示器,每个像素为1位,称为1bpp,对于256色显示器,每个像素为8位,为8bpp。
显示是从左上方开始,一行一行的将每个像素的颜色显示出来,当一行显示数据到最右边是,自动跳至下一列最左边开始执行,当数据至右下角时,调至左上方显示下一帧数据。
该图结合下面时序图一起理解
LCD时序图
其中,VSYNC表示每一帧数据的开始,HSYNC表示每一行数据的开始,VCLK表示传输一个像素的数据。xrse个像素构成一行数据,yrse行数据构成一帧数据。xrse和yrse为分辨率,也就是一行有xrse个像素点,一列由yrse个像素点。
时序图讲解
1)当VSYNC有效时,一帧数据开始传输。
2)VSPW表示VSYNC信号脉冲宽度为(VSPW+1)个HSYNC信号周期,这几行数据无效。
3)在经过HBPD+1个HSYNC个信号后,有效的数据行开始出现。
注:在上图,我们可以看到VSYNC出现高电平之后,需要经过(VSPW+1)+(VBPD+1)个信号后,LINEVA+1的有效行才开始出现。(上图上黑框)
4)LINEVA+1的有效行开始发送数据
5)随后发送VFPD+1个无效行,(上图下黑框)
一行数据讲解。
1)HSYNC信号有效后,一行数据开始传输
2)首先前(HSPW+1)+(HBPD+1)个数据无效,第一个有效行像素开始出现。(上图左黑框)
3)发送完一行数据之后,经过HFPD+1个无效数据点,(上图右黑框)
4)下一个HSYN信号出现,开始传输下一行数据。
由图中我们可以知道,VSYNC他的频率表示了数据的帧率。一秒钟发送多少个VSYNC,则显示多少帧图片。
VCLK= HCLK/[CLKVAL+1*2]
VSYNC是帧频率。
将VSYNC、HSYNC、VCLK等时间参数设置好之后,将帧内存Framebuffer地址告诉LCD控制器,他即可自动发起DMA传输从Framebuffer中得到数据,在上述信号的作用下出现在数据总线VD[23:0]上,用户只需要把现实的图像数据写入帧内存即可。
注意:
Framebuffer这个地址中,可以按照下图理解,假设每个像素位置占据2个字节,那么(0,0)所在的内存地址即为FB+0, (1,0)为FB+2,以此类推,下一列地址(0,1)内存地址为(FB+ xres*2).
像素存储
对于 24 16 BPP来说
他们的Framebuffer存放的就是颜色
对于8bpp来说
8BPP访问256调色板中对应的地址,将改地址的颜色 通过VD[23:0]发送出去。
1:16M(24BPP)像素
即通过24条数据线VD[23:0]发起数据传输,RGB三色每个占据8条数据线。为方便BMA传输,每个像素使用4个字节32位来存储数据。
设置不同的寄存器格式,数据排列方式不同
2:64K,16BPP
16BPP代表使用16条数据线传输RGB三色,分为两种接线方式 RGB为 5:6:5 和 5:6:5:1。
1个四字节表示两个BPP像素,
3:256色,8BPP
在8PP中,8条数据线要分给RGB,因此在这里,设置8条数据线的作用不是显示颜色了。而是我们定义一个256的调色板,这8条数据线的作用就是访问这256调色板中对应的地址,每个地址存放一种颜色。
8BBPP访问256调色板中对应的地址,将改地址的颜色 通过VD[23:0]发送出去。
调色板的起始地址 需要根据手册来确定。
2:寄存器
那么我门TTF只需要设置上2组寄存器即可。
2.1:LCDCON1
用于设置像素时钟,使能LCD信号输出等
CLKVAL 与我们在时序中的 VCLK相关,负责1s有多少帧数据
BPPMOODE 像素格式
ENVID 使能位
2.2LCDCON2
用于设置垂直方向的时间参数
即我们图中的上下黑框,不同的LCD参数不同
2.3LCDCON3
设置水平方向的参数
即我们图中的左右黑框,不同的LCD参数不同
2.4 LCDCON4
对于TFT,这个寄存器只设置HSYNC信号的脉冲宽度【7:0】为HSPW
2.5 LCDCON5
设置控制信号的极性,并读取一些状态信息
2.6 帧内存地址 LCDSADDR1-LCDSADDR3
ADDR1寄存器格式
ADDR2寄存器格式
ADDR3寄存器格式
2.7 调色板
3:代码
为了方便调用LCD代码,以及以后添加新的LCD屏幕参数,和不同控制芯片对LCD的控制,采用面向对象编程方式。
首先,定义control.c和control.h文件,
作用:将LCD_4_3所对应的参数,例如时序,bpp等,写入到S3C2440_LCD_Control.c中。
LCD_4_3
作用,主要是配置LCD屏幕的参数。
S3C2440_LCD_Control.c
配置LCD寄存器。
3.1:LCD.h函数编写
在LCD.h中,我们需要定义LCD屏幕他们需要的参数,并将其封装为结构体,在4.3存屏幕参数设置中,我们可以直接调用该结构体赋值。
#ifndef _LCD_H
#define _LCD_Henum {NORMAL = 0,INVERT = 1,
};/* NORMAL : 正常极性* INVERT : 反转极性*/
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;/* 分辨率, bpp */int xres;int yres;int bpp;/* framebuffer的地址 */unsigned int fb_base;
}lcd_params, *p_lcd_params;void get_lcd_params(unsigned int *fb_base, int *xres, int *yres, int *bpp);#endif /* _LCD_H */
3.2:LCD.c函数编写
我们设置一个包含多种类型LCD屏幕参数结构体 的数组 p_lcd_params p_array_lcd[LCD_NUM];
并设置一个选择函数,根据选择的LCD的名字,来给g_p_lcd_selected赋予不同的屏幕参数。
get_lcd_params函数的作用是,获取 所选择的LCD 屏幕的 Framebuffer xres yres bpp。
LCD的初始化再函数lcd_init()中,再调用LCD时,一般只修改这里即可。就不需要涉及底层文件。
#include "lcd.h"
#include "lcd_controller.h"#define LCD_NUM 10static 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);
}
3.3 LCD_4_3.c设计
这里我们所起的作用即将我们的4.3LCD的参数,写入到这个结构体中,为后续使用做铺垫
这里的参数现需要根据LCD手册来获取。
#include "lcd.h"#define LCD_FB_BASE 0x33c00000lcd_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 = 32, /* 16, no 24bpp */.fb_base = LCD_FB_BASE,
};void lcd_4_3_add(void)
{register_lcd(&lcd_4_3_params);
}
注:
3.1 3.2 3.3这三节是相互关联的。
3.1 设计参数结构体, 3.3 将4.3屏幕的参数写入结构体 3.2将屏幕参数写入数组,为后续写入寄存器做准备。
3.4 LCD_Controller.h
这里,我们实现了一个结构体,该结构体中 包含除了名字之外,剩下的四个均为 函数指针
我们需要这四个函数指针 分别指向 控制器初始化函数 、 使能函数 、 失能函数 、 调色板函数。
#ifndef _LCD_CONTROLLER_H
#define _LCD_CONTROLLER_H#include "lcd.h"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;#endif /* _LCD_CONTROLLER_H */
3.5 LCD_Controller.c
我们设置一个包含多种类型LCD屏幕控制器结构体的数组 static p_lcd_controller p_array_lcd_controller[LCD_CONTROLLER_NUM];
并设置一个选择函数,根据选择的LCD的控制器的名字,来给g_p_lcd_controller_selected赋予不同的控制器函数。
#include "lcd_controller.h"#define LCD_CONTROLLER_NUM 10static 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();
}
3.6 S3C2440_Controller.c
将LCD参数写入到寄存器中
#include "lcd.h"
#include "lcd_controller.h"
#include "../s3c2440_soc.h"#define HCLK 100void 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控制器
void s3c2440_lcd_controller_init_palette(void)
{int bit = LCDCON1 & (1 << 0);if (bit)LCDCON1 &= ~(1 << 0);// 4d000400-4d00041f 16色调色板volatile unsigned int *palette_base = (volatile unsigned int *)0x4d000400;int i;for (i = 0; i < 256; i++){// 存放565格式 低16位*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);
}
再所有设置结束后,我们看LCD.C的init函数
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);
}
1) 首先将LCD4_3添加到 参数 数组中 ,将 S3C2440控制器的的结构体添加至 控制器数组中
lcd_4_3.c函数中
void lcd_4_3_add(void){register_lcd(&lcd_4_3_params);}S3C2440_LCD_Controller.c函数中
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);
}
2) 选择LCD参数,和控制器参数
位于LCD.C和LCD_Controller.c中。
3)将所选的 LCD参数 放入 控制器 初始化函数中, 使用该参数对控制器进行初始化。
该函数位于LCD_controller.c函数中,根据传入的参数对选择的控制器进行in
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;
}
4:测试
根据不同的BPP值,分别显示 红绿蓝。
void lcd_test(void)
{unsigned int fb_base;int xres, yres, bpp;int x, y;unsigned short *p;unsigned int *p2;unsigned char *p0;/* 初始化LCD */lcd_init();/* 使能LCD */lcd_enable();/* 获得LCD的参数: fb_base, xres, yres, bpp */get_lcd_params(&fb_base, &xres, &yres, &bpp);fb_get_lcd_params();font_init();/* 往framebuffer中写数据 */if (bpp == 16){/* 让LCD输出整屏的红色 *//* 565: 0xf800 */p = (unsigned short *)fb_base;for (x = 0; x < xres; x++)for (y = 0; y < yres; y++)*p++ = 0xf800;/* green */p = (unsigned short *)fb_base;for (x = 0; x < xres; x++)for (y = 0; y < yres; y++)*p++ = 0x7e0;/* blue */p = (unsigned short *)fb_base;for (x = 0; x < xres; x++)for (y = 0; y < yres; y++)*p++ = 0x1f;/* black */p = (unsigned short *)fb_base;for (x = 0; x < xres; x++)for (y = 0; y < yres; y++)*p++ = 0;}else if (bpp == 32){/* 让LCD输出整屏的红色 *//* 0xRRGGBB */p2 = (unsigned int *)fb_base;for (x = 0; x < xres; x++)for (y = 0; y < yres; y++)*p2++ = 0xff0000;/* green */p2 = (unsigned int *)fb_base;for (x = 0; x < xres; x++)for (y = 0; y < yres; y++)*p2++ = 0x00ff00;/* blue */p2 = (unsigned int *)fb_base;for (x = 0; x < xres; x++)for (y = 0; y < yres; y++)*p2++ = 0x0000ff;/* black */p2 = (unsigned int *)fb_base;for (x = 0; x < xres; x++)for (y = 0; y < yres; y++)*p2++ = 0;}else if (bpp == 8){/* 让LCD输出整屏的红色 *//* bpp = 8: 调色板中某个数值*/p0 = (unsigned char *)fb_base;for (x = 0; x < xres; x++)for (y = 0; y < yres; y++)*p0++ = 0xf12;/* green */p0 = (unsigned char *)fb_base;for (x = 0; x < xres; x++)for (y = 0; y < yres; y++)*p0++ = 0x49;/* blue */p0 = (unsigned char *)fb_base;for (x = 0; x < xres; x++)for (y = 0; y < yres; y++)*p0++ = 0x1f;/* black */p0 = (unsigned char *)fb_base;for (x = 0; x < xres; x++)for (y = 0; y < yres; y++)*p0++ = 0;}
}