STM32 使用FSMC驱动ILI9341LCD显示屏(16bit 8080)

一、前期准备

        1.1 资料简介

                文档中有关于2.8寸屏ILI9341的驱动手册以及网络获取的中文手册供大家参考

        1.2 硬件环境

                M3开发板

                ST-Link调试器

                ILI9341 2.8寸LCD屏

二、FSMC与LCD

        2.1 8080接口

        一般情况下,在嵌入式行业中常用的屏幕(OLED、TFT-LCD)会提供多个接口,其中比较常见的为SPI、IIC、8080、6800接口等,可硬件选择接口,选择方式参考官方手册,本次课程中使用屏幕硬件接口选择方式如下:

        串行通讯SPI与IIC这里我们不做过多介绍,基本都是一些常规的底层硬件接口。对于屏幕显示而言,串行通信数据传输较慢,对于很多情况而言,我们更倾向于使用并行通信。并行通讯接口在屏幕中一般提供了6800与8080接口,6800总线又叫做摩托罗拉总线、8080时序也叫做英特尔总线。

        在底层MCU驱动环境下,我们比较多的会使用MCU接口中的8080时序,在本次课程中我们使用了16位的8080接口,8080接口需要如下驱动管脚:

管脚名称

管脚功能

其他说明

CS

片选

低电平有效

RES

复位

低电平有效

WR

写使能

低电平有效

RD

读使能

低电平有效

DC

数据命令选择端

低电平命令、高电平数据

D[15:0]

16位并行数据线

        8080-I/8080-II串并接口,通过D[17:0]数据引脚实现寄存器的存取

        8080写数据时序图:

        8080读数据时序图:

        2.2 MCU中的FSMC

        FSMC是嵌入式单片机MCU提供的一个接口:灵活的静态存储器控制器

        FSMC可外接外部RAM到存储器上,地址空间为0X60000000~0X9FFFFFFF,共1G空间。

        而这1G空间由分为了16个区,其中4个区为1个块。即一个区为64M大小,一个块为256M大小。

                存储块1用于访问NOR闪存或PSRAM存储设备。

                存储块2和3用于访问NAND闪存设备,每个存储块连接一个NAND闪存。

                存储块4用于访问PC卡设备

        根据以上内容,结合如下FSMC存储块示意图,我们可以计算出每一个区的空间首地址,此操作在此次操作中尤为重要。

比如如何计算出块1的第4个区空间首地址

  addr = 0X60000000 + 3*64M

  即空间首地址为基地址加上前面三个区的总空间大小

  那么addr = 0X60000000 + 192M

      addr = 0X60000000 + 201326592字节

      addr = 0X60000000 + 0XC000000

      addr = 0X6C000000

        接下来我们就需要关注如何接入将外部存储器,并且FSMC如何控制该控制块

        其中HADDR是需要转换到外部存储器的内部AHB地址线。

        当外部存储器的数宽为8位时,FSMC的地址线将与MCU内存空间地址线从0开始依次对应连接。

        当外部存储器的数宽为16位时,FSMC的地址线将与MCU内存空间地址线从1开始依次错位连接。

        此时,我们会发现FSMC的模式A与LCD屏的8080接口时序是非常相似的:

                 FSMC模式A读操作、读操作:

        2.3 FSMC与8080时序

        此时,我们可以将FSMC于8080时序在硬件接口上先做一个对比

8080时序

FSMC

CS

片选信号,低电平有效

NEx

片选,低电平有效(x=1...4)

WR

写使能

NWR

写使能

RD

读使能

NOE

输出使能

D[15:0]

并行数据线

D[15:0]

并行数据线

D/C

数据命令选择端

A[25:0]

地址线

        而通讯的时序也可以做一个简单的对比:

        经过对比我们将会发现8080接口的硬件管脚定义与FSMC接口的硬件管脚定义基本吻合,但是8080时序需要有一个数据命令选择管脚,而FSMC没有。但是FSMC接口也多出了26根地址线供我们使用,那么我们是否可以将地址线其中的一根线作为数据命令选择端与LCD屏连接呢?

        此时我们必须注意D/C管脚为低电平时代表传输的信号为command,高电平是data。此时我们便发现了一个巧妙的方法使用FSMC地址线其中的一条线作为我们8080接口的D/C管脚(任意一条地址线都可以)。那么本次课程中使用的硬件设计如下所示:

LCD管脚

MCU IO

FSMC功能

管脚功能

配置模式

BL

PD12

背光板

通用推挽输出

CS

PD7

FSMC_NE1

片选

复用推挽输出

RD

PD4

FSMC_NOE

读使能

复用推挽输出

WR

PD5

FSMC_NWE

写使能

复用推挽输出

RS(D/C)

PD11

FSMC_A16

数据命令选择

复用推挽输出

D0

PD14

FSMC_D0

并行数据线0

复用推挽输出

D1

PD15

FSMC_D1

并行数据线1

复用推挽输出

D2

PD0

FSMC_D2

并行数据线2

复用推挽输出

D3

PD1

FSMC_D3

并行数据线3

复用推挽输出

D4

PE7

FSMC_D4

并行数据线4

复用推挽输出

D5

PE8

FSMC_D5

并行数据线5

复用推挽输出

D6

PE9

FSMC_D6

并行数据线6

复用推挽输出

D7

PE10

FSMC_D7

并行数据线7

复用推挽输出

D8

PE11

FSMC_D8

并行数据线8

复用推挽输出

D9

PE12

FSMC_D9

并行数据线9

复用推挽输出

D10

PE13

FSMC_D10

并行数据线10

复用推挽输出

D11

PE14

FSMC_D11

并行数据线11

复用推挽输出

D12

PE15

FSMC_D12

并行数据线12

复用推挽输出

D13

PD8

FSMC_D13

并行数据线13

复用推挽输出

D14

PD9

FSMC_D14

并行数据线14

复用推挽输出

D15

PD10

FSMC_D15

并行数据线15

复用推挽输出

        2.4 代码实现

        关于LCD屏显示原理在此我们不做过多的赘述,本文我们重点关注FSMC如何驱动LCD屏,本文我们以逐日开发板(ARM Cortex-M3内核STM32F103VET6)与ILI9341 LCD屏为硬件平台(各位使用市面上常见的开发板即可,了解原理即可灵活修改),驱动代码初始化框架为:

/************************
函数名称:LCD_Config
函数作用:LCD 初始化
函数入口:无
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
************************/
void LCD_Config(void)
{
	LCD_PortConfig();
	LCD_FSMCConfig();
	LCD_9341Config();
	LCD_Clear(WHITE);
}

在此我们对此代码按步骤分析:

  • 底层接口初始化—LCD_PortConfig

                初始化所有IO口,包含FSMC接口与背光板模式,并将背光板关闭。

  • FSMC初始化—LCD_FSMCConfig

                初始化FSMC工作模式为模式A

                选择FSMC_Bank 为FSMC_Bank1_NORSRAM1,即块1的区1(因为我们CS管脚连接的PD7为FSMC_NE1)

                配置数宽为16位,因为我们LCD使用16位8080并行接口

  • ILI9341驱动初始化—LCD_9341Config

                        对于ILI9341驱动来说(其他屏幕也基本类似),驱动的初始化主要靠写命令与写数据两个函数,将我们的配置写入

                LCD屏对应的配置寄存器。

那么我们在此使用了一个巧妙的方式提供了这两个基本函数:

//LCD在FSMC中寄存器操作
//使用NOR/SRAM的 Bank1.sector1,地址位HADDR[27,26]=16 A16作为数据命令区分线 
//1 1111 1111 1111 1110
//01100000 000000X1 11111111 11111110
//01100000 00000010 00000000 00000000
//注意设置时STM32内部会右移一位对其!
#define LCD_BASE        ((u32)(0x60000000 | 0x0001FFFE))
#define TFTLCD          ((LCD_TypeDef *) LCD_BASE)
//LCD寄存器结构体
typedef struct
{
	u16 LCD_REG;//命令寄存器
	u16 LCD_RAM;
} LCD_TypeDef;

/************************
函数名称:LCD_WR_REG
函数作用:LCD 读写命令函数
函数入口:
	data	写入指令
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
************************/
void LCD_WR_REG(volatile uint16_t regval)
{
  regval=regval;		//使用-O2优化的时候,必须插入的延时
  TFTLCD->LCD_REG=regval;//写入要写的寄存器序号
}

/************************
函数名称:LCD_WR_DATA
函数作用:LCD 读写数据函数
函数入口:
	data	写入数据
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
************************/
void LCD_WR_DATA(volatile uint16_t data)
{
  data=data;			//使用-O2优化的时候,必须插入的延时
  TFTLCD->LCD_RAM=data;
//0110 1100 0000 0000 0000 1000 0000 0000
}

                 实现命令与数据传输的关键其实就是数据命令选择端管脚的状态,我们参照单片机内部寄存器的方式制作了一个类似于

        GPIO的外设:TFTLCD

                 TFTLCD是一个类似于GPIO、USART1、ADC1之类的外设,其类型为结构体指针,数据为存储器空间的基地址,我们异

        步一步解决这个问题。

                 结构体指针:

                我们定义了TFTLCD的结构体指针为LCD_TypeDef类型,内含两个寄存器—LCD_REG寄存器与LCD_RAM寄存器。两个

        变量都为u16类型,也就是说当我操作TFTLCD->RAM寄存器时,会在TFTLCD基地址的基础上地址偏移两个字节,这个也是我

        们寄存器偏移量的原理。

               所以最终我们将TFTLCD宏定义为((LCD_TypeDef *) LCD_BASE)

               此处我们给出寄存器GPIOA的两张截图,大家可以对比参考,领悟其中奥妙

(PS:大家可以借此机会领悟寄存器空间的魅力^_^)

基地址:

        基地址计算就要我们关注上面的一些知识了,整个FSMC的基地址为0X60000000,我们接入的是SRAM即块1。我们连接的片选是FSMC_NE1,所以我们此时基地址偏移量为0。

        但是如果我们使用的是块2并且片选为NE2的话,基地址则为0X70000000+64M,得到0X74000000。

        然后就是如何将DC管脚切换的问题了,在驱动LCD屏时,FSMC所有的地址线是不影响我们的操作的,所以我们将DC接入了其中任意一个地址线上,我们在硬件设计时接入的是A16。

        也就是说当我使用TFTLCD->REG时,A16应该是低电平,此时地址在基地址基础上不变化

        当我使用TFTLCD->RAM时,A16应该是高电平,此时地址在基地址基础上偏移两个字节

并且上文提到,当数宽为16时,HADDR与FSMC_A引脚是错位相连的,即HADDR[0]未使用

       由此我们可以得到,当地址偏移两字节时A16=1,地址线数据为10 0000 0000 0000 0000(十六进制0X20000)

       反推不偏移两字节时,A16=0,地址数据线为01 1111 1111 1111 1110(十六进制0X1FFFE)

        所以最终我们得到最终的基地址LCD_BASE为0x60000000 | 0x0001FFFE

  • 刷新屏幕—LCD_Clear

                此代码大家需要去详细了解LCD屏幕的工作模式,本文我们制作测试使用。如果前面配置没有问题,那么屏幕初始化后将

        变为大家想使用的颜色。

本文到此便结束了,如有不对之处,欢迎大家的批评与指正!!!

LCD源文件

#include "tft_lcd.h"

/************************
函数名称:LCD_Config
函数作用:LCD 初始化
函数入口:无
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
************************/
void LCD_Config(void)
{
	LCD_PortConfig();
	LCD_FSMCConfig();
	LCD_9341Config();
	LCD_Clear(WHITE);
}

/************************
函数名称:LCD_PortConfig
函数作用:LCD管脚初始化
函数入口:无
函数出口:无
函数作者:WYC
创建时间:2020.12.31
修改时间:2020.12.31
补充说明:
	管脚说明
	LCD_BL					PD12			通用推挽输出
	FSMC_NE1(CS)		PD7				复用推挽输出
	FSMC_NOE				PD4				复用推挽输出
	FSMC_NWE				PD5				复用推挽输出
	FSMC_A16(RS)		PD11			复用推挽输出
	FSMC_D0~D15								复用推挽输出
		PD14	PD15	PD0		PD1		PE7		PE8 PE9 PE10
		PE11	PE12	PE13	PE14	PE15	PD8	PD9 PD10
************************/
void LCD_PortConfig(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	//PORT:D E
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);
	
	//PD12背光板
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOD,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_14|GPIO_Pin_15;
	GPIO_Init(GPIOD,&GPIO_InitStructure);
	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;
	GPIO_Init(GPIOE,&GPIO_InitStructure);
}

/************************
函数名称:LCD_FSMCConfig
函数作用:LCD FSMC初始化
函数入口:无
函数出口:无
函数作者:WYC
创建时间:2020.12.31
修改时间:2020.12.31
************************/
void LCD_FSMCConfig(void)
{
	FSMC_NORSRAMInitTypeDef FSMC_NORSRAMInitStruct;
	FSMC_NORSRAMTimingInitTypeDef FSMC_NORSRAMTimingInitWirte;
	FSMC_NORSRAMTimingInitTypeDef FSMC_NORSRAMTimingInitRead;
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC,ENABLE);	//使能FSMC时钟
	
	FSMC_NORSRAMTimingInitRead.FSMC_AddressSetupTime = 0x01;	 //地址建立时间(ADDSET)为2个HCLK 1/36M=27ns
	FSMC_NORSRAMTimingInitRead.FSMC_AddressHoldTime = 0x00;	 //地址保持时间(ADDHLD)模式A未用到	
	FSMC_NORSRAMTimingInitRead.FSMC_DataSetupTime = 0x0f;		 // 数据保存时间为16个HCLK,因为液晶驱动IC的读数据的时候,速度不能太快,尤其对1289这个IC。
	FSMC_NORSRAMTimingInitRead.FSMC_BusTurnAroundDuration = 0x00;
	FSMC_NORSRAMTimingInitRead.FSMC_CLKDivision = 0x00;
	FSMC_NORSRAMTimingInitRead.FSMC_DataLatency = 0x00;
	FSMC_NORSRAMTimingInitRead.FSMC_AccessMode = FSMC_AccessMode_A;	 //模式A 
    
	FSMC_NORSRAMTimingInitWirte.FSMC_AddressSetupTime = 0x00;	 //地址建立时间(ADDSET)为1个HCLK  
	FSMC_NORSRAMTimingInitWirte.FSMC_AddressHoldTime = 0x00;	 //地址保持时间(A		
	FSMC_NORSRAMTimingInitWirte.FSMC_DataSetupTime = 0x03;		 ////数据保存时间为4个HCLK	
	FSMC_NORSRAMTimingInitWirte.FSMC_BusTurnAroundDuration = 0x00;
	FSMC_NORSRAMTimingInitWirte.FSMC_CLKDivision = 0x00;
	FSMC_NORSRAMTimingInitWirte.FSMC_DataLatency = 0x00;
	FSMC_NORSRAMTimingInitWirte.FSMC_AccessMode = FSMC_AccessMode_A;	 //模式A 

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

/************************
函数名称:LCD_9341Config
函数作用:LCD 驱动初始化
函数入口:无
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
************************/
void LCD_9341Config(void)
{
	Delay_Nopnms(100);

	LCD_WR_REG(0xCF);
	LCD_WR_DATA(0x00);
	LCD_WR_DATA(0xC1);
	LCD_WR_DATA(0X30);
	LCD_WR_REG(0xED);
	LCD_WR_DATA(0x64);
	LCD_WR_DATA(0x03);
	LCD_WR_DATA(0X12);
	LCD_WR_DATA(0X81);
	LCD_WR_REG(0xE8);
	LCD_WR_DATA(0x85);
	LCD_WR_DATA(0x10);
	LCD_WR_DATA(0x7A);
	LCD_WR_REG(0xCB);
	LCD_WR_DATA(0x39);
	LCD_WR_DATA(0x2C);
	LCD_WR_DATA(0x00);
	LCD_WR_DATA(0x34);
	LCD_WR_DATA(0x02);
	LCD_WR_REG(0xF7);
	LCD_WR_DATA(0x20);
	LCD_WR_REG(0xEA);
	LCD_WR_DATA(0x00);
	LCD_WR_DATA(0x00);
	LCD_WR_REG(0xC0);    //Power control
	LCD_WR_DATA(0x1B);   //VRH[5:0]
	LCD_WR_REG(0xC1);    //Power control
	LCD_WR_DATA(0x01);   //SAP[2:0];BT[3:0]
	LCD_WR_REG(0xC5);    //VCM control
	LCD_WR_DATA(0x30); 	 //3F
	LCD_WR_DATA(0x30); 	 //3C
	LCD_WR_REG(0xC7);    //VCM control2
	LCD_WR_DATA(0XB7);
	LCD_WR_REG(0x36);    // Memory Access Control
	LCD_WR_DATA(0x48);
	LCD_WR_REG(0x3A);
	LCD_WR_DATA(0x55);
	LCD_WR_REG(0xB1);
	LCD_WR_DATA(0x00);
	LCD_WR_DATA(0x1A);
	LCD_WR_REG(0xB6);    // Display Function Control
	LCD_WR_DATA(0x0A);
	LCD_WR_DATA(0xA2);
	LCD_WR_REG(0xF2);    // 3Gamma Function Disable
	LCD_WR_DATA(0x00);
	LCD_WR_REG(0x26);    //Gamma curve selected
	LCD_WR_DATA(0x01);
	LCD_WR_REG(0xE0);    //Set Gamma
	LCD_WR_DATA(0x0F);
	LCD_WR_DATA(0x2A);
	LCD_WR_DATA(0x28);
	LCD_WR_DATA(0x08);
	LCD_WR_DATA(0x0E);
	LCD_WR_DATA(0x08);
	LCD_WR_DATA(0x54);
	LCD_WR_DATA(0XA9);
	LCD_WR_DATA(0x43);
	LCD_WR_DATA(0x0A);
	LCD_WR_DATA(0x0F);
	LCD_WR_DATA(0x00);
	LCD_WR_DATA(0x00);
	LCD_WR_DATA(0x00);
	LCD_WR_DATA(0x00);
	LCD_WR_REG(0XE1);    //Set Gamma
	LCD_WR_DATA(0x00);
	LCD_WR_DATA(0x15);
	LCD_WR_DATA(0x17);
	LCD_WR_DATA(0x07);
	LCD_WR_DATA(0x11);
	LCD_WR_DATA(0x06);
	LCD_WR_DATA(0x2B);
	LCD_WR_DATA(0x56);
	LCD_WR_DATA(0x3C);
	LCD_WR_DATA(0x05);
	LCD_WR_DATA(0x10);
	LCD_WR_DATA(0x0F);
	LCD_WR_DATA(0x3F);
	LCD_WR_DATA(0x3F);
	LCD_WR_DATA(0x0F);
	LCD_WR_REG(0x2B);
	LCD_WR_DATA(0x00);
	LCD_WR_DATA(0x00);
	LCD_WR_DATA(0x01);
	LCD_WR_DATA(0x3f);
	LCD_WR_REG(0x2A);
	LCD_WR_DATA(0x00);
	LCD_WR_DATA(0x00);
	LCD_WR_DATA(0x00);
	LCD_WR_DATA(0xef);
	LCD_WR_REG(0x11); //Exit Sleep
	Delay_Nopnms(120);
	LCD_WR_REG(0x29); //display on


	LCD_WR_REG(0x36);
	LCD_WR_DATA(0x08);//II9341必须这样

	//整个LCD就初始化完成
	//lCD有个背光灯
	LCD_LED_ON;//打开LCD屏幕背光灯
	//REG :写命令
	//DATA:写数据
	LCD_WR_REG(0x2A);
	LCD_WR_DATA(0);
	LCD_WR_DATA(0);  //
	LCD_WR_DATA(0);
	LCD_WR_DATA((240-1));
	//X轴是从 0开始   到240结束
	LCD_WR_REG(0x2B);
	LCD_WR_DATA(0);
	LCD_WR_DATA(0);
	LCD_WR_DATA((320-1)>>8);
	LCD_WR_DATA((320-1)&0xFF);
	//y轴的起始 0
	//y轴的结束 320
	LCD_WR_REG(0x2C);	
}

/************************
函数名称:LCD_WR_REG
函数作用:LCD 读写命令函数
函数入口:
	data	写入指令
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
************************/
void LCD_WR_REG(volatile uint16_t regval)
{
  regval=regval;		//使用-O2优化的时候,必须插入的延时
  TFTLCD->LCD_REG=regval;//写入要写的寄存器序号
}

/************************
函数名称:LCD_WR_DATA
函数作用:LCD 读写数据函数
函数入口:
	data	写入数据
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
************************/
void LCD_WR_DATA(volatile uint16_t data)
{
  data=data;			//使用-O2优化的时候,必须插入的延时
  TFTLCD->LCD_RAM=data;
//0110 1100 0000 0000 0000 1000 0000 0000
}

/************************
函数名称:LCD_Clear
函数作用:LCD 读写数据函数
函数入口:
	Color		清屏颜色(RGB565)
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
************************/
void LCD_Clear(u16 Color)
{
	u32 i=0;
	//设置列:0~240
	LCD_WR_REG(0x2A);
	LCD_WR_DATA(0);	//起始列高八位
	LCD_WR_DATA(0);	//起始列低八位
	LCD_WR_DATA(0);	//停止列高八位
	LCD_WR_DATA((240-1));	//停止列低八位
	//设置行:0~320
	LCD_WR_REG(0x2B);
	LCD_WR_DATA(0);
	LCD_WR_DATA(0);
	LCD_WR_DATA((320-1)>>8);
	//0000 0001 0011 1111 >> 8 = 0000 0001
	LCD_WR_DATA((320-1)&0xFF);
	//0000 0001 0011 1111 & 0000 0000 1111 1111=0011 1111
	//写数据
	LCD_WR_REG(0x2C);
	for(i=0;i<240*320;i++)
	{
		LCD_WR_DATA(Color);//绿色
	}
}

/************************
函数名称:Set_Dispaly
函数作用:设置LCD显示窗口
函数入口:
	(point_x1,point_y1)	起始坐标
	(point_x1,point_y1)	停止坐标
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
************************/
void Set_Dispaly(uint16_t Line_x1,uint16_t Line_y1,uint16_t Line_x2,uint16_t Line_y2)
{
	LCD_WR_REG(0x2A);
	LCD_WR_DATA(Line_x1 >> 8);	
	LCD_WR_DATA(Line_x1 & 0XFF);
	LCD_WR_DATA(Line_x2 >> 8);	
	LCD_WR_DATA(Line_x2 & 0XFF);
	LCD_WR_REG(0x2B);
	LCD_WR_DATA(Line_y1 >> 8);	
	LCD_WR_DATA(Line_y1 & 0XFF);
	LCD_WR_DATA(Line_y2 >> 8);	
	LCD_WR_DATA(Line_y2 & 0XFF);
}

/************************
函数名称:LCD_Point
函数作用:LCD 打点函数
函数入口:
	(point_x,point_y)	坐标
	Color		颜色(RGB565)
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
************************/
void LCD_Point(uint16_t point_x,uint16_t point_y,u16 Color)
{
	LCD_WR_REG(0x2A);
	LCD_WR_DATA(point_x >> 8);	
	LCD_WR_DATA(point_x & 0XFF);
	LCD_WR_DATA(point_x >> 8);	
	LCD_WR_DATA(point_x & 0XFF);
	LCD_WR_REG(0x2B);
	LCD_WR_DATA(point_y >> 8);	
	LCD_WR_DATA(point_y & 0XFF);
	LCD_WR_DATA(point_y >> 8);	
	LCD_WR_DATA(point_y & 0XFF);
	LCD_WR_REG(0x2C);
	LCD_WR_DATA(Color);
}

/************************
函数名称:LCD_Line
函数作用:LCD 画线函数
函数入口:
	Line_x1,Line_y1	起始坐标
	Line_x2,Line_y2	停止坐标
	Wight		线宽
	Color		颜色(RGB565)
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
************************/
void LCD_Line(uint16_t Line_x1,uint16_t Line_y1,uint16_t Line_x2,uint16_t Line_y2,uint16_t Wight,u16 Color)
{
	uint16_t Line_len = 0;
	uint16_t i = 0,j=0;
	uint16_t x=0,y=0;
	uint16_t offset= 0;
	float Slope = 0;
	
	if(Line_x1 == Line_x2)//竖线
	{
		Set_Dispaly(Line_x1-(Wight/2),Line_y1,Line_x1-(Wight/2)+Wight-1,Line_y2);
		if(Line_y1 > Line_y2)
			Line_len = Line_y1-Line_y2;
		else
			Line_len = Line_y2-Line_y1;
		LCD_WR_REG(0x2C);
		for(i=0;i<Line_len*Wight;i++)
			LCD_WR_DATA(Color);	
	}
	else if(Line_y1 == Line_y2)//横线
	{
		Set_Dispaly(Line_x1,Line_y1-(Wight/2),Line_x2,Line_y1-(Wight/2)+Wight-1);
		if(Line_x1 > Line_x2)
			Line_len = Line_x1-Line_x2;
		else
			Line_len = Line_x2-Line_x1;
		LCD_WR_REG(0x2C);
		for(i=0;i<Line_len*Wight;i++)
			LCD_WR_DATA(Color);	
	}
	else//斜线
	{
		if(Line_x1 > Line_x2)
			x = Line_x1-Line_x2;
		else
			x = Line_x2-Line_x1;
		if(Line_y1 > Line_y2)
			y = Line_y1-Line_y2;
		else
			y = Line_y2-Line_y1;
	
		Slope = y/(x*1.0);
		for(i=0;i<x;i++)
		{
			offset = (Slope*i)-(Wight/2);
			for(j=0;j<Wight;j++)
			{
				LCD_Point(Line_x1+i,Line_y1+offset,Color);
				offset++;
			}
		}
	}
}

/************************
函数名称:LCD_Rectangle
函数作用:LCD 画矩形函数
函数入口:
	Line_x1,Line_y1	起始坐标
	Line_x2,Line_y2	停止坐标
	Color		颜色(RGB565)
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
************************/
void LCD_Rectangle(uint16_t Line_x1,uint16_t Line_y1,uint16_t Line_x2,uint16_t Line_y2,uint16_t Color)
{
	uint16_t i=0;
	uint16_t x=0,y=0;
	if(Line_x1 > Line_x2)
		x = Line_x1-Line_x2;
	else
		x = Line_x2-Line_x1;
	if(Line_y1 > Line_y2)
		y = Line_y1-Line_y2;
	else
		y = Line_y2-Line_y1;
	
	Set_Dispaly(Line_x1,Line_y1,Line_x2,Line_y2);
	
	LCD_WR_REG(0x2C);
	for(i=0;i<x*y;i++)
		LCD_WR_DATA(Color);
}

/************************
函数名称:LCD_Rectangle
函数作用:LCD 画矩形函数
函数入口:
	Circ_x,Circ_y	起始坐标
	Circ_r	半径
	Color		颜色(RGB565)
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
************************/
uint8_t Get_CircularPoint(uint16_t Circ_x,uint16_t Circ_y,uint16_t Point_x,uint16_t Point_y,uint16_t Circ_r)
{
	int x=0,y=0;
	
	x=Circ_x-Point_x;
	y=Circ_y-Point_y;
	
	if((Circ_r*Circ_r) > ((x*x)+(y*y)))
		return 1;
	else
		return 0;
}
void LCD_Circular(uint16_t Circ_x,uint16_t Circ_y,uint16_t Circ_r,uint16_t Color)
{
	uint16_t i=0,j=0;

	for(i=Circ_x-Circ_r;i<Circ_x+Circ_r;i++)
	{
		for(j=Circ_y-Circ_r;j<Circ_y+Circ_r;j++)
		{
			if(Get_CircularPoint(Circ_x,Circ_y,i,j,Circ_r)==1)
				LCD_Point(i,j,Color);
		}
	}
}

/************************
函数名称:LCD_DrawFont
函数作用:LCD 写字函数
函数入口:
	start_x,start_y	起始坐标
	Font_wide 字宽
	Font_high	字高
	Back_color	背景颜色(RGB565)
	Font_color	字体颜色(RGB565)
	Font_buf		字模数组指针
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
************************/
void LCD_DrawFont(uint16_t start_x,uint16_t start_y,uint16_t Font_wide,uint16_t Font_high,uint16_t Back_color,uint16_t Font_color,uint8_t *Font_buf)
{
	uint16_t Display_Buf[4] = {0};
	uint16_t i=0,j=0;
	uint16_t Buf_Count = 0;
	
	Display_Buf[0] = start_x;
	Display_Buf[1] = start_y;
	Display_Buf[2] = start_x+Font_wide-1;
	Display_Buf[3] = start_y+Font_high-1;
	Buf_Count = Font_wide*Font_high/8;
	//画LCD显示范围
	Set_Dispaly(Display_Buf[0],Display_Buf[1],Display_Buf[2],Display_Buf[3]);
	
	LCD_WR_REG(0x2C);
	for(i=0;i<Buf_Count;i++)
	{
		for(j=0;j<8;j++)
		{
			//0XA5<<0 & 0X80 = 1010 0101 & 1000 0000 = 1000 0000
			//0XA5<<1 & 0X80 = 0100 1010 & 0000 0000 = 0000 0000
			if((Font_buf[i]<<j)&0X80)
				LCD_WR_DATA(Font_color);
			else
				LCD_WR_DATA(Back_color);
		}
	}
}

/************************
函数名称:LCD_DrawString
函数作用:LCD 写字符串
函数入口:
	start_x			起始x坐标
	start_y			起始y坐标
	Back_color	背景色
	Font_color	字体色
	Fontsize		字体大小(Fontsize_16、Fontsize_24、Fontsize_32)
	*Font_buf		字符串
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
补充说明:
	横向取模
	字节不倒叙
************************/
void LCD_DrawString(uint16_t start_x,uint16_t start_y,uint16_t Back_color,uint16_t Font_color,_FontSize Fontsize,char *Font_buf)
{
	uint16_t x_offset = 0,y_offset = 0;
	int Font_offset = 0;
	x_offset = start_x;
	y_offset = start_y;
	
	//判断字符串是否结束
	while(*Font_buf != '\0')
	{
		//判断汉字?字符?
		if(*Font_buf == 10)
		{
			x_offset = start_x;
			start_y += 16;
			goto PP1;
		}
		if(*Font_buf <= 127)//字符
		{
			Font_offset = ASCII_GetFontAddr(*Font_buf);
			if(Font_offset >= 0)
			{
				switch(Fontsize)
				{
					case Fontsize_16:
						if(x_offset > 232)
						{
							x_offset = start_x;
							y_offset += 16;
						}
						LCD_DrawFont(x_offset,y_offset,8,16,Back_color,Font_color,&ASCII_Font16Buf[Font_offset*16]);
						x_offset += 8;
					break;
					case Fontsize_24:
						if(x_offset > 228)
						{
							x_offset = start_x;
							y_offset += 24;
						}
						LCD_DrawFont(x_offset,y_offset,16,24,Back_color,Font_color,&ASCII_Font24Buf[Font_offset*48]);
						x_offset += 16;//此处改为12可以让字符紧密				
					break;
					case Fontsize_32:
						if(x_offset > 226)
						{
							x_offset = start_x;
							y_offset += 32;
						}
						LCD_DrawFont(x_offset,y_offset,16,32,Back_color,Font_color,&ASCII_Font32Buf[Font_offset*64]);
						x_offset += 16;
					break;
				}	
			}
PP1:
			Font_buf += 1;
		}
		else//汉字
		{
						
			switch(Fontsize)
			{
				case Fontsize_16:
					if(x_offset > 224)
					{
						x_offset = start_x;
						y_offset += 16;
					}	
					Font_offset = GB2312_GetFont16Addr(*Font_buf,*(Font_buf+1));
					if(Font_offset >= 0)
						LCD_DrawFont(x_offset,y_offset,16,16,Back_color,Font_color,&GB2312_Font16Buf[Font_offset*32]);
					x_offset += 16;
				break;
				case Fontsize_24:
					if(x_offset > 212)
					{
						x_offset = start_x;
						y_offset += 24;
					}
					Font_offset = GB2312_GetFont24Addr(*Font_buf,*(Font_buf+1));
					if(Font_offset >= 0)
						LCD_DrawFont(x_offset,y_offset,24,24,Back_color,Font_color,&GB2312_Font24Buf[Font_offset*72]);
					x_offset += 24;
				break;
				case Fontsize_32:
					if(x_offset > 208)
					{
						x_offset = start_x;
						y_offset += 32;
					}
					Font_offset = GB2312_GetFont32Addr(*Font_buf,*(Font_buf+1));
					if(Font_offset >= 0)
						LCD_DrawFont(x_offset,y_offset,32,32,Back_color,Font_color,&GB2312_Font32Buf[Font_offset*128]);
					x_offset += 32;	
				break;
			}	
			Font_buf += 2;
		}
	}
}

/************************
函数名称:LCD_DrawPhoto
函数作用:LCD 画图函数
函数入口:
	start_x,start_y	起始坐标
	Photo_wide 	图片宽
	Photo_high	图片高
	Photo_buf		图片数组指针
函数出口:无
函数作者:WYC
创建时间:2021.05.25
修改时间:2021.05.25
补充说明:
	水平扫描
	高位在前
************************/
void LCD_DrawPhoto(uint16_t start_x,uint16_t start_y,uint16_t Photo_wide,uint16_t Photo_high,const unsigned char *Photo_buf)
{
	uint16_t Display_Buf[4] = {0};
	uint32_t i=0;
	uint32_t Buf_Count = 0,Color_Temp=0;
	
	Display_Buf[0] = start_x;
	Display_Buf[1] = start_y;
	Display_Buf[2] = start_x+Photo_wide-1;
	Display_Buf[3] = start_y+Photo_high-1;
	Buf_Count = Photo_wide*Photo_high*2;
	//画LCD显示范围
	Set_Dispaly(Display_Buf[0],Display_Buf[1],Display_Buf[2],Display_Buf[3]);
	
	LCD_WR_REG(0x2C);
	for(i=0;i<Buf_Count;i+=2)
	{
		Color_Temp = (Photo_buf[i]<<8)|Photo_buf[i+1];
		LCD_WR_DATA(Color_Temp);
	}

}

LCD头文件

#ifndef _TFT_LCD_H_
#define _TFT_LCD_H_

#include "stm32f10x.h"
#include "..\User\API\Delay\delay.h"
#include "font.h"

//LCD在FSMC中寄存器操作
//使用NOR/SRAM的 Bank1.sector1,地址位HADDR[27,26]=16 A16作为数据命令区分线 
//1 1111 1111 1111 1110
//01100000 000000X1 11111111 11111110
//01100000 00000010 00000000 00000000
//注意设置时STM32内部会右移一位对其!
#define LCD_BASE        ((u32)(0x60000000 | 0x0001FFFE))
#define TFTLCD          ((LCD_TypeDef *) LCD_BASE)
//LCD寄存器结构体
typedef struct
{
	u16 LCD_REG;//命令寄存器
	u16 LCD_RAM;
} LCD_TypeDef;
//背光驱动
#define LCD_LED_ON 	GPIO_SetBits(GPIOD,GPIO_Pin_12)
#define LCD_LED_OFF GPIO_ResetBits(GPIOD,GPIO_Pin_12)

typedef enum{
	Fontsize_16 = 0,//字符8*16,汉字16*16
	Fontsize_24,		//字符16*24,汉字24*24
	Fontsize_32			//字符16*32,汉字32*32
}_FontSize; 

//RGB565部分颜色宏定义
#define WHITE         0xFFFF
#define BLACK         0x0000
#define BLUE         	0x001F
#define BRED          0XF81F
#define GRED 			    0XFFE0
#define GBLUE			    0X07FF
#define RED           0xF800
#define MAGENTA       0xF81F
#define GREEN         0x07E0
#define CYAN          0x7FFF
#define YELLOW        0xFFE0
#define BROWN 			 	0XBC40 //棕色
#define BRRED 			 	0XFC07 //棕红色
#define GRAY  			 	0X8430 //灰色

//初始化函数
void LCD_Config(void);
void LCD_PortConfig(void);
void LCD_FSMCConfig(void);
void LCD_9341Config(void);
//底层接口函数
void LCD_WR_REG(volatile uint16_t regval);
void LCD_WR_DATA(volatile uint16_t data);

void Set_Dispaly(uint16_t Line_x1,uint16_t Line_y1,uint16_t Line_x2,uint16_t Line_y2);
//LCD常用函数
void LCD_Clear(u16 Color);
void LCD_Point(uint16_t point_x,uint16_t point_y,u16 Color);
void LCD_Line(uint16_t Line_x1,uint16_t Line_y1,uint16_t Line_x2,uint16_t Line_y2,uint16_t Wight,u16 Color);
void LCD_Rectangle(uint16_t Line_x1,uint16_t Line_y1,uint16_t Line_x2,uint16_t Line_y2,uint16_t Color);
void LCD_Circular(uint16_t Circ_x,uint16_t Circ_y,uint16_t Circ_r,uint16_t Color);
void LCD_DrawFont(uint16_t start_x,uint16_t start_y,uint16_t Font_wide,uint16_t Font_high,uint16_t Back_color,uint16_t Font_color,uint8_t *Font_buf);
void LCD_DrawPhoto(uint16_t start_x,uint16_t start_y,uint16_t Photo_wide,uint16_t Photo_high,const unsigned char *Photo_buf);
void LCD_DrawString(uint16_t start_x,uint16_t start_y,uint16_t Back_color,uint16_t Font_color,_FontSize Fontsize,char *Font_buf);

#endif

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值