Part18: LCD的裸板编程

0 写在前面
本应先讲述LCD工作原理的,但为了脑海中有个框架性、目的性,事先讲解整个框架,然后再次基础上不断完善。
在完善过程中,遇到需要理解LCD原理时,再补充说明即可。
我觉得,下面的框架值得好好学习(这其实也是“输入子系统”的缩影),还有面向对象的编程方式(封装、抽象)
讲解顺序:框架的搭建从上到下,框架的实现从下到上  
备注:下面的LCD裸板编程基于s3c2440这款开发板,只是硬件有些许差异,软件部分(整个框架)是适用任何板子的。
1 目标与框架
1)目标:在s3c2440裸板的LCD绘制点、线、圆、字母。
注意:重在理解框架的搭建和面向对象的编程方式,以及后面LCD的工作原理。
2)框架:如下图

LCD裸机编程框架

图解:
分层思想:对应不同硬件(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 为例,其工作原理如下图所示

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控制器方图

下面来看下LCD控制器的时序图(TFT LCD为例)

LCD时序图分析(TFT-LCD)

备注:上图的tvb tvp tvf /  thb thp thf 时间值可查看LCD芯片手册得到

从上图的时序分析,有没发现一个问题?
除了同步信号(HSYNC/VSYNC)需要一定的脉宽要求,电子枪移动还需等待一些时间(tvp tvf 和 thp thf)
这四个时间刚好对应LCD屏幕的四条边(黑色的,这段时间电子枪移动,不能发射RGB组合光),因此,
这也是电阻屏四边呈现黑色的原因(个人看法:这会是电阻屏被电容屏取代的原因之一?)

OK,好像还没看到对应LCD的引脚图,这里贴出来以让心里有个底:)

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,可运行 )

代码中有不懂的可评论或私信我呀,欢迎交流,共同进步 : )
最后贴一张效果图,如下

LCD效果图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值