STM32利用FSMC驱动ILI9341 LCD显示屏(详尽指南,支持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
    
    

    作者:Mask_ARM

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32利用FSMC驱动ILI9341 LCD显示屏(详尽指南,支持16bit 8080接口)

    发表回复