0 写在前面
本应先讲述LCD工作原理的,但为了脑海中有个框架性、目的性,事先讲解整个框架,然后再次基础上不断完善。
在完善过程中,遇到需要理解LCD原理时,再补充说明即可。
我觉得,下面的框架值得好好学习(这其实也是“输入子系统”的缩影),还有面向对象的编程方式(封装、抽象)
讲解顺序:框架的搭建从上到下,框架的实现从下到上
备注:下面的LCD裸板编程基于s3c2440这款开发板,只是硬件有些许差异,软件部分(整个框架)是适用任何板子的。
1 目标与框架
1)目标:在s3c2440裸板的LCD绘制点、线、圆、字母。
注意:重在理解框架的搭建和面向对象的编程方式,以及后面LCD的工作原理。
2)框架:如下图
图解:
分层思想:对应不同硬件(4.3寸、3.5寸等)有不同的lcd控制器,此外,纯软件层应该避免这些复杂的硬件操作,
因此,必须分层处理。这种分层思想能够降低框架的耦合性,以方便新增/替换某一层功能。
分离思想:不同硬件的lcd控制器互不干扰,需增加一个lcd控制器时只需增加对应代码即可。不影响原来的控制器,且
框架不需任何改动,只需更换lcd控制器即可。这种同层之间分离的思想,让框架更具灵活性、可拓展性、稳定性
此外,值得注意的是,分层分离的实现依赖于面向对象的编程。
核心在于lec.c 和 lcd_controller.c 这两个管理者,在C++中可构造类提取共性,而在C中可使用结构体。
类和结构体的本质是一样的,抽象出一些共性供上层使用,然后差异性由下层实现。
OK,下面先看看整个框架是如何搭建的。
2 框架搭建
---------------------------*测试层*-----------------------------------------------
lcd_test()
{
/*lcd 初始化 和 使能 ,这些由lcd.c 提供*/
lcd_init();
lcd_enable();
/* 获取一些lcd硬件参数(后面绘制需要) ,这些由lcd.c 提供*/
get_lcd_params(&fb_base, &xres, &yres, &bpp);
fb_get_lcd_params();
font_init();
if (bpp == 16)
{
/* 依次画出红、绿、蓝 <--通过写入显存framebuffer */
}else if(bpp == 32)
{
/* 依次画出红、绿、蓝 <--通过写入显存framebuffer */
}
/* 画线 */
draw_line(0, 0, xres - 1, 0, 0xff0000);
/* 画圆 */
draw_circle(xres/2, yres/2, yres/4, 0xff00);
/* 画字母 */
fb_print_string(10, 10, "www.100ask.net\n\r100ask.taobao.com", 0xff00);
}
--------------------------*纯软件层(稳定的代码)*--------------------------------------------
1. framebuffer.c 文件
void fb_get_lcd_params(void); // 获取lcd硬件参数:x/y分辨率(xres/yres)、
// bpp(bit per pixel每个像素占据的位数)、fb_base
unsigned short convert32bppto16bpp(unsigned int rgb); //把32bpp转换为16bpp(565:红色:5bit 绿色:6bit 蓝色:5bit)
void fb_put_pixel(int x, int y, unsigned int color); //实现在lcd某点绘制
2. geometry.c 绘制几何图形,如线、圆
void draw_circle(int x, int y, int r, int color); // 在中心坐标(x,y)绘制一个半径r的圆
void draw_line(int x1,int y1,int x2,int y2,int color); // 起点(x1,y1)--->终点(x2,y2)绘制一条直线
3.font.c
void font_init(void); // 字体初始化,实际调用了get_lcd_params获取lcd硬件参数,后面打印一个字符需要使用这些参数
void fb_print_char(int x, int y, char c, unsigned int color); // 在(x,y)处打印一个color颜色的字符c
void fb_print_string(int x, int y, char* str, unsigned int color); // 在(x,y)处打印一个color的字符串str
--------------------------*lcd(管理者)*--------------------------------------------
lcd.h 定义一些结构体
typedef struct lcd_params {
char* name; // lcd 尺寸名字
/* 引脚极性 */
pins_polarity pins_pol; // pins_polarity也是一个结构体
/* 时序 */
time_sequence time_seq; //
/* 分辨率,bpp */
int xres;
int yres;
int bpp;
/* framebuffer地址 */
unsigned int fb_base;
}lcd_params, *p_lcd_params;
lcd.c 文件
#define LCD_NUM 10
static p_lcd_params p_array_lcd[LCD_NUM]; // lcd维护的结构体数组,核心结构体
static p_lcd_params g_p_lcd_selected; // 选择的lcd
// 提供上层(纯软件层)使用的函数,使得上层只需选择不同尺寸的LCD,而无需考虑LCD具体的硬件操作
void lcd_enable(void);
void lcd_disable(void);
int select_lcd(char *name); // 由lcd_init()调用
void get_lcd_params(unsigned int*fb_base, int *xres, int* yres,int* bpp);
int lcd_init(void);
//提供给下层(lcd控制器层)的注册函数:下层即是不同尺寸的lcd,如lcd_4.3、lcd_3.5等
int register_lcd(p_lcd_params plcd); // 下层调用这个函数初始化p_array_lcd数组供lcd.c使用
--------------------------*不同尺寸的lcd层*--------------------------------------------
以4.3寸LCD为例:
#define LCD_FB_BASE 0x33c00000 // 定义内存(SDRAM)中显存的基址
lcd_params lcd_4_3_params = { .... }; // 初始化对应尺寸的lcd参数(包含显存的基址)
void lcd_4_3_add(void)
{
register_lcd(&lcd_4_3_params); // 使用lcd.c 提供的,注册
}
--------------------------*lcd控制器(管理者)*--------------------------------------------
lcd_controller.h
typedef struct lcd_controller{
char * name;
void (*init)(p_lcd_params plcdparams);
void (*enable)(void);
void (*disable)(void);
}lcd_controller, *p_lcd_controller;
lcd_controller.c
#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;
提供上层(lcd.c)使用的函数
int select_lcd_controller(char* name);
int lcd_controller_init(p_lcd_params plcdparams); //lcd控制器初始化,
//调用g_p_lcd_controller_selected指向的init函数
void lcd_controller_enable(void); // 打开lcd控制器
void lcd_controller_disable(void); // 关闭lcd控制器
void lcd_controller_add(void) // 添加一个lcd控制器
{
s3c2440_lcd_controller_add();
}
提供下层(具体lcd控制器)使用的函数
int register_lcd_controller(p_lcd_controller plcdcon); //初始化p_array_lcd_controller数组
--------------------------*具体的lcd控制器层*--------------------------------------------
以s3c2440的LCD控制器为例
1)定义一个lcd_controller结构体
lcd_controller s3c2440_lcd_controller = {
.name = "s3c2440",
.init = s3c2/440_lcd_controller_init,
.enable = s3c2440_lcd_controller_enable,
.disable = s3c2440_lcd_controller_disable,
};
2)注册到上层(lcd_controller.c)
void s3c2440_lcd_controller_add(void)
{
register_lcd_controller(&s3c2440_lcd_controller);
}
3)实现上面结构体的对应硬件操作函数
void s3c2440_lcd_controller_disable(void); // 关闭lcd控制器、lcd背光板、lcd电源
void s3c2440_lcd_controller_enable(void); // 打开lcd控制器、lcd背光板、lcd电源
void s3c2440_lcd_controller_init(p_lcd_params plcdparams); //初始化s3c2440的lcd控制器
void jz2440_lcd_pin_init(void) //LCD相关引脚的初始化,供s3c2440_lcd_controller_init调用
3 框架实现
从下到上实现,理由很简单,只有明确了底层的实现,上层才好进一步串讲。
1)编写 s3c2440_lcd_controller.c 文件
在编程之前,需明确LCD的工作原理,然后结合s3c2440芯片手册和LCD手册编程实现。因此分两步完成。
第一步:理解 LCD工作原理
LCD ( Liquid Crystal Display 的简称)液晶显示器,LCD种类很多,以TFT-LCD 为例,其工作原理如下图所示
声明:上图截取自下面链接:
http://wiki.100ask.org/第017课_LCD编 #.E7.AC.AC004.E8.8A.82_.E7.BC.96.E7.A8.8B_.E6.8A.BD.E8.B1.A1.E5.87.BA.E9.87.8D.E8.A6.81.E7.BB.93.E6.9E.84.E4.BD.93
这篇文章讲得很好,这里我就不闭门绘图了(偷个懒~,哈哈.. :)
上面的控制信号如何协同工作呢,以s3c2440的LCD控制器为例,看第二步
第二步:查看LCD芯片手册,读懂时序图,再编程(设置一堆相关寄存器以满足时序要求)
下面来看下LCD控制器的时序图(TFT LCD为例)
备注:上图的tvb tvp tvf / thb thp thf 时间值可查看LCD芯片手册得到
从上图的时序分析,有没发现一个问题?
除了同步信号(HSYNC/VSYNC)需要一定的脉宽要求,电子枪移动还需等待一些时间(tvp tvf 和 thp thf)
这四个时间刚好对应LCD屏幕的四条边(黑色的,这段时间电子枪移动,不能发射RGB组合光),因此,
这也是电阻屏四边呈现黑色的原因(个人看法:这会是电阻屏被电容屏取代的原因之一?)
OK,好像还没看到对应LCD的引脚图,这里贴出来以让心里有个底:)
OK,原理讲到这里,如果我没讲清楚,可参考这篇文章呀
http://wiki.100ask.org/第017课_LCD编程#.E7.AC.AC004.E8.8A.82_.E7.BC.96.E7.A8.8B_.E6.8A.BD.E8.B1.A1.E5.87.BA.E9.87.8D.E8.A6.81.E7.BB.93.E6.9E.84.E4.BD.93
下面看下s3c2440_lcd_controller.c 代码,如下
#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 = (double)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);
}
struct lcd_controller s3c2440_lcd_controller = {
.name = "s3c2440",
.init = s3c2440_lcd_controller_init,
.enable = s3c2440_lcd_controller_enalbe,
.disable = s3c2440_lcd_controller_disable,
};
void s3c2440_lcd_contoller_add(void)
{
register_lcd_controller(&s3c2440_lcd_controller);
}
贴出这个代码,瞬间发现代码量太大,哈哈,这里偷个懒。
我把代码上传到了代码集合:https://blog.csdn.net/qq_42800075/article/details/105670841
有需要可下载(注意:代码集合的都是只是部分有或无注释的,因为 lan ...原谅我(虽然这是不好习惯)
但是代码集合都是亲自写的,已验证ok,可运行 )
代码中有不懂的可评论或私信我呀,欢迎交流,共同进步 : )
最后贴一张效果图,如下