stm32开发 -- TFTLCD相关

发布于:2025-07-29 ⋅ 阅读:(16) ⋅ 点赞:(0)

一.TFTLCD简介

        ALIENTEK TFTLCD模块,该模块有如下特点:

        1,2.4’/2.8’/3.5’/4.3’/7’ 5 种大小的屏幕可选。

        2,320×240 的分辨率(3.5’分辨率为:320*480,4.3’和7’分辨率为:800*480)。

        3,16位真彩显示。

        4,自带触摸屏,可以用来作为控制输入。

       以2.8寸的ALIENTEK TFTLCD模块为例介绍,该模块支持65K色显示,显示分辨率为320×240,接口为16位的80并口,自带触摸屏。外观如下图所示:

        TFTLCD 模块采用2*17的2.54公排针与外部连接,接口定义如下图所示: 

        ALIENTEK提供2.8/3.5/4.3/7 寸等不同尺寸的TFTLCD模块,其驱动芯片有很多种类型, 比如有:ILI9341/ILI9325/RM68042/RM68021/ILI9320/ILI9328/LGDP4531/LGDP4535/SPFD5408 /SSD1289/1505/B505/C505/NT35310/NT35510等(具体的型号,大家可以通过下载本章实验代码, 通过串口或者LCD显示查看),以ILI9341 控制器为例进行介绍。

        ILI9341 液晶控制器自带显存,其显存总大小为172800(240*320*18/8),即18位模式(26 万色)下的显存量。在16位模式下,ILI9341采用RGB565格式存储颜色数据,此时ILI9341 的18位数据线与MCU的16位数据线以及LCD GRAM的对应关系如下图所示:

        从图中可以看出,ILI9341 在 16 位模式下面,数据线有用的是:D17~D13 和 D11~D1,D0和 D12 没有用到,实际上在我们 LCD 模块里面,ILI9341 的 D0 和 D12 压根就没有引出来,这样,ILI9341 的 D17~D13 和 D11~D1 对应 MCU 的 D15~D0。

        这样 MCU 的 16 位数据,最低 5 位代表蓝色,中间 6 位为绿色,最高 5 位为红色。数值越大,表示该颜色越深。另外,特别注意ILI9341所有的指令都是 8 位的(高 8 位无效),且参数除了读写 GRAM 的时候是 16 位,其他操作参数,都是 8 位的,这个和 ILI9320 等驱动器不一样,必须加以注意。

        TFTLCD显示需要的相关设置步骤如下:

        1)设置STM32F1与TFTLCD模块相连接的IO。 这一步,先将我们与TFTLCD模块相连的IO口进行初始化,以便驱动LCD。这里用到的是FSMC。         

        2)初始化TFTLCD模块。 即图18.1.1.5 的初始化序列,这里我们没有硬复位LCD,因为精英STM32F103的LCD接 口,将TFTLCD的RST同STM32F1的RESET连接在一起了,只要按下开发板的RESET键, 就会对LCD进行硬复位。初始化序列,就是向LCD控制器写入一系列的设置值(比如伽马校 准),这些初始化序列一般LCD供应商会提供给客户,我们直接使用这些序列即可,不需要深 入研究。在初始化之后,LCD才可以正常使用。

         3)通过函数将字符和数字显示到TFTLCD模块上。 这一步则通过图18.1.1.5左侧的流程,即:设置坐标→写GRAM指令→写GRAM来实现, 但是这个步骤,只是一个点的处理,我们要显示字符/数字,就必须要多次使用这个步骤,从而 达到显示字符/数字的目的,所以需要设计一个函数来实现数字/字符的显示,之后调用该函数, 就可以实现数字/字符的显示了。

二.FSMC 简介

        FSMC,即灵活的静态存储控制器,能够与同步或异步存储器和16位PC存储器卡连接, STM32 的FSMC接口支持包括SRAM、NAND FLASH、NOR FLASH和PSRAM等存储器。 FSMC的框图如下图所示:

        从上图我们可以看出,STM32的FSMC将外部设备分为3类:NOR/PSRAM设备、NAND 设备、PC卡设备。他们共用地址数据总线等信号,他们具有不同的CS以区分不同的设备,比 如本章我们用到的TFTLCD就是用的FSMC_NE4做片选,其实就是将TFTLCD当成SRAM来控制。

        为什么可以把TFTLCD当成SRAM设备用:首先我们了解下外部SRAM 的连接,外部SRAM的控制一般有:地址线(如A0~A18)、数据线(如D0~D15)、写信号(WE)、 读信号(OE)、片选信号(CS),如果SRAM支持字节控制,那么还有UB/LB信号。而 TFTLCD 的信号包括:RS、D0~D15、WR、RD、CS、RST和BL等,其中真 正在操作LCD的时候需要用到的就只有:RS、D0~D15、WR、RD和CS。其操作时序和SRAM 的控制完全类似,唯一不同就是TFTLCD有RS信号,但是没有地址信号。

        FSMC的地址映射:

        在使用FSMC时,配置模式后,使用指针就可以访问到外设存储器的内容,不用我们去控制产生时序,FSMC自动完成,很是方便。使用FSMC外接存储器时,其存储单元是映射到STM32的内部寻址空间的;在程序里,定义一个指向这些地址的指针,然后就可以通过指针直接修改该存储单元的内容,FSMC外设会自动完成数据访问过程,读写命令之类的操作不需要程序控制(配置好工作模式的前提下)。

        FSMC_NE[X]片选: 

        NE[X]片选就是选择存储块中的哪一个区。存储块1分为 4个区,每个区管理 64M 字节空间,每个区都有 独立的寄存器 对所连接的存储器进行配置。如下图的Bank1:

三.TFTLCD软件设计

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

        其中LCD_BASE,必须根据我们外部电路的连接来确定,我们使用 Bank1.sector4 就是从 地址 0X6C000000 开始,而 0X000007FE,则是 A10 的偏移量。我们将这个地址强制转换为LCD_TypeDef结构体地址,那么可以得到LCD->LCD_REG的地址就是0X6C00,07FE,对应 A10的状态为0(即RS=0),而LCD-> LCD_RAM的地址就是0X6C00,0800(结构体地址自增), 对应A10的状态为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横竖屏状态等,这个结构体虽然占用了10个字节的内存,但是却可以让我们的驱动函数支持不同尺寸的LCD,同时可以实现LCD横竖屏切换等重要功能,所以还是利大于弊的。有了以上了解,下面我们开始介绍lcd.c里面的一些重要函数。

        先看7个简单,但是很重要的函数:

//写寄存器函数 
//regval:寄存器值 
void LCD_WR_REG(u16 regval) 
{    
 LCD->LCD_REG=regval; //写入要写的寄存器序号   
} 
//写LCD数据 
//data:要写入的值 
void LCD_WR_DATA(u16 data) 
{   
 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(u16 LCD_Reg, u16 LCD_RegValue) 
{  
 LCD->LCD_REG = LCD_Reg;  //写入要写的寄存器序号   
 LCD->LCD_RAM = LCD_RegValue; //写入数据         
}     
//读寄存器 
//LCD_Reg:寄存器地址 
//返回值:读到的数据 
u16 LCD_ReadReg(u16 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个函数实现起来都非常简单。

        函数LCD_SetCursor实现了将LCD的当前操作点设置到指定坐标(x,y),有了该函数, 我们就可以在液晶上任意作图了。源代码如下所示:

//设置光标位置
//Xpos:横坐标
//Ypos:纵坐标
void LCD_SetCursor(u16 Xpos, u16 Ypos)
{
    if (lcddev.id == 0X1963)
    {
        if (lcddev.dir == 0)   //x坐标需要变换
        {
            Xpos = lcddev.width - 1 - Xpos;
            LCD_WR_REG(lcddev.setxcmd);
            LCD_WR_DATA(0);
            LCD_WR_DATA(0);
            LCD_WR_DATA(Xpos >> 8);
            LCD_WR_DATA(Xpos & 0XFF);
        }
        else
        {
            LCD_WR_REG(lcddev.setxcmd);
            LCD_WR_DATA(Xpos >> 8);
            LCD_WR_DATA(Xpos & 0XFF);
            LCD_WR_DATA((lcddev.width - 1) >> 8);
            LCD_WR_DATA((lcddev.width - 1) & 0XFF);
        }

        LCD_WR_REG(lcddev.setycmd);
        LCD_WR_DATA(Ypos >> 8);
        LCD_WR_DATA(Ypos & 0XFF);
        LCD_WR_DATA((lcddev.height - 1) >> 8);
        LCD_WR_DATA((lcddev.height - 1) & 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     //9341/5310/7789等设置坐标
    {
        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);
    }
}

        画点函数LCD_DrawPoint实现比较简单,就是先设置坐标,然后往坐标写颜色。LCD_DrawPoint函数虽然简单,但是至关重要,其他几乎所有上 层函数,都是通过调用这个函数实现的。

        读取TFTLCD模块数据的函数为 LCD_ReadPoint,该函数直接返回读到的GRAM值。该函数使用之前要先设置读取的GRAM 地址,通过LCD_SetCursor函数来实现。LCD_ReadPoint的代码如下:

//读取个某点的颜色值
//x,y:坐标
//返回值:此点的颜色
u16 LCD_ReadPoint(u16 x, u16 y)
{
    u16 r, g, b;

    if (x >= lcddev.width || y >= lcddev.height)return 0;   //超过了范围,直接返回

    LCD_SetCursor(x, y);

    if (lcddev.id == 0X5510)    //5510 发送读GRAM指令
    {
        LCD_WR_REG(0X2E00);
    }
    else                        //其他IC(9341/5310/1963/7789)发送读GRAM指令
    {
        LCD_WR_REG(0X2E);
    }

    r = LCD_RD_DATA();          //假读

    if (lcddev.id == 0X1963)    //对1963来说,是真读
    {
        return r;               //1963直接读就可以
    }

    r = LCD_RD_DATA();          //实际坐标颜色

    //9341/5310/5510/7789 要分2次读出
    b = LCD_RD_DATA();
    g = r & 0XFF;               //对于 9341/5310/5510/7789, 第一次读取的是RG的值,R在前,G在后,各占8位
    g <<= 8;
    return (((r >> 11) << 11) | ((g >> 10) << 5) | (b >> 11));  // 9341/5310/5510/7789 需要公式转换一下
}

        字符显示函数LCD_ShowChar,该函数同OLED模块的字符显示函数差不多,但是这里的字符显示函数多了1个功能,就是可以以叠加方式显示,或者以非叠加方式显示。叠加方式显示多用于在显示的图片上再显示字符。非叠加方式一般用于普通的显示。该函数实现代码如下:

//在指定位置显示一个字符
//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 - ' ';    //得到偏移后的值(ASCII字库是从空格开始取模,所以-' '就是对应字符的字库)

    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_ShowChar函数里面,我们采用快速画点函数LCD_Fast_DrawPoint来画点显示字 符,该函数同LCD_DrawPoint一样,只是带了颜色参数,且减少了函数调用的时间。

        TFTLCD模块的初始化函数LCD_Init,该函数先初始化STM32与 TFTLCD连接的IO口,并配置FSMC控制器,然后读取LCD控制器的型号,根据控制IC的 型号执行不同的初始化代码,其简化代码如下:

//初始化lcd
//该初始化函数可以初始化各种ILI93XX液晶,但是其他函数是基于ILI9320的!!!
//在其他型号的驱动芯片上没有测试!
void LCD_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    FSMC_NORSRAMInitTypeDef  FSMC_NORSRAMInitStructure;
    FSMC_NORSRAMTimingInitTypeDef  readWriteTiming; 
    FSMC_NORSRAMTimingInitTypeDef  writeTiming;

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC,ENABLE);   //使能FSMC时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOE|RCC_APB2Periph_GPIOG,ENABLE); //使能PORTB,D,E,G以及AFIO复用功能时钟

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;           //PB0 推挽输出 背光
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;    //推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    //PORTD复用推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_14|GPIO_Pin_15;    //PORTD复用推挽输出
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;     //复用推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOD, &GPIO_InitStructure); 
     
    //PORTE复用推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15; //PORTE复用推挽输出
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;     //复用推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOE, &GPIO_InitStructure);

    //PORTG12复用推挽输出 P0RTG0-->RS
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_12;   //PORTD复用推挽输出
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;         //复用推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOG, &GPIO_InitStructure); 

    readWriteTiming.FSMC_AddressSetupTime = 0x01;   //地址建立时间(ADDSET)为2个HCLK 1/36M=27ns
    readWriteTiming.FSMC_AddressHoldTime = 0x00;    //地址保持时间(ADDHLD)模式A未用到
    readWriteTiming.FSMC_DataSetupTime = 0x0f;      //数据保存时间为16个HCLK,因为液晶驱动IC的读数据的时候,速度不能太快,尤其对1289这个IC。
    readWriteTiming.FSMC_BusTurnAroundDuration = 0x00;
    readWriteTiming.FSMC_CLKDivision = 0x00;
    readWriteTiming.FSMC_DataLatency = 0x00;
    readWriteTiming.FSMC_AccessMode = FSMC_AccessMode_A;    //模式A 


    writeTiming.FSMC_AddressSetupTime = 0x00;   //地址建立时间(ADDSET)为1个HCLK
    writeTiming.FSMC_AddressHoldTime = 0x00;    //地址保持时间(ADDHLD)模式A未用到
    writeTiming.FSMC_DataSetupTime = 0x03;      //数据保存时间为4个HCLK
    writeTiming.FSMC_BusTurnAroundDuration = 0x00;
    writeTiming.FSMC_CLKDivision = 0x00;
    writeTiming.FSMC_DataLatency = 0x00;
    writeTiming.FSMC_AccessMode = FSMC_AccessMode_A;    //模式A 


    FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM4;                      //这里我们使用NE4,也就对应BTCR[6],[7]。
    FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable;    //不复用数据地址
    FSMC_NORSRAMInitStructure.FSMC_MemoryType =FSMC_MemoryType_SRAM;                //FSMC_MemoryType_SRAM;  SRAM
    FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;      //存储器数据宽度为16bit
    FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode =FSMC_BurstAccessMode_Disable;   //FSMC_BurstAccessMode_Disable;
    FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
    FSMC_NORSRAMInitStructure.FSMC_AsynchronousWait=FSMC_AsynchronousWait_Disable; 
    FSMC_NORSRAMInitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable;   
    FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;  
    FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable;     //存储器写使能
    FSMC_NORSRAMInitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable;   
    FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Enable;         //读写使用不同的时序
    FSMC_NORSRAMInitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable; 
    FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &readWriteTiming;        //读写时序
    FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &writeTiming;                //写时序

    FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure);  //初始化FSMC配置

    FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM4, ENABLE);  //使能BANK1 
        
    delay_ms(50);       // delay 50 ms
    
    //尝试9341 ID的读取
    LCD_WR_REG(0XD3);
    lcddev.id = LCD_RD_DATA();          //dummy read
    lcddev.id = LCD_RD_DATA();          //读到0X00
    lcddev.id = LCD_RD_DATA();          //读取0X93
    lcddev.id <<= 8;
    lcddev.id |= LCD_RD_DATA();         //读取0X41
    
    if (lcddev.id != 0X9341)            //不是 9341 , 尝试看看是不是 ST7789
    {
        LCD_WR_REG(0X04);
        lcddev.id = LCD_RD_DATA();      //dummy read
        lcddev.id = LCD_RD_DATA();      //读到0X85
        lcddev.id = LCD_RD_DATA();      //读取0X85
        lcddev.id <<= 8;
        lcddev.id |= LCD_RD_DATA();     //读取0X52
        
        if (lcddev.id == 0X8552)        //将8552的ID转换成7789
        {
            lcddev.id = 0x7789;
        }
        
        if (lcddev.id != 0x7789)        //也不是ST7789, 尝试是不是 NT35310
        {
            LCD_WR_REG(0XD4);
            lcddev.id = LCD_RD_DATA();  //dummy read
            lcddev.id = LCD_RD_DATA();  //读回0X01
            lcddev.id = LCD_RD_DATA();  //读回0X53
            lcddev.id <<= 8;
            lcddev.id |= LCD_RD_DATA(); //这里读回0X10

            if (lcddev.id != 0X5310)    //也不是NT35310,尝试看看是不是NT35510
            {
                //发送秘钥(厂家提供,照搬即可)
                LCD_WriteReg(0xF000, 0x0055);
                LCD_WriteReg(0xF001, 0x00AA);
                LCD_WriteReg(0xF002, 0x0052);
                LCD_WriteReg(0xF003, 0x0008);
                LCD_WriteReg(0xF004, 0x0001);

                LCD_WR_REG(0xC500);             //读取ID高8位
                lcddev.id = LCD_RD_DATA();      //读回0X55
                lcddev.id <<= 8;

                LCD_WR_REG(0xC501);             //读取ID低8位
                lcddev.id |= LCD_RD_DATA();     //读回0X10
                delay_ms(5);
                
                if (lcddev.id != 0X5510)        //也不是NT5510,尝试看看是不是SSD1963
                {
                    LCD_WR_REG(0XA1);
                    lcddev.id = LCD_RD_DATA();
                    lcddev.id = LCD_RD_DATA();  //读回0X57
                    lcddev.id <<= 8;
                    lcddev.id |= LCD_RD_DATA(); //读回0X61

                    if (lcddev.id == 0X5761)lcddev.id = 0X1963; //SSD1963读回的ID是5761H,为方便区分,我们强制设置为1963
                }
            }
        }
    }

    printf(" LCD ID:%x\r\n", lcddev.id); //打印LCD ID

    if (lcddev.id == 0X9341)    //9341初始化
    {
        //.......9341初始化代码
    }
    else if (lcddev.id == 0x7789)   //7789初始化
    {
        //.......7789初始化代码
    }
    else if (lcddev.id == 0x5310)
    {
        //.......5310初始化代码
    }
    else if (lcddev.id == 0x5510)
    {
        //.......5510初始化代码
    }
    else if (lcddev.id == 0X1963)
    {
        //.......1963初始化代码
    }
    
    LCD_Display_Dir(0);         //默认为竖屏
    LCD_LED = 1;                //点亮背光
    LCD_Clear(WHITE);
} 

        从初始化代码可以看出,LCD初始化步骤为①~⑤在代码中标注:

        ①GPIO,FSMC,AFIO时钟使能。

        ②GPIO初始化:GPIO_Init()函数

        ③FSMC初始化:FSMC_NORSRAMInit()函数。

        ④ FSMC使能:FSMC_NORSRAMCmd()函数。

        ⑤ 不同的LCD驱动器的初始化代码。

 四.触摸屏简介

1.电阻式触摸屏

         ALIENTEK 2.4/2.8/3.5 寸 TFTLCD 模块自带的触摸屏都属于电阻式触摸屏,下面简单介绍 下电阻式触摸屏的原理。

        当手指触摸屏幕时,两层导电层在触摸点位置就有了接触,电阻发生变化,在X和Y两个方向上产生 信号,然后送触摸屏控制器。控制器侦测到这一接触并计算出(X,Y)的位置,再根据获得的 位置模拟鼠标的方式运作。这就是电阻技术触摸屏的最基本的原理。

        电阻触摸屏的优点:精度高、价格便宜、抗干扰能力强、稳定性好。

        电阻触摸屏的缺点:容易被划伤、透光性不太好、不支持多点触摸。

        ALIENTEK TFTLCD 模块自带的触摸屏控制芯片为XPT2046。XPT2046是一款4导线制触 摸屏控制器,内含12位分辨率125KHz转换速率逐步逼近型A/D转换器。XPT2046支持从1.5V 到5.25V 的低电压I/O 接口。XPT2046 能通过执行两次A/D转换查出被按的屏幕位置, 除此 之外,还可以测量加在触摸屏上的压力。内部自带2.5V参考电压可以作为辅助输入、温度测量 和电池监测模式之用,电池监测的电压范围可以从0V到6V。XPT2046片内集成有一个温度传 感器。 在2.7V的典型工作状态下,关闭参考电压,功耗可小于0.75mW。XPT2046采用微小 的封装形式:TSSOP-16,QFN-16(0.75mm厚度)和VFBGA-48。工作温度范围为-40℃~+85℃。

2.电容式触摸屏

        ALIENTEK 4.3/7 寸 TFTLCD模块自带的触摸屏采用的是电容式触摸屏,电容式触摸屏主要分为两种:

        表面电容式电容触摸屏:

        表面电容式触摸屏技术是利用 ITO(铟锡氧化物,是一种透明的导电材料)导电膜,通过电 场感应方式感测屏幕表面的触摸行为进行。但是表面电容式触摸屏有一些局限性,它只能识别一个手指或者一次触摸。

        投射式电容触摸屏:

        投射电容式触摸屏是传感器利用触摸屏电极发射出静电场线。一般用于投射电容传感技术的电容类型有两种:自我电容和交互电容。 

        自我电容又称绝对电容,是最广为采用的一种方法,自我电容通常是指扫描电极与地构成的电容。在玻璃表面有用ITO制成的横向与纵向的扫描电极,这些电极和地之间就构成一个电容的两极。当用手或触摸笔触摸的时候就会并联一个电容到电路中去,从而使在该条扫描线上的总体的电容量有所改变。在扫描的时候,控制IC依次扫描纵向和横向电极,并根据扫描前后的电容变化来确定触摸点坐标位置。

五.触摸屏软件设计

        TFTLCD模块的触摸屏(电阻触摸屏)总共有5跟线与STM32F1连接,连接电路图如下图所示: 

         TP_Read_XY2函数:

//连续2次读取触摸屏IC,且这两次的偏差不能超过 
//ERR_RANGE,满足条件,则认为读数正确,否则读数错误.     
//该函数能大大提高准确度 
//x,y:读取到的坐标值 
//返回值:0,失败;1,成功。 
#define ERR_RANGE 50 //误差范围  
u8 TP_Read_XY2(u16 *x,u16 *y)  
{ 
    u16 x1,y1; 
    u16 x2,y2; 
    u8 flag;     
    flag=TP_Read_XY(&x1,&y1);    
    if(flag==0)return(0); 
    flag=TP_Read_XY(&x2,&y2);     
    if(flag==0)return(0);    
//前后两次采样在+- ERR_RANGE 内 
    if(((x2<=x1&&x1<x2+ERR_RANGE)||(x1<=x2&&x2<x1+ERR_RANGE)) 
    &&((y2<=y1&&y1<y2+ERR_RANGE)||(y1<=y2&&y2<y1+ERR_RANGE))) 
    { 
        *x=(x1+x2)/2; 
        *y=(y1+y2)/2; 
        return 1; 
    }else return 0;    
} 

        该函数采用了一个非常好的办法来读取屏幕坐标值,就是连续读两次,两次读取的值之差不能超过一个特定的值(ERR_RANGE),通过这种方式,我们可以大大提高触摸屏的准确度。另 外该函数调用的TP_Read_XY函数,用于单次读取坐标值。TP_Read_XY也采用了一些软件滤波 算法。

        TP_Adjust函数:

//触摸屏校准代码
//得到四个校准参数
void TP_Adjust(void)
{								 
	u16 pos_temp[4][2];//坐标缓存值
	u8  cnt=0;	
	u16 d1,d2;
	u32 tem1,tem2;
	double fac; 	
	u16 outtime=0;
 	cnt=0;				
	POINT_COLOR=BLUE;
	BACK_COLOR =WHITE;
	LCD_Clear(WHITE);//清屏   
	POINT_COLOR=RED;//红色 
	LCD_Clear(WHITE);//清屏 	   
	POINT_COLOR=BLACK;
	LCD_ShowString(40,40,160,100,16,(u8*)TP_REMIND_MSG_TBL);//显示提示信息
	TP_Drow_Touch_Point(20,20,RED);//画点1 
	tp_dev.sta=0;//消除触发信号 
	tp_dev.xfac=0;//xfac用来标记是否校准过,所以校准之前必须清掉!以免错误	 
	while(1)//如果连续10秒钟没有按下,则自动退出
	{
		tp_dev.scan(1);//扫描物理坐标
		if((tp_dev.sta&0xc0)==TP_CATH_PRES)//按键按下了一次(此时按键松开了.)
		{	
			outtime=0;		
			tp_dev.sta&=~(1<<6);//标记按键已经被处理过了.
						   			   
			pos_temp[cnt][0]=tp_dev.x[0];
			pos_temp[cnt][1]=tp_dev.y[0];
			cnt++;	  
			switch(cnt)
			{			   
				case 1:						 
					TP_Drow_Touch_Point(20,20,WHITE);				//清除点1 
					TP_Drow_Touch_Point(lcddev.width-20,20,RED);	//画点2
					break;
				case 2:
 					TP_Drow_Touch_Point(lcddev.width-20,20,WHITE);	//清除点2
					TP_Drow_Touch_Point(20,lcddev.height-20,RED);	//画点3
					break;
				case 3:
 					TP_Drow_Touch_Point(20,lcddev.height-20,WHITE);			//清除点3
 					TP_Drow_Touch_Point(lcddev.width-20,lcddev.height-20,RED);	//画点4
					break;
				case 4:	 //全部四个点已经得到
	    		    //对边相等
					tem1=abs(pos_temp[0][0]-pos_temp[1][0]);//x1-x2
					tem2=abs(pos_temp[0][1]-pos_temp[1][1]);//y1-y2
					tem1*=tem1;
					tem2*=tem2;
					d1=sqrt(tem1+tem2);//得到1,2的距离
					
					tem1=abs(pos_temp[2][0]-pos_temp[3][0]);//x3-x4
					tem2=abs(pos_temp[2][1]-pos_temp[3][1]);//y3-y4
					tem1*=tem1;
					tem2*=tem2;
					d2=sqrt(tem1+tem2);//得到3,4的距离
					fac=(float)d1/d2;
					if(fac<0.95||fac>1.05||d1==0||d2==0)//不合格
					{
						cnt=0;
 				    	TP_Drow_Touch_Point(lcddev.width-20,lcddev.height-20,WHITE);	//清除点4
   	 					TP_Drow_Touch_Point(20,20,RED);								//画点1
 						TP_Adj_Info_Show(pos_temp[0][0],pos_temp[0][1],pos_temp[1][0],pos_temp[1][1],pos_temp[2][0],pos_temp[2][1],pos_temp[3][0],pos_temp[3][1],fac*100);//显示数据   
 						continue;
					}
					tem1=abs(pos_temp[0][0]-pos_temp[2][0]);//x1-x3
					tem2=abs(pos_temp[0][1]-pos_temp[2][1]);//y1-y3
					tem1*=tem1;
					tem2*=tem2;
					d1=sqrt(tem1+tem2);//得到1,3的距离
					
					tem1=abs(pos_temp[1][0]-pos_temp[3][0]);//x2-x4
					tem2=abs(pos_temp[1][1]-pos_temp[3][1]);//y2-y4
					tem1*=tem1;
					tem2*=tem2;
					d2=sqrt(tem1+tem2);//得到2,4的距离
					fac=(float)d1/d2;
					if(fac<0.95||fac>1.05)//不合格
					{
						cnt=0;
 				    	TP_Drow_Touch_Point(lcddev.width-20,lcddev.height-20,WHITE);	//清除点4
   	 					TP_Drow_Touch_Point(20,20,RED);								//画点1
 						TP_Adj_Info_Show(pos_temp[0][0],pos_temp[0][1],pos_temp[1][0],pos_temp[1][1],pos_temp[2][0],pos_temp[2][1],pos_temp[3][0],pos_temp[3][1],fac*100);//显示数据   
						continue;
					}//正确了
								   
					//对角线相等
					tem1=abs(pos_temp[1][0]-pos_temp[2][0]);//x1-x3
					tem2=abs(pos_temp[1][1]-pos_temp[2][1]);//y1-y3
					tem1*=tem1;
					tem2*=tem2;
					d1=sqrt(tem1+tem2);//得到1,4的距离
	
					tem1=abs(pos_temp[0][0]-pos_temp[3][0]);//x2-x4
					tem2=abs(pos_temp[0][1]-pos_temp[3][1]);//y2-y4
					tem1*=tem1;
					tem2*=tem2;
					d2=sqrt(tem1+tem2);//得到2,3的距离
					fac=(float)d1/d2;
					if(fac<0.95||fac>1.05)//不合格
					{
						cnt=0;
 				    	TP_Drow_Touch_Point(lcddev.width-20,lcddev.height-20,WHITE);	//清除点4
   	 					TP_Drow_Touch_Point(20,20,RED);								//画点1
 						TP_Adj_Info_Show(pos_temp[0][0],pos_temp[0][1],pos_temp[1][0],pos_temp[1][1],pos_temp[2][0],pos_temp[2][1],pos_temp[3][0],pos_temp[3][1],fac*100);//显示数据   
						continue;
					}//正确了
					//计算结果
					tp_dev.xfac=(float)(lcddev.width-40)/(pos_temp[1][0]-pos_temp[0][0]);//得到xfac		 
					tp_dev.xoff=(lcddev.width-tp_dev.xfac*(pos_temp[1][0]+pos_temp[0][0]))/2;//得到xoff
						  
					tp_dev.yfac=(float)(lcddev.height-40)/(pos_temp[2][1]-pos_temp[0][1]);//得到yfac
					tp_dev.yoff=(lcddev.height-tp_dev.yfac*(pos_temp[2][1]+pos_temp[0][1]))/2;//得到yoff  
					if(abs(tp_dev.xfac)>2||abs(tp_dev.yfac)>2)//触屏和预设的相反了.
					{
						cnt=0;
 				    	TP_Drow_Touch_Point(lcddev.width-20,lcddev.height-20,WHITE);	//清除点4
   	 					TP_Drow_Touch_Point(20,20,RED);								//画点1
						LCD_ShowString(40,26,lcddev.width,lcddev.height,16,"TP Need readjust!");
						tp_dev.touchtype=!tp_dev.touchtype;//修改触屏类型.
						if(tp_dev.touchtype)//X,Y方向与屏幕相反
						{
							CMD_RDX=0X90;
							CMD_RDY=0XD0;	 
						}else				   //X,Y方向与屏幕相同
						{
							CMD_RDX=0XD0;
							CMD_RDY=0X90;	 
						}			    
						continue;
					}		
					POINT_COLOR=BLUE;
					LCD_Clear(WHITE);//清屏
					LCD_ShowString(35,110,lcddev.width,lcddev.height,16,"Touch Screen Adjust OK!");//校正完成
					delay_ms(1000);
					TP_Save_Adjdata();  
 					LCD_Clear(WHITE);//清屏   
					return;//校正完成				 
			}
		}
		delay_ms(10);
		outtime++;
		if(outtime>1000)
		{
			TP_Get_Adjdata();
			break;
	 	} 
 	}
}	 

        校正思路:可以得出下面的一个从物理坐标到像素坐标的转 换关系式:                 LCDx=xfac*Px+xoff; LCDy=yfac*Py+yoff;

        其中(LCDx,LCDy)是在LCD上的像素坐标,(Px,Py)是从触摸屏读到的物理坐标。xfac, yfac分别是X轴方向和Y轴方向的比例因子,而xoff和yoff则是这两个方向的偏移量。 这样我们只要事先在屏幕上面显示4个点(这四个点的坐标是已知的),分别按这四个点就 可以从触摸屏读到4个物理坐标,这样就可以通过待定系数法求出xfac、yfac、xoff、yoff这四 个参数。我们保存好这四个参数,在以后的使用中,我们把所有得到的物理坐标都按照这个关 系式来计算,得到的就是准确的屏幕坐标。达到了触摸屏校准的目的。

        TP_Init函数:

//触摸屏初始化  		    
//返回值:0,没有进行校准
//       1,进行过校准
u8 TP_Init(void)
{	
	if(lcddev.id==0X5510)				//4.3寸电容触摸屏
	{
		if(GT9147_Init()==0)			//是GT9147
		{ 
			tp_dev.scan=GT9147_Scan;	//扫描函数指向GT9147触摸屏扫描
		}else
		{
			OTT2001A_Init();
			tp_dev.scan=OTT2001A_Scan;	//扫描函数指向OTT2001A触摸屏扫描
		}
		tp_dev.touchtype|=0X80;			//电容屏 
		tp_dev.touchtype|=lcddev.dir&0X01;//横屏还是竖屏 
		return 0;
	}else if(lcddev.id==0X1963)			//7寸电容触摸屏
	{
		FT5206_Init();
		tp_dev.scan=FT5206_Scan;		//扫描函数指向GT9147触摸屏扫描		
		tp_dev.touchtype|=0X80;			//电容屏 
		tp_dev.touchtype|=lcddev.dir&0X01;//横屏还是竖屏 
		return 0;
	}else
	{
	  GPIO_InitTypeDef  GPIO_InitStructure;

		//注意,时钟使能之后,对GPIO的操作才有效
		//所以上拉之前,必须使能时钟.才能实现真正的上拉输出
   	 	
	 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOF, ENABLE);	 //使能PB,PF端口时钟
		
		GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;				 // PB1端口配置
	 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
	 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	 	GPIO_Init(GPIOB, &GPIO_InitStructure);//B1推挽输出
	 	GPIO_SetBits(GPIOB,GPIO_Pin_1);//上拉
		
		GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;				 // PB2端口配置
	 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; 		 //上拉输入
	 	GPIO_Init(GPIOB, &GPIO_InitStructure);//B2上拉输入
	 	GPIO_SetBits(GPIOB,GPIO_Pin_2);//上拉		
		
		GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11|GPIO_Pin_9;				 // F9,PF11端口配置
	 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
	 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	 	GPIO_Init(GPIOF, &GPIO_InitStructure);//PF9,PF11推挽输出
	 	GPIO_SetBits(GPIOF, GPIO_Pin_11|GPIO_Pin_9);//上拉
		
		GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;				 // PF10端口配置
	 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; 		 //上拉输入
	 	GPIO_Init(GPIOF, &GPIO_InitStructure);//PF10上拉输入
	 	GPIO_SetBits(GPIOF,GPIO_Pin_10);//上拉		
 
		TP_Read_XY(&tp_dev.x[0],&tp_dev.y[0]);//第一次读取初始化	 
		AT24CXX_Init();			//初始化24CXX
		if(TP_Get_Adjdata())return 0;//已经校准
		else			  		//未校准?
		{ 										    
			LCD_Clear(WHITE);	//清屏
			TP_Adjust();  		//屏幕校准  
		}			
		TP_Get_Adjdata();	
	}
	return 1; 									 
}

        函数根据LCD的ID(即lcddev.id)判别是电 阻屏还是电容屏,执行不同的初始化。tp_dev.scan,这个结构体函数指针,默认是指向TP_Scan 的,如果是电阻屏则用默认的即可,如果是电容屏,则指向新的扫描函数GT9147_Scan、 OTT2001A_Scan或FT5206_Scan(根据芯片ID判断到底指向那个),执行电容触摸屏的扫描函数。

        结构体_m_tp_dev:

        结构体_m_tp_dev,用于管理和记录触摸屏相关信息,在外部调用的时候,我们一般直接调用tp_dev 的相关成员函数/变量屏即可达到需要的效果。这样种设计简化了接口,另外管理和维护也比较 方便,定义如下:

//触摸屏控制器
typedef struct 
{
	u8 (*init)(void);			//初始化触摸屏控制器
	u8 (*scan)(u8);				//扫描触摸屏.0,屏幕扫描;1,物理坐标;	 
	void (*adjust)(void);		//触摸屏校准 
	u16 x[CT_MAX_TOUCH]; 		//当前坐标
	u16 y[CT_MAX_TOUCH];		//电容屏有最多5组坐标,电阻屏则用x[0],y[0]代表:此次扫描时,触屏的坐标,用
								//x[4],y[4]存储第一次按下时的坐标. 
	u8  sta;					//笔的状态 
								//b7:按下1/松开0; 
	                            //b6:0,没有按键按下;1,有按键按下. 
								//b5:保留
								//b4~b0:电容触摸屏按下的点数(0,表示未按下,1表示按下)
/////////////////////触摸屏校准参数(电容屏不需要校准)//////////////////////								
	float xfac;					
	float yfac;
	short xoff;
	short yoff;	   
//新增的参数,当触摸屏的左右上下完全颠倒时需要用到.
//b0:0,竖屏(适合左右为X坐标,上下为Y坐标的TP)
//   1,横屏(适合左右为Y坐标,上下为X坐标的TP) 
//b1~6:保留.
//b7:0,电阻屏
//   1,电容屏 
	u8 touchtype;
}_m_tp_dev;

网站公告

今日签到

点亮在社区的每一天
去签到