原文出自http://blog.csdn.net/zhaocj/article/details/5397180
人機交互是嵌入式系統必須具有的功能。比較簡單的人機交互有按鍵、LED、蜂鳴器,稍微複雜的有7段數碼管和點陣。但如今這些都不能滿足人們的需求了,所以又出現了LCD和觸摸屏技術。s3c2440具有LCD和觸摸屏接口,可以很好的連接LCD和觸摸屏。這篇文章主要介紹TFT型LCD的用法。
要想正確使用LCD,必須注意兩點:1、時序;2、顯示緩存區。
1、時序
LCD一般需要三個時序信號:VSYNC、HSYNC和VCLK。VSYNC是垂直同步信號,在每進行一個幀(即一個屏)的掃描之前,該信號就有效一次,由該信號可以確定LCD的場頻,即每秒屏幕刷新的次數(單位Hz)。HSYNC是水平同步信號,在每進行一行的掃描之前,該信號就有效一次,由該信號可以確定LCD的行頻,即每秒屏幕從左到右掃描一行的次數(單位Hz)。VCLK是像素時鐘信號。
s3c2440處理LCD的時鐘源是HCLK,通過寄存器LCDCON1中的CLKVAL可以調整VCLK頻率大小,它的公式爲:
VCLK=HCLK÷[(CLKVAL+1)×2]
例如,HCLK的頻率爲100MHz,要想驅動像素時鐘信號爲6.4MHz的LCD屏,則通過上式計算CLKVAL值,結果CLKVAL爲6.8,取整後(值爲6)放入寄存器LCDCON1中相應的位置即可。由於CLKVAL進行了取整,因此我們把取整後的值代入上式,重新計算VCLK,得到VCLK=7.1MHz。
按理說,對於一個已知尺寸(即水平顯示尺寸HOZVAL和垂直顯示尺寸LINEVAL已知)的LCD屏,只要確定了VCLK值,行頻和場頻就應該知道了。但這樣還不行的,因爲在每一幀時鐘信號中,還會有一些與屏顯示無關的時鐘出現,這就給確定行頻和場頻帶來了一定的複雜性。如在HSYNC信號先後會有水平同步信號前肩(HFPD)和水平同步信號後肩(HBPD)出現,在VSYNC信號先後會有垂直同步信號前肩(VFPD)和垂直同步信號後肩(VBPD)出現,在這些信號時序內,不會有有效像素信號出現,另外HSYNC和VSYNC信號有效時,其電平要保持一定的時間,它們分別叫做水平同步信號脈寬HSPW和垂直同步信號脈寬VSPW,這段時間也不能有像素信號。因此計算行頻和場頻時,一定要包括這些信號。HBPD、HFPD和HSPW的單位是一個VCLK的時間,而VSPW、VFPD和VBPD的單位是掃描一行所用的時間。在s3c2440中,所有的這些信號(VSPW、VFPD、VBPD、LINEVAL、HBPD、HFPD、HSPW和HOZVAL)都是實際值減1的結果。這些值是通過寄存器LCDCON2、LCDCON3和LCDCON4來配置,只要把這些值配置成與所要驅動的LCD中相關內容的數據一致即可。例如,我們所要顯示的LCD屏大小爲320×240,因此HOZVAL=320-1,LINEVAL=240-1。水平同步信號的脈寬、前肩和後肩分別爲30、20和38,則HSPW=30-1,HFPD=20-1,HBPD=38-1;垂直同步信號的脈寬、前肩和後肩分別爲3、12和15,則VSPW=3-1,VFPD=12-1,VBPD=15-1。
下面我們就具體計算一下行頻(HSF)和場頻(VSF):
HSF=VCLK÷[(HSPW+1)+(HSPD+1)+(HFPD+1)+(HOZVAL+1)]
=7.1÷408=17.5kHz
VSF=HSF÷[(VSPW+1)+(VBPD+1)+(VFPD+1)+(LINEVAL+1)]
=17.5÷270=64.8Hz
在有些情況下,s3c2440的LCD時鐘信號的默認極性與所控制的LCD時鐘信號的極性相反,這時可以通過寄存器LCDCON5的相關位來改變某些時鐘信號的極性。
2、顯示緩存區
只要把所要顯示的數據放入顯示緩存區內,就可以在屏幕上呈現內容。該緩存區是我們自己編程時開闢的一段內存區。一般我們是通過定義一個與屏幕尺寸大小相同的二維數組來開闢該空間的,這樣控制屏幕內容會方便一些,如當屏幕的尺寸爲320×240時,可以定義該緩存區爲LCD_BUFFER[240][320]。由於s3c2440支持16位和24位的非調色板真彩色的TFT型LCD模式,而24位顏色模式是用32位數據來表示的,所以前面定義的那個二維數據的數據類型應該是半字整型或全字整型的。例如,在24位顏色模式下,我們想要在尺寸大小爲320×240屏幕的中心處設置爲白色像素,則:LCD_BUFFER[120][160]=0xffffffff。
在s3c2440中,寄存器LCDSADDR1和LCDSADDR2用於設置顯示緩存區,即把我們定義的那個二維數組告訴s3c2440。其中LCDBANK的9位數據指定LCD的BANK,即顯示緩存區的第30位到第22位地址;LCDBASEU的21位數據指定了LCD的基址,即顯示緩存區開始地址的第21位到第1位;LCDBASEL的21位數據指定了LCD的尾址,即顯示緩存區結束地址的第21位到第1位。例如,我們想要在尺寸爲320×240的屏幕上顯示24位顏色,定義的顯示緩存區數組爲LCD_BUFFER[240][320],則LCDBANK等於LCD_BUFFER的第30位到第22位數據值(因爲LCD_BUFFER表示的就是數組的首地址),LCDBASEU等於LCD_BUFFER的第21位到第1位數據值,由於是用32位數據表示24爲顏色,因此每個像素值是4個字節,所以LCDBASEL等於(LCD_BUFFER+(240×320×4))結果的第21位到第1位的數據值。另外寄存器LCDSADDR3有兩個內容:OFFSIZE和PAGEWIDTH。OFFSIZE用於虛擬屏幕的偏移長度,如果我們不使用虛擬屏幕,就把它置爲0;PAGEWIDTH定義了視口的寬,單位是半字,如在上面的例子中,PAGEWIDTH應該爲320×32÷16。
下面我們給出一段具體的TFT型LCD顯示的實例,其中,屏幕的大小爲320×240,所設置的顏色爲24位真彩色模式。
#define U32 unsigned int
#define M5D(n) ((n) & 0x1fffff) //用於設置顯示緩存區時,取低21位地址
#define rGPCCON (*(volatile unsigned *)0x56000020) //Port C control
#define rGPCDAT (*(volatile unsigned *)0x56000024) //Port C data
#define rGPCUP (*(volatile unsigned *)0x56000028) //Pull-up control C
#define rGPDCON (*(volatile unsigned *)0x56000030) //Port D control
#define rGPDDAT (*(volatile unsigned *)0x56000034) //Port D data
#define rGPDUP (*(volatile unsigned *)0x56000038) //Pull-up control D
#define rGPGCON (*(volatile unsigned *)0x56000060) //Port G control
#define rGPGDAT (*(volatile unsigned *)0x56000064) //Port G data
#define rGPGUP (*(volatile unsigned *)0x56000068) //Pull-up control G
#define rLCDCON1 (*(volatile unsigned *)0x4d000000) //LCD control 1
#define rLCDCON2 (*(volatile unsigned *)0x4d000004) //LCD control 2
#define rLCDCON3 (*(volatile unsigned *)0x4d000008) //LCD control 3
#define rLCDCON4 (*(volatile unsigned *)0x4d00000c) //LCD control 4
#define rLCDCON5 (*(volatile unsigned *)0x4d000010) //LCD control 5
#define rLCDSADDR1 (*(volatile unsigned *)0x4d000014) //STN/TFT Frame buffer start address 1
#define rLCDSADDR2 (*(volatile unsigned *)0x4d000018) //STN/TFT Frame buffer start address 2
#define rLCDSADDR3 (*(volatile unsigned *)0x4d00001c) //STN/TFT Virtual screen address set
#define rLCDINTMSK (*(volatile unsigned *)0x4d00005c) //LCD Interrupt mask
#define rTCONSEL (*(volatile unsigned *)0x4d000060) //LPC3600 Control --- edited by junon
#define LCD_WIDTH 320 //屏幕的寬
#define LCD_HEIGHT 240 //屏幕的高
//垂直同步信號的脈寬、後肩和前肩
#define VSPW (3-1)
#define VBPD (15-1)
#define VFPD (12-1)
//水平同步信號的脈寬、後肩和前肩
#define HSPW (30-1)
#define HBPD (38-1)
#define HFPD (20-1)
//顯示尺寸
#define LINEVAL (LCD_HEIGHT-1)
#define HOZVAL (LCD_WIDTH-1)
//for LCDCON1
#define CLKVAL_TFT 6 //設置時鐘信號
#define MVAL_USED 0 //
#define PNRMODE_TFT 3 //TFT型LCD
#define BPPMODE_TFT 13 //24位TFT型LCD
//for LCDCON5
#define BPP24BL 0 //32位數據表示24位顏色值時,低位數據有效,高8位無效
#define INVVCLK 0 //像素值在VCLK下降沿有效
#define INVVLINE 1 //翻轉HSYNC信號
#define INVVFRAME 1 //翻轉VSYNC信號
#define INVVD 0 //正常VD信號極性
#define INVVDEN 0 //正常VDEN信號極性
#define PWREN 1 //使能PWREN信號
#define BSWP 0 //顏色數據字節不交換
#define HWSWP 0 //顏色數據半字不交換
//定義顯示緩存區
volatile U32 LCD_BUFFER[LCD_HEIGHT][LCD_WIDTH];
//延時程序
void delay(int a)
{
int k;
for(k=0;k<a;k++)
;
}
//繪製屏幕背景顏色,顏色爲c
void Brush_Background( U32 c)
{
int x,y ;
for( y = 0 ; y < LCD_HEIGHT ; y++ )
{
for( x = 0 ; x < LCD_WIDTH ; x++ )
{
LCD_BUFFER[y][x] = c ;
}
}
}
//畫實心圓,顏色爲c。圓心在屏幕中心,半徑爲80個像素
void Draw_Circular(U32 c)
{
int x,y ;
int tempX,tempY;
int radius = 80;
int SquareOfR = radius*radius;
for( y = 0 ; y < LCD_HEIGHT ; y++ )
{
for( x = 0 ; x < LCD_WIDTH ; x++ )
{
if(y<=120 && x<=160)
{
tempY=120-y;
tempX=160-x;
}
else if(y<=120&& x>=160)
{
tempY=120-y;
tempX=x-160;
}
else if(y>=120&& x<=160)
{
tempY=y-120;
tempX=160-x;
}
else
{
tempY = y-120;
tempX = x-160;
}
if ((tempY*tempY+tempX*tempX)<=SquareOfR)
LCD_BUFFER[y][x] = c ;
}
}
}
void Main(void)
{
//配置LCD相關引腳
rGPCUP = 0x00000000;
rGPCCON = 0xaaaa02a9;
rGPDUP = 0x00000000;
rGPDCON=0xaaaaaaaa;
rLCDCON1=(CLKVAL_TFT<<8)|(MVAL_USED<<7)|(PNRMODE_TFT<<5)|(BPPMODE_TFT<<1)|0;
rLCDCON2=(VBPD<<24)|(LINEVAL<<14)|(VFPD<<6)|(VSPW);
rLCDCON3=(HBPD<<19)|(HOZVAL<<8)|(HFPD);
rLCDCON4=(HSPW);
rLCDCON5 = (BPP24BL<<12) | (INVVCLK<<10) | (INVVLINE<<9) | (INVVFRAME<<8) | (0<<7) | (INVVDEN<<6) | (PWREN<<3) |(BSWP<<1) | (HWSWP);
rLCDSADDR1=(((U32)LCD_BUFFER>>22)<<21)|M5D((U32)LCD_BUFFER>>1);
rLCDSADDR2=M5D( ((U32)LCD_BUFFER+(LCD_WIDTH*LCD_HEIGHT*4))>>1 );
rLCDSADDR3=LCD_WIDTH*32/16;
rLCDINTMSK|=(3); // 屏蔽LCD中斷
rTCONSEL = 0; //無效LPC3480
rGPGUP=rGPGUP&(~(1<<4))|(1<<4); //GPG4上拉電阻無效
rGPGCON=rGPGCON&(~(3<<8))|(3<<8); //設置GPG4爲LCD_PWREN
rGPGDAT = rGPGDAT | (1<<4) ; //GPG4置1
rLCDCON5=rLCDCON5&(~(1<<3))|(1<<3); //有效PWREN信號
rLCDCON5=rLCDCON5&(~(1<<5))|(0<<5); //PWREN信號極性不翻轉
rLCDCON1|=1; //LCD開啓
while(1)
{
//黑色背景,白色實心圓
Brush_Background(0x0);
Draw_Circular(0xffffff);
delay(5000000);
//白色背景,黑色實心圓
Brush_Background(0xffffff);
Draw_Circular(0x0);
delay(5000000);
//藍色背景,黃色實心圓
Brush_Background(0xff);
Draw_Circular(0xffff00);
delay(5000000);
//綠色背景,品色實心圓
Brush_Background(0xff00);
Draw_Circular(0xff00ff);
delay(5000000);
//紅色背景,青色實心圓
Brush_Background(0xff0000);
Draw_Circular(0xffff);
delay(5000000);
//青色背景,紅色實心圓
Brush_Background(0xffff);
Draw_Circular(0xff0000);
delay(5000000);
//品色背景,綠色實心圓
Brush_Background(0xff00ff);
Draw_Circular(0xff00);
delay(5000000);
//黃色背景,藍色實心圓
Brush_Background(0xffff00);
Draw_Circular(0xff);
delay(5000000);
}
}