LCD液晶显示实验

硬件设计

图中圈出来的部分就是连接 TFTLCD 模块的接口,液晶模块直接插上去即可。 在硬件上,TFTLCD 模块与探索者 STM32F4 开发板的 IO 口对应关系如下:

LCD_BL(背光控制)对应 PB0;

LCD_CS 对应 PG12 即 FSMC_NE4;

LCD _RS 对应 PF12 即 FSMC_A6;

LCD _WR 对应 PD5 即 FSMC_NWE;

LCD _RD 对应 PD4 即 FSMC_NOE;

LCD _D[15:0]则直接连接在FSMC_D15~FSMC_D0;

软件设计

我们用到 FSMC 驱动 LCD,TFTLCD 的 RS 接在 FSMC 的 A6 上面,CS 接在 FSMC_NE4 上,并且是 16 位数据总线。即我们使用的是 FSMC 存储器 1 的第 4 区,我们定义如下 LCD 操作结构体(在 lcd.h 里面定义):

//LCD 操作结构体

typedef struct

{

        vu16 LCD_REG;

        vu16 LCD_RAM;

} LCD_TypeDef;

//使用 NOR/SRAM 的 Bank1.sector4,地址位 HADDR[27,26]=11 A6 作为数据命令区分线

//注意 16 位数据总线时,STM32 内部地址会右移一位对齐!

#define LCD_BASE ((u32)(0x6C000000 | 0x0000007E))

#define LCD ((LCD_TypeDef *) LCD_BASE)

其中 LCD_BASE,必须根据我们外部电路的连接来确定,我们使用 Bank1.sector4 就是从地址 0X6C000000 开始,而 0X0000007E,则是 A6 的偏移量,偏移量的概念:以 A6 为例,7E 转换成二进制就是:1111110,而 16 位数据时,地址右 移一位对齐,那么实际对应到地址引脚的时候,就是:A6:A0=0111111,此时 A6 是 0,但是如果 16 位地址再加 1(注意:对应到 8 位地址是加 2,即 7E+0X02),那么:A6:A0=1000000,此 时 A6 就是 1 了,即实现了对 RS 的 0 和 1 的控制。 我们将这个地址强制转换为 LCD_TypeDef 结构体地址,那么可以得到 LCD->LCD_REG 的地址就是 0X6C00,007E,对应 A6 的状态为 0(即 RS=0),而 LCD-> LCD_RAM 的地址就是 0X6C00,0080(结构体地址自增),对应 A6 的状态为 1(即 RS=1)。 所以,有了这个定义,当我们要往 LCD 写命令/数据的时候,可以这样写:

LCD->LCD_REG=CMD; //写命令

LCD->LCD_RAM=DATA; //写数据

而读的时候反过来操作就可以了,如下所示:

CMD= LCD->LCD_REG;//读 LCD 寄存器

DATA = LCD->LCD_RAM;//读 LCD 数据

这其中,CS、WR、RD 和 IO 口方向都是由 FSMC 控制,不需要我们手动设置了。

 lcd.h 里面的另一个重要结构体:

//LCD 重要参数集

typedef struct

{

         u16 width; //LCD 宽度

        u16 height; //LCD 高度

        u16 id; //LCD ID

        u8 dir; //横屏还是竖屏控制:0,竖屏;1,横屏。

        u16 wramcmd; //开始写 gram 指令

        u16 setxcmd; //设置 x 坐标指令

        u16 setycmd; //设置 y 坐标指令

}_lcd_dev;

//LCD 参数

extern _lcd_dev lcddev; //管理 LCD 重要参数

该结构体用于保存一些 LCD 重要参数信息,比如 LCD 的长宽、LCD ID(驱动 IC 型号)、 LCD 横竖屏状态等,可以让我们的驱动函数 支持不同尺寸的 LCD,同时可以实现 LCD 横竖屏切换等重要功能。

lcd.c 里面的一些重要函数:

//写寄存器函数
//regval:寄存器值
void LCD_WR_REG(vu16 regval)
{ 
    regval=regval; //使用-O2 优化的时候,必须插入的延时
    LCD->LCD_REG=regval;//写入要写的寄存器序号
}
//写 LCD 数据
//data:要写入的值
void LCD_WR_DATA(vu16 data)
{ 
    data=data; //使用-O2 优化的时候,必须插入的延时
    LCD->LCD_RAM=data;
}
//读 LCD 数据
//返回值:读到的值
u16 LCD_RD_DATA(void)
{ 
    vu16 ram; //防止被优化
    ram=LCD->LCD_RAM;
    return ram;
} 
//写寄存器
//LCD_Reg:寄存器地址
//LCD_RegValue:要写入的数据
void LCD_WriteReg(vu16 LCD_Reg, vu16 LCD_RegValue)
{ 
    LCD->LCD_REG = LCD_Reg; //写入要写的寄存器序号
    LCD->LCD_RAM = LCD_RegValue; //写入数据 
} 
//读寄存器
//LCD_Reg:寄存器地址
//返回值:读到的数据
u16 LCD_ReadReg(vu16 LCD_Reg)
{ 
    LCD_WR_REG(LCD_Reg); //写入要读的寄存器序号
    delay_us(5); 
    return LCD_RD_DATA(); //返回读到的值
} 
//开始写 GRAM
void LCD_WriteRAM_Prepare(void)
{ 
    LCD->LCD_REG=lcddev.wramcmd; 
}
//LCD 写 GRAM
//RGB_Code:颜色值
void LCD_WriteRAM(u16 RGB_Code)
{ 
    LCD->LCD_RAM = RGB_Code;//写十六位 GRAM
}

因为 FSMC 自动控制了 WR/RD/CS 等这些信号,所以这 7 个函数实现起来都非常简单,注意,上面有几个函数,添加了一些对 MDK –O2 优化的支持,去掉的话, 在-O2 优化的时候会出问题。

坐标设置函数:

//设置光标位置
//Xpos:横坐标
//Ypos:纵坐标
void LCD_SetCursor(u16 Xpos, u16 Ypos)
{
    if(lcddev.id==0X9341||lcddev.id==0X5310)
    {
        LCD_WR_REG(lcddev.setxcmd); 
        LCD_WR_DATA(Xpos>>8); 
        LCD_WR_DATA(Xpos&0XFF);
        LCD_WR_REG(lcddev.setycmd); 
        LCD_WR_DATA(Ypos>>8); 
        LCD_WR_DATA(Ypos&0XFF);
    }else 
    if(lcddev.id==0X6804)
    {
        if(lcddev.dir==1)Xpos=lcddev.width-1-Xpos;//横屏时处理
        LCD_WR_REG(lcddev.setxcmd); 
        LCD_WR_DATA(Xpos>>8); 
        LCD_WR_DATA(Xpos&0XFF);
        LCD_WR_REG(lcddev.setycmd); 
        LCD_WR_DATA(Ypos>>8); 
        LCD_WR_DATA(Ypos&0XFF);
    }else 
    if(lcddev.id==0X5510)
    {
        LCD_WR_REG(lcddev.setxcmd); 
        LCD_WR_DATA(Xpos>>8); 
        LCD_WR_REG(lcddev.setxcmd+1); 
        LCD_WR_DATA(Xpos&0XFF);
        LCD_WR_REG(lcddev.setycmd); 
        LCD_WR_DATA(Ypos>>8); 
        LCD_WR_REG(lcddev.setycmd+1); 
        LCD_WR_DATA(Ypos&0XFF);
    }else
    {
        if(lcddev.dir==1)Xpos=lcddev.width-1-Xpos;//横屏其实就是调转 x,y 坐标
        LCD_WriteReg(lcddev.setxcmd, Xpos);
        LCD_WriteReg(lcddev.setycmd, Ypos);
    }
}

该函数实现将 LCD 的当前操作点设置到指定坐标(x,y)。因为 9341/5310/6804/5510 等的设 置同其他屏有些不太一样,所以进行了区别对待。

画点函数:

//画点
//x,y:坐标
//POINT_COLOR:此点的颜色
void LCD_DrawPoint(u16 x,u16 y)
{
    LCD_SetCursor(x,y); //设置光标位置
    LCD_WriteRAM_Prepare(); //开始写入 GRAM
    LCD->LCD_RAM=POINT_COLOR; 
}

其中 POINT_COLOR 是我们 定义的一个全局变量,用于存放画笔颜色,BACK_COLOR, 该变量代表 LCD 的背景色。LCD_DrawPoint 函数虽然简单,但是至关重要,其他几乎所有上层函数,都是通过调用这个函数实现的。

读点函数:

//读取个某点的颜色值
//x,y:坐标
//返回值:此点的颜色
u16 LCD_ReadPoint(u16 x,u16 y)
{
    vu16 r=0,g=0,b=0;
    if(x>=lcddev.width||y>=lcddev.height)return 0; //超过了范围,直接返回 
    LCD_SetCursor(x,y); 
    if(lcddev.id==0X9341||lcddev.id==0X6804||lcddev.id==0X5310)LCD_WR_REG(0X2E);
    //9341/6804/3510 发送读 GRAM 指令
    else if(lcddev.id==0X5510)LCD_WR_REG(0X2E00);//5510 发送读 GRAM 指令
    else LCD_WR_REG(R34); //其他 IC 发送读 GRAM 指令
    if(lcddev.id==0X9320)opt_delay(2); //FOR 9320,延时 2us 
    LCD_RD_DATA(); //dummy Read 
    opt_delay(2); 
    r=LCD_RD_DATA(); //实际坐标颜色
    if(lcddev.id==0X9341||lcddev.id==0X5310||lcddev.id==0X5510)
    { //9341/NT35310/NT35510 要分 2 次读出
        opt_delay(2); 
        b=LCD_RD_DATA(); 
        g=r&0XFF;//9341/5310/5510 等,第一次读取的是 RG 的值,R 在前,G 在后,各占 8 位
        g<<=8;
    } 
    if(lcddev.id==0X9325||lcddev.id==0X4535||lcddev.id==0X4531||lcddev.id==0XB505||
    lcddev.id==0XC505)return r; //这几种 IC 直接返回颜色值
    else if(lcddev.id==0X9341||lcddev.id==0X5310||lcddev.id==0X5510)
            return (((r>>11)<<11)|((g>>10)<<5)|(b>>11)); //ILI9341/NT35310/NT35510 需要公式转换一下
    else return LCD_BGR2RGB(r); //其他 IC
}

字符显示函数:

//在指定位置显示一个字符
//x,y:起始坐标
//num:要显示的字符:" "--->"~"
//size:字体大小 12/16/24
//mode:叠加方式(1)还是非叠加方式(0)
void LCD_ShowChar(u16 x,u16 y,u8 num,u8 size,u8 mode)
{ 
    u8 temp,t1,t; u16 y0=y;
    u8 csize=(size/8+((size%8)?1:0))*(size/2);//得到字体一个字符对应点阵集所占的字节数
    //设置窗口 
    num=num-' ';//得到偏移后的值
    for(t=0;t<csize;t++)
    { 
        if(size==12)temp=asc2_1206[num][t]; //调用 1206 字体
        else if(size==16)temp=asc2_1608[num][t]; //调用 1608 字体
        else if(size==24)temp=asc2_2412[num][t]; //调用 2412 字体
        else return; //没有的字库
        for(t1=0;t1<8;t1++)
        { 
            if(temp&0x80)LCD_Fast_DrawPoint(x,y,POINT_COLOR);
            else if(mode==0)LCD_Fast_DrawPoint(x,y,BACK_COLOR);
            temp<<=1;
            y++;
            if(y>=lcddev.height)return; //超区域了
            if((y-y0)==size)
            {
                y=y0; x++;
                if(x>=lcddev.width)return; //超区域了
                break;
            }
        } 
    } 
} 

LCD初始化函数

LCD初始化函数伪代码:

//LCD初始化

void LCD_Init(void)

{

    初始化GPIO;

    初始化FSMC;                 

    读取LCD ID; 

    printf(“LCD ID:%x\r\n”,lcddev.id);//打印LCD ID,用到了串口1

                                                       //所以必须初始化串口1,否则黑屏 

    根据不同的ID执行LCD初始化代码;

    LCD_Display_Dir(0);    //默认为竖屏

    LCD_LED=1;    //点亮背光

    LCD_Clear(WHITE);  //清屏

}

以上是lcd驱动的相关函数,下面是main函数

int main(void)
{ 
    u8 x=0;
    u8 lcd_id[12]; //存放 LCD ID 字符串
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组 2
    delay_init(168); //初始化延时函数
    uart_init(115200); //初始化串口波特率为 115200
    LED_Init(); //初始化 LED
    LCD_Init(); //初始化 LCD FSMC 接口
    POINT_COLOR=RED; 
    sprintf((char*)lcd_id,"LCD ID:%04X",lcddev.id);//将 LCD ID 打印到 lcd_id 数组。
                                                   //该函数用法同 printf,只是 sprintf把打印内容输出到指定的内存区间上
     while(1) 
    {
        switch(x)
        {
            case 0:LCD_Clear(WHITE);break;
            case 1:LCD_Clear(BLACK);break;
            case 2:LCD_Clear(BLUE);break;
            case 3:LCD_Clear(RED);break;
            case 4:LCD_Clear(MAGENTA);break;
            case 5:LCD_Clear(GREEN);break;
            case 6:LCD_Clear(CYAN);break; 
            case 7:LCD_Clear(YELLOW);break;
            case 8:LCD_Clear(BRRED);break;
            case 9:LCD_Clear(GRAY);break;
            case 10:LCD_Clear(LGRAY);break;
            case 11:LCD_Clear(BROWN);break;
        }
        POINT_COLOR=RED; 
        LCD_ShowString(30,40,210,24,24,"Explorer STM32F4");
        LCD_ShowString(30,70,200,16,16,"TFTLCD TEST");
        LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
        LCD_ShowString(30,110,200,16,16,lcd_id); //显示 LCD ID 
        LCD_ShowString(30,130,200,12,12,"2014/5/4"); 
        x++;
        if(x==12)x=0;
        LED0=!LED0;delay_ms(1000);
    } 
}

运行结果:

 我们可以看到屏幕的背景是不停切换的,同时 DS0 不停的闪烁。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值