TFTLCD屏幕实验

更多交流欢迎关注作者抖音号:81849645041

目的

        了解TFTLCD屏幕和FSMC的驱动原理,通过STM32F4的FSMC接口来控制TFTLCD的显示。

原理

1.液晶显示器

        显示器属于计算机的I/O设备,即输入输出设备。它是一种将特定电子信息输出到屏幕上再反射到人眼的显示设备。

        液晶显示器,简称LCD(Liquid Crystal Display),相对于上一代CRT显示器(阴极射线管显示器),LCD显示器具有功耗低、体积小、承载的信息量大及不伤眼的优点,因而它成为了现在的主流电子显示设备,其中包括电视、电脑显示器、手机屏幕及各种嵌入式设备的显示器。

        液晶是一种介于固体和液体之间的特殊物质,它是一种有机化合物,常态下呈液态,但是它的分子排列却和固体晶体一样非常规则,因此取名液晶。如果给液晶施加电场,会改变它的分子排列,从而改变光线的传播方向,配合偏振光片,它就具有控制光线透过率的作用,再配合彩色滤光片,改变加给液晶电压大小,就能改变某一颜色透光量的多少。利用这种原理,做出可控红、绿、蓝光输出强度的显示结构,把三种显示结构组成一个显示单位,通过控制红绿蓝的强度,可以使该单位混合输出不同的色彩,这样的一个显示单位被称为像素。

        注意液晶本身是不发光的,所以需要有一个背光灯提供电源,光线经过一系列处理过程才到输出,所以输出的光线强度是要比光源的强度低很多的,比较浪费能源。而且这些处理过程会导致显示方向比较窄,也就是它的视觉较小,从侧面看屏幕会看不清它的显示内容。另外,输出的色彩变换时,液晶分子转动也需要消耗一定的时间,导致屏幕的响应速度低。

        TFT是LCD的一个变种,是薄膜晶体管的缩写。TFT是指液晶显示器上的每一液晶像素点都是由集成在其后的薄膜晶体管来驱动,从而可以做到高速度、高亮度、高对比度显示屏幕信息。TFT式显示屏是一类有源矩阵液晶显示设备,是最好的LCD彩色显示器之一。

2.显示器的基本参数

        不管是哪一种显示器,都有一定的参数用来描述它们的特性,各个参数介绍如下:

  • 像素

        像素是组成图像的最基本单元要素,显示器的像素指它成像最小的点,即前面讲解液晶原理中提到的一个显示单元。

  • 分辨率

        一些嵌入式设备的显示器常常以“行像素值x列像素值”表示屏幕的分辨率。如分辨率800x480表示该显示器的每一行有800个像素点,每一列有480个像素点。

  • 色彩深度

        色彩深度指显示器的每个像素点能表示多少种颜色,一般用“位”(bit)来表示。如单色屏的每个像素点能表示亮或灭两种状态(即实际上能显示2种颜色),用1个数据位就可以表示像素点的所有状态,所以它的色彩深度为1bit,其它常见的显示屏色深为16bit、24bit。

  • 显示器尺寸

        显示器的大小一般以英寸表示,如5英寸、21英寸、24英寸等,这个长度是指屏幕对角线的长度,通过显示器的对角线长度及长宽比可确定显示器的实际长宽尺寸。

  • 点距

        点距指两个相邻像素点之间的距离,它会影响画质的细腻度及观看距离,相同尺寸的屏幕,若分辨率越高,则点距越小,画质越细腻。如现在有些手机的屏幕分辨率比电脑显示器的还大,这是手机屏幕点距小的原因;LED点阵显示屏的点距一般都比较大,所以适合远距离观看。

3.液晶控制原理

        完整的显示屏由液晶显示面板、电容触摸面板以及PCB底板构成。触摸面板带有触摸控制芯片,该芯片处理触摸信号并通过引出的信号线与外部器件通讯,触摸面板中间是透明的,它贴在液晶面板上面,一起构成屏幕的主体,触摸面板与液晶面板引出的排线连接到PCB底板上,根据实际需要,PCB底板上可能会带有“液晶控制器芯片”,而不带液晶控制器的PCB底板,只有小部分的电源管理电路,液晶面板的信号线与外部微控制器相连,直接控制。

4.显存

        液晶屏中的每个像素点都是数据,在实际应用中需要把每个像素点的数据缓存起来,再传输给液晶屏,一般会使用SRAM或SDRAM性质的存储器,而这些专门用于存储显示数据的存储器,则被称为显存。显存一般至少要能存储液晶屏的一帧显示数据,如分辨率为800x480的液晶屏,使用RGB888格式显示,它的一帧显示数据大小为:3x800x480=1152000字节;若使用RGB565格式显示,一帧显示数据大小为:2x800x480=768000字节。

        一般来说,外置的液晶控制器会自带显存,而像STM32F429等集成液晶控制器的芯片可使用内部SRAM或外扩SDRAM用于显存空间。

5.TFTLCD模块简介

        本实验选用4.3寸的TFTLCD模块,显示分辨率为480*800,驱动IC为NT35510,电容触摸屏,该模块原理如下所示:

        从图中可以看出,TFTLCD模块采用16位的并口方式与外部连接,该模块有如下信号线:

  1. LCD_CS:LCD片选信号。
  2. WR:LCD写信号。
  3. RD:LCD读信号。
  4. DB[17:1]:16位双向信号线。
  5. RST:硬复位LCD信号。
  6. RS:命令/数据标志(0:命令,1:数据)。
  7. BL_CTR:背光控制信号。
  8. MOSI/MISO/T_PEN/T_CS/T_CLK:触摸屏接口信号。

        需要说明的是,TFTLCD模块的RST信号线是直接接到STM32F4的复位脚上,并不由软件控制,这样可以省下来一个IO口。

6.FSMC简介

        STM32F407系列芯片使用FSMC外设来管理扩展的存储器,FSMC是Flexible Static Memory Controller的缩写,译为灵活的静态存储控制器。它可以用于驱动包括SRAM、NOR FLASH以及NAND FLSAH类型的存储器。

        FSMC可以驱动LCD的主要原因是因为FSMC的读写时序和LCD的读写时序相似,于是把LCD当成一个外部存储器来用,利用FSMC在相应的地址读或写相关数值时,STM32的FSMC会在硬件上自动完成时序的控制,所以我们只要设置好读写相关时序寄存器后,FSMC就可以帮我们完成时序上的控制了。

        STM32的FSMC外设内部结构如下图所示:

  • 通讯引脚

在框图的右侧是FSMC外设相关的控制引脚,由于控制不同类型存储器的时候会有一些不同的引脚,看起来有非常多,其中地址线FSMC_A和数据线FSMC_D是所有控制器都共用的。本实验中使用的引脚说明如下所示:

FSMC引脚名称

说明

FSMC_NWE

写入使能

FSMC_NOE

输出使能(读使能)

FSMC_NE

片选信号

FSMC_A[15:0]

行地址线

        其中比较特殊的FSMC_NE是用于控制SRAM芯片的片选控制信号线,STM32具有FSMC_NE1/2/3/4号引脚,不同的引脚对应STM32内部不同的地址区域。

  • 存储器控制器

        上面不同类型的引脚是连接到FSMC内部对应的存储控制器中的。NOR/PSRAM/SRAM设备使用相同的控制器,NAND/PC卡设备使用相同的控制器,不同的控制器有专用的寄存器用于配置其工作模式。

        控制SRAM的有FSMC_BCR1/2/3/4控制寄存器、FSMC_BTR1/2/3/4片选时序寄存器以及FSMC_BWTR1/2/3/4写时序寄存器。每种寄存器都有4个,分别对应于4个不同的存储区域,各种寄存器介绍如下:

  1. FSMC_BCR控制寄存器可配置要控制的存储器类型、数据线宽度以及信号有效极性能参数。
  2. FSMC_BTR时序寄存器用于配置SRAM访问时的各种时间延迟,如数据保持时间、地址保持时间等。
  3. FSMC_BWTR写时序寄存器与FMC_BTR寄存器控制的参数类似,它专门用于控制写时序的时间参数。
  • 时钟控制逻辑

        FSMC外设挂载在AHB总线上,时钟信号来自于HCLK(默认168MHz),控制器的同步时钟输出就是由它分频得到。例如,NOR控制器的FSMC_CLK引脚输出的时钟,它可用于与同步类型的SRAM芯片进行同步通讯,它的时钟频率可通过FSMC_BTR寄存器的CLKDIV位配置,可以配置为HCLK的1/2或1/3,也就是说,若它与同步类型的SRAM通讯时,同步时钟最高频率为84MHz。

        FSMC连接好外部的存储器并初始化后,就可以直接通过访问地址来读写数据,这种地址访问与I2C EEPROM、SPI FLASH的不一样,后两种方式都需要控制I2C或SPI总线给存储器发送地址,然后获取数据。在程序里,这个地址和数据都需要分开使用不同的变量存储,并且访问时还需要使用代码控制发送读写命令。而使用FSMC外接存储器时,其存储单元是映射到STM32的内部寻址空间的;在程序里,定义一个指向这些地址的指针,然后就可以通过指针直接修改该存储单元的内容,FSMC外设会自动完成数据访问过程,读写命令之类的操作不需要程序控制。

        STM32F4的FSMC支持8/16/32位数据宽度,我们这里用到的LCD是16位宽度的,所以在设置的时候,选择16位宽就OK了。我们再来看看FSMC的外部设备地址映像,STM32F4的FSMC将外部存储器划分为固定大小为256M字节的四个存储块,如下图:

        从上图可以看出,FSMC总共管理1GB空间,拥有4个存储块,本章,我们用到的是块1,所以在本章我们仅讨论块1的相关地址,其他块的配置,请参考《STM32F4参考手册》。

        STM32F4的FSMC存储块1(Bank1)被分为4个区,每个区管理64M字节空间,每个区都有独立的寄存器对所连接的存储器进行配置。Bank1的256M字节空间由28根地址线(HADDR[27:0])寻址。

        这里HADDR是内部AHB地址总线,其中HADDR[25:0]来自外部存储器地址FSMC_A[25:0],而HADDR[26:7]对4个区进行寻址。如下表所示:

         表中我们要特别注意HADDR[25:0]的对应关系:

        当Bank1接的是16位宽度存储器的时候:HADDR[25:1]->FSMC_A[24:0]。

        当Bank1接的是8位宽度存储器的时候:HADDR[25:0] -> FSMC_A[25:0]。

        不论外部接8位/16位宽设备,FSMC_A[0]永远接在外部设备地址A[0]。这里,TFTLCD使用的是16位数据宽度,所以HADDR[0]并没有用到,只有HADDR[25:1]是有效的,对应关系变为:HADDR[25:1]->FSMC_A[24:0],相当于右移了一位,这里请大家特别留意。另外,HADDR[27:26]的设置,是不需要我们干预的,比如:当你选择使用Bank1的第三个区,即使用FSMC_NE3来连接外部设备的时候,即对应了HADDR[27:26]=10,我们要做的就是配置对应第3区的寄存器组,来适应外部设备即可。

7.驱动器NT35510指令

        因为NT35510的命令很多,我们这里就不全部介绍了,有兴趣的大家可以参考NT35510数据手册。首先来看0xD3指令,这是一个读ID指令,用于读取LCD控制器的ID,该指令如下表所示:

        从上表可以看出,0xD3指令后面跟了4个参数,最后两个参数,读出来是0x55和0x10,刚好是我们控制器NT35510的后面数字部分,从而,通过该指令,即可判别所用的LCD驱动器是什么型号,这样,我们的代码,就可以根据控制器的型号去执行对应IC的初始化代码,从而兼容不同驱动IC的屏,使得一个代码支持多款LCD。

        接下来看指令:0x36,这是存储访问控制指令,可以控制NT35510存储器的读写方向,简单的说,就是在连续写GRAM的时候,可以控制GRAM指针的增长方向,从而控制显示方式(读GRAM也是一样)。该指令如下表所示:

         从上表可以看出,0x36指令后面,紧跟一个参数,这里我们主要关注:MY、MX和MV这三个位,通过这三个位的设置,我们可以控制整个NT35510的全部扫描方向,如下表所示:

        这样,我们在利用NT35510显示内容的时候,就有很大灵活性了,比如显示BMP图片,BMP解码数据,就是从图片的左下角开始,慢慢显示到右上角,如果设置LCD扫描方向为从左到右,从下到上,那么我们只需要设置一次坐标,然后不停的往LCD填充数据即可,这样可以大大提高显示速度。

        0x2A指令,该指令是列地址设置指令,在从左到右,从上到下的扫描方式(默认)下面,该指令用于设置横坐标(x坐标),如下表:

        该指令带有4个参数,实际上是2个坐标值:SC和EC,即列地址的起始值和结束值,SC必须小于等于EC。一般在设置x坐标的时候,我们只需要带2个参数即可,也就是设置SC即可,因为如果EC没有变化,我们只需要设置一次即可(在初始化NT35510的时候设置),从而提高速度。

        0x2B,该指令是页地址设置指令,在从左到右,从上到下的扫描方式(默认)下面,该指令用于设置纵坐标(y坐标) ,如下表:

        该指令带有4个参数,实际上是2个坐标值:SP和EP,即页地址的起始值和结束值,SP必须小于等于EP。一般在设置y坐标的时候,我们只需要带2个参数即可,也就是设置SP即可,因为如果EP没有变化,我们只需要设置一次即可(在初始化NT35510的时候设置),从而提高速度。

        0x2C,该指令是写GRAM指令,在发送该指令之后,我们便可以往LCD的GRAM里面写入颜色数据了,该指令支持连续写 (地址自动递增)。

        在收到指令0X2C之后,数据有效位宽变为16位,我们可以连续写入LCD GRAM值,而GRAM的地址将根据MY/MX/MV设置的扫描方向进行自增。例如:假设设置的是从左到右,从上到下的扫描方式,那么设置好起始坐标(通过SC,SP设置)后,每写入一个颜色值,GRAM地址将会自动自增1(SC++),如果碰到EC,则回到SC,同时SP++,一直到坐标:EC,EP结束,其间无需再次设置的坐标,从而大大提高写入速度。

        0x2E,该指令是读GRAM指令,用于读取NT35510的显存(GRAM),同0X2C指令,该指令支持连续读 (地址自动递增)。

        在收到指令0X2C之后,数据有效位宽变为16位,我们可以连续写入LCD GRAM值,而GRAM的地址将根据MY/MX/MV设置的扫描方向进行自增。例如:假设设置的是从左到右,从上到下的扫描方式,那么设置好起始坐标(通过SC,SP设置)后,每写入一个颜色值,GRAM地址将会自动自增1(SC++),如果碰到EC,则回到SC,同时SP++,一直到坐标:EC,EP结束,其间无需再次设置的坐标,从而大大提高写入速度。

        0x2E,该指令是读GRAM指令,用于读取NT35510的显存(GRAM),同0X2C指令,该指令支持连续读 (地址自动递增)。

        NT35510在收到该指令后,第一次输出的是无效数据,第二次开始,读取到的才是有效的GRAM数据(从坐标:SC,SP开始),输出规律为:每个颜色分量占8个位,一次输出2个颜色分量。比如:第一次输出是R1G1,随后的规律为:B1R2àG2B2àR3G3àB3R4àG4B4àR5G5... 以此类推。

        NT35510 自带LCD GRAM(480*864*3字节),并且最高支持24位颜色深度(1600万色),不过我们一般使用16位颜色深度(65k),RGB565格式,这样,在16位模式下,可以达到最快的速度。在16位模式下,NT35510采用RGB565格式存储颜色数据,此时NT35510的低16位数据总线(高8位没有用到)与MCU的16位数据线以及24位LCD GRAM的对应关系如下图所示:

         从上表可以看出,NT35510的24位GRAM与16位RGB565的对应关系,其实就是分别将高位的R、G、B数据,搬运到低位做填充,“凑成”24位,再显示。MCU的16位数据中,最低5位代表颜色,中间6位为绿色,最高5位为红色。数值越大,表示该颜色越深。另外,特别注意NT33510的指令是16位宽,数据除了GRAM读写的时候是16位宽,其他都是8位宽(高8位无效)。

8.TFTLCD驱动流程

        TFTLCD驱动流程如下:

        任何LCD驱动流程都可以简单的用以上流程图表示,其中硬复位和初始化序列,只需要执行一次即可。而画点流程就是:设置坐标à写GRAM指令à写入颜色数据,然后在LCD上面,我们就可以看到对应点显示我们写入的颜色了。读点流程为:设置坐标à读GRAM指令à读取颜色数据,这样就可以获取到对应点的颜色数据了。

        通过以上介绍,我们可以得出TFTLCD显示需要的相关设置步骤如下:

  • 设置STM32F4与TFTLCD模块相连接的IO。这一步,先将我们与TFTLCD模块相连的IO口进行初始化,以便驱动LCD。
  • 初始化TFTLCD模块。本次实验我们没有硬复位LCD,因为本实验STM32F4开发板的LCD接口,将TFTLCD的RST同STM32F4的RESET连接在一起了,只要按下开发板的RESET键,就会对LCD进行硬复位。初始化序列就是向LCD控制器写入一系列的设置值,这些初始化序列一般LCD供应商会提供给客户,我们直接使用这些序列即可,不需要深入研究。在初始化后,LCD才可以正常使用。
  • 这个步骤根据设置坐标à写GRAM指令à写入颜色数据来实现,但是这个步骤,只是一个点的处理,我们要显示字符/数字,就必须要多次使用这个步骤,从而达到显示字符/数字的目的。

准备

        MDK5 开发环境。

        STM32F4xx HAL库。

        STM32F407 开发板。

        STM32F4xx 参考手册。

        STM32F407 开发板及显示屏电路原理图。

        4.3寸TFTLCD显示屏。

步骤

  • 在LCD头文件中分别定义了LCD参数结构体,对LCD驱动地址、控制指令,同时列出了32*32的ASCII字符集点阵(因代码较多,这里不再列出,详情请参考源码)。
  • 在LCD头文件中定义FSMC存储器的地址,也就是控制LCD命令和数据的地址。通过前面的介绍,我们知道 TFTLCD 的 RS 接在 FSMC的 A6 上面,CS 接在 FSMC_NE4 上,并且是 16 位数据总线。即我们使用的是 FSMC 存储器 1的第 4 区,我们使用 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 的控制。
#define LCD_BASE ((uint32_t)(0x6C000000 | 0x0000007E))

#define TFTLCD ((LCD_Typedef *)LCD_BASE)
  • 在源文件中定义LCD配置参数结构体变量,并添加LCD写命令和写数据函数,在函数中分别将参数赋给TFTLCD结构体中的命令和数据变量。
_LCD_Dev lcddev; // 定义结构体变量

// LCD写命令函数
static void LCD_Write_Command(uint16_t Command)
{
		TFTLCD->LCD_Command = Command;
}

// LCD写数据函数
static void LCD_Write_Data(uint16_t Data)
{
		TFTLCD->LCD_Data = Data;
}
  • 创建LCD_Write_CMD_Data()函数,将LCD写命令和写数据进行封装。
// LCD写命令和数据
static void LCD_Write_CMD_Data(uint16_t Command, uint16_t Data)
{
	TFTLCD->LCD_Command = Command;
	TFTLCD->LCD_Data = Data;	
}
  • 创建LCD_W_Read_DATA()函数,函数中调用LCD_Write_Command向模块中写入命令,然后再调用结构体变量TFTLCD->LCD_Data读取数据值。
// 写入命令后读取寄存器值
static uint16_t LCD_W_Read_DATA(uint16_t Command)
{
		LCD_Write_Command(Command); // 写入命令
		HAL_Delay(5); // 延迟5us
		return TFTLCD->LCD_Data; 
}
  • 创建LCD_Write_GRAMCmd()函数,函数将LCD配置参数结构体中写gramcmd指令赋给TFTLCD结构体中的命令,调用该函数表示开始写GRAM命令。
// 写Gram指令
static void LCD_Write_GRAMCmd(void)
{
		TFTLCD->LCD_Command = lcddev.gramcmd;
}
  • 创建LCD_SetCursor()函数,函数中通过LCD_Write_Command()函数向模块中写入设置坐标x及设置坐标y命令,写入命令后调用LCD_Write_Data()函数向模块写入坐标数据(其中数据分为高8位和低8位写入)。
// 设置光标位置
static void LCD_SetCursor(uint16_t x, uint16_t y)
{
	// 设置坐标x的高位和低位
	LCD_Write_Command(lcddev.setxcmd);
	LCD_Write_Data(x >> 8);
	LCD_Write_Command(lcddev.setxcmd+1);
	LCD_Write_Data(x & 0xFF);
	
	// 设置坐标y的高位和低位
	LCD_Write_Command(lcddev.setycmd);
	LCD_Write_Data(y >> 8);
	LCD_Write_Command(lcddev.setycmd+1);
	LCD_Write_Data(y & 0xFF);	
}
  • 创建LCD_Fast_DrawPoint()函数,函数实现快速画点功能。函数中首先设置x坐标的高8位和低8位数据,然后设置y坐标的高8位和低8位数据,最后设置显示颜色。
// 快速画点函数
static void LCD_Fast_DrawPoint(uint16_t x, uint16_t y, uint16_t color)
{
		// 设置坐标x的高位和低位
		LCD_Write_Command(lcddev.setxcmd);
		LCD_Write_Data(x >> 8);
		LCD_Write_Command(lcddev.setxcmd+1);
		LCD_Write_Data(x & 0xFF);
	
		// 设置坐标y的高位和低位
		LCD_Write_Command(lcddev.setycmd);
		LCD_Write_Data(y >> 8);
		LCD_Write_Command(lcddev.setycmd+1);
		LCD_Write_Data(y & 0xFF);	

		LCD_Write_GRAMCmd(); // 写Gram指令
		LCD_Write_Data(color); // 设置颜色	
}
  • 创建LCD_Scan_Dir()函数,用来设置屏幕的扫描方向及坐标位置。

        第一步:通过switch语句判断屏幕扫描方向命令,并将命令的相应的数据放入Data变量中。

        第二步:通过LCD_Write_CMD_Data()函数将变量Data写入模块中。

        第三步:初始化x、y的起始和结束坐标位置。

// 设置扫描方向
static void LCD_Scan_Dir(uint8_t dir)
{	
	uint16_t Data = 0; // 存储读写方向变量
	switch(dir)
	{
		case L2R_U2D: // 从左到右,从上到下
			Data|=(0<<7)|(0<<6)|(0<<5); 
			break;
		case D2U_L2R: // 从下到上,从左到右
			Data|=(1<<7)|(0<<6)|(1<<5); 
			break;
	}		
	LCD_Write_CMD_Data(0x3600, Data); // 设置读写方向
	
	LCD_Write_Command(lcddev.setxcmd); LCD_Write_Data(0); // 设置横坐标起始位置
	LCD_Write_Command(lcddev.setxcmd+1); LCD_Write_Data(0);
	LCD_Write_Command(lcddev.setxcmd+2); LCD_Write_Data((xSize-1)>>8); // 设置横坐标结束位置高字节
	LCD_Write_Command(lcddev.setxcmd+3); LCD_Write_Data((xSize-1)&0xFF); // 设置横坐标结束位置低字节
	LCD_Write_Command(lcddev.setycmd); LCD_Write_Data(0); // 设置纵坐标起始位置
	LCD_Write_Command(lcddev.setycmd+1); LCD_Write_Data(0);
	LCD_Write_Command(lcddev.setycmd+2); LCD_Write_Data((ySize-1)>>8); // 设置纵坐标结束位置高字节
	LCD_Write_Command(lcddev.setycmd+3); LCD_Write_Data((ySize-1)&0xFF); // 设置纵坐标结束位置低字节
}
  • 在源文件中添加LCD_Config()函数,用来配置屏幕相应参数。函数中首先将0x2C、0x2A、0x2B分别赋给LCD配置参数结构体中的gramcmd、setxcmd、setycmd,分别用来设置GRAM、列地址和页地址的指令。然后通过判断横竖屏命令将480、800分别赋给LCD配置参数结构体中屏幕宽度和高度变量。
// 设置LCD屏幕高度、宽度、横竖屏以及相关命令
void LCD_Config(uint8_t dir)
{
	lcddev.gramcmd = 0x2C00; // 写Gram指令
	lcddev.setxcmd = 0x2A00; // 列地址设置指令
	lcddev.setycmd = 0x2B00; // 页地址设置指令
	
	if(dir)
	{
		lcddev.dir = 1; // 横屏
		lcddev.height = 480; // 屏幕高度
		lcddev.width = 800; // 屏幕宽度
	}
	else
	{
		lcddev.dir = 0; // 竖屏
		lcddev.height = 800; // 屏幕高度 
		lcddev.width = 480; // 屏幕宽度
	}
}
  • 创建LCD_Clear()函数,用来刷新屏幕。函数首先通过LCD_SetCursor设置x和y坐标,然后调用LCD_Write_GRAMCmd开始写GRAM指令,最后将颜色变量赋值给结构体中的LCD_Data,经过循环将整个屏幕都刷新成该颜色。
// 清屏函数
static void LCD_Clear(uint16_t color)
{
	uint32_t pixel_count = lcddev.height * lcddev.width; // 计算像素总数
	
	LCD_SetCursor(0x0000, 0x0000); // 设置起始坐标
	LCD_Write_GRAMCmd(); // 开始写Gram指令
	
	for(uint32_t i=0; i<pixel_count; i++)
	{
		TFTLCD->LCD_Data = color; // 写入颜色
	}
}
  • 创建TFTLCD_Init()函数,初始化LCD屏幕。

        第一步:初始化LCD屏幕、LCD屏幕使用到的GPIO引脚以及FSMC。

        第二步:通过LCD_Config()函数配置LCD屏幕参数。

        第三步:通过LCD_Scan_Dir初始化画点的扫描方向(从左到右,从上到下)。

        第四步:调用LCD_ON()开启屏幕背光。

        第五步:通过LCD_Clear将屏幕刷新成白色。

  • 重定义HAL_SRAM_MspInit()函数,初始化FSMC使用的引脚。
/*
* SRAM底层驱动,时钟使能,引脚分配
* 此函数会被HAL_SRAM_Init()调用
* hsram:SRAM句柄
*/
void HAL_SRAM_MspInit(SRAM_HandleTypeDef *hsram)
{	
	GPIO_InitTypeDef GPIO_Initure;
	
	__HAL_RCC_FSMC_CLK_ENABLE(); // 使能FSMC时钟
	__HAL_RCC_GPIOD_CLK_ENABLE(); // 使能GPIOD时钟
	__HAL_RCC_GPIOE_CLK_ENABLE(); // 使能GPIOE时钟
	__HAL_RCC_GPIOF_CLK_ENABLE(); // 使能GPIOF时钟
	__HAL_RCC_GPIOG_CLK_ENABLE(); // 使能GPIOG时钟
	
	// 初始化PD0,1,4,5,8,9,10,14,15
	GPIO_Initure.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;
	GPIO_Initure.Mode = GPIO_MODE_AF_PP; // 推挽复用
	GPIO_Initure.Pull = GPIO_PULLUP; // 上拉
	GPIO_Initure.Speed = GPIO_SPEED_HIGH; // 高速
	GPIO_Initure.Alternate = GPIO_AF12_FSMC; // 复用为FSMC
	HAL_GPIO_Init(GPIOD, &GPIO_Initure); // 初始化
	
	// 初始化PE7,8,9,10,11,12,13,14,15
	GPIO_Initure.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;
	HAL_GPIO_Init(GPIOE, &GPIO_Initure);
	
	// 初始化PF12
	GPIO_Initure.Pin = GPIO_PIN_12;
	HAL_GPIO_Init(GPIOF, &GPIO_Initure);
	
	// 初始化PG12
	GPIO_Initure.Pin = GPIO_PIN_12;
	HAL_GPIO_Init(GPIOG, &GPIO_Initure);
}
  • 创建LCD_ShowChar()函数,实现在屏幕指定位置显示一个字符。函数通过显示字符的ASCII码,在ASCII码集中获取该字符的点阵数据,然后通过快速画点函数将数据显示到屏幕上。
/*
* 在指定位置显示一个字符
* x,y:起始坐标
*/
void LCD_ShowChar(uint16_t x, uint16_t y, uint8_t num, uint16_t color)
{  							  
	uint8_t temp;
	uint16_t y0 = y;
	
 	num = num - ' '; // 得到偏移后的值(ASCII字库是从空格开始取模,所以-' '就是对应字符的字库)
	
	for(uint8_t t=0; t<64; t++) // 64为点阵集中一个字符的字节数
	{   
		temp = ASC2_3216[num][t];	// 调用字体
		for(uint8_t i=0; i<8; i++) // 循环画出一个字节中8个位
		{			    
			if(temp&0x80) LCD_Fast_DrawPoint(x, y, color);
			else LCD_Fast_DrawPoint(x, y, WHITE);
			temp <<= 1;
			y++;
			if(y >= lcddev.height)return;		//超区域了
			if((y-y0) == 32)
			{
				y = y0;
				x++;
				if(x >= lcddev.width)return;	//超区域了
				break;
			}
		}  	 
	}
}
  • 创建LCD_ShowString()函数,实现在屏幕上显示字符串。函数中通过判断起点x和y坐标,在规定的显示区域范围内调用LCD_ShowChar()函数显示连续的字符。
/*
*	显示字符串
*		x,y: 起点坐标
* 	width,height: 区域大小  
*		size: 字体大小
* 	*p: 字符串起始地址
*		color: 显示颜色
*/
void LCD_ShowString(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t *p, uint16_t color)
{         
	uint8_t x0 = x;
	width += x;
	height += y;
	
  	while((*p<='~') && (*p>=' '))//判断是不是非法字符!
  	{       
    	if(x >= width) {x = x0; y += 32;}
    	if(y >= height) break; // 退出
    	LCD_ShowChar(x, y, *p, color);
    	x += 16;
    	p++;
  	}  
}
  • 创建LCD_DrawLine()函数,实现画线功能。函数首先计算坐标增量,然后选取增量坐标轴,最后进行连续画点。
// 画线
//x1, y1:起点坐标
//x2, y2:终点坐标
void LCD_DrawLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color)
{
	uint16_t xerr=0, yerr=0, distance;
	int16_t delta_x, delta_y; // 起点/终点的x、y距离
	uint16_t x, y; // 绘制的x,y坐标
	int16_t x_Inc, y_Inc; // 单步增量
	
	delta_x = x2-x1; // 计算坐标增量
	delta_y = y2-y1; 
	x = x1; 
	y = y1; 
	if(delta_x>0) x_Inc = 1; // 设置单步方向 
	else if(delta_x == 0) x_Inc = 0; // 垂直线
	else {x_Inc = -1; delta_x = -delta_x;}
	if(delta_y > 0) y_Inc = 1; 
	else if (delta_y == 0) y_Inc = 0; // 水平线
	else {y_Inc = -1; delta_y = -delta_y;} 
	if(delta_x > delta_y) distance = delta_x; // 选取基本增量坐标轴 
	else distance = delta_y; 
	
	for(uint16_t i=0; i <= distance+1; i++) // 画线输出
	{  
		LCD_WritePoint(x, y, color); // 画点 
		xerr += delta_x ; 
		yerr += delta_y ; 
		if(xerr > distance) 
		{ 
			xerr -= distance; 
			x += x_Inc; 
		} 
		if(yerr > distance) 
		{ 
			yerr -= distance; 
			y += y_Inc; 
		} 
	}  
}
  • 创建LCD_DrawRectangle()函数,实现画矩形功能。
// 画矩形  
//(x1, y1),(x2, y2):矩形的对角坐标
void LCD_DrawRectangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color)
{
	LCD_DrawLine(x1, y1, x2, y1, color);
	LCD_DrawLine(x1, y1, x1, y2, color);
	LCD_DrawLine(x1, y2, x2, y2, color);
	LCD_DrawLine(x2, y1, x2, y2, color);
}
  • 创建Transform_X()和Transform_Y()函数,实现根据屏幕比例转换x和y坐标,供下面的横竖屏切换函数使用。
// 根据屏幕比例转换x坐标
static uint16_t Transform_X(uint16_t x)
{
		return (uint16_t)(x * 800/480);
}
// 根据屏幕比例转换y坐标
static uint16_t Transform_Y(uint16_t y)
{
		return (uint16_t)(y * 480/800);
}
  • 创建Attitude_Update()函数,实现屏幕的重力感应功能。函数中通过判断MPU6050传感器测量的俯仰角和横滚角的边界值来控制屏幕的扫描方向及显示坐标,调用LCD_ShowString()、LCD_DrawLine()、LCD_DrawRectangle()函数分别实现在屏幕上显示字符串、画线、画矩形功能。
uint8_t status; // 横竖屏切换状态
uint8_t string[] = "Flight Technology";
/*
* 重力感应控制屏幕旋转
* 	pitch:俯仰角 精度:0.1 范围:-90.0 --- +90.0
* 	roll:横滚角  精度:0.1 范围:-180.0 --- +180.0
*/
void Attitude_Update(float pitch, float roll)
{
	// 竖屏显示
	if((pitch > -50) && (roll > 0) && (status == 0))
	{
		status = 1;
		LCD_Config(0); // 初始化显示屏相关参数 1:横屏 0:竖屏
		LCD_Scan_Dir(L2R_U2D); // 初始化扫描方向
		LCD_Clear(WHITE); // 清屏 白色
		LCD_ShowString(80, 70, 270, 16, string, BLUE); // 显示字符串
		LCD_DrawLine(80, 200, 350, 200, RED); // 画线
		LCD_DrawRectangle(80, 280, 420, 350, BLUE); // 画矩形	
	}
	// 横屏显示
	if((pitch < -50) && (roll < 70) && (status == 1))
	{
		status = 0;
		LCD_Config(1); // 初始化显示屏相关参数 1:横屏 0:竖屏
		LCD_Scan_Dir(D2U_L2R); // 初始化扫描方向
		LCD_Clear(WHITE); // 清屏 白色
		LCD_ShowString(Transform_X(80), Transform_Y(70), 270, 16, string, BLUE); // 显示字符串
		LCD_DrawLine(Transform_X(80), Transform_Y(200), Transform_X(350), Transform_Y(200), RED); // 画线
		LCD_DrawRectangle(Transform_X(80), Transform_Y(280), Transform_X(420),
							Transform_Y(350), BLUE); // 画矩形		
	}
}
  • 主函数main程序如下:

        第一步:初始化系统时钟、LCD屏幕及MPU6050传感器。

        第二步:在while循环中调用MPU6050_Read()和AHRS()函数分别计算加速度、角速度以及欧拉角。

        第三步:调用Attitude_Update()函数,根据欧拉角实现重力感应功能。

Attitude attitude; // 姿态角

int main(void)
{
	CLOCK_Init(); // 初始化系统时钟
	TFTLCD_Init(); // LCD屏幕初始化
		
	MPU6050_Init(); // MPU6050初始化
			
	while(1)
	{
		HAL_Delay(100); // 延时
		MPU6050_Read(); // 读取加速度计和陀螺仪值
		AHRS(0.1); // 计算欧拉角
		Attitude_Update(attitude.pitch, attitude.roll); // 重力感应
	}
}

现象

        将程序下载到开发板中,倾斜开发板,可以看到屏幕上分别显示出一行字符串,一条直线和一个矩形,当屏幕横过来时,显示位置也相应改变。

        竖屏时:

横屏时:

  • 9
    点赞
  • 93
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奚海蛟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值