TFTLCD显示

1. 简介

1.1 TFTLCD简介

TFT-LCD(thin film transistor-liquid crystal display)即薄膜晶体管液晶显示器。液晶显示屏的每一个像素上都设置有一个薄膜晶体管(TFT),每个像素都可以通过点脉冲直接控制,因而每个节点都相对独立,并可以连续控制,不仅提高了显示屏的反应速度,同时可以精确控制显示色阶,所以TFT液晶的色彩更真,因此TFT-LCD也被叫做真彩液晶显示器

常用的TFT液晶屏接口有8位、9位、16位、18位,这里的位数表示的是彩屏数据线的数量。常用的通信模式有6800模式和8080模式,本例程使用8080并口模式(简称80并口),8080接口有5条基本的控制线和多条数据线(8/9/16/18位),它们的功能如下表:

STM32CubeMX系列|TFTLCD显示_单片机

8080接口模式的时序如下图所示:

STM32CubeMX系列|TFTLCD显示_嵌入式_02


  • 在WR跳变为低电平后,液晶屏开始读取总线上面的数据
  • 在RD跳变为低电平后,液晶屏开始放置数据到总线上面

本例程使用3.0寸的TFT-LCD模块,驱动芯片为R61509VN,分辨率240*400,接口为16位的80并口,自带触摸功能,该模块原理图如下所示:

STM32CubeMX系列|TFTLCD显示_c语言_03

该模块的80并口有如下信号线

信号线

作用

CS

TFTLCD片选信号

WR

向TFTLCD写入数据

RD

从TFTLCD读取数据

RS

命令/数据选择(0:读写命令,1:读写数据)

DB[15:0]

16位双向数据线

RST

TFTLCD复位

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

STM32CubeMX系列|TFTLCD显示_stm32_04

以上是最简单也是最常用的操作,有了这些操作一般就可以正常使用TFTLCD了。设置TFT液晶显示通常需要以下几个步骤:


  • 设置STM32F1与TFTLCD模块箱连接的IO口,将与TFTLCD模块相连的IO口进行初始化,以便驱动LCD,这里使用的是STM32F1的FSMC
  • 初始化TFTLCD模块(写入一系列设置值),本例程中LCD模块的复位引脚是接在STM32F1的复位上,所以直接按下开发板复位键即可;然后就是初始化序列,即向LCD控制器写入一系列的设置值(比如RGB格式、LCD显示方向、伽马校准等),这部分代码一般LCD厂商会提供。初始化之后LCD才可以正常使用
  • 通过函数将字符和数字显示到TFTLCD模块上,画点流程只是一个点的处理,因此需要设计一个函数来多次使用这个步骤以实现字符和数字的显示

1.2 FSMC简介

FSMC(Flexible Static Memory Controller即灵活的静态存储控制器)是STM32系列采用的一种新型存储器扩展技术,能够连接同步、异步存储器和16位PC存储卡,STM32通过FSMC可以与SRAM、ROM、PSRAM、NOR Flash和NAND Flash等存储器的引脚直接相连。STM32F1的FSMC内部框图如下图示:

STM32CubeMX系列|TFTLCD显示_单片机_05


  • 时钟输入:FSMC的时钟来自时钟控制器HCLK
  • AHB接口:CPU和其他AHB总线主设备可通过该AHB从设备接口访问外部静态存储器
  • 外部设备:FSMC将外部设备分为2类,NOR/PSRAM设备和NAND/PC卡设备。它们共用地址数据总线等信号,但具有不同的CS以区分不同的设备

本例程中使用的是FSMC的NOR/PSRAM存储器控制器部分,即把TFTLCD当成SRAM设备使用。NOR/PSRAM存储器控制器的接口信号功能如下图示:

STM32CubeMX系列|TFTLCD显示_嵌入式_06

从上图中可以看出外部SRAM的控制一般有:A0 ~ A25、D0 ~ D15、NWE、NOE、NE[x],如果支持字节控制还有UB/LB信号。而TFTLCD的信号线包括:RS、DB0 ~ DB15、WR、RD、CS、RST等。由此可见它们的操作接口信号完全类似,唯一不同就是TFTLCD有RS信号没有地址信号

TFTLCD通过RS信号来决定传送的数据是数据还是命令,可以理解为一个地址信号。比如我们把 RS 接在A0上面,当FSMC控制器写地址0的时候,会使得A0变为0,对TFTLCD来说就是写命令;而FSMC写地址1的时候,A0将会变为1,对TFTLCD来说就是写数据了。这样就把数据和命令区分开了,其实就是对应SRAM操作的两个连续地址。 当然RS也可以接在其他地址线上,本例程中RS是连接在 A10 上面的

下面介绍一下FSMC的外部设备地址映射,从FSMC的角度外部存储器被划分为4个固定大小的存储区域(Bank),每个存储区域的大小为256MB,共1GB空间,详见下图:

STM32CubeMX系列|TFTLCD显示_stm32_07

Bank1可连接多达4个NOR Flash或PSRAM存储器器件,此存储区域被划分为4个NOR/PSRAM区域,带4个专用片选信号;Bank2和Bank3用于连接NAND Flash器件;Bank4用于连接PC卡设备;对于每个存储区域,所要使用的存储器类型有用户在配置寄存器中定义。FSMC各Bank配置寄存器如下图示:

STM32CubeMX系列|TFTLCD显示_嵌入式_08

这里使用的是Bank1,Bank1又被分为4个区,每个区管理64MB空间,每个区都有独立的寄存器对所连接的存储器进行配置。Bank1的256MB空间由28根地址线(HADDR[27:0])寻址,HADDR是内部AHB地址总线,其中HADDR[25:0]来自外部存储器地址FSMC_A[25:0],HADDR[27:26]用于对4个区寻址:

STM32CubeMX系列|TFTLCD显示_嵌入式_09

本例程使用的是Bank1的第4区,即起始地址为0x6C000000,这里需要特别注意HADDR[25:0],HADDR[25:0]包含外部存储器地址,由于HADDR为字节地址,而存储器按字寻址,所以根据存储器数据宽度不同,实际向存储器发送的地址也有所不同,如下图示:

STM32CubeMX系列|TFTLCD显示_单片机_10

不论外部接8位/16位宽设备,FSMC_A[0]始终接在外部设备地址A[0]。本例程中TFTLCD使用的是16位数据宽度,所以HADDR[0]并没有用到,只有HADDR[25:1]是有效的,对应关系变为:HADDR[25:1] --> FSMC_A[24:0],相当于右移了一位。比如地址0x7E对应的二进制是01111110,此时FSMC_A6是0而不是1,因为要右移一位。另外HADDR[27:26]是不需要我们干预的。

FSMC的NOR Flash控制器支持同步和异步突发两种访问方式,选用不同的时序模型时,需要设置不同的时序参数:

STM32CubeMX系列|TFTLCD显示_数据_11


  • 同步突发访问方式:需要设置CLKDIV(分频系数)、DATLAT(获得第一个数据所需要的等待延迟)
  • 异步突发访问方式:需要设置DATAST(数据建立时间)、ADDSET(地址建立时间)、ADDHLD(地址保持时间)

在实际扩展时根据选用存储器的特征确定时序模型,从而确定个时间参数与存储器读写周期参数指标之间的计算关系;利用该计算关系和存储芯片数据手册中给定的参数指标,可计算出FSMC所需要的各时间参数,从而对时间参数寄存器进行合理的配置

这里使用异步模式A(ModeA)方式来控制TFTLCD,模式A的读操作时序图如下所示:

STM32CubeMX系列|TFTLCD显示_数据_12

模式A的写操作时序图如下所示:

STM32CubeMX系列|TFTLCD显示_嵌入式_13

2. 硬件设计

D1指示灯用来提示系统运行状态,TFTLCD的ID通过串口1打印输出



  • D1指示灯
  • USART1
  • FSMC
  • TFTLCD模块


STM32CubeMX系列|TFTLCD显示_嵌入式_14

3. 软件设计

3.1 STM32CubeMX设置

  • RCC设置外接HSE,时钟设置为72M
  • PC0设置为GPIO推挽输出模式、上拉、高速、默认输出电平为高电平
  • USART1选择为异步通讯方式,波特率设置为115200Bits/s,传输数据长度为8Bit,无奇偶校验,1位停止位
  • 选择FSMC,选中Bank1,片选为NE4(即Bank1的第4区),存储器类型选为LCD Interface,LCD寄存器选择(RS)设置为A10,数据宽度设为16位

STM32CubeMX系列|TFTLCD显示_数据_15

  • 在配置菜单中,使能存储器写,根据LCD驱动芯片的数据参数设置地址建立时间、数据保存时间、总线周转阶段持续时间(设置为0),模式设置为A

STM32CubeMX系列|TFTLCD显示_嵌入式_16

  • 输入工程名,选择工程路径(不要有中文),选择MDK-ARM V5;勾选Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;点击GENERATE CODE,生成工程代码
3.2 MDK-ARM编程
  • 在fsmc.c文件下可以看到FSMC的初始化函数
void MX_FSMC_Init(void){
FSMC_NORSRAM_TimingTypeDef Timing = {0};
FSMC_NORSRAM_TimingTypeDef ExtTiming = {0};
hsram1.Instance = FSMC_NORSRAM_DEVICE;
hsram1.Extended = FSMC_NORSRAM_EXTENDED_DEVICE;
/* hsram1.Init */
hsram1.Init.NSBank = FSMC_NORSRAM_BANK4;
hsram1.Init.DataAddressMux = FSMC_DATA_ADDRESS_MUX_DISABLE;
hsram1.Init.MemoryType = FSMC_MEMORY_TYPE_SRAM;
hsram1.Init.MemoryDataWidth = FSMC_NORSRAM_MEM_BUS_WIDTH_16;
hsram1.Init.BurstAccessMode = FSMC_BURST_ACCESS_MODE_DISABLE;
hsram1.Init.WaitSignalPolarity = FSMC_WAIT_SIGNAL_POLARITY_LOW;
hsram1.Init.WrapMode = FSMC_WRAP_MODE_DISABLE;
hsram1.Init.WaitSignalActive = FSMC_WAIT_TIMING_BEFORE_WS;
hsram1.Init.WriteOperation = FSMC_WRITE_OPERATION_ENABLE;
hsram1.Init.WaitSignal = FSMC_WAIT_SIGNAL_DISABLE;
hsram1.Init.ExtendedMode = FSMC_EXTENDED_MODE_ENABLE;
hsram1.Init.AsynchronousWait = FSMC_ASYNCHRONOUS_WAIT_DISABLE;
hsram1.Init.WriteBurst = FSMC_WRITE_BURST_DISABLE;
/* Timing */
Timing.AddressSetupTime = 0x01; //地址建立时间(ADDSET)为2个HCLK,即2/72M = 27.8ns
Timing.AddressHoldTime = 15; //默认值,地址保持时间(ADDHLD)模式A未用到
Timing.DataSetupTime = 0x0f; //数据建立时间(DATST)为16个HCLK,即16/72M = 222.2ns
Timing.BusTurnAroundDuration = 0x00; //总线恢复时间
Timing.CLKDivision = 16; //默认值(时钟分频),同步突发访问方式才需要设置此值
Timing.DataLatency = 17; //默认值(数据保持时间),同步突发访问方式才需要设置此值
Timing.AccessMode = FSMC_ACCESS_MODE_A;
/* ExtTiming */
ExtTiming.AddressSetupTime = 0x0f;//地址建立时间(ADDSET)为16个HCLK,即16/72M = 222.2ns
ExtTiming.AddressHoldTime = 15; //默认值,地址保持时间(ADDHLD)模式A未用到
ExtTiming.DataSetupTime = 0x05; //数据建立时间(DATST)为6个HCLK,即6/72M = 83.3ns
ExtTiming.BusTurnAroundDuration = 0x00; //总线恢复时间
ExtTiming.CLKDivision = 16; //默认值(时钟分频),同步突发访问方式才需要设置此值
ExtTiming.DataLatency = 17; //默认值(数据保持时间),同步突发访问方式才需要设置此值
ExtTiming.AccessMode = FSMC_ACCESS_MODE_A;
if (HAL_SRAM_Init(&hsram1, &Timing, &ExtTiming) != HAL_OK){
Error_Handler( );
}
__HAL_AFIO_FSMCNADV_DISCONNECTED();
}
  • 创建TFTLCD驱动文件tftlcd.c 和相关头文件tftlcd.h


本例程使用FSMC存储器1的第4区驱动LCD,TFTLCD的RS接在FSMC_A10上,CS接在FSMC_NE4上,并且是16位数据总线,可以在tftlcd.h里定义如下LCD操作结构体:


//TFTLCD操作结构体
typedef struct{
uint16_t LCD_CMD;
uint16_t LCD_DATA;
}TFTLCD_TypeDef;
//HADDR[27,26]=11,A10作为数据命令区分线
//设置时STM32内部会右移以为对齐
#define TFTLCD_BASE ((uint32_t)(0x6C000000 | 0x000007FE))
#define TFTLCD ((TFTLCD_TypeDef *) TFTLCD_BASE)
//0x6C000000是Bank1.sector4的起始地址;0x000007FE是A10的偏移量
//0x7FE即11111111110,16位数据宽度时地址需要右移一位对齐
//因此实际对应到引脚时A10:A0=01111111111,此时A10是0
//如果16位地址再加1,则A10:A0=10000000000,此时A10是1
//有了以上定义,LCD写命令/数据极为方便
TFTLCD->LCD_CMD = CMD; //写命令
TFTLCD->LCD_DATA = DATA; //写数据
//读的时候反过来操作即可
CMD = TFTLCD->LCD_CMD //读LCD寄存器
DATA = TFTLCD->LCD_DATA //读LCD数据


tftlcd.h中另一个重要结构体:管理LCD重要参数


//LCD重要参数集
typedef struct{
uint16_t width; //LCD宽度
uint16_t height; //LCD高度
uint16_t id; //LCD ID
uint8_t dir; //LCD显示方向
}_tftlcd_data;
extern _tftlcd_data tftlcd_data; //管理LCD重要参数


几个简单但是很重要的函数:


#define TFTLCD_R61509VN   //本例程的TFTLCD屏驱动类型为R61509VN
//写寄存器函数,cmd:寄存器地址
void LCD_WriteCmd(uint16_t cmd){
TFTLCD->LCD_CMD=(cmd>>8)<<1;
TFTLCD->LCD_CMD=(cmd&0xff)<<1; //写入要写的寄存器序号
}
//写LCD数据,data:要写入的值
void LCD_WriteData(uint16_t data){
TFTLCD->LCD_DATA=(data>>8)<<1;
TFTLCD->LCD_DATA=(data&0xff)<<1;
}
//写寄存器,cmd:寄存器地址;data:要写入的数据
void LCD_WriteCmdData(uint16_t cmd,uint16_t data){
LCD_WriteCmd(cmd);
LCD_WriteData(data);
}
//读LCD数据,返回读到的值
uint16_t LCD_ReadData(void){
uint16_t ram;
ram=(TFTLCD->LCD_DATA)>>1;
ram=ram<<8;
ram|=(TFTLCD->LCD_DATA)>>1;
return ram;
}


其他TFTLCD操作的API函数,就不一一介绍了,如需源码可联系我


void LCD_Display_Dir(uint8_t dir);  //设置LCD显示方向
void LCD_Set_Window(uint16_t sx,uint16_t sy,uint16_t width,uint16_t height); //设置窗口
void LCD_Clear(uint16_t Color); //清屏
void LCD_Fill(uint16_t xState,uint16_t yState,uint16_t xEnd,uint16_t yEnd,uint16_t color); //在指定区域内填充颜色
void LCD_DrawPoint(uint16_t x,uint16_t y); //画点
void LCD_DrawFRONT_COLOR(uint16_t x,uint16_t y,uint16_t color); //指定颜色画点
uint16_t LCD_ReadPoint(uint16_t x,uint16_t y); //读取某点的颜色
void LCD_DrawLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2); //画线
void LCD_DrawLine_Color(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2,uint16_t color); //指定颜色画线
void LCD_DrowSign(uint16_t x, uint16_t y, uint16_t color); //画十字标记
void LCD_DrawRectangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2); //画矩形
void LCD_Draw_Circle(uint16_t x0,uint16_t y0,uint8_t r); //指定位置画指定大小的圆
void LCD_ShowChar(uint16_t x,uint16_t y,uint8_t num,uint8_t size,uint8_t mode); //在指定位置显示一个字符
void LCD_ShowNum(uint16_t x,uint16_t y,uint32_t num,uint8_t len,uint8_t size); //显示数字,不显示高位的0
void LCD_ShowxNum(uint16_t x,uint16_t y,uint32_t num,uint8_t len,uint8_t size,uint8_t mode); //显示数字,显示高位的0
void LCD_ShowString(uint16_t x,uint16_t y,uint16_t width,uint16_t height,uint8_t size,uint8_t *p); //显示字符串
void LCD_ShowFontHZ(uint16_t x, uint16_t y, uint8_t *cn); //显示汉字
void LCD_ShowPicture(uint16_t x, uint16_t y, uint16_t wide, uint16_t high,uint8_t *pic); //显示图片
  • 在main.c文件下编写TFTLCD测试代码
int main(void){
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_FSMC_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
TFTLCD_Init();
FRONT_COLOR=BLACK;
LCD_ShowString(10,10,240,400,12,(uint8_t*)"TFTLCD Testing...!");
FRONT_COLOR=RED;
LCD_ShowString(10,30,240,400,16,(uint8_t*)"TFTLCD Testing...!");
FRONT_COLOR=GREEN;
LCD_ShowString(10,50,240,400,24,(uint8_t*)"TFTLCD Testing...!");
/* USER CODE END 2 */
while (1){
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0);
HAL_Delay(200);
}
}

4. 下载验证

STM32CubeMX生成的代码默认优化级别为Level 3,在此优化级别下编译无误下载到开发板后,可以看到D1指示灯不断闪烁,但是LCD屏不能被点亮;将优化级别调整到Level 0编译下载后,LCD屏能够被点亮


KEIL5中C/C++优化等级介绍:
-O0:最少的优化,可以最大程度上配合产生代码调试信息,可以在任何代码行打断点,特别是死代码处。
-O1:有限的优化,去除无用的inline和无用的static函数、死代码消除等,在影响到调试信息的地方均不进行优化。在适当的代码体积和充分的调试之间平衡,代码编写阶段最常用的优化等级。
-O2:高度优化,调试信息不友好,有可能会修改代码和函数调用执行流程,自动对函数进行内联等。
-O3:最大程度优化,产生极少量的调试信息。会进行更多代码优化,例如循环展开,更激进的函数内联等。


STM32CubeMX系列|TFTLCD显示_单片机_17

编译无误下载到开发板后,可以看到D1指示灯不断闪烁,LCD屏显示相应的文字,串口打印出LCD的ID信息

STM32CubeMX系列|TFTLCD显示_stm32_18

STM32CubeMX系列|TFTLCD显示_数据_19


关注我的公众号,在公众号里发如下消息,即可获取相应的工程源代码:

玩转STM32CubeMX | TFTLCD显示

STM32CubeMX系列|TFTLCD显示_单片机_20