课程内容:
LCD的信号引脚:
VSYNC 垂直方向的同步信号
HSYNC 水平方向的同步信号
VDEN 使能信号
LED+和LED- 背光信号
VCLK 时钟信号
背光芯片的使能要将GPIO BL引脚置高电平使能
水平同步信号和垂直同步信号如何如何运用
对该信号引脚的运用看时序图,该时序图在2440的芯片手册里面
对于该开发板用的LCD分辨率为240(宽)*320(长)
LCD的扫描方向,从左到右,从上到下
时序图如下图所示:
时序图讲解:
一帧的时序:
一开始HSYNC变高电平,表示喷枪(打印光点的位置)回到最上一行,HSYNC第一个上升沿和第二个上升沿之间是扫描行数,扫描的行数是VSPW+1,第二个上升沿和第三个上升沿的时间是VBPD+1,这些时间是可以设置的。直到第三个上升沿开始才是开始打印行光点,之前时序为打印光点做准备。
HSYNC每一次上升沿便是一个新的行开始,即Lineval+1。当行结束后,VFPD+1,然后又从第一行开始打印光点。
行时序:
行打印前的准备工作,HSYNC信号持续时间为HSPW+1,HSYNC变低电平后与开始打印行光点的时间为HBPD+1.每行的观点个数也是可以控制的,有HOZVAL决定。然后因为HSYNC上升沿,喷枪再跳回行的第一个光点处。HBPD表示无效的像素点,和HFPD相同,表示黑色边框。一个VCLK对应一个像素。
一行内的像素有效由VDEN表示,HSYNC表示一行开始。
因此写程序时有HSYNC、HBPD、HOZVAL、HFPD、VSPW、VBPD、VFPD这些参数要设置,还有VCLK这个时钟。
参数设置
2440有LCD控制器,2440里面会开一块内存,里面的数据对应LCD上的像素,每个像素两个字节。要显示图片前会将图片数据写到内存里面,然后通过设置的参数驱动LCD并显示图片。
程序流程
图上有两种存放方法,因为是32位的内存,而像素是16位的,所以像素的存放存在大小端
打算如上的方式存放时,也是符合我们正常的思维的存放方式时,BSWP=0,HWSWP=1。
8bpp存放格式时
要确定一点,LCD选定后,像素宽度是固定的,开发板上的LCD已经是确定了的16bpp,而要用8bpp就要涉及到一个新的概念,调色板。
Frame Buffer=>2440LCD控制器=>LCD。LCD每个像素一定是16bpp,但是如果FrameBuffer的数据是8bpp的格式,要转变成16bpp的方法就是用调色板。
调色板存256种颜色,2^8=256,颜色的存放是以两个字节的格式存放的。此时,要将8bpp转换成16bpp,此时8bpp的图像数据就已经不是像素,而是去调色板进行索引的数字。
源码分析
head.s的代码如下所示:
Reset:
ldr sp, =4096 @ 设置栈指针,以下都是C函数,调用前需要设好栈
bl disable_watch_dog @ 关闭WATCHDOG,否则CPU会不断重启
bl clock_init @ 设置MPLL,改变FCLK、HCLK、PCLK
bl memsetup @ 设置存储控制器以使用SDRAM
bl nand_init @ 初始化NAND Flash
@ 复制代码到SDRAM中
ldr r0, =0x30000000 @ 1. 目标地址 = 0x30000000,这是SDRAM的起始地址
mov r1, #4096 @ 2. 源地址 = 4096,运行地址在SDRAM中的代码保存在NAND Flash 4096地址开始处
mov r2, #16*1024 @ 3. 复制长度 = 16K,对于本实验,这是足够了
bl CopyCode2SDRAM @ 调用C函数CopyCode2SDRAM
bl clean_bss @ 清除bss段,未初始化或初值为0的全局/静态变量保存在bss段
msr cpsr_c, #0xd2 @ 进入中断模式
ldr sp, =0x31000000 @ 设置中断模式栈指针
msr cpsr_c, #0xdf @ 进入系统模式
ldr sp, =0x34000000 @ 设置系统模式栈指针,
ldr lr, =ret_initirq @ 设置返回地址
ldr pc, =init_irq @ 调用中断初始化函数
ret_initirq:
msr cpsr_c, #0x5f @ 设置I-bit=0,开IRQ中断
ldr lr, =halt_loop @ 设置返回地址
ldr pc, =main @ 调用main函数
halt_loop:
b halt_loop
HandleIRQ:
sub lr, lr, #4 @ 计算返回地址
stmdb sp!, { r0-r12,lr } @ 保存使用到的寄存器
@ 注意,此时的sp是中断模式的sp
@ 初始值是上面设置的4096
ldr lr, =int_return @ 设置调用IRQ_Handle函数后的返回地址
ldr pc, =IRQ_Handle @ 调用中断分发函数,在interrupt.c中
int_return:
ldmia sp!, { r0-r12,pc }^ @ 中断返回, ^表示将spsr的值复制到cpsr
程序过程
核心函数是Test_Lcd_Tft_16Bit_240320和Test_Lcd_Tft_8Bit_240320
Test_Lcd_Tft_8Bit_240320的内容
void Test_Lcd_Tft_8Bit_240320(void)
{
Lcd_Port_Init(); // 设置LCD引脚
Tft_Lcd_Init(MODE_TFT_8BIT_240320); // 初始化LCD控制器
Lcd_PowerEnable(0, 1); // 设置LCD_PWREN有效,它用于打开LCD的电源
Lcd_EnvidOnOff(1); // 使能LCD控制器输出信号
Lcd_Palette8Bit_Init(); // 初始化调色板
ClearScr(0x0); // 清屏
printf("[TFT 64K COLOR(16bpp) LCD TEST]\n");
printf("1. Press any key to draw line\n");
getc();
DrawLine(0 , 0 , 239, 0 , 0); // 颜色为DEMO256pal[0]
DrawLine(0 , 0 , 0 , 319, 1); // 颜色为DEMO256pal[1]
DrawLine(239, 0 , 239, 319, 2); // ……
DrawLine(0 , 319, 239, 319, 4);
DrawLine(0 , 0 , 239, 319, 8);
DrawLine(239, 0 , 0 , 319, 16);
DrawLine(120, 0 , 120, 319, 32);
DrawLine(0 , 160, 239, 160, 64);
printf("2. Press any key to draw circles\n");
getc();
Mire();
printf("3. Press any key to fill the screem with one color\n");
getc();
ClearScr(128); // 输出单色图像,颜色为DEMO256pal[128]
printf("4. Press any key to fill the screem by temporary palette\n");
getc();
ClearScrWithTmpPlt(0x0000ff); // 输出单色图像,颜色为蓝色
printf("5. Press any key to fill the screem by palette\n");
getc();
DisableTmpPlt(); // 关闭临时调色板寄存器
ChangePalette(0xffff00); // 改变整个调色板为黄色,输出单色图像
printf("6. Press any key stop the testing\n");
getc();
Lcd_EnvidOnOff(0);
}
Test_Lcd_Tft_16Bit_240320的内容,数据格式是5:6:5格式
void Test_Lcd_Tft_16Bit_240320(void)
{
Lcd_Port_Init(); // 设置LCD引脚
Tft_Lcd_Init(MODE_TFT_16BIT_240320); // 初始化LCD控制器
Lcd_PowerEnable(0, 1); // 设置LCD_PWREN有效,它用于打开LCD的电源,该函数可以不管
Lcd_EnvidOnOff(1); // 使能LCD控制器输出信号
ClearScr(0x0); // 清屏,黑色
printf("[TFT 64K COLOR(16bpp) LCD TEST]\n");
printf("1. Press any key to draw line\n");
getc();
DrawLine(0 , 0 , 239, 0 , 0xff0000); // 红色
DrawLine(0 , 0 , 0 , 319, 0x00ff00); // 绿色
DrawLine(239, 0 , 239, 319, 0x0000ff); // 蓝色
DrawLine(0 , 319, 239, 319, 0xffffff); // 白色
DrawLine(0 , 0 , 239, 319, 0xffff00); // 黄色
DrawLine(239, 0 , 0 , 319, 0x8000ff); // 紫色
DrawLine(120, 0 , 120, 319, 0xe6e8fa); // 银色
DrawLine(0 , 160, 239, 160, 0xcd7f32); // 金色
printf("2. Press any key to draw circles\n");
getc();
Mire();
printf("3. Press any key to fill the screem with one color\n");
getc();
ClearScr(0xff0000); // 红色
printf("4. Press any key to fill the screem by temporary palette\n");
getc();
ClearScrWithTmpPlt(0x0000ff); // 蓝色
printf("5. Press any key stop the testing\n");
getc();
Lcd_EnvidOnOff(0);
}
Tft_Lcd_Init(MODE_TFT_16BIT_240320)是本程序最重要的函数,内容如下:
void Tft_Lcd_Init(int type)
{
switch(type)
{
case MODE_TFT_8BIT_240320:
/*
* 设置LCD控制器的控制寄存器LCDCON1~5
* 1. LCDCON1:
* 设置VCLK的频率:VCLK(Hz) = HCLK/[(CLKVAL+1)x2]
* 选择LCD类型: TFT LCD
* 设置显示模式: 8BPP
* 先禁止LCD信号输出
* 2. LCDCON2/3/4:
* 设置控制信号的时间参数
* 设置分辨率,即行数及列数
* 现在,可以根据公式计算出显示器的频率:
* 当HCLK=100MHz时,
* Frame Rate = 1/[{(VSPW+1)+(VBPD+1)+(LIINEVAL+1)+(VFPD+1)}x
* {(HSPW+1)+(HBPD+1)+(HFPD+1)+(HOZVAL+1)}x
* {2x(CLKVAL+1)/(HCLK)}]
* = 60Hz
* 3. LCDCON5:
* 设置显示模式为8BPP时,调色板中的数据格式: 5:6:5
* 设置HSYNC、VSYNC脉冲的极性(这需要参考具体LCD的接口信号): 反转
* 字节交换使能
*/
LCDCON1 = (CLKVAL_TFT_240320<<8) | (LCDTYPE_TFT<<5) | \
(BPPMODE_8BPP<<1) | (ENVID_DISABLE<<0);
LCDCON2 = (VBPD_240320<<24) | (LINEVAL_TFT_240320<<14) | \
(VFPD_240320<<6) | (VSPW_240320);
LCDCON3 = (HBPD_240320<<19) | (HOZVAL_TFT_240320<<8) | (HFPD_240320);
LCDCON4 = HSPW_240320;
LCDCON5 = (FORMAT8BPP_565<<11) | (HSYNC_INV<<9) | (VSYNC_INV<<8) | \
(BSWP<<1);
/*
* 设置LCD控制器的地址寄存器LCDSADDR1~3
* 帧内存与视口(view point)完全吻合,
* 图像数据格式如下(8BPP时,帧缓冲区中的数据为调色板中的索引值):
* |----PAGEWIDTH----|
* y/x 0 1 2 239
* 0 idx idx idx ... idx
* 1 idx idx idx ... idx
* 1. LCDSADDR1:
* 设置LCDBANK、LCDBASEU
* 2. LCDSADDR2:
* 设置LCDBASEL: 帧缓冲区的结束地址A[21:1]
* 3. LCDSADDR3:
* OFFSIZE等于0,PAGEWIDTH等于(240/2)
*/
LCDSADDR1 = ((LCDFRAMEBUFFER>>22)<<21) | LOWER21BITS(LCDFRAMEBUFFER>>1);
LCDSADDR2 = LOWER21BITS((LCDFRAMEBUFFER+ \
(LINEVAL_TFT_240320+1)*(HOZVAL_TFT_240320+1)*1)>>1);
LCDSADDR3 = (0<<11) | (LCD_XSIZE_TFT_240320/2);
/* 禁止临时调色板寄存器 */
TPAL = 0;
fb_base_addr = LCDFRAMEBUFFER;
bpp = 8;
xsize = 240;
ysize = 320;
break;
case MODE_TFT_16BIT_240320:
/*
* 设置LCD控制器的控制寄存器LCDCON1~5
* 1. LCDCON1:
* 设置VCLK的频率:VCLK(Hz) = HCLK/[(CLKVAL+1)x2]
* 选择LCD类型: TFT LCD
* 设置显示模式: 16BPP
* 先禁止LCD信号输出
* 2. LCDCON2/3/4:
* 设置控制信号的时间参数
* 设置分辨率,即行数及列数
* 现在,可以根据公式计算出显示器的频率:
* 当HCLK=100MHz时,
* Frame Rate = 1/[{(VSPW+1)+(VBPD+1)+(LIINEVAL+1)+(VFPD+1)}x
* {(HSPW+1)+(HBPD+1)+(HFPD+1)+(HOZVAL+1)}x
* {2x(CLKVAL+1)/(HCLK)}]
* = 60Hz
* 3. LCDCON5:
* 设置显示模式为16BPP时的数据格式: 5:6:5
* 设置HSYNC、VSYNC脉冲的极性(这需要参考具体LCD的接口信号): 反转
* 半字(2字节)交换使能
*/
LCDCON1 = (CLKVAL_TFT_240320<<8) | (LCDTYPE_TFT<<5) | \
(BPPMODE_16BPP<<1) | (ENVID_DISABLE<<0);
LCDCON2 = (VBPD_240320<<24) | (LINEVAL_TFT_240320<<14) | \
(VFPD_240320<<6) | (VSPW_240320);
LCDCON3 = (HBPD_240320<<19) | (HOZVAL_TFT_240320<<8) | (HFPD_240320);
LCDCON4 = HSPW_240320;
LCDCON5 = (FORMAT8BPP_565<<11) | (HSYNC_INV<<9) | (VSYNC_INV<<8) | \
(HWSWP<<1);
/*
* 设置LCD控制器的地址寄存器LCDSADDR1~3
* 帧内存与视口(view point)完全吻合,
* 图像数据格式如下:
* |----PAGEWIDTH----|
* y/x 0 1 2 239
* 0 rgb rgb rgb ... rgb
* 1 rgb rgb rgb ... rgb
* 1. LCDSADDR1:
* 设置LCDBANK、LCDBASEU
* 2. LCDSADDR2:
* 设置LCDBASEL: 帧缓冲区的结束地址A[21:1]
* 3. LCDSADDR3:
* OFFSIZE等于0,PAGEWIDTH等于(240*2/2)
*/
LCDSADDR1 = ((LCDFRAMEBUFFER>>22)<<21) | LOWER21BITS(LCDFRAMEBUFFER>>1);
LCDSADDR2 = LOWER21BITS((LCDFRAMEBUFFER+ \
(LINEVAL_TFT_240320+1)*(HOZVAL_TFT_240320+1)*2)>>1);
LCDSADDR3 = (0<<11) | (LCD_XSIZE_TFT_240320*2/2);
/* 禁止临时调色板寄存器 */
TPAL = 0;
fb_base_addr = LCDFRAMEBUFFER;
bpp = 16;
xsize = 240;
ysize = 320;
break;
case MODE_TFT_8BIT_640480:
/*
* 设置LCD控制器的控制寄存器LCDCON1~5
* 1. LCDCON1:
* 设置VCLK的频率:VCLK(Hz) = HCLK/[(CLKVAL+1)x2]
* 选择LCD类型: TFT LCD
* 设置显示模式: 8BPP
* 先禁止LCD信号输出
* 2. LCDCON2/3/4:
* 设置控制信号的时间参数
* 设置分辨率,即行数及列数
* 现在,可以根据公式计算出显示器的频率:
* 当HCLK=100MHz时,
* Frame Rate = 1/[{(VSPW+1)+(VBPD+1)+(LIINEVAL+1)+(VFPD+1)}x
* {(HSPW+1)+(HBPD+1)+(HFPD+1)+(HOZVAL+1)}x
* {2x(CLKVAL+1)/(HCLK)}]
* = 60Hz
* 3. LCDCON5:
* 设置显示模式为8BPP时,调色板中的数据格式: 5:6:5
* 设置HSYNC、VSYNC脉冲的极性(这需要参考具体LCD的接口信号): 反转
* 字节交换使能
*/
LCDCON1 = (CLKVAL_TFT_640480<<8) | (LCDTYPE_TFT<<5) | \
(BPPMODE_8BPP<<1) | (ENVID_DISABLE<<0);
LCDCON2 = (VBPD_640480<<24) | (LINEVAL_TFT_640480<<14) | \
(VFPD_640480<<6) | (VSPW_640480);
LCDCON3 = (HBPD_640480<<19) | (HOZVAL_TFT_640480<<8) | (HFPD_640480);
LCDCON4 = HSPW_640480;
LCDCON5 = (FORMAT8BPP_565<<11) | (HSYNC_INV<<9) | (VSYNC_INV<<8) | \
(BSWP<<1);
/*
* 设置LCD控制器的地址寄存器LCDSADDR1~3
* 帧内存与视口(view point)完全吻合,
* 图像数据格式如下(8BPP时,帧缓冲区中的数据为调色板中的索引值):
* |----PAGEWIDTH----|
* y/x 0 1 2 639
* 0 idx idx idx ... idx
* 1 idx idx idx ... idx
* 1. LCDSADDR1:
* 设置LCDBANK、LCDBASEU
* 2. LCDSADDR2:
* 设置LCDBASEL: 帧缓冲区的结束地址A[21:1]
* 3. LCDSADDR3:
* OFFSIZE等于0,PAGEWIDTH等于(640/2)
*/
LCDSADDR1 = ((LCDFRAMEBUFFER>>22)<<21) | LOWER21BITS(LCDFRAMEBUFFER>>1);
LCDSADDR2 = LOWER21BITS((LCDFRAMEBUFFER+ \
(LINEVAL_TFT_640480+1)*(HOZVAL_TFT_640480+1)*1)>>1);
LCDSADDR3 = (0<<11) | (LCD_XSIZE_TFT_640480/2);
/* 禁止临时调色板寄存器 */
TPAL = 0;
fb_base_addr = LCDFRAMEBUFFER;
bpp = 8;
xsize = 640;
ysize = 480;
break;
case MODE_TFT_16BIT_640480:
/*
* 设置LCD控制器的控制寄存器LCDCON1~5
* 1. LCDCON1:
* 设置VCLK的频率:VCLK(Hz) = HCLK/[(CLKVAL+1)x2]
* 选择LCD类型: TFT LCD
* 设置显示模式: 16BPP
* 先禁止LCD信号输出
* 2. LCDCON2/3/4:
* 设置控制信号的时间参数
* 设置分辨率,即行数及列数
* 现在,可以根据公式计算出显示器的频率:
* 当HCLK=100MHz时,
* Frame Rate = 1/[{(VSPW+1)+(VBPD+1)+(LIINEVAL+1)+(VFPD+1)}x
* {(HSPW+1)+(HBPD+1)+(HFPD+1)+(HOZVAL+1)}x
* {2x(CLKVAL+1)/(HCLK)}]
* = 60Hz
* 3. LCDCON5:
* 设置显示模式为16BPP时的数据格式: 5:6:5
* 设置HSYNC、VSYNC脉冲的极性(这需要参考具体LCD的接口信号): 反转
* 半字(2字节)交换使能
*/
LCDCON1 = (CLKVAL_TFT_640480<<8) | (LCDTYPE_TFT<<5) | \
(BPPMODE_16BPP<<1) | (ENVID_DISABLE<<0);
LCDCON2 = (VBPD_640480<<24) | (LINEVAL_TFT_640480<<14) | \
(VFPD_640480<<6) | (VSPW_640480);
LCDCON3 = (HBPD_640480<<19) | (HOZVAL_TFT_640480<<8) | (HFPD_640480);
LCDCON4 = HSPW_640480;
LCDCON5 = (FORMAT8BPP_565<<11) | (HSYNC_INV<<9) | (VSYNC_INV<<8) | \
(HWSWP<<1);
/*
* 设置LCD控制器的地址寄存器LCDSADDR1~3
* 帧内存与视口(view point)完全吻合,
* 图像数据格式如下:
* |----PAGEWIDTH----|
* y/x 0 1 2 639
* 0 rgb rgb rgb ... rgb
* 1 rgb rgb rgb ... rgb
* 1. LCDSADDR1:
* 设置LCDBANK、LCDBASEU
* 2. LCDSADDR2:
* 设置LCDBASEL: 帧缓冲区的结束地址A[21:1]
* 3. LCDSADDR3:
* OFFSIZE等于0,PAGEWIDTH等于(640*2/2)
*/
LCDSADDR1 = ((LCDFRAMEBUFFER>>22)<<21) | LOWER21BITS(LCDFRAMEBUFFER>>1);
LCDSADDR2 = LOWER21BITS((LCDFRAMEBUFFER+ \
(LINEVAL_TFT_640480+1)*(HOZVAL_TFT_640480+1)*2)>>1);
LCDSADDR3 = (0<<11) | (LCD_XSIZE_TFT_640480*2/2);
/* 禁止临时调色板寄存器 */
TPAL = 0;
fb_base_addr = LCDFRAMEBUFFER;
bpp = 16;
xsize = 640;
ysize = 480;
break;
default:
break;
}
}
先看case MODE_TFT_16BIT_240320:下的内容
另一个核心函数PutPixel
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);
red = (color >> 19) & 0x1f; // 5 BIT
green = (color >> 10) & 0x3f; // 6 bit
blue = (color >> 3) & 0x1f; // 5 bit
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;
}
}
清屏方法,LCD变为同一种颜色,可以把颜色写到寄存器(TPAL)中,然后使能这个寄存器。
8bpp与16bpp的区别就是要调用调色板,因此要设置调色板