Mini2440裸机开发之LCD编程

在上一节我们介绍了LCD的硬件基础只是、以及S3C2440 LCD控制器相关的寄存器。这一节我们将会动手在LCD上显示一幅日落的图片。

一、LCD初始化编程步骤

1.1 初始化GPIO,引脚复用

Mini2440裸机开发之LCD编程

 Mini2440裸机开发之LCD编程

 Mini2440裸机开发之LCD编程

在上一节我们介绍了S3C2440这些引脚对应的LCD TFT上的引脚。这里就不在重复介绍了。我们需要配置Port C和Port D为LCD功能。

端口C相关寄存器的相关信息。

寄存器 地址 R/W 描述 复位值
GPCCON 0x56000020 R/W 配置端口C的引脚 0x00
GPCDAT 0x56000024 R/W 配置C的数据寄存器 -
GPCUP 0x56000028 R/W 端口C的上拉使能寄存器 0x00
保留 0x5600002C - 保留 -

(1) GPCCON

GPCCON 描述 初始状态
GPC15 [31:30] 00 = 输入 01 = 输出 10 = VD[7] 11 = 保留 0
GPC14 [29:28] 00 = 输入 01 = 输出 10 = VD[6] 11 = 保留 0
GPC13 [27:26] 00 = 输入 01 = 输出 10 = VD[5] 11 = 保留 0
GPC12 [25:24] 00 = 输入 01 = 输出 10 = VD[4] 11 = 保留 0
GPC11 [23:22] 00 = 输入 01 = 输出 10 = VD[3] 11 = 保留 0
GPC10 [21:20] 00 = 输入 01 = 输出 10 = VD[2] 11 = 保留 0
GPC9 [19:18] 00 = 输入 01 = 输出 10 = VD[1] 11 = 保留 0
GPC8 [17:16] 00 = 输入 01 = 输出 10 = VD[0] 11 = 保留 0
GPC7 [15:14] 00 = 输入 01 = 输出 10 = LCD_LPCREVB 11 = 保留 0
GPC6 [13:12] 00 = 输入 01 = 输出 10 = LCD_LPCREV 11 = 保留 0
GPC5 [11:10] 00 = 输入 01 = 输出 10 = LCD_LPCOE 11 = 保留 0
GPC4 [9:8] 00 = 输入 01 = 输出 10 = VM 11 = 保留 0
GPC3 [7:6] 00 = 输入 01 = 输出 10 = VFRAME 11 = 保留 0
GPC2 [5:4] 00 = 输入 01 = 输出 10 = VLINE 11 = 保留 0
GPC1 [3:2] 00 = 输入 01 = 输出 10 = VCLK 11 = 保留 0
GPC0 [1:0] 00 = 输入 01 = 输出 10 = LEND 11 = 保留 0

由上表可知,B端口的控制寄存器可以将每个引脚配置为四种模式:

  • 00:输入模式
  • 01:输出模式
  • 10:功能扩展模式
  • 11:保留模式

配置端口C功能复用为LCD:

GPCCON=0xaaaaaaaa

(2) GPCDAT

GPCDAT 描述
GPC[15:0] [15:0]

当端口配置为输入端口时,相应位为引脚状态。当端口配置为输出端口时,引脚状态将与相应位相同。当端口配置为功能引脚,将读取到未定义值。

(3) GPCUP

GPCUP 描述
GPC[15:0] [15:0]

0:使能附加上拉功能到相应端口引脚

1:禁止附加上拉功能到相应端口引脚

禁止上拉:

 GPCUP = 0xffffffff;

端口D相关寄存器的相关信息。

寄存器 地址 R/W 描述 复位值
GPDCON 0x56000030 R/W 配置端口D的引脚 0x00
GPDDAT 0x56000034 R/W 配置D的数据寄存器 -
GPDUP 0x56000038 R/W 端口D的上拉使能寄存器 0xF000
保留 0x5600003C - 保留 -

(1) GPDCON

GPDCON 描述 初始状态
GPD15 [31:30] 00 = 输入 01 = 输出 10 = VD[23] 11 = 保留 0
GPD14 [29:28] 00 = 输入 01 = 输出 10 = VD[22] 11 = 保留 0
GPD13 [27:26] 00 = 输入 01 = 输出 10 = VD[21] 11 = 保留 0
GPD12 [25:24] 00 = 输入 01 = 输出 10 = VD[20] 11 = 保留 0
GPD11 [23:22] 00 = 输入 01 = 输出 10 = VD[19] 11 = 保留 0
GPD10 [21:20] 00 = 输入 01 = 输出 10 = VD[18] 11 = 保留 0
GPD9 [19:18] 00 = 输入 01 = 输出 10 = VD[17] 11 = 保留 0
GPD8 [17:16] 00 = 输入 01 = 输出 10 = VD[16] 11 = 保留 0
GPD7 [15:14] 00 = 输入 01 = 输出 10 = VD[15] 11 = 保留 0
GPD6 [13:12] 00 = 输入 01 = 输出 10 = VD[14] 11 = 保留 0
GPD5 [11:10] 00 = 输入 01 = 输出 10 = VD[13] 11 = 保留 0
GPD4 [9:8] 00 = 输入 01 = 输出 10 = VD[12] 11 = 保留 0
GPD3 [7:6] 00 = 输入 01 = 输出 10 =VD[11] 11 = 保留 0
GPD2 [5:4] 00 = 输入 01 = 输出 10 = VD[10] 11 = 保留 0
GPD1 [3:2] 00 = 输入 01 = 输出 10 = VD[9] 11 = 保留 0
GPD0 [1:0] 00 = 输入 01 = 输出 10 = VD[8] 11 = 保留  0

由上表可知,B端口的控制寄存器可以将每个引脚配置为四种模式:

  • 00:输入模式
  • 01:输出模式
  • 10:功能扩展模式
  • 11:保留模式

配置端口D功能复用为LCD:

GPDCON=0xaaaaaaaa

(2) GPDDAT

GPCDAT 描述
GPD[15:0] [15:0]

当端口配置为输入端口时,相应位为引脚状态。当端口配置为输出端口时,引脚状态将与相应位相同。当端口配置为功能引脚,将读取到未定义值。

(3) GPDUP

GPCUP 描述
GPD[15:0] [15:0]

0:使能附加上拉功能到相应端口引脚

1:禁止附加上拉功能到相应端口引脚

禁止上拉:

 GPDUP = 0xffffffff;

总结下来:

void _lcd_gpio_init()
{
    GPCUP = 0xffffffff;         /* Disable Pull - up register */
    GPCCON = 0xaaaaaaaa;        /* Initialize VD[7:0], VM, VFRAME, VLINE, VCLK */

    GPDUP = 0xffffffff;         /* Disable Pull - up register */
    GPDCON = 0xaaaaaaaa;        /* Initialize VD[15:8] */
}

1.2 打开LCD电源

LCD_PWR对应的引脚,所以设置GPG4就可以控制LED背光电源了。(GPGCON:9-8写入11),这时候LCD电源的打开/关闭可以通过LCDCON5位3来控制:

LCD_PWREN 输出信号使能/禁止:0 = 禁止PWREN 信号 1 = 允许PWREN信号:

void _lcd_power_on() 
{
    /*  打开LCD电源  */
    GPGUP |= (1 << 4);            /* Pull - up disable */
    GPGCON |= (3 << 8);            /* 设置GPG4引脚为LCD_PWRDN模式 */
    LCDCON5 |= (1 << 3);              /* LCD_PWREN输出信号使能 */
}

1.3 设置其他信号线

其他信号线包括VD0-VD23和VFRAME、VLINE、VCLK等,分别在GPCCON,GPDCON中选择相应功能。这些在上一节已经介绍过。这里就稍微提一下

1) 设置VLCK、BPPMODE、PNRMODE

(VCLK)LCD的Datasheet上一般会写有一个推荐的频率,上一节已经介绍过我使用的屏幕推荐频率为6.4M,我们通过计算得到的CLKVAL=7比较合适。写入LCDCON1位17-8;

位4-1选择BPP(位每像素)模式    1100 = TFT 的16 BPP;

位6-5选择显示模式  11 = TFT LCD 面板

2) 设置其他相关参数

LCD相关的参数主要还有这几个:LINEVAL: LCD水平像素-1,如240-1 = 239;
HOZVAL:  LCD垂直像素-1,如320-1 = 319;
HFPD:    行开始前的VCLK时钟数(LCD屏幕的Datasheet一般有推荐值);
HBPD:    行结束后的VCLK时钟数(LCD屏幕的Datasheet一般有推荐值);
HSPW:    行之间水平同步的无效VCLK时钟数(LCD屏幕的Datasheet一般有推荐值);
VFPD:    帧数据开始前的空白行数(LCD屏幕的Datasheet一般有推荐值);
VBPD:    帧数据结束后的空白行数(LCD屏幕的Datasheet一般有推荐值);
VSPW:    帧之间垂直同步的无效行数(LCD屏幕的Datasheet一般有推荐值);
(相关寄存器LCDCON2, LCDCON3, LCDCON4);

3) 设置视频缓冲区的地址

S3C2440支持虚拟屏幕,可以通过改变LCD寄存器实现屏幕快速移动;
PAGEWIDTH:虚拟屏幕一行的字节数,如果不使用虚拟屏幕,设置为实际屏幕的行半字数,如16位宽320像素,设为320 ;
OFFSIZE:虚拟屏幕左侧偏移的字节数,如果不使用虚拟屏幕,设置为0;
LCDBANK: 视频帧缓冲区内存地址30-22位;
LCDBASEU: 视频帧缓冲区的开始地址21-1位;
LCDBASEL: 视频帧缓冲区的结束地址21-1位;
(相关寄存器LCDSADDR1,LCDSADDR2,LCDSADDR3);

4) 确定信号的极性

S3C2440的LCD控制器允许设置VCLK、VLINE、VFRAME等信号的极性(上升沿有效还是下降沿有效),需要对照LCD的Datasheet一一确认。
(相关寄存器LCDCON5)

5) 关中断

设置LCDINTMSK位1-0。

6) 禁止LPC3600/LCC3600模式!

如果不是使用三星LPC3600/LCC3600 LCD,必须禁止LPC3600/LCC3600模式(写入0到TCONSEL位4、0)。

7)  关闭临时调色板

TPAL位24设置为0。

总体设置代码如下:

void _lcd_control_init()
{
    /*  [17:8]  CLKVAL
     *  [6:5]   PNRMODE;选择显示模式
     *                  00 = 4 位双扫描显示模式   01 = 4 位单扫描显示模式(STN)
     *                  10 = 8 位单扫描显示模式   11 = TFT LCD 面板
     *  [4:1]  BPPMODE  选择BPP(位每像素)模式    1100 = TFT 的16 BPP
     *  [0]    ENVID    LCD 视频输出和逻辑使能/禁止。
     *                  0 = 禁止视频输出和LCD 控制信号  1 = 允许视频输出和LCD 控制信号
     */
    LCDCON1 = (CLKVAL<<8)| (3<<5)|(0xC<<1);               /* 16 bpp for TFT */

    /*  [31:24]   VBPD:帧同步信号的后肩
     *  [23:14]   LINEVAL:LCD面板的垂直尺寸
     *  [13:6]    VFPD:帧同步信号的前肩
     *  [5:0]     VSPW:同步信号的脉宽
     */
    LCDCON2 = (VBPD<<24)|(LINEVAL<<14)|(VFPD<<6)|(VSPW);

    /* [25:19] HBPD: 行同步信号的后肩
     * [18:8] HOZVAL: LCD面板的水平尺寸
     * [7:0] HFPD: 行同步信号的前肩
     */
    LCDCON3 = (HBPD<<19)|(HOZVAL<<8)|(HFPD);
    LCDCON4 = (HSPW);

    /* [11] FRM565: 此位选择16 BPP 输出视频数据的格式   0 = 5:5:5:1 格式   1= 5:6:5 格式
     * [10] STN/TFT: 此位控制VCLK 有效沿的极性
     * [9]    INVVLINE: STN/TFT:此位表明VLINE/HSYNC 脉冲极性  0 = 正常 1 = 反转
     * [8]  INVVFRAME: STN/TFT:此位表明VFRAME/VSYNC 脉冲极性 0 = 正常 1 = 反转
     * VLINE/HSYNC 脉冲极性、VFRAME/VSYNC 脉冲极性反转(LCD型号决定)
     * [0]    HWSWP: STN/TFT:半字节交换控制位   0 = 交换禁止 1 = 交换使能
     */
    LCDCON5 = ((1<<11) | (1<<10) | (1 << 9) | (1 << 8) | (1 << 0));


#define    M5D(n)        ((n)&0x1fffff)
#define  LCD_ADDR   ((u32)LCD_BUFFER)                      /* FrameBuffer地址 */

    /* [29:21] LCDBANK:存放帧缓冲起始地址的[30:22]
     * [20:0] LCDBASEU: 存放帧缓冲起始地址的[21:1]
     */   
    LCDSADDR1 = ((LCD_ADDR >> 22) << 21) | (M5D(LCD_ADDR >> 1)) ;

    /* 存放帧结束地址[21:1] */
    LCDSADDR2 = M5D((LCD_ADDR + (LCD_WIDTH * LCD_HEIGHT * 2 )) >> 1);

    /* [21:11] OFFSIZE:表示虚拟屏偏移尺寸  即上一行最后像素点到下一行第一个像素点之间间隔多少个像素点
    *  [10:0] PAGEWIDTH:表示行的宽度(半字为单位 16bit)
    */
    LCDSADDR3 = LCD_WIDTH;

    LCDINTMSK |= 3;
    TCONSEL &= ~((1 << 4) | 1);
    TPAL = 0x00;        /* 关闭临时调色板 */
}

1.4 打开视频输出

ENVID设为1 (LCDCON1:0写入1)。

    /* LCD使能 */
    LCDCON1 |= 1;    

到此LCD初始化步骤就介绍完成了,整体代码如下:

void lcd_init()
{
    _lcd_gpio_init();
    _lcd_power_on();
    _lcd_control_init();   

    /* LCD使能 */
    LCDCON1 |= 1;                    
}

二、显示BMP图片

如果想在LCD上显示一幅图片,只需要向LCD_BUFFER缓冲区写入图片数据即可。

void lcd_draw_bmp(u16 x0,u16 y0,u16 width,u16 height,const u8 *bmp)
{
    u16 x,y;
    u16 c;
    u32 p = 0;

    /* 绘制每一行 */
    for( y = 0 ; y < height; y++ )
    {
        /* 绘制每一点 */
        for( x = 0 ; x < width; x++ )
        {
            c = bmp[p + 1] | (bmp[p] << 8);

            /* bmp.c中的数组元素大小是8bit,屏幕像素设置16bit */
            if (((x0 + x) < LCD_WIDTH) && ( (y0 + y) < LCD_HEIGHT))   /* LCD尺寸范围,根据实际LCD设置 */
            {
                LCD_BUFFER[y0 + y][x0 + x] = c;
            }
            p = p + 2 ;      /* 屏幕像素设置16bit,所以要+2 */
        }
    }
}

在Ecllispe调试模式下,将代码直接下载SDRAM地址0x30000000运行,图片显示正常:

Mini2440裸机开发之LCD编程

但是通过MiniTools下载到SDRAM地址0x30000000运行,图片缺显示正常:

Mini2440裸机开发之LCD编程

目前原因还未排查清楚。

三、显示字符

3.1 字符显示原理

3.2 字符制作

四、源码下载

上一篇:Docker8_3:Docker容器数据卷:DockerFile文件挂载(一般都用这个)


下一篇:Docker镜像介绍