2440的lcd控制器可以驱动 STN LCD 和 TFT LCD 本文以较为常见的 TFT LCD 来讲解使用方法:
首先, 我们来看下2440的 lcd controller 组成框图:
REGBANK: LCD控制器的寄存器组, 含有17个寄存器及一块256x16的调色板
LCDCDMA: LCD控制器的专用DMA通道, 可以自动从系统总线上获取图像数据, 显示图像时不需要cpu core的参与
TIMEGEN / LPC3600: 产生控制时序, 如: VSYNC, HSYNC, VCLK, VDEN, 而这些信号又与REGBANK中的LCDCON1/2/3/4的配置密切相关, 通过不同的配置产生不同的控制信号. 然后从VIDEO MUX中传递给液晶屏(LPC3600为STN屏专用)
VIDPRCS: 接收LCDCDMA 的数据, 然后转换为合适的数据格式, 比如 4 bit单扫 / 8ibt单扫 /4bit双扫, 然后由 VD[23:0]来显示
其次, 我们来分析一下lcd controller的时序:
VSYNC/VFRAME/STV:垂直同步信号(TFT)/帧同步信号(STN)/SEC TFT信号 HSYNC/VLINE/CPV: 水平同步信号(TFT)/行同步脉冲信号(STN)/SEC TFT信号 VCLK/LCD_HCLK: 像素时钟信号(TFT/STN)/SEC TFT信号 VD[23:0]: LCD像素数据输出端口(TFT/STN/SEC TFT) VDEN/VM/TP: 数据使能信号(TFT)/LCD驱动交流偏置信号(STN)/SEC TFT 信号 LEND/STH: 行结束信号(TFT)/SEC TFT信号 LCD_LPCOE: SEC TFT OE信号 LCD_LPCREV: SEC TFT REV信号 LCD_LPCREVB: SEC TFT REVB信号
所有显示器显示图像的原理都是从上到下,从左到右的。这是什么意思呢?这么说吧,一副图像可以看做是一个矩形,由很多排列整齐的点一行一行组成,这些点称之为像素。那么这幅图在LCD上的显示原理就是:
A:显示指针从矩形左上角的第一行第一个点开始,一个点一个点的在LCD上显示,在上面的时序图上表示为VCLK,我们称之为像素时钟信号 B:当显示指针一直显示到矩形的右边就结束这一行,那么这一行的动作在上面的时序图中就称之为1 Line C:接下来显示指针又回到矩形的左边从第二行开始显示,注意,显示指针在从第一行的右边回到第二行的左边是需要一定的时间的,我们称之为行切换 D:如此类推,显示指针就这样一行一行的显示至矩形的右下角才把一副图显示完成。行的显示在时序图上看就是HSYNC E:然而,LCD要显示多个图片就要一幅一幅的切换, 那么这每一幅图像就称之为帧,在时序图上就表示为1 Frame,因此从时序图上可以看出1 Line只是1 Frame中的一行 F:同样的,在帧与帧切换之间也是需要一定的时间的,我们称之为帧切换,那么LCD整个显示的过程在时间线上看,就可表示为时序图上的VSYNC
上面时序图上各时钟延时参数的含义如下:(这些参数的值,LCD产生厂商会提供相应的数据手册)
VBPD(vertical back porch):表示在一帧图像开始时,垂直同步信号以后的无效的行数,对应驱动中的upper_margin VFBD(vertical front porch):表示在一帧图像结束后,垂直同步信号以前的无效的行数,对应驱动中的lower_margin VSPW(vertical sync pulse width):表示垂直同步脉冲的宽度,用行数计算,对应驱动中的vsync_len HBPD(horizontal back porch):表示从水平同步信号开始到一行的有效数据开始之间的VCLK的个数,对应驱动中的left_margin HFPD(horizontal front porth):表示一行的有效数据结束到下一个水平同步信号开始之间的VCLK的个数,对应驱动中的right_margin HSPW(horizontal sync pulse width):表示水平同步信号的宽度,用VCLK计算,对应驱动中的hsync_len
使用LCD的步骤:
Lcd_Port_Init(); // 设置LCD引脚 Tft_Lcd_Init(MODE_TFT_16BIT_240320); // 初始化LCD控制器, 这里配置了液晶的显示模式, 如: 分辨率 240x320 颜色深度 16bit Lcd_PowerEnable(0, 1); // 设置LCD_PWREN有效,它用于打开LCD的电源 Lcd_EnvidOnOff(1); // 使能LCD控制器输出信号 ClearScr(0x0); // 清屏,黑色
GPCUP = 0xffffffff; // 禁止内部上拉 GPCCON = 0xaaaaaaaa; // GPIO管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND GPDUP = 0xffffffff; // 禁止内部上拉 GPDCON = 0xaaaaaaaa; // GPIO管脚用于VD[23:8] GPBCON &= ~(GPB0_MSK); // Power enable pin GPBCON |= GPB0_out; // GPB0控制液晶的背光ic输出使能 GPBDAT &= ~(1<<0); // Power off printf("Initializing GPIO ports..........\n");
#define CLKVAL_TFT_640480 (1) #define LCDTYPE_TFT 0x3 #define BPPMODE_16BPP 0xC #define ENVID_DISABLE 0 LCDCON1 = (CLKVAL_TFT_640480<<8) | (LCDTYPE_TFT<<5) | (BPPMODE_16BPP<<1) | (ENVID_DISABLE<<0); /* 设置VCLK的频率:VCLK(Hz) = HCLK/[(CLKVAL+1)x2] 选择LCD类型: TFT LCD 设置显示模式: 16BPP 先禁止LCD信号输出 */
#define VBPD_640480 ((33-1)&0xff) #define LINEVAL_TFT_640480 (LCD_YSIZE_TFT_640480-1) #define VFPD_640480 ((10-1)&0xff) #define VSPW_640480 ((2-1) &0x3f) LCDCON2 = (VBPD_640480<<24) | (LINEVAL_TFT_640480<<14) | (VFPD_640480<<6) | (VSPW_640480);
#define HBPD_640480 ((48-1)&0x7f) #define HOZVAL_TFT_640480 (LCD_XSIZE_TFT_640480-1) #define HFPD_640480 ((16-1)&0xff) LCDCON3 = (HBPD_640480<<19) | (HOZVAL_TFT_640480<<8) | (HFPD_640480);
#define HSPW_640480 ((96-1)&0xff) LCDCON4 = HSPW_640480;
#define FORMAT8BPP_565 1 #define HSYNC_INV 1 #define VSYNC_INV 1 #define HWSWP 1 LCDCON5 = (FORMAT8BPP_565<<11) | (HSYNC_INV<<9) | (VSYNC_INV<<8) | (HWSWP<<1); /* 16bpp 565 设置HSYNC、VSYNC脉冲的极性(这需要参考具体LCD的接口信号): 反转 半字(2字节)交换使能 */
#define LCDFRAMEBUFFER 0x30400000 #define LOWER21BITS(n) ((n) & 0x1fffff) LCDSADDR1 = ((LCDFRAMEBUFFER>>22)<<21) | LOWER21BITS(LCDFRAMEBUFFER>>1); /* 0x30400000的[30:22]值为LCDSADDR1[29:21]的值, 所以0x30400000>>22之后再左移21位 0x30400000的[21:1]值为LCDSADDR1[20:0]的值, 所以0x30400000>>1 为应该配置的值, 这个值只保留低21位, 所以 又与 0x1fffff相与 */
#define HOZVAL_TFT_640480 (LCD_XSIZE_TFT_640480-1) #define LINEVAL_TFT_640480 (LCD_YSIZE_TFT_640480-1) LCDSADDR2 = LOWER21BITS((LCDFRAMEBUFFER+(LINEVAL_TFT_640480+1)*(HOZVAL_TFT_640480+1)*2)>>1); /* 这里是计算帧缓冲的结束地址, 本例中占用缓冲区大小为: (LINEVAL_TFT_640480+1) * 640 * 480 * 2, 这里 *2 是因为16bpp, 如果是8bpp则应 *1. 这个大小再加上起始地址LCDFRAMEBUFFER就得到了结束地址 */
#define LCD_XSIZE_TFT_640480 (640) #define LCD_YSIZE_TFT_640480 (480) LCDSADDR3 = (0<<11) | (LCD_XSIZE_TFT_640480*2/2);
如果需要禁止调色板:
/* 禁止临时调色板寄存器 */ TPAL = 0;
帧地址:
unsigned int fb_base_addr; unsigned int bpp; unsigned int xsize; unsigned int ysize; fb_base_addr = LCDFRAMEBUFFER; bpp = 16; xsize = 640; ysize = 480;
* 设置是否输出LCD电源开关信号LCD_PWREN * 输入参数: * invpwren: 0 - LCD_PWREN有效时为正常极性 * 1 - LCD_PWREN有效时为反转极性 * pwren: 0 - LCD_PWREN输出有效 * 1 - LCD_PWREN输出无效 */ void Lcd_PowerEnable(int invpwren, int pwren) { GPGCON = (GPGCON & (~(3<<8))) | (3<<8); // GPG4用作LCD_PWREN GPGUP = (GPGUP & (~(1<<4))) | (1<<4); // 禁止内部上拉 LCDCON5 = (LCDCON5 & (~(1<<5))) | (invpwren<<5); // 设置LCD_PWREN的极性: 正常/反转 LCDCON5 = (LCDCON5 & (~(1<<3))) | (pwren<<3); // 设置是否输出LCD_PWREN }
/* * 设置LCD控制器是否输出信号 * 输入参数: * onoff: * 0 : 关闭 * 1 : 打开 */ void Lcd_EnvidOnOff(int onoff) { if (onoff == 1) { LCDCON1 |= 1; // ENVID ON GPBDAT |= (1<<0); // Power on 背光 } else { LCDCON1 &= 0x3fffe; // ENVID Off GPBDAT &= ~(1<<0); // Power off 背光 } }
屏幕上任何写操作都是由写一个个的点来组合完成的, 写点函数如下:
/* * 画点 * 输入参数: * x、y : 象素坐标 * color: 颜色值 * 对于16BPP: color的格式为0xAARRGGBB (AA = 透明度), * 需要转换为5:6:5格式 * 对于8BPP: color为调色板中的索引值, * 其颜色取决于调色板中的数值 */ void PutPixel(UINT32 x, UINT32 y, UINT32 color) { UINT8 red,green,blue; switch (bpp){ case 16: { UINT16 *addr = (UINT16 *)fb_base_addr + (y * xsize + x); //这里fb_base_addr已经指向了帧内存首地址, 还有帧内存与视图虽然类比为窗口一样的形状, 但是在内存里只有线性结构, 就如同二维数组照样是线性存储的. 所以这里可以这样寻址每个点的内存中的位置 red = (color >> 19) & 0x1f; green = (color >> 10) & 0x3f; blue = (color >> 3) & 0x1f; color = (red << 11) | (green << 5) | blue; // 格式5:6:5 *addr = (UINT16) color; break; } case 8: { UINT8 *addr = (UINT8 *)fb_base_addr + (y * xsize + x); *addr = (UINT8) color; break; } default: break; } }