本次使用的硬件设备为野火的霸道V2开发板,显示器控制芯片型号为ILI9341,实际型号为ST7789V。在编写代码时参考的是ILI9341数据手册,二者差别不大,都是240*320分辨率。

1. 简介

        ILI9341是一个用于TFT液晶显示的单芯片控制驱动器,具有262144色的240RGB x 320像素显示解决方案。ILI9341支持8/9/16/18位数据总线的MCU接口,6/16/18位数据总线RGB接口以及3/4线的SPI接口。移动图像区域可以通过窗口地址功能再内部GRAM来指定。指定的窗口区域可以选择性地更新,因此,可以在图像区域同时独立的显示移动图像。

系统接口:

        8080-Ⅰ/8080-Ⅱ系列MCU的8/9/16/18位接口。

        图形控制的6/16/18位RGB接口。

        3/4线的SPI接口。

        在控制方式上,一般采用16位控制方式(RGB565)。可详读ILI9341数据手册,在3AH寄存器中可进行配置。

 2. 引脚连接

LCD显示屏处的排针:

 开发板上对应的LCD接口:(FSMC_8080模式)

 对应引脚连接:

控制引脚
CS PG12 FSMC_NE4
#RS PE2

FSMC_A23

#WR PD5 FSMC_NWE
#RD PD4

FSMC_NOE

复位和背光引脚
RES PG11 复位
#BK PG6 背光
数据引脚
DB0 PD14 FSMC_D0
DB1 PD15 FSMC_D1
DB2 PD0 FSMC_D2
DB3 PD1 FSMC_D3
DB4 PE7 FSMC_D4
DB5 PE8 FSMC_D5
DB6 PE9 FSMC_D6
DB7 PE10 FSMC_D7
DB8 PE11 FSMC_D8
DB9 PE12 FSMC_D9
DB10 PE13 FSMC_D10
DB11 PE14 FSMC_D11
DB12 PE15 FSMC_D12
DB13 PD8 FSMC_D13
DB14 PD9 FSMC_D14
DB15 PD10 FSMC_D15

        在引脚连接时,特地将LCD的控制引脚和数据引脚与MCU的FSMC外设连接,在使用FSMC模拟8080时序时,这些引脚便可交由FSMC控制,只需将FSMC配置好就可以了。当然,也可使用模拟SPI对这些引脚进行控制。所以在编写代码时,除了读写接口函数配置不同以外,两种控制方式的其他带啊吗都可相同。

3. FSMC与“8080”

如果说你问我:你怎么知道FSMC可以模拟8080?

我只能回答:我也是听别人说的。

首先我们先对比8080与FSMC二者时序的异同。

LCD 8080时序

 注:①写命令;②写数据。

FSMC写NOR时序(模式B)
LCD 8080时序 FSMC 写NOR
#CS 片选 #NEx 片选
RDX 读使能 #NOE 读使能
WRX 写使能 #NWE 写使能
D/CX 数据#命令 A[25:0] 地址线
D[17:0] 数据引脚 D[15:0] 数据引脚

如图可见,LCD的8080时序与FSMC写NOR(模式B)时序近乎相同,数据引脚选用16位模式。

不同的则是FSMC没有数据命令选择引脚,只有地址线,我们可选择地址线中的一根地址线充当数据命令控制引脚即可(0表示命令模式,1表示数据模式)。因此,只要配置好FSMC,便可模拟8080时序驱动LCD屏幕实现数据显示。

3.1 FSMC设备地址

FSMC 存储块

         如图所示,NOR/PSRAM的地址范围为 0x60000000~0x6FFFFFFF。NOR/PSRAM又分为4个存储块,如下图所示,存储块的选择由地址的26和27位控制。

60000000H二进制表示为:0110 0000 0000 0000 0000 0000 0000 0000

                                          [27:26]↑↑

HADDR[27:26]=00表示选择了NOR/PSRAM 1,即起始地址为60000000H

HADDR[27:26]=01表示选择了NOR/PSRAM 2,即起始地址为64000000H

HADDR[27:26]=10表示选择了NOR/PSRAM 3,即起始地址为68000000H

HADDR[27:26]=11表示选择了NOR/PSRAM 4,即起始地址为6C000000H

注意:NOR存储区划分了四个区并有四个专用的片选FSMC_NE[4:1]

那么就以为着我选择NOR/PSRAM 1就需要使用FSMC_NE1对应的片选线。

 外部存储地址:地址位对应地址线

         对于控制LCD屏而言,我们采取的数据宽度为16位即【RGB565】,地址线FSMC_A[24:0]对应着存储器地址HADDR[25:1]。假设我们使用FSMC_A0地址线作为数据命令控制线,选择的存储块为NOR/PSRAM 1 时,地址设置为0x60000000,地址线A0上的电平输出为低电平,表示命令模式;地址设置为0x60000002,地址线A0上的电平输出为高电平,表示数据模式。

        而本次使用的开发板与8080数据命令引脚连接的引脚为PE2(FSMC_A23),对应地址位为HADDR[24]。片选引脚为FSMC_NE4,HADDR[27:26]=11。存储块为NOR/PSRAM 4 ,令地址位的第24位为0时表示命令模式,即0x6C000000;数据模式:0x6D000000。

         当我们在该地址上写入数据时,FSMC便会控制数据线输出对应的数据,读取数据时,也可直接读取对应的地址即可。

3.2 FSMC-NOR/PSRAM配置

API(接口函数):

void FSMC_NORSRAMInit(FSMC_NORSRAMInitTypeDef* FSMC_NORSRAMInitStruct)

FSMC_NORSRAMInitTypeDef 初始化结构体

结构体原型:

/** 
  * @brief  FSMC NOR/SRAM Init structure definition
  */

typedef struct
{
  uint32_t FSMC_Bank;              //选择控制存储块                         
  uint32_t FSMC_DataAddressMux;    //地址总线与数据总线是否复用
  uint32_t FSMC_MemoryType;        //存储器类型
  uint32_t FSMC_MemoryDataWidth;   //设置存储器数据宽度
  uint32_t FSMC_BurstAccessMode;   //设置是否支持突发访问模式
  uint32_t FSMC_AsynchronousWait;  //设置同步等待传输时的等待信号
  uint32_t FSMC_WaitSignalPolarity;//设置等待信号极性
  uint32_t FSMC_WrapMode;          //设置是否支持对齐的突发模式
  uint32_t FSMC_WaitSignalActive;  //配置等待信号在等待前有效还是等待期间有效
  uint32_t FSMC_WriteOperation;    //设置写使能
  uint32_t FSMC_WaitSignal;        //设置等待状态插入使能 
  uint32_t FSMC_ExtendedMode;      //设置扩展模式使能
  uint32_t FSMC_WriteBurst;        //设置突发模式使能

/*当不使用扩展模式时,本参数用于配置读写时序,否则用于配置读时序*/
  FSMC_NORSRAMTimingInitTypeDef* FSMC_ReadWriteTimingStruct; 
/*当使用扩展模式时,本参数用于配置写时序*/
  FSMC_NORSRAMTimingInitTypeDef* FSMC_WriteTimingStruct;     
}FSMC_NORSRAMInitTypeDef;

FSMC_NORSRAMTimingInitTypeDef

时序结构体:

/** 
  * @brief  Timing parameters For NOR/SRAM Banks  
  */

typedef struct
{
  uint32_t FSMC_AddressSetupTime;      //地址建立时间
  uint32_t FSMC_AddressHoldTime;       //地址保持时间
  uint32_t FSMC_DataSetupTime;         //数据建立时间
  uint32_t FSMC_BusTurnAroundDuration; //总线转换周期
  uint32_t FSMC_CLKDivision;           //时钟分频因子(异步模式下无效)
  uint32_t FSMC_DataLatency;           //数据延迟时间(异步模式下无效)
  uint32_t FSMC_AccessMode;            //设置访问模式
}FSMC_NORSRAMTimingInitTypeDef;

3.3 配置FSMC

时钟和中断优先级的配置都在main.c中做统一配置。

引脚配置:除了RES和BK引脚以为,其他引脚都配置为复用输出模式。这里为了减少代码行数,就直接使用16进制代替GPIO_Pin。

void ILI9341_GPIO_Config(void)
{
	//复位和背光引脚:通用推挽输出
	GPIO_InitTypeDef ILI9341_GPIO;
	ILI9341_GPIO.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_11;
	ILI9341_GPIO.GPIO_Mode = GPIO_Mode_Out_PP;
	ILI9341_GPIO.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOG,&ILI9341_GPIO);
	//数据引脚和控制引脚:复用推挽输出
	//GPIOD
	ILI9341_GPIO.GPIO_Pin = 0xC733;
	ILI9341_GPIO.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_Init(GPIOD,&ILI9341_GPIO);
	//GPIOE
	ILI9341_GPIO.GPIO_Pin = 0xFF84;//PE 2、7~15
	GPIO_Init(GPIOE,&ILI9341_GPIO);
	//GPIOG
	ILI9341_GPIO.GPIO_Pin = GPIO_Pin_12;
	GPIO_Init(GPIOG,&ILI9341_GPIO);
}

FSMC配置:此处就不做过多解释,详情参考STM32F10x用户手册。

void ILI9341_FSMC_Config(void)
{
	FSMC_NORSRAMDeInit(FSMC_Bank1_NORSRAM4);		//复位存储块NOR/PSRAM 4
	FSMC_NORSRAMInitTypeDef ili9341_FSMC={0};		//NOR初始化结构体
	/*时序配置*/
	FSMC_NORSRAMTimingInitTypeDef FSMC_ReadWrite_Timing={0};//时序结构体
	FSMC_ReadWrite_Timing.FSMC_AddressSetupTime = 0x01;//地址建立时间
	FSMC_ReadWrite_Timing.FSMC_DataSetupTime = 0x04;//数据建立时间
	FSMC_ReadWrite_Timing.FSMC_AccessMode = FSMC_AccessMode_B;//访问模式:模式B
	
	/*以下配置与模式B无关*/
	FSMC_ReadWrite_Timing.FSMC_AddressHoldTime = 0x00;//地址保持时间
	//仅适用于总线复用模式的NOR闪存操作
	FSMC_ReadWrite_Timing.FSMC_BusTurnAroundDuration = 0x00;//总线转换周期
	//在访问异步NOR闪存、SRAM或ROM时,这个参数不起作用
	FSMC_ReadWrite_Timing.FSMC_CLKDivision = 0x00;//时钟分频因子
	FSMC_ReadWrite_Timing.FSMC_DataLatency = 0x00;//数据延迟时间
	
	/*NOR初始化配置*/
	ili9341_FSMC.FSMC_Bank = FSMC_Bank1_NORSRAM4;	// NOR/PSRAM 4
	ili9341_FSMC.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable;//地址数据总线不复用
	ili9341_FSMC.FSMC_MemoryType = FSMC_MemoryType_NOR;
	ili9341_FSMC.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;
	ili9341_FSMC.FSMC_BurstAccessMode = FSMC_BurstAccessMode_Disable;//同步突发模式
	ili9341_FSMC.FSMC_AsynchronousWait = FSMC_AsynchronousWait_Disable;//不使能等待
	ili9341_FSMC.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
	ili9341_FSMC.FSMC_WrapMode = FSMC_WrapMode_Disable;//不支持对齐突发模式
	ili9341_FSMC.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;//等待信号在等待前有效
	ili9341_FSMC.FSMC_WriteOperation = FSMC_WriteOperation_Enable;//写使能
	ili9341_FSMC.FSMC_WaitSignal = FSMC_WaitSignal_Disable;//不使能等待状态插入
	ili9341_FSMC.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable;//不使能扩展模式
	ili9341_FSMC.FSMC_WriteBurst = FSMC_WriteBurst_Disable;//不使能写突发模式
	ili9341_FSMC.FSMC_ReadWriteTimingStruct = &FSMC_ReadWrite_Timing;
	ili9341_FSMC.FSMC_WriteTimingStruct = &FSMC_ReadWrite_Timing;
	
	FSMC_NORSRAMInit(&ili9341_FSMC);
	FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM4,ENABLE);//使能FSMC
}

        接下来就是写数据、写命令和读数据函数。由于使用了FSMC外设,所以读写数据都可直接对地址操作。这里我定义的地址为:

#define FSMC_ADDR_CMD()     *(volatile uint16_t *)0x6C000000
#define FSMC_ADDR_DATA()    *(volatile uint16_t *)0x6D000000

改地址为32位地址,因为我们读取的数据位数为16位,所以做了个地址对齐并使用volatile对这段地址进行防止被优化。再取个*表示值,可读取和改变这个值。

/*
	\brief:	写指令
	\param:	cmd: ili9341控制指令
	\retval:	none
*/
void ILI9341_WriteCmd(uint16_t cmd)
{
	FSMC_ADDR_CMD() = cmd;
}
/*
	\brief:	写数据
	\param:	data: 写入的数据
	\retval:	none
*/
void ILI9341_WriteData(uint16_t data)
{
	FSMC_ADDR_DATA() = data;
}
/*
	\brief:	读数据
	\param:	none
	\retval:	none
*/
uint16_t ILI9341_ReadData(void)
{
	return FSMC_ADDR_DATA();
}

在此,对FSMC的操作已经结束,重要的就是用到这三个函数对ILI9341进行读写操作,换言之,使用SPI也是用到读写函数。

4. 4线SPI

此处先略。

5. LCD配置

        驱动LCD屏的关键是在屏幕任意位置画一个点,相对于OLED的画点只是一个位表示亮和不亮,这里的画点,一个点表示一个16位的RGB像素点。

5.1 获取LCD显示屏ID

         读取ID指令为04H,在未对屏幕进行任何配置前,可用该指令验证编写好的读写函数是否可行。

/*
	\brief:	读显示ID信息
	\param:	none
	\retval:	ID信息
*/
uint16_t Read_LCD_ID(void)
{
	uint16_t id=0;
	ILI9341_WriteCmd(0x04);//读显示ID信息
	ILI9341_ReadData();
	ILI9341_ReadData();//LCD制造商ID
	id = ILI9341_ReadData();//驱动版文号ID
	id <<= 8;
	id |= (ILI9341_ReadData()&0x00FF);//驱动ID
	return id;
}

        读取ID信息,首先需要使用发送命令函数发送指令0x04,随后直接读取ID信息。这里我只需要后两个ID数据,前两个字节数据就不做保存。ILI9341的16位ID号为9341,ST7789V的ID号为8552。

根据ID号配置LCD屏初始化序列,当然,在知道自己所用LCD型号时不需要根据ID配置。

/*
	\brief:	ILI9341初始化序列配置(寄存器配置)
	\param:	none
	\retval:	none
*/
void ILI9341_InitSequence(void)
{
	if(Read_LCD_ID() == 0x9341)
	{
		/*  Power control B (CFh)  */
		ILI9341_WriteCmd ( 0xCF  );
		ILI9341_WriteData ( 0x00  );
		ILI9341_WriteData ( 0x81  );
		ILI9341_WriteData ( 0x30  );
		
		/*  Power on sequence control (EDh) */
		ILI9341_WriteCmd ( 0xED );
		ILI9341_WriteData ( 0x64 );
		ILI9341_WriteData ( 0x03 );
		ILI9341_WriteData ( 0x12 );
		ILI9341_WriteData ( 0x81 );
		
		/*  Driver timing control A (E8h) */
		ILI9341_WriteCmd ( 0xE8 );
		ILI9341_WriteData ( 0x85 );
		ILI9341_WriteData ( 0x10 );
		ILI9341_WriteData ( 0x78 );
		
		/*  Power control A (CBh) */
		ILI9341_WriteCmd ( 0xCB );
		ILI9341_WriteData ( 0x39 );
		ILI9341_WriteData ( 0x2C );
		ILI9341_WriteData ( 0x00 );
		ILI9341_WriteData ( 0x34 );
		//ILI9341_WriteData ( 0x02 );
		ILI9341_WriteData ( 0x06 ); //原来是0x02改为0x06可防止液晶显示白屏时有条纹的情况
		
		/* Pump ratio control (F7h) */
		ILI9341_WriteCmd ( 0xF7 );
		ILI9341_WriteData ( 0x20 );
		
		/* Driver timing control B */
		ILI9341_WriteCmd ( 0xEA );
		ILI9341_WriteData ( 0x00 );
		ILI9341_WriteData ( 0x00 );
		
		/* Frame Rate Control (In Normal Mode/Full Colors) (B1h) */
		ILI9341_WriteCmd ( 0xB1 );
		ILI9341_WriteData ( 0x00 );
		ILI9341_WriteData ( 0x1B );
		
		/*  Display Function Control (B6h) */
		ILI9341_WriteCmd ( 0xB6 );
		ILI9341_WriteData ( 0x0A );
		ILI9341_WriteData ( 0xA2 );
		
		/* Power Control 1 (C0h) */
		ILI9341_WriteCmd ( 0xC0 );
		ILI9341_WriteData ( 0x35 );
		
		/* Power Control 2 (C1h) */
		ILI9341_WriteCmd ( 0xC1 );
		ILI9341_WriteData ( 0x11 );
		
		/* VCOM Control 1 (C5h) */
		ILI9341_WriteCmd ( 0xC5 );
		ILI9341_WriteData ( 0x45 );
		ILI9341_WriteData ( 0x45 );
		
		/*  VCOM Control 2 (C7h)  */
		ILI9341_WriteCmd ( 0xC7 );
		ILI9341_WriteData ( 0xA2 );
		
		/* Enable 3G (F2h) */
		ILI9341_WriteCmd ( 0xF2 );
		ILI9341_WriteData ( 0x00 );
		
		/* Gamma Set (26h) */
		ILI9341_WriteCmd ( 0x26 );
		ILI9341_WriteData ( 0x01 );
		
		/* Positive Gamma Correction */
		ILI9341_WriteCmd ( 0xE0 ); //Set Gamma
		ILI9341_WriteData ( 0x0F );
		ILI9341_WriteData ( 0x26 );
		ILI9341_WriteData ( 0x24 );
		ILI9341_WriteData ( 0x0B );
		ILI9341_WriteData ( 0x0E );
		ILI9341_WriteData ( 0x09 );
		ILI9341_WriteData ( 0x54 );
		ILI9341_WriteData ( 0xA8 );
		ILI9341_WriteData ( 0x46 );
		ILI9341_WriteData ( 0x0C );
		ILI9341_WriteData ( 0x17 );
		ILI9341_WriteData ( 0x09 );
		ILI9341_WriteData ( 0x0F );
		ILI9341_WriteData ( 0x07 );
		ILI9341_WriteData ( 0x00 );
		
		/* Negative Gamma Correction (E1h) */
		ILI9341_WriteCmd ( 0XE1 ); //Set Gamma
		ILI9341_WriteData ( 0x00 );
		ILI9341_WriteData ( 0x19 );
		ILI9341_WriteData ( 0x1B );
		ILI9341_WriteData ( 0x04 );
		ILI9341_WriteData ( 0x10 );
		ILI9341_WriteData ( 0x07 );
		ILI9341_WriteData ( 0x2A );
		ILI9341_WriteData ( 0x47 );
		ILI9341_WriteData ( 0x39 );
		ILI9341_WriteData ( 0x03 );
		ILI9341_WriteData ( 0x06 );
		ILI9341_WriteData ( 0x06 );
		ILI9341_WriteData ( 0x30 );
		ILI9341_WriteData ( 0x38 );
		ILI9341_WriteData ( 0x0F );
		
		/* memory access control set */
		ILI9341_WriteCmd ( 0x36 ); 	
		ILI9341_WriteData ( 0xC8 );    /*竖屏  左上角到 (起点)到右下角 (终点)扫描方式*/
		
		/* column address control set */
		ILI9341_WriteCmd ( 0x2A ); 
		ILI9341_WriteData ( 0x00 );
		ILI9341_WriteData ( 0x00 );
		ILI9341_WriteData ( 0x00 );
		ILI9341_WriteData ( 0xEF );
		
		/* page address control set */
		ILI9341_WriteCmd ( 0x2B ); 
		ILI9341_WriteData ( 0x00 );
		ILI9341_WriteData ( 0x00 );
		ILI9341_WriteData ( 0x01 );
		ILI9341_WriteData ( 0x3F );
		
		/*  Pixel Format Set (3Ah)  */
		ILI9341_WriteCmd ( 0x3a ); 
		ILI9341_WriteData ( 0x55 );
		
		/* Sleep Out (11h)  */
		ILI9341_WriteCmd ( 0x11 );	
		Delay_ms(120);
		/* Display ON (29h) */
		ILI9341_WriteCmd ( 0x29 ); 
	}
	if(Read_LCD_ID() == 0x8552)
	{
		 /*  Power control B (CFh)  */
		ILI9341_WriteCmd ( 0xCF  );
		ILI9341_WriteData ( 0x00  );
		ILI9341_WriteData ( 0xC1  );
		ILI9341_WriteData ( 0x30  );
		
		/*  Power on sequence control (EDh) */
		ILI9341_WriteCmd ( 0xED );
		ILI9341_WriteData ( 0x64 );
		ILI9341_WriteData ( 0x03 );
		ILI9341_WriteData ( 0x12 );
		ILI9341_WriteData ( 0x81 );
		
		/*  Driver timing control A (E8h) */
		ILI9341_WriteCmd ( 0xE8 );
		ILI9341_WriteData ( 0x85 );
		ILI9341_WriteData ( 0x10 );
		ILI9341_WriteData ( 0x78 );
		
		/*  Power control A (CBh) */
		ILI9341_WriteCmd ( 0xCB );
		ILI9341_WriteData ( 0x39 );
		ILI9341_WriteData ( 0x2C );
		ILI9341_WriteData ( 0x00 );
		ILI9341_WriteData ( 0x34 );
		ILI9341_WriteData ( 0x02 );
		
		/* Pump ratio control (F7h) */
		ILI9341_WriteCmd ( 0xF7 );
		ILI9341_WriteData ( 0x20 );
		
		/* Driver timing control B */
		ILI9341_WriteCmd ( 0xEA );
		ILI9341_WriteData ( 0x00 );
		ILI9341_WriteData ( 0x00 );
		
		
		/* Power Control 1 (C0h) */
		ILI9341_WriteCmd ( 0xC0 );   //Power control
		ILI9341_WriteData ( 0x21 );  //VRH[5:0]
		
		/* Power Control 2 (C1h) */
		ILI9341_WriteCmd ( 0xC1 );   //Power control
		ILI9341_WriteData ( 0x11 );  //SAP[2:0];BT[3:0]
		
		/* VCOM Control 1 (C5h) */
		ILI9341_WriteCmd ( 0xC5 );
		ILI9341_WriteData ( 0x2D );
		ILI9341_WriteData ( 0x33 );
		
		/*  VCOM Control 2 (C7h)  */
	//	ILI9341_WriteCmd ( 0xC7 );
	//	ILI9341_WriteData ( 0XC0 );
		
		/* memory access control set */
		ILI9341_WriteCmd ( 0x36 );   //Memory Access Control
		ILI9341_WriteData ( 0x00 );  /*竖屏  左上角到 (起点)到右下角 (终点)扫描方式*/
		
		ILI9341_WriteCmd(0x3A);   
		ILI9341_WriteData(0x55); 
		
		  /* Frame Rate Control (In Normal Mode/Full Colors) (B1h) */
		ILI9341_WriteCmd ( 0xB1 );
		ILI9341_WriteData ( 0x00 );
		ILI9341_WriteData ( 0x17 );
		
		/*  Display Function Control (B6h) */
		ILI9341_WriteCmd ( 0xB6 );
		ILI9341_WriteData ( 0x0A );
		ILI9341_WriteData ( 0xA2 );
		
		ILI9341_WriteCmd(0xF6);    			
		ILI9341_WriteData(0x01); 
		ILI9341_WriteData(0x30); 
		
		/* Enable 3G (F2h) */
		ILI9341_WriteCmd ( 0xF2 );
		ILI9341_WriteData ( 0x00 );
		
		/* Gamma Set (26h) */
		ILI9341_WriteCmd ( 0x26 );
		ILI9341_WriteData ( 0x01 );
		
		/* Positive Gamma Correction */
		ILI9341_WriteCmd(0xe0); //Positive gamma
		ILI9341_WriteData(0xd0);         
		ILI9341_WriteData(0x00); 
		ILI9341_WriteData(0x02); 
		ILI9341_WriteData(0x07); 
		ILI9341_WriteData(0x0b); 
		ILI9341_WriteData(0x1a); 
		ILI9341_WriteData(0x31); 
		ILI9341_WriteData(0x54); 
		ILI9341_WriteData(0x40); 
		ILI9341_WriteData(0x29); 
		ILI9341_WriteData(0x12); 
		ILI9341_WriteData(0x12); 
		ILI9341_WriteData(0x12); 
		ILI9341_WriteData(0x17);

		/* Negative Gamma Correction (E1h) */
		ILI9341_WriteCmd(0xe1); //Negative gamma
		ILI9341_WriteData(0xd0); 
		ILI9341_WriteData(0x00); 
		ILI9341_WriteData(0x02); 
		ILI9341_WriteData(0x07); 
		ILI9341_WriteData(0x05); 
		ILI9341_WriteData(0x25); 
		ILI9341_WriteData(0x2d); 
		ILI9341_WriteData(0x44); 
		ILI9341_WriteData(0x45); 
		ILI9341_WriteData(0x1c); 
		ILI9341_WriteData(0x18); 
		ILI9341_WriteData(0x16); 
		ILI9341_WriteData(0x1c); 
		ILI9341_WriteData(0x1d); 
		
		/* Sleep Out (11h)  */
		ILI9341_WriteCmd ( 0x11 );	  //Exit Sleep
		Delay_ms(120);
		
		/* Display ON (29h) */
		ILI9341_WriteCmd ( 0x29 );   //Display on
		
		ILI9341_WriteCmd(0x2c);
	}
}

为了快速使用,直接复制官方提供的初始化序列。也可自行查看寄存器进行配置。

5.2 初始化LCD

/*
	\brief:	ILI9341驱动LCD屏初始化配置
	\param:	none
	\retval:	none
*/

void ILI9341_LCD_InitConfig(void)
{
	ILI9341_GPIO_Config();	//引脚配置
	ILI9341_FSMC_Config();	//FSMC外设配置
	//复位
	ILI9341_RES(Bit_RESET); //开始复位
	Delay_ms(5);
	ILI9341_RES(Bit_SET);   //结束复位
	Delay_ms(5);
	
	ILI9341_InitSequence();	//配置初始化序列
	ILI9341_BK(Bit_RESET);	//打开背光
}

(1)配置GPIO工作模式;

(2)配置FSMC外设;

(3)复位LCD;

(4)配置LCD初始化序列;

(5)打开背光。

5.3 画点函数(重要)

        在控制LCD显示时,没有配置LCD存储器的扫描方式,即保持初始化序列中的配置(从上到下,从左到右),(0,0)为屏幕左上角的顶点。

5.3.1 设置坐标

         SC[15:0]设置起始列地址;EC[15:0]设置结束列地址。列地址可以设置一个范围,也可设置为指定一列。设置行地址(2BH)也是如此。

/*
	\brief:	设置坐标
	\param:	x: 横坐标
				y: 列坐标
	\retval:	none
*/

void LCD_SetCoord(uint16_t x,uint16_t y)
{
	ILI9341_WriteCmd(0x2A);//设置列地址
	//起始地址
	ILI9341_WriteData(x>>8);//高位
	ILI9341_WriteData(x&0xFF);
	//结束地址(与起始地址相同)
	ILI9341_WriteData(x>>8);//高位
	ILI9341_WriteData(x&0xFF);
	
	ILI9341_WriteCmd(0x2B);//设置页地址
	//起始地址
	ILI9341_WriteData(y>>8);//高位
	ILI9341_WriteData(y&0xFF);
	//结束地址(与起始地址相同)
	ILI9341_WriteData(y>>8);//高位
	ILI9341_WriteData(y&0xFF);
}

        在设置行列地址时,将起始地址和结束地址设置为同一只,行列交叉的点便是设置的坐标点。

5.3.2 画点函数

        在画一个点前,首先需要给定一个坐标点,然后再把像素点画上去。当然,不能直接使用写数据函数直接把像素点画上去,还得发送写存储器指令。

/*
	\brief:	画点函数(在屏幕的任意位置画一个像素)
	\param:	x: 横坐标
				y: 列坐标
				colour: 颜色(RGB565)
	\retval:	none
*/
void LCD_DrawDot(uint16_t x,uint16_t y,uint16_t colour)
{
	if(x+1>LCD_WIDTH || y+1>LCD_HIGH) return ;//超出屏幕范围
	LCD_SetCoord(x,y);//设置坐标
	ILI9341_WriteCmd(0x2C);
	ILI9341_WriteData(colour);//绘制一个像素点
}

5.4 清屏函数

        将屏幕清成一个颜色,这里使用的画点函数,将屏幕上的240×320个像素点逐一画上一个点。

void LCD_Clear(uint16_t colour)
{
	uint16_t i,j;
	for(i=0;i<LCD_WIDTH;i++)
	{
		for(j=0;j<LCD_HIGH;j++)
		{
			LCD_DrawDot(i,j,colour);
		}
	}
}

        当然,这个清屏函数会很慢,再调用这个函数清屏时,会重复发送设置坐标函数和写储存器指令。快一点的方法就是在设置行列坐标时,直接将坐标设置为一整个屏幕范围,再发送一次写存储器指令,然后直接写像素点到屏幕上即可。可省去反复设置坐标的时间。

void LCD_Clear(uint16_t colour)
{
	uint16_t i,j;
	ILI9341_WriteCmd(0x2A);//设置列地址
	//起始地址
	ILI9341_WriteData(0>>8);//高位
	ILI9341_WriteData(0&0xFF);
	//结束地址
	ILI9341_WriteData(239>>8);//高位
	ILI9341_WriteData(239&0xFF);
	
	ILI9341_WriteCmd(0x2B);//设置页地址
	//起始地址
	ILI9341_WriteData(0>>8);//高位
	ILI9341_WriteData(0&0xFF);
	//结束地址
	ILI9341_WriteData(319>>8);//高位
	ILI9341_WriteData(319&0xFF);
	
	ILI9341_WriteCmd(0x2C);//写存储
	for(i=0;i<LCD_WIDTH;i++)
	{
		for(j=0;j<LCD_HIGH;j++)
		{
			ILI9341_WriteData(colour);//绘制一个像素点
		}
	}
}

6. 显示字符串

        画点函数编写完成,便可根据该函数封装各种显示函数,这里我先编写显示字符串函数用于测试。随后可编写显示汉字,一直画直线画园等函数。

6.1 取模

         在编写显示函数前,先制作ascii码字库,然后根据字库的显示配置编写显示函数。这里采取的阳码格式,1为点亮(1画一个点,0不画点);高位在前行列式,先在一列中画一个字节像素点,高位在最前面,随后绘制下一列。

6.2 显示字符


/*
	\brief:	在屏幕任意位置显示字符(行列式,高位在前)
	\param:	x:横坐标,y:纵坐标
				w:字符宽度  h字符高度  colour:显示颜色
	\retval:	none
*/
void LCD_DisplayChar(uint16_t x,uint16_t y,uint16_t w,uint8_t h,uint8_t c,uint16_t colour)
{
	uint16_t x0=x;//记录初始位置
	uint16_t y0=y;
	uint8_t temp;
	uint16_t i,j;
	for(i=0;i<(w*h/8);i++)//计算字节数
	{
		//从字库里读取一字节
		switch(w)
		{
			case 8:temp = ascii_8x16[c-' '][i];break;
			case 16:break;
			default :temp = ascii_8x16[c-' '][i];break;
		}
		for(j=0;j<8;j++)
		{
			if(temp & 0x80)	//高位在前
			{
				LCD_DrawDot(x,y,colour);
			}
			temp <<=1;
			y++;
		}
		x++;
		if(x-x0==w)
		{
			y0 += 8;
			x = x0;
		}
		y = y0;
	}
}

        例:一个8×16的字符生成的字库为16个字节,排布顺序为行列式高位在前,阳码。所以我们在编写函数时可按照分析字节的方式对一个坐标的判断是否绘制,绘制完一个字节表示画完一列的8个像素,可以再对下一列进行画点。同时判断画的列数是否等于字符宽度:这里字符宽度为8,高度16,从开始画点开始一共画了8列8个字节时,再从该字符的起始横坐标开始,纵坐标向下偏移一个字节再画剩下的8个字节。对不同的字符大小,可根据判断字符大小偏移坐标。

(1)使用x0,y0保存起始坐标,防止坐标偏移以后找不到了。

(2)定义一个uint8_t类型的变量temp保存要绘制的一个字节数据。

(3)定义变量i用于记录字节数,j用于记录字节位。

(4)首先根据字符宽度选择对应的字库,ascii的宽度是高的一半,c-' '就是该字符在数组中的位置,用temp保存字节用于显示。

(5)取模时高位在前,先对高位进行判断,为真则画一个点,纵坐标+1,接下来判断下一位,直到一个字节都画完。

(6)横坐标+1,并判断画的横坐标是否与宽度相同。不相同则将纵坐标恢复到起始值,相同则需将横坐标恢复至起始值并改变纵坐标的起始值(向下偏移一个字节),在将纵坐标恢复至改变的起始站。反复将所有一个像素点的所有字节都画完即可。

调用该函数就可以在屏幕任意位置绘制带颜色的字符,字符大小可改变,需要自己将ascii码取模,保存到工程中在用temp去取即可。

6.3 显示字符串

        这个函数就比较简单了,当我们可以在屏幕上绘制一个字符时,就可以在此基础上绘制多个。

void LCD_DisplayString(uint16_t x,uint16_t y,uint16_t w,uint8_t h,uint8_t *pstr,uint16_t colour)
{
	uint8_t *p=pstr;
	while(*p != '\0')
	{
		LCD_DisplayChar(x,y,w,h,*p,colour);
		x += w;
		p++;//取下一个字符数据
	}
}

这里传入的是一个uint8_t *pstr,可以理解为一个字符串的首地址。可以根据这个首地址访问到字符串中的所有字符,在调用显示字符函数足以显示即可。

(1)定义一个指针指向这个字符串的首地址,一般不要直接使用传入的首地址,因为在下面的指针偏移中会改变指针的指向。

(2)字符串以‘\0’结束,只要等于这个值可以理解为字符串已经全部绘制完成。

(3)绘制一个字符串,横坐标偏移一个字符宽度,防止下一个字符与当前字符重合。

由于屏幕宽度有限,当纵坐标超过屏幕范围,将字符将不会显示到屏幕上,可修改函数,在屏幕剩余横坐标不足显示一个字符时换一行显示。

7. 显示

        在屏幕上显示"hello world !",字体颜色红色,屏幕背景颜色绿色。

int main(void)
{
	CLOCK_Config();		//配置外设时钟
	NVIC_Config();		//中断优先级配置
	ILI9341_LCD_InitConfig();
	
	LCD_Clear(YELLOW);//将屏幕清为绿色
	LCD_DisplayString(10,0,8,16,(uint8_t *)"hello world !",RED);//从(10,0)坐标开始显示字符串
	while(1)
	{
		
	}
}

LCD显示效果:

LCD显示

 8. 显示中文

        由于STM32的能存储的数据有限,可将几个汉字取模保存到flash中,无法保存一个汉字字库,下篇将讲述从外部flash中读取数据至LCD显示。

附件:ILI9341显示屏驱动工程

链接:https://pan.baidu.com/s/1R919i2Lh0lL-YUvKrh96sg?pwd=1234 
提取码:1234

2023/07/15

物联沃分享整理
物联沃-IOTWORD物联网 » STM32驱动LCD显示屏详解

发表评论