【STM32篇】驱动LCD显示屏

这篇具有很好参考价值的文章主要介绍了【STM32篇】驱动LCD显示屏。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本次使用的硬件设备为野火的霸道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寄存器中可进行配置。

stm32驱动lcd屏,M3,stm32,嵌入式硬件,单片机

 2. 引脚连接

LCD显示屏处的排针:

stm32驱动lcd屏,M3,stm32,嵌入式硬件,单片机

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

stm32驱动lcd屏,M3,stm32,嵌入式硬件,单片机

 对应引脚连接:

控制引脚
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”

stm32驱动lcd屏,M3,stm32,嵌入式硬件,单片机

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

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

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

stm32驱动lcd屏,M3,stm32,嵌入式硬件,单片机
LCD 8080时序

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

stm32驱动lcd屏,M3,stm32,嵌入式硬件,单片机
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设备地址

stm32驱动lcd屏,M3,stm32,嵌入式硬件,单片机
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

stm32驱动lcd屏,M3,stm32,嵌入式硬件,单片机

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

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

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

stm32驱动lcd屏,M3,stm32,嵌入式硬件,单片机

         对于控制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。

stm32驱动lcd屏,M3,stm32,嵌入式硬件,单片机

         当我们在该地址上写入数据时,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

stm32驱动lcd屏,M3,stm32,嵌入式硬件,单片机

         读取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 设置坐标

stm32驱动lcd屏,M3,stm32,嵌入式硬件,单片机

         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 画点函数

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

stm32驱动lcd屏,M3,stm32,嵌入式硬件,单片机

/*
	\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 清屏函数

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

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 取模

stm32驱动lcd屏,M3,stm32,嵌入式硬件,单片机

         在编写显示函数前,先制作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;
	}
}

        例:一个8x16的字符生成的字库为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显示效果:

stm32驱动lcd屏,M3,stm32,嵌入式硬件,单片机
LCD显示

 8. 显示中文

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

附件:ILI9341显示屏驱动工程

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

2023/07/15文章来源地址https://www.toymoban.com/news/detail-661195.html

到了这里,关于【STM32篇】驱动LCD显示屏的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • ESP32设备驱动-I2C-LCD1602显示屏驱动

    LCD1602液晶显示器是广泛使用的一种字符型液晶显示模块。它是由字符型液晶显示屏(LCD)、控制驱动主电路HD44780及其扩展驱动电路HD44100,以及少量电阻、电容元件和结构件等装配在PCB板上而组成。 通过前面的实例我们知道,并口方式连接LCD1602将占用一定数量的GPIO口,在

    2024年02月07日
    浏览(54)
  • STM32F103驱动oled显示屏

    oled显示屏和其他显示屏类似,不过他只有0.96英寸,屏幕较小,但是使用起来比较方便。有二种驱动方式,分别为IIC,和SPI驱动。驱动方式比较简单。IIC驱动的话只需要4根线,电源,地线,数据线,和时钟线。 我这里使用的是IIC协议驱动oled显示屏,如果想了解IIC协议的可以看

    2024年02月11日
    浏览(52)
  • 6.5物联网RK3399项目开发实录-驱动开发之LCD显示屏使用(wulianjishu666)

    90款行业常用传感器单片机程序及资料【stm32,stc89c52,arduino适用】 链接:https://pan.baidu.com/s/1M3u8lcznKuXfN8NRoLYtTA?pwd=c53f  ======================================================== AIO-3399J开发板外置了两个LCD屏接口,一个是EDP,一个是LVDS,接口对应板子上的位置如下图: DTS配置 引脚配置 AIO-3

    2024年04月09日
    浏览(49)
  • STM32单片机LED显示屏驱动原理与实现

    STM32单片机驱动LED显示屏的原理与实现方法与Arduino类似,但涉及到的具体硬件资源和库函数可能会有所不同。下面是一个详细的介绍:   原理: STM32单片机驱动LED显示屏的原理是通过控制GPIO引脚的电平状态来控制LED的亮灭。通过设置引脚的输出电平为高电平(VCC)或低电平

    2024年02月10日
    浏览(51)
  • LCD1602液晶显示屏

    主函数 LCD1602.c LCD1602.h 接线图:   1、1602屏幕=16x2=32个字符,总共有32个字符  2、每个字符由35个像素组成 每个像素由一小块液晶控制 --------------------------------------------------------------------------------------------------------------------------------- 液晶的控制原理: 不施加电压——液晶完

    2024年02月07日
    浏览(50)
  • FPGA实现LCD显示屏显示彩条

    目录 总体设计  读显示屏ID  读显示屏ID代码 时钟分频  时钟分频代码  LCD显示 lcd显示模块 LCD驱动模块 lcd驱动代码 顶层模块 顶层模块代码 系统总体分为五个模块,分别是:rd_id(读显示屏ID模块),clk_div(时钟分频模块),lcd_display(lcd屏显示模块),lcd_driver(lcd屏驱动模块),和顶

    2024年02月16日
    浏览(39)
  • 矩阵键盘控制LCD1602显示屏显示数字

     主函数部分,其中的LCD1602.h的头文件是在哔哩哔哩江科大自化协的博主的视频资料 总结:     首先是我学习时遇到的问题: 在我一开始运行的时候出现的问题就是,一开始在给主函数的keynumber赋值的时候,等号的左值是叫做Matrixkey的函数,当我按下1按键时显示屏显示01,

    2024年02月11日
    浏览(45)
  • LCD拼接屏、LED显示屏和OLED显示屏的主要区别

    我们在生活或工作中经常看到大大小小的显示屏,但很多人却分不清楚这些屏到底属于哪一类,今天sostron与大家一起来分享下关于:LCD拼接屏、LED显示屏、OLED透明屏三者的区别。 LCD拼接屏、LED显示屏和OLED显示屏是不同类型的显示技术,它们在构成、工作原理和特点上存在明

    2024年02月17日
    浏览(48)
  • STM32F407驱动GC9A01+CST816D触摸显示屏

    STM32F407驱动GC9A01+CST816D触摸显示屏 GC9A01是一款spi接口的1.28寸圆形屏,分辨率240*240,3.3v供电。 CST816D是一款IIC接口的触摸屏,模块上有4根信号线RST-复位线,INT-触摸中断线,当触摸屏检测到触摸信号后会输出高电平,SCL-数据时钟线,SDA-数据线。如果只是简单的使用INT线可以不

    2024年01月17日
    浏览(60)
  • LCD12864显示屏原理及使用教程

            LCD12864液晶显示模块是 128×64点阵的汉字图形型液晶显示模块,可显示汉字及图形,内置 8192个中文汉字(16X16 点阵)(需带有字库的型号才能显示中文)、128 个字符(8X16 点阵)及 64X256 点阵显示 RAM(GDRAM)。可与 CPU 直接接口,提供两种界面来连接微处理机:8

    2024年01月19日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包