平台 mini2440
编译工具 ads1.2
lcd型号 索尼3.5寸TFT液晶屏
开始lcd的驱动是在上个学期末,那时候快要期末了,要准备考试什么的,大概看了一下没看一下的,感觉学习效率很低,考试完后家里面要帮忙干农活,就回家呆了半个月,回到学校已经是8月份了,本来在回来后不久把lcd驱动了弄了一下,也准备总结一下的,但是后面的linux计划,也不知道是什么刺激 了我的神经,就感觉要加快进度学习了,后来拖着就到了今天,突然感觉这个学习要踏实,总结对于学习来说是非常重要的,有利于自己把知识归类,划分出重点,然后提高分析总结能力,同时总结的时候还能看到一些问题和不足。基于以上的一些原因,我还是继续在回顾一些裸机系列的最后的lcd和触摸屏俩个驱动,然后在做个系列小结吧!
1.0 LCD液晶屏与触摸屏
首先要区分这俩个,很多买的液晶屏都带了触摸屏,但是就只有一块屏,我一开始一直没有分清楚这俩个屏,其实这里有俩个屏,是分开的,独立的,我做触 摸屏的实验的时候我一开始就不明白这个屏亮都不亮(就是lcd没有驱动)可以驱动触摸屏吗,事实上是可以的,他们的接口是分开的,触摸屏是和AD放在一起的,主要是因为触摸屏的实现主要运用AD计算位置。
1.1 lcd的单色 灰度 16bpp 24bpp 调色板
lcd用于显示设备是一个一个像素点来显示的,回想一起的RTC电视机也是一个一个的点来显示的。所以显示一个像素点可以用单色或者用彩色来显示,就和黑白电视和彩电 一样,单色就是没有彩色只有黑白,一个像素点用一位来表示,要么黑要么白,对于美术我们不是很了解,但是我们都知道美术的素描,就用铅笔画画,这个我们就是素描一幅画的时候有的地方有点地方浓一些有的地方淡一些,这样就能看到一些立体感,同样,对于显示我们也可以把黑与白之间分为几个层次,比如四级灰度, 这样一来一个像素点需要用俩位来表示,要表示黑白之间的四种,同时我们用bpp表示每一位像素点用多少位表示,四级灰度同时也是2bpp。当然灰度再多也是黑白的,彩色看起来才鲜艳,这里首先有需要了解一下,自然界的颜色都可以用red green blue三种颜色混合而成(这么说应该是不对的,因为这只是人眼的视觉效应而已,并不是说三种颜色混合后就变成其他颜色了,不是这样的,只是眼睛看起来变成其他颜色了),所以对于16bpp就是每一位像素16位,这样就可以用5,6,5 或者5,5,5,1 分别表示红绿蓝占的位数,后面的1就用来表示透 明度。24bpp的话就红绿蓝各占8位,这样一来使用不同的搭配就可以表示各种颜色了,但是对于8bpp来说使用8位,红绿蓝使用不到3位来表示,这样颜色表示的不丰富而且显示能力太弱,这样就引入了调色板,调试板其实是一块内存,mini2440 的是256x 16,256作为8bpp的索引,每一个单元16位表示一个像素点,这样就在lcd上显示出来的是16bpp的颜色。
2.0帧频率,行频率,像素时钟
理解lcd的关键在于对lcd工作方式的了解,首先一个很重要也很简单的事实是lcd显示器都是从左到右,从上到下像我们写一页本子一样,突然想到一起的作文本和这样很像,真的,那这个来说明比较好,一个一个像素显示出来。显示一页的频率就是帧频率,也叫场频率,垂直频率和我们比较熟悉的显示器频率(就是我们设置的那个显示器频率,记得我们大一的时候,学校电脑好烂,那个计算机老师和我们说,要我们去上机的时候把显示器频率调到75吧,不记得了反正说调到最高,要不然我们就会看到那个显示器上有波纹),然后是行频率,就是显示 一行的频率,液晶水平频率,最后就是像素频率啦,每个像素点的频率。
当然还有一个很重要的问题是,每一帧数据开始前都要有准备的时间,一行之后也有一些时间然后要回到下一行开始的时间,还有显示最后一个像素后也有一些时间不可以显示像素点,这就像我们的作文本一样,上下左右都有一些地方是没有写东西的,我们写东西的只有中间那一些方格。
而真正需要用到程序中的就是设置lcd的时钟频率,而这些时钟频率就需要考虑到这些不能用的时间和我们需要显示用的时间一起,而这些不能用的时间详细看2440手册上的TFT LCD时序图,那个图要看懂,那个时序图包含俩部分,上面是一帧的时序,下面是一帧当中的一个小周期就是一行的时序,具体对应的参数像 VSPW,VBPD, VFPD,HSPW,HBPD ,HFPD这些参数要看懂,还有LINEVAL HOZVAL这俩个分别是有效的行数和一行有效的点数,当然这里的参数手册上都是对应要加1的,这里要注意,比如我们屏宽240,这里一行有效的像素点就是LINEVAL + 1 = 240 ,所以LINEVAL 的取值就是239 。
最后知道屏的宽和高,然后参考lcd手册上的那些不能显示的时间参数,最后就可以计算出lcd的帧频 Frame Rate了,当然这里还涉及一个时钟的基准信号,就是lcd使用的VCLK是从系统的HCLK分频过来的,所以还有注意设置这个分频的参数。
3.0寄存器的设置
lcd的寄存器比较多共有17个,不过我们使用TFT液晶屏是需要设置的寄存器并不多,主要是lcd的控制寄存器, LCDCON1~5 和 lcd帧内存地址寄存器LCDSADDR1~3 。具体设置根据需要来,主要参考手册还有提供的参考程序。
4.0 遇到的几个问题
1 我买的是索尼3.5寸lcd,友善之臂称为X-35,我手上的这款屏的手册不是很好,而且网上也没有很多这款屏的资料,感觉手册写成那样子,不像是索尼这样公司的作风,怀疑不是原厂的手册,那手册上找不到上面所说的那些无用的时间的参数,试着看时序图看能不能得出结果也是不能,不知道是不是我水平太低没看 到了,要是有人知道怎么得出这些参数一定要记得告诉我,非常感谢。后面没办法,网上搜,还有论坛里面问,也没找到,后面突然想到不是有参考程序的吗,赶紧去看,里面果然有这么参数的值,不过你需要对着一个一个的看,因为命名的关系,不过还是很好了。
2.写好程序后液晶屏没有反应,因为程序开始比较简单,而且我还是依照参考程序和赵老师博客来写的,所以我的第一感觉就是这个问题应该是:要么是时钟的问题要么就是内存地址设置的问题,果然查看代码时是帧内存地址错误。
3.液晶屏出现条纹,没有图像,这个要检查一下lcd信号的极性,关于寄存器LCDCON5中信号的极性具体应该参考相应数据手册,但是我的手册上也没找到,所以我就参考的参考代码的,这里INVVDEN信号的极性一定要反转,这里是针对我的X-35屏来说,要不然会出现前面的液晶屏条纹。当然出现条纹可能还有其他的原因。
代码:
/************************************************* file name lcd.c function 无系统驱动TFT液晶屏 显示sunflower图片 硬件参数 mini2440学习板 索尼3.5寸TFT液晶屏 X35 驱动参数 宽和高 240 x 320 16bpp非调试板模式 没有使用虚拟屏 程序结构 前面部分是串口程序,用于控制和调试用 后面部分是lcd驱动部分 完成时间 2011-08-06 作者 周茂夫 problem 暂无 修改 暂无 *************************************************/ #define GLOBAL_CLK 1 #include <stdlib.h> #include <string.h> #include "def.h" #include "option.h" #include "2440addr.h" #include "2440lib.h" #include "2440slib.h" #include "mmu.h" #include "profile.h" #include "memtest.h" #define baudrate 115200 #define LCD_WIDTH 240 #define LCD_HEIGHT 320 //#define LCD_CLKCAL 17 //这个我计算出来是17参考程序给的是4 测试俩个都可以 //影响不大 测试25 30 都还可以 #define LCD_CLKCAL 17 #define LCD_RIGHT_MARGIN 25 #define LCD_LEFT_MARGIN 0 #define LCD_HSYNC_LEN 4 #define LCD_UPPER_MARGIN 0 #define LCD_LOWER_MARGIN 4 #define LCD_VSYNC_LEN 9 #define LCD_XSIZE LCD_WIDTH #define LCD_YSIZE LCD_HEIGHT #define SCR_XSIZE LCD_WIDTH #define SCR_YSIZE LCD_HEIGHT extern const unsigned char sunflower_240x320[] ; volatile static unsigned short LCD_BUFFER[SCR_YSIZE][SCR_XSIZE] ; //LCD BUFFER #define M5D(n) ((n)&0x1fffff) //设置显示缓存区时取地址的低21位 #define LCD_ADDR ((U32)(LCD_BUFFER)) /********************************** void delay(int times) { int i = 1000 ; while(times--) { for(; i>0; --i) ; } } ************************************/ /*********************************** UART_int初始化led IO端口GPBCON5-8 初始化GPBHCON为串口通信 配置串口通信寄存器 配置中断寄存器 ************************************/ void UART_int_init(void) { /********configuration LED IO port**********/ rGPBCON &= ~(0xff<<10) ; rGPBCON |= 0x55<<10 ; /*******configuration GPHCON to UART*******/ rGPHCON &= ~(0xf<<4) ; rGPHCON |= 0xa<<4 ; /****configuration UART0 communication register******/ rULCON0 = 0x03 ; //8-bits,1 stop bit, no parity rUCON0 = 0x05 ; rUBRDIV0= (int)(PCLK/baudrate/16) -1 ; //configuration UART baudrate /*****clean interrupt bit clea RX_INT******/ rSUBSRCPND |= 0x1 ; rSRCPND |= 1<<28 ; rINTPND |= 1<<28 ; /******open UART interrupt*********/ rINTSUBMSK &= ~(0x1) ; rINTMSK &= ~(0x1<<28) ; } //UART send byte void UART_send_byte(char Tx_data) { while(!(rUTRSTAT0&0x2)) ;//wait Tx empty if(Tx_data == '\n') //Tx '\n' { rUTXH0 = 0x0d ; while(!(rUTRSTAT0&0x2)) ; rUTXH0 = 0x0a ; } else { rUTXH0 = Tx_data ; } } //UART send string void UART_send_string(const char *str) { while(*str) { UART_send_byte(*str) ; str++ ; } } //UART receive byte void UART_receive_byte(void) { char temp ; while(!(rUTRSTAT0&0x1)) ; //wait RX ready temp = rURXH0 ; switch(temp) //测试发送单个字符 { case 's': rGPBDAT &= ~(0xf<<5) ; break ; case 'p': rGPBDAT |= (0xf<<5) ; break ; } UART_send_byte(temp) ; } /******************************************* 中断处理函数 置1清除中断,注意顺序,先子中断后父中断 点亮led灯 ********************************************/ void __irq UART0_interrupt(void) { /******clean interrupt bit*************/ rSUBSRCPND |= 0x1 ; rSRCPND |= 1<<28 ; rINTPND |= 1<<28 ; rGPBDAT &= ~(0xf<<5) ; //lighten led UART_receive_byte(); } /**************************************************************** function initialize LCD IO port VD[0:15] VM VLINE VCLK VFREAM input void return void *****************************************************************/ static void Lcd_port_init(void) { rGPCUP = 0xffffffff ; //Disable Pull-up register rGPCCON = 0xaaaa02a8 ; //Initialize VD[7:0],VM,VFREAM,VLINE,VCLK rGPDUP = 0xffffffff ; //Disable Pull-up register rGPDCON = 0xaaaaaaaa ; //Initialize VD[15:8] } /**************************************************************** function configarution LCDCON1-5 LCDSADDR1-3 LCD INTERRUPT TPAL etc register TFT 16bpp input void return void *****************************************************************/ static void Lcd_init(void) { rLCDCON1 = (LCD_CLKCAL<<8) | (3<<5) | (12<<1) ; rLCDCON2 = (LCD_UPPER_MARGIN << 24) | ((LCD_HEIGHT - 1) << 14) | (LCD_LOWER_MARGIN << 6) | (LCD_VSYNC_LEN << 0); rLCDCON3 = (LCD_RIGHT_MARGIN << 19) | ((LCD_WIDTH - 1) << 8) | (LCD_LEFT_MARGIN << 0); rLCDCON4 = (LCD_HSYNC_LEN << 0); rLCDCON5 = (1<<11) | (1 << 9) | (1 << 8) | (1<<6) | (1 << 3) | (1 << 0) ; rLCDSADDR1 = ((LCD_ADDR>>22)<<21) | (M5D(LCD_ADDR>>1)) ; //LCDBASEL OFFSIZE=0,PAGEWIDTH=LCD_WIDTH, x2的原因 16bpp 每个像素点2个字节,>>1见地址对应关系 16bpp rLCDSADDR2 = M5D((LCD_ADDR + LCD_WIDTH * LCD_HEIGHT * 2)>>1) ; rLCDSADDR3 = LCD_WIDTH ; rLCDINTMSK |= 3 ; //屏蔽中断 rTCONSEL = 0 ; //LPC3600 LCC3600 无效 rTPAL = 0 ; //禁止临时调色板 } /**************************************************************** function Envid turn on or off input onoff 1,Envid turn on return void *****************************************************************/ static void Lcd_EnvidOnOff(int onoff) { if(onoff==1) rLCDCON1 |= 1 ; //ENVID ON else rLCDCON1 &= ~(1<<0) ; //ENBID OFF } /**************************************************************** function LCD power enable input pwren 1, enable lcd power return void *****************************************************************/ static void Lcd_PowerEnable(int pwren) { rGPGUP |= (1<<4) ; //Pull-up Disable rGPGCON |= (3<<8) ; //GPG4 is LCD_PWREN rLCDCON5&= ~(1<<5) ; //invpwren 正常极性 rLCDCON5 = rLCDCON5 & ~(1<<3)|(pwren<<3) ; //PWREN 使能 } /**************************************************************** function Filling sole colour into LCD background input c colour return void *****************************************************************/ static void Lcd_FillCor(U16 c) { unsigned int x,y ; for(y=0; y<SCR_YSIZE; y++) { for(x=0; x<SCR_XSIZE; x++) { LCD_BUFFER[y][x] = c ; } } } /**************************************************************** function Paint picture input x0 y0 assign start bit level vertical the wide and high of picture *bmp the picture string return void *****************************************************************/ static void Paint_BMP(int x0, int y0, int level, int vertical, const unsigned char *bmp) { int x, y ; U32 col ; int p = 0 ; for(y=0; y<vertical; y++) { for(x=0; x<level; x++) { col = bmp[p+1] | (bmp[p]<<8) ; //16bpp 一个像素点用俩个字节 if( ((x0+x)<SCR_XSIZE) && ((y0+y)<SCR_YSIZE)) LCD_BUFFER[y0+y][x0+x] = col ; p += 2 ; } } } /******************************************************************* 串口UART是挂在PCLK总线上的,需要设置PCLK时钟,即需要设置2440时钟。 首先需要设置PLLCON寄存器设置CPU时钟(FCLK),然后设置FCLK HCLK PCLK的 分频比,确定好PCLK时钟。 *****************************************************************/ int Main(void) { /*****************【CLOCK】******************/ MMU_Init(); ChangeMPllValue(127,2,1); //405MHZ ChangeClockDivider(13,12); //1:3:6 /**************【UART】**********************/ UART_int_init() ; UART_send_string("wo zhen de hen yun si \n") ; pISR_UART0 = (U32)UART0_interrupt ; /*************【LCD】************************/ Lcd_port_init() ; Lcd_init() ; Lcd_PowerEnable(1) ; Lcd_EnvidOnOff(1) ; UART_send_string("LCD initial \n") ; while(1) { //Lcd_FillCor((0x00<<11) | (0x00<<5) | (0x00<<0)) ; //clear screen //Paint_BMP(0,0,240,320,sunflower_240x320) ; //filling picture sunflower Lcd_FillCor(0xffff) ; } return 0 ; }