STM32-LCD液晶显示

目录

LCD液晶显示

ILI9341液晶控制器简介

液晶屏的信号线和8080时序

使用STM32的FSMC外设模拟8080接口时序

FSMC

功能框图

通讯引脚

存储器控制器

时钟控制逻辑

FSMC的地址映射

[FSMC控制异步Nor Flash的时序](#FSMC控制异步Nor Flash的时序)

FSMC模拟8080时序

FSMC结构体

[NOR FLASH时序结构体](#NOR FLASH时序结构体)

[FSMC初始化结构体(针对NOR FLASH)](#FSMC初始化结构体(针对NOR FLASH))

实验环节(野火例程-HAL库液晶显示)

硬件原理图

基础配置

FSMC初始化(结构体)

访问地址

初始化ILI9341寄存器

总体初始化

设置液晶显示窗口和光标位置

填充像素点和清窗口屏

获取某一个坐标点的像素数据

画线

画矩形

画圆形

显示字符和字符串

设置或获取当前字体类型

设置或获取LCD前景和背景颜色

清除某行文字

测试代码1:液晶屏幕显示

实验现象

测试代码2:液晶坐标方向

实验现象


LCD液晶显示

针对野火指南者配套资料:3.2寸 LCD电阻屏,屏幕里自带ILI9341液晶控制器芯片该控制器芯片中存在GRAM(即显存)该液晶控制器使用8080接口与单片机通讯,液晶面板引出来的FPC信号线为8080接口,单片机把要显示的数据通过引出的8080接口发送到液晶控制器,要显示的数据存储到它内部的显存中,然后液晶控制器不断把显存的内容刷新到液晶面板,显示内容。

还有个电阻触摸屏的控制器XPT2046,实际上是一个ADC芯片,通过检测电压值来计算触摸坐标。
液晶屏的每个像素点都是数据,在实际应用中需要把每个像素点的数据缓存起来,再传输给液晶屏,一般会使用SRAM和SDRAM性质的存储器,而这些专门用于存储显示数据的存储器,被称为显存。显存一般至少要能存储一帧显示数据,如分辨率为800*480的液晶屏,若使用RGB888格式显示,一帧数据大小 = 3 * 800 * 480 = 1152000字节;若使用RGB565格式显示,一帧数据大小 = 2 * 800 * 480 = 768000字节.

一般来说,外置的液晶控制器会自带显存,而像STM32F429等集成液晶控制器的芯片可使用内部SRAM或外扩SDRAM用于显存空间。

ILI9341液晶控制器简介

内部结构复杂。芯片中含有GRAM(即显存),GRAM中每个存储单元都对应液晶面板的一个像素点。通过液晶控制器内部各种模块共同作用把GRAM存储单元的数据转化为液晶面板的控制信号,使像素点呈现特定的颜色,而像素点组合起来则成为了一幅完整的图像。
ILI9341液晶控制器根据自身的IM[3:0]信号线电平决定了它与MCU的通讯方式,支持SPI、8080通讯方式。野火指南者中固定搭配8080 (内部硬件电路处理),使用16根数据线的RGB565 格式。即当IM[3:0]=0x8时,MCU接口模式为8080 MCU 16-bit bus interface II

液晶屏的信号线和8080时序

|----------------|---------------|--------------------------------------------------------------------|
| 信号线 | ILI9341对应的信号线 | 说明 |
| LCD_DB[15:0] | D[15:0] | 数据信号 |
| LCD_RD | RDX | 读数据信号,低电平有效 |
| LCD_RS | D/CX | 数据/命令信号。 高电平时,D[15:0]表示的是数据(RGB像素数据或命令数据) 低电平时,D[15:0]表示的是控制命令 |
| LCD_RESET | RESX | 复位信号,低电平有效 |
| LCD_WR | WRX | 写数据信号,低电平有效 |
| LCD_CS | CSX | 片选信号,低电平有效 |
| LCD_BK | - | 背光信号,低电平有效 |
| GPIO[5:1] | - | 触摸屏的控制信号线 |

这些数据线为8080通讯接口,带X的表示低电平有效。

由图知:

写命令时序由片选信号线CSX拉低开始,数据/命令选择信号线D/CX拉低表示写入的是命令地址。

写信号线WRX拉低、读信号线RDX拉高时,传输方向为写入,同时数据线D[17:0](或D[15:0])输出命令地址。

在第二个传输阶段传输的是命令参数,所以D/CX信号线要置高电平,表示写入的是命令数据。
当需要把像素数据写入GRAM时,把CSX拉低,再把D/CX置高,这时D[17:0](或D[15:0])传输的数据则会被ILI9341保存到它的GRAM里。

使用STM32的FSMC外设模拟8080接口时序

ILI9341的8080通讯接口时序可以由STM32使用普通IO口进行模拟(效率低),也可以由STM32使用FSMC外设模拟8080接口时序。
STM32F1系列芯片使用FSMC外设来管理扩展的存储器 ,可以用来驱动SRAM、Nor Flash和Nand Flash类型的存储器,不能驱动SDRAM这种动态的存储器(STM32F429系列的控制器中的FMC外设才支持控制SDRAM存储器)。
由于FSMC外设可以用于控制扩展的外部存储器,而MCU对液晶屏的操作实际上是把显示数据写入到显存中,与控制存储器类似,且8080接口的通讯时序完全可以使用FSMC外设产生,因此非常适合使用FSMC控制液晶屏。

FSMC

功能框图

通讯引脚

框图右侧是FSMC外设相关的控制引脚,只有share signals是共用引脚,其他引脚要看控制的存储器类型。

本篇中控制LCD时是使用FSMC的NOR/PSRAM类型的存储器,而且控制LCD时使用的是Nor Flash类型的模式B,所以主要分析Nor Flash控制信号线部分。

|---------------------|-------|------------------|
| FSMC控制Nor Flash的信号线 | 信号方向 | 功能 |
| FSMC_NE[4:1] | 输出 | 片选 |
| FSMC_NL(FSMC_NADV) | 输出 | 地址、数据线复用时作锁存信号 |
| FSMC_CLK | 输出 | 时钟(同步突发模式使用) |
| FSMC_A[25:0] | 输出 | 地址总线 |
| FSMC_D[15:0] | 输入/输出 | 双向数据总线 |
| FSMC_NOE | 输出 | 输出使能 |
| FSMC_NWE | 输出 | 写使能 |
| FSMC_NWAIT | 输入 | NOR闪存要求FSMC等待的信号 |

在控制LCD时,使用的是类似异步、地址和数据线独立的Nor Flash控制方式,所以实际上FSMC_CLK、FSMC_NWAIT和FSMC_NL(FSMC_NADV)引脚没有使用到。
STM32具有FSMC_NE1/2/3/4号引脚,不同的引脚对应STM32内部不同的地址区域。例如,当STM32访问0x6800 0000~0x6bff ffff地址空间时,FSMC_NE3引脚会自动设置为低电平,由于它一般连接到外部存储器的片选信号且低电平有效,所以外部存储器的片选被使能,而访问0x6000 0000~0x63ff ffff地址空间时,FSMC_NE1会输出低电平。

当使用不同的FSMC_NE引脚连接外部存储器时,STM32访问外部存储的地址不一样,从而达到控制多个外部存储器芯片的目的。

存储器控制器

控制Nor Flash的有FSMC_BCR1/2/3/4控制寄存器、FSMC_BTR1/2/3/4片选时序寄存器和FSMC_BWTR1/2/3/4写时序寄存器。每种寄存器都有4个,对应不同存储区域。

FSMC_BCRx寄存器:配置要控制的存储器类型、数据线宽度和信号有效极性等参数。

FSMC_BTRx寄存器:配置SRAM访问时的各种时间延迟,如数据保持时间、地址保持时间等。

FSMC_BWTR寄存器:和FSMC_BTRx寄存器类似,专门用于控制写时序的时间参数。

时钟控制逻辑

FSMC外设挂载在AHB总线上,时钟信号来源于HCLK(默认72MHz),控制器的同步时钟输出就是由它分配得到。例如NOR控制器的FSMC_CLK引脚输出的时钟,可用于与同步类型的Nor Flash芯片进行同步通讯,它的时钟频率可通过FSMC_BTRx:CLKDIV位配置,可以配置为HCLK的1/2或1/3,即若它与同步类型的Nor Flash芯片进行同步通讯时,同步时钟最高频率为36MHz。本篇的Nor Flash为异步类型的存储器,不适用同步时钟信号,所以时钟分频配置没用。

FSMC的地址映射

FSMC连接好外部存储器并初始化后就可以直接通过访问地址来读写数据。这种地址访问与I2C EEPROM、SPI FLASH不一样,后两种方式都需要控制I2C或SPI总线给存储器发送地址,然后获取数据。而使用FSMC外接存储器时,其存储单元是映射到STM32的内部寻址空间的在程序上,定义一个执行这些地址的指针,然后就可以通过指针直接修改该存储单元的内容,FSMC外设会自动完成数据访问过程,读写命令的操作不需要程序控制

如:

cpp 复制代码
#define Bank1_SRAM_ADDR    ((uint32_t)(0x68000000))

// 写入16位数据0xAA55到地址0x68000010
*(uint16_t *)(Bank1_SRAM_ADDR + 10) = (uint16_t)0xAA55;

// 从地址0x68000010读取16位数据
temp = *(uint16_t *)(Bank1_SRAM_ADDR + 10);

以上是标准的C语言对特定地址的指针式访问,只是由于该地址被STM32映射到FSMC外设,所以访问这些地址时,FSMC会自动输出地址、数据等访问时序。

如FSMC_NE[4:1]信号线可用于选择BANK1内部的4小块地址区域,当STM32访问0x6C00 0000 ~ 0x6FFF FFFF地址空间时,会访问到Bank1的第一个小块区域,相应的FSMC_NE1信号线会输出控制信号。

FSMC控制异步Nor Flash的时序

FSMC外设支持输出多种不同的时序以便于控制不同的存储器,具有ABCD四种模式。

当内核发出访问某个指向外部存储器地址时,FSMC外设会根据配置控制信号线产生时序访问存储器。

以读时序为例,该图表示一个存储器操作周期由地址建立周期(ADDSET)、数据建立周期(DATAST)和2个HCLK周期组成。

在地址建立周期中,地址线发出要访问的地址,数据掩码信号线指示出要读取地址的高、低字节部分,片选信号使能存储器芯片。

地址建立周期结束后读使能信号线发出读使能信号,然后存储器通过数据信号线把目标数据传输给FSMC,FSMC把它交给内核。

写时序类似,区别是它的一个存储器操作周期仅由地址建立周期(ADDSET)、数据建立周期(DATAST)组成,且在数据建立周期期间写使能信号线发出写信号,然后FSMC把数据通过数据线传输到存储器中。
当FSMC外设被配置为正常工作,并且外部接了Nor Flash时,若向0x6000 0000地址写入数据(如0xABCD),FSMC会自动在各信号线上产生相应的电平信号,写入数据。FSMC会控制片选型FSMC_NE1选择相应的Nor芯片,然后使用地址线FSMC_A[25:0]输出0x6000 0000,在FSMC_NWE写使能信号线上发出低电平的写使能信号,而要写入的数据信号(0xABCD)则从数据线FSMC_D[15:0]输出,然后数据就被保存到了Nor Flash。

FSMC模拟8080时序

对比FSMC NOR/PSRAM中的模式B时序和ILI9341液晶控制器芯片使用的8080时序,这两个时序是非常相似的(除了FSMC的地址线A和8080的D/CX线外,可以说是完全一样的)。

|----------------|------|-----------|---------|
| FSMC-NOR信号线 | 功能 | 8080信号线 | 功能 |
| FSMC_NEx | 片选信号 | CSX | 片选信号 |
| FSMC_NWR | 写使能 | WRX | 写使能 |
| FSMC_NOE | 读使能 | RDX | 读使能 |
| FSMC_D[15:0] | 数据信号 | D[15:0] | 数据信号 |
| FSMC_A[25:0] | 地址信号 | D/CX | 数据/命令选择 |

对于FSMC和8080接口,前四种信号线都是完全一样的,仅仅是FSMC的地址信号A[25:0]和8080的数据/命令选择线D/CX有区别。对于8080的数据/命令选择线D/CX,高电平表示数值,低电平表示命令,如果能使用FSMC的A地址线根据不同情况来产生对应的电平,那么完全可以使用FSMC来产生8080接口需要的时序了。

为了模拟8080时序,可以把FSMC_A0地址线(也可以是其他地址线,如A1、A2等)与ILI9341芯片8080接口的数据/命令选择线D/CX相连接。那么A0高电平时,数据线FSMC_D[15:0]的信号会被ILI9341理解为数值,反之为命令。

由于FSMC会自动产生地址信号,当使用FSMC向0x6xxx xxx1、0x6xxx xxx3、0x6xxx xxx5等奇数地址写入数据时,地址最低位的值为1,FSMC会控制FSMC_A0地址线输出高电平,此时数据线FSMC_D[15:0]的信号会被ILI9341理解为数值;当使用FSMC向0x6xxx xxx0、0x6xxx xxx2、0x6xxx xxx4等偶数地址写入数据时,地址最低位的值为0,FSMC会控制FSMC_A0地址线输出低电平,此时数据线FSMC_D[15:0]的信号会被ILI9341理解为命令。
因此,只有配置好FSMC外设,然后在代码中利用指针变量,向不同地址单元写入数据就能由FSMC模拟出的8080接口向ILI9341写入控制命令或GRAM的数据。

FSMC结构体

与控制SRAM一样,控制FSMC使用NOR FLASH存储器时主要是配置时序寄存器和控制寄存器。在HAL中,主要由时序结构体和初始化结构体来体现。

NOR FLASH时序结构体

cpp 复制代码
typedef struct
{
    uint32_t AddressSetupTime;       /* 地址建立时间,0-0xF个HCLK周期 */
    uint32_t AddressHoldTime;        /* 地址保持时间,0-0xF个HCLK周期*/
    uint32_t DataSetupTime;          /* 数据建立时间,0-0xF个HCLK周期*/
    uint32_t BusTurnAroundDuration;  /* 总线转换周期,0-0xF个HCLK周期,在NOR FLASH */
    uint32_t CLKDivision;  /* 时钟分频因子,1-0xF,若控制异步存储器,本参数无效 */
    uint32_t DataLatency;  /* 数据延迟时间,若控制异步存储器,本参数无效 */
    uint32_t AccessMode;   /* 设置访问模式 */
}FSMC_NORSRAMTimingInitTypeDef;

AddressSetupTime:地址建立时间

即FSMC读写时序图的ADDSET值,它可以被设置为0~0x0f个HCLK周期数。按STM32 HAL库的默认配置,HCLK的时钟频率为72MHz,即一个HCLK周期为1/72微秒。

DataSetupTime:数据建立时间

即FSMC读写时序图的DATAST值,它可以被设置为0~0x0f个HCLK周期数。

AccessMode:存储器访问模式

不同模式下FSMC访问存储器地址时引脚输出的时序不一样,可选FSMC_ACCESS_MODE_A/B/C/D模式。控制异步NOR FLASH时使用B模式。

|--------|----------|------|-----|-----|
| 参数 | 访问模式 | 单位 | 最小值 | 最大值 |
| 地址建立时间 | 异步 | HCLK | 1 | 16 |
| 地址保持时间 | 异步复用I/O | HCLK | 2 | 16 |
| 数据建立时间 | 异步 | HCLK | 2 | 256 |
| 总线转换周期 | 异步和同步 读写 | HCLK | 1 | 16 |
| 时钟分频因子 | 同步 | HCLK | 2 | 16 |
| 数据延迟时间 | 同步 | CLK | 2 | 17 |

FSMC初始化结构体(针对NOR FLASH)

cpp 复制代码
/**
 * @brief  FSMC NOR/SRAM Init structure definition
 */
typedef struct
{
    uint32_t NSBank;              /* 设置要控制的Bank区域 */
    uint32_t DataAddressMux;      /* 设置地址总线与数据总线是否复用 */
    uint32_t MemoryType;          /* 设置存储器的类型 */
    uint32_t MemoryDataWidth;     /* 设置存储器的数据宽度 */
    uint32_t BurstAccessMode;     /* 设置是否支持突发访问模式,只支持同步类型的存储器 */
    uint32_t WaitSignalPolarity;  /* 设置等待信号的极性 */
    uint32_t WrapMode;            /* 设置是否支持对齐的突发模式 */
    uint32_t WaitSignalActive;    /* 配置等待信号在等待前有效还是等待期间有效 */
    uint32_t WriteOperation;      /* 设置是否写使能 */
    uint32_t WaitSignal;          /* 设置是否使能等待状态插入 */
    uint32_t ExtendedMode;        /* 设置是否使能扩展模式 */
    uint32_t WriteBurst;          /* 设置是否使能写突发操作 */
    uint32_t AsynchronousWait;    /* 设置是否使能等待信号 */
    uint32_t ContinuousClock;     /* 设置是否使能FMC时钟输出到外部存储设备 */
    uint32_t WriteFifo;           /* 设置是否使能FIFO */
    uint32_t PageSize;            /* 指定页的大小 */

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

    /* 当使用扩展模式时,本参数用于配置写时序 */
    FSMC_NORSRAM_TimingTypeDef * FSMC_WriteTimingStruct;
  } FSMC_NORSRAM_InitTypeDef;

NSBank:bank地址区域

|--------------------|--------------------------|
| 宏 | 地址区域 |
| FSMC_NORSRAM_BANK1 | 0x6000 0000~0x63FF FFFF |
| FSMC_NORSRAM_BANK2 | 0x6400 0000~0x67FF FFFF |
| FSMC_NORSRAM_BANK3 | 0x6800 0000~0x6BFF FFFF |
| FSMC_NORSRAM_BANK4 | 0x6C00 0000~0x6FFF FFFF |

MemoryType:存储器类型

|------------------------|-----------|
| FSMC_MEMORY_TYPE_SRAM | SRAM |
| FSMC_MEMORY_TYPE_PSRAM | PSRAM |
| FSMC_MEMORY_TYPE_NOR | NOR FLASH |

MemoryDataWidth:存储器的数据宽度

|-------------------------------|-----|
| FSMC_NORSRAM_MEM_BUS_WIDTH_8 | 8位 |
| FSMC_NORSRAM_MEM_BUS_WIDTH_16 | 16位 |
| FSMC_NORSRAM_MEM_BUS_WIDTH_32 | 32位 |

WaitSignalActive:配置等待信号在等待前有效还是在等待期间有效

|----------------------------|-------------|
| FSMC_WAIT_TIMING_BEFORE_WS | 等待信号在等待前有效 |
| FSMC_WAIT_TIMING_DURING_WS | 等待信号在等待期间有效 |

ExtendedMode:是否使用扩展模式

在非扩展模式下,对存储器读写的时序都只使用FSMC_BCD寄存器配置;

在扩展模式下,对存储器读写的时序可以分开配置,读时序使用FSMC_BCD寄存器配置,写时序使用FSMC_BWTR寄存器配置。

实验环节(野火例程-HAL库液晶显示)

硬件原理图

原理图中可看出一些信息:

PD7使用了FSMC_NE1,该引脚对应这STM32内部的地址区域,访问的是0x6000 0000~0x63FF FFFF地址空间,FSMC_NE1会输出低电平。

PD11使用了FSMC_A16,为命令/数据选择引脚。高电平传输数据,低电平传输命令。

使用了FSMC_D0~FSMC_D15,使用了16个数据引脚,选择16bit数据宽度。

基础配置

LED(推挽上拉,默认高电平灭灯状态):

RLED-PB5、GLED-PB0、BLED-PB1。

串口:使用USART1,9600波特率,8-N-1。使用重定向printf输出。勾选使用C库。

FSMC_GPIO:

LCD_RST:LCD复位引脚,低电平有效。推挽上拉。

LCD_BL:LCD背光引脚,低电平有效。推挽上拉。

FSMC初始化(结构体)

cpp 复制代码
static SRAM_HandleTypeDef  SRAM_Handler;
static FSMC_NORSRAM_TimingTypeDef Timing;

static void ILI9341_FSMC_Config(void)
{
    SRAM_Handler.Instance = FSMC_NORSRAM_DEVICE;
    SRAM_Handler.Extended = FSMC_NORSRAM_EXTENDED_DEVICE;

    /* SRAM device configuration */
	// B模式下读写时序只考虑ADDSET的值和DATAST的值
	// 单位为1个HCLK的时钟周期,即1/72us = 0.0138us = 13.8ns
    Timing.AddressSetupTime      = 0x00;	// ADDSET的值
    Timing.AddressHoldTime       = 0x00;
    Timing.DataSetupTime         = 0x01;	// DATAST的值
    Timing.BusTurnAroundDuration = 0x00;
    Timing.CLKDivision           = 0x00;
    Timing.DataLatency           = 0x00;
    Timing.AccessMode            = FSMC_ACCESS_MODE_B;

    SRAM_Handler.Init.NSBank 			= FSMC_NORSRAM_BANK1;
    SRAM_Handler.Init.DataAddressMux 	= FSMC_DATA_ADDRESS_MUX_DISABLE;
    SRAM_Handler.Init.MemoryType 		= FSMC_MEMORY_TYPE_NOR;
    SRAM_Handler.Init.MemoryDataWidth 	= FSMC_NORSRAM_MEM_BUS_WIDTH_16;    // FSMC_D0~FSMC_D15
    // 是否使能突发访问,仅对同步突发存储器有效,此处未用到
    SRAM_Handler.Init.BurstAccessMode 	= FSMC_BURST_ACCESS_MODE_DISABLE;
    // 等待信号的极性,仅在突发模式访问下有用
    SRAM_Handler.Init.WaitSignalPolarity = FSMC_WAIT_SIGNAL_POLARITY_LOW;
    // 存储器是在等待周期之前的一个时钟周期使能NWAIT
    SRAM_Handler.Init.WaitSignalActive 	= FSMC_WAIT_TIMING_BEFORE_WS;
    SRAM_Handler.Init.WriteOperation 	= FSMC_WRITE_OPERATION_ENABLE;
    SRAM_Handler.Init.WaitSignal 		= FSMC_WAIT_SIGNAL_DISABLE;         // 等待使能位,此处未用到
    SRAM_Handler.Init.ExtendedMode 		= FSMC_EXTENDED_MODE_DISABLE;       // 读写使用相同的时序
    // 是否使能同步传输模式下的等待信号,此处未用到
    SRAM_Handler.Init.AsynchronousWait 	= FSMC_ASYNCHRONOUS_WAIT_DISABLE;
    SRAM_Handler.Init.WriteBurst 		= FSMC_WRITE_BURST_DISABLE;         // 禁止突发写

    /* SRAM controller initialization */
    ILI9341_GPIO_Config();    // FSMC的相关GPIO配置
    /* 读写使用相同的时序 */
    HAL_SRAM_Init(& SRAM_Handler, &Timing, &Timing);
}

初始化FSMC后,当访问特定地址时,FSMC会产生相应的模拟8080时序(控制FSMC地址线输出要访问的内存地址,使用FSMC数据信号线接收或发送数据,其它FSMC_NE片选信号、读使能信号FSMC_NOE、写使能信号FSMC_NWE辅助产生完整的时序),所以FSMC产生的信号被ILI9341接收,并且使用FSMC_A16控制命令/数据选择引脚RS(即D/CX)。切记,FSMC_A16引脚(看硬件原理图得知)输出高电平表示传输数据,低电平表示传输命令。

访问地址

关于访问地址:

由于结构体配置了FSMC_NORSRAM_BANK1,即使用了FSMC_NE1作为8080时序CS的片选信号,所以当访问0x6000 0000~0x63FF FFFF时,FSMC才对外产生片选有效的访问时序

由于硬件上选择了FSMC_A16地址线作为命令/数据选择RS信号线,所以0x6000 0000~0x63FF FFFF地址的范围内,再选择地址的第16位输出1表示产生数据,选择地址的第16位输出0表示产生命令。例程中选择了0x6000 0000 &= ~(0<<16) ==> 0x6000 0000,0x6000 0000 |= (1<<16) ==> 0x6001 0000。即0x6000 000可表示命令地址,0x6001 0000可表示数据地址。

但是这样选择访问地址不是对的,根据《STM32参考手册》对FSMC访问NOR FLASH的说明,STM32内部访问地址使用的是HADDR总线,HADDR总线是需要转换到外部存储器的内部AHB地址线,HADDR总线是字节地址,而存储器访问不都是按字节访问,因此接到存储器的地址线依照存储器的数据宽度而有所不同。

|------|----------------------|---------------------------|
| 内存宽度 | 发给存储器的数据地址 | 最大内存容量(位) |
| 8位 | HADDR[25:0] | 64MByte * 8 = 512Mbit |
| 16位 | HADDR[25:1] >> 1 | 64MByte/2 * 16 = 512Mbit |

在16位外部存储器宽度的情况下,FSMC将在内部使用HADR[25:1]来生成外部存储器FSMC_A[24:0]

无论外部存储器宽度是8位还是16位,FSMC_A[0]都应该连接到外部存储器地址A[0]。

也就是说,HADDR1与FSMC_A0对应,以此类推。因此当FSMC_A16为1时,内部地址是第17位为1,所以命令访问地址计算应该为0x6000 0000 &= ~(0<<(16+1)) ==> 0x6000 0000,数据访问地址计算应该为0x6000 0000 |= (1<<(16+1)) ==> 0x6002 0000。因此0x6000 0000可表示命令地址,0x6002 0000可表示数据地址。
当STM32访问内部的0x6002 0000地址时,FSMC自动输出时序,且使得与液晶屏的数据/命令选择线RS(即D/CX)相连的FSMC_A16输出高电平,使得液晶屏会把传输过程理解为数据传输;当STM32访问内部的0x6000 0000地址时,FSMC自动输出时序,且使得与液晶屏的数据/命令选择线RS(即D/CX)相连的FSMC_A16输出低电平,使得液晶屏会把传输过程理解为命令传输。

cpp 复制代码
//FSMC_Bank1_NORSRAM用于LCD命令操作的地址
#define      FSMC_Addr_ILI9341_CMD         ( ( uint32_t ) 0x60000000 )

//FSMC_Bank1_NORSRAM用于LCD数据操作的地址      
#define      FSMC_Addr_ILI9341_DATA        ( ( uint32_t ) 0x60020000 )

/**
  * @brief  向ILI9341写入命令
  * @param  usCmd :要写入的命令(寄存器地址)
  * @retval 无
  */
void ILI9341_Write_Cmd(uint16_t usCmd)
{
    * (__IO uint16_t *)(FSMC_Addr_ILI9341_CMD) = usCmd;
}

/**
  * @brief  向ILI9341写入数据
  * @param  usData :要写入的数据
  * @retval 无
  */
void ILI9341_Write_Data(uint16_t usData)
{
    * (__IO uint16_t *)(FSMC_Addr_ILI9341_DATA) = usData;
}

/**
  * @brief  从ILI9341读取数据
  * @param  无
  * @retval 读取到的数据
  */
uint16_t ILI9341_Read_Data(void)
{
    return (* (__IO uint16_t *)(FSMC_Addr_ILI9341_DATA));
}

初始化ILI9341寄存器

厂家写好的,直接用。

cpp 复制代码
static void ILI9341_REG_Config(void)
{
    lcdid = ILI9341_ReadID();

    if (lcdid == LCDID_ILI9341)
    {
        /*  Power control B (CFh)  */
        DEBUG_DELAY();
        ILI9341_Write_Cmd(0xCF);
        ILI9341_Write_Data(0x00);
        ILI9341_Write_Data(0x81);
        ILI9341_Write_Data(0x30);

        /*  Power on sequence control (EDh) */
        DEBUG_DELAY();
        ILI9341_Write_Cmd(0xED);
        ILI9341_Write_Data(0x64);
        ILI9341_Write_Data(0x03);
        ILI9341_Write_Data(0x12);
        ILI9341_Write_Data(0x81);

        /*  Driver timing control A (E8h) */
        DEBUG_DELAY();
        ILI9341_Write_Cmd(0xE8);
        ILI9341_Write_Data(0x85);
        ILI9341_Write_Data(0x10);
        ILI9341_Write_Data(0x78);

        /*  Power control A (CBh) */
        DEBUG_DELAY();
        ILI9341_Write_Cmd(0xCB);
        ILI9341_Write_Data(0x39);
        ILI9341_Write_Data(0x2C);
        ILI9341_Write_Data(0x00);
        ILI9341_Write_Data(0x34);
//        ILI9341_Write_Data ( 0x02 );
		// 原来是0x02,改为0x06可防止液晶显示白屏时有条纹的情况
        ILI9341_Write_Data(0x06);    

        /* Pump ratio control (F7h) */
        DEBUG_DELAY();
        ILI9341_Write_Cmd(0xF7);
        ILI9341_Write_Data(0x20);

        /* Driver timing control B */
        DEBUG_DELAY();
        ILI9341_Write_Cmd(0xEA);
        ILI9341_Write_Data(0x00);
        ILI9341_Write_Data(0x00);

        /* Frame Rate Control (In Normal Mode/Full Colors) (B1h) */
        DEBUG_DELAY();
        ILI9341_Write_Cmd(0xB1);
        ILI9341_Write_Data(0x00);
        ILI9341_Write_Data(0x1B);

        /*  Display Function Control (B6h) */
        DEBUG_DELAY();
        ILI9341_Write_Cmd(0xB6);
        ILI9341_Write_Data(0x0A);
        ILI9341_Write_Data(0xA2);

        /* Power Control 1 (C0h) */
        DEBUG_DELAY();
        ILI9341_Write_Cmd(0xC0);
        ILI9341_Write_Data(0x35);

        /* Power Control 2 (C1h) */
        DEBUG_DELAY();
        ILI9341_Write_Cmd(0xC1);
        ILI9341_Write_Data(0x11);

        /* VCOM Control 1 (C5h) */
        ILI9341_Write_Cmd(0xC5);
        ILI9341_Write_Data(0x45);
        ILI9341_Write_Data(0x45);

        /*  VCOM Control 2 (C7h)  */
        ILI9341_Write_Cmd(0xC7);
        ILI9341_Write_Data(0xA2);

        /* Enable 3G (F2h) */
        ILI9341_Write_Cmd(0xF2);
        ILI9341_Write_Data(0x00);

        /* Gamma Set (26h) */
        ILI9341_Write_Cmd(0x26);
        ILI9341_Write_Data(0x01);
        DEBUG_DELAY();

        /* Positive Gamma Correction */
        ILI9341_Write_Cmd(0xE0);    //Set Gamma
        ILI9341_Write_Data(0x0F);
        ILI9341_Write_Data(0x26);
        ILI9341_Write_Data(0x24);
        ILI9341_Write_Data(0x0B);
        ILI9341_Write_Data(0x0E);
        ILI9341_Write_Data(0x09);
        ILI9341_Write_Data(0x54);
        ILI9341_Write_Data(0xA8);
        ILI9341_Write_Data(0x46);
        ILI9341_Write_Data(0x0C);
        ILI9341_Write_Data(0x17);
        ILI9341_Write_Data(0x09);
        ILI9341_Write_Data(0x0F);
        ILI9341_Write_Data(0x07);
        ILI9341_Write_Data(0x00);

        /* Negative Gamma Correction (E1h) */
        ILI9341_Write_Cmd(0XE1);    //Set Gamma
        ILI9341_Write_Data(0x00);
        ILI9341_Write_Data(0x19);
        ILI9341_Write_Data(0x1B);
        ILI9341_Write_Data(0x04);
        ILI9341_Write_Data(0x10);
        ILI9341_Write_Data(0x07);
        ILI9341_Write_Data(0x2A);
        ILI9341_Write_Data(0x47);
        ILI9341_Write_Data(0x39);
        ILI9341_Write_Data(0x03);
        ILI9341_Write_Data(0x06);
        ILI9341_Write_Data(0x06);
        ILI9341_Write_Data(0x30);
        ILI9341_Write_Data(0x38);
        ILI9341_Write_Data(0x0F);

        /* memory access control set */
        DEBUG_DELAY();
        ILI9341_Write_Cmd(0x36);
        ILI9341_Write_Data(0xC8);       /*竖屏  左上角到 (起点)到右下角 (终点)扫描方式*/
        DEBUG_DELAY();

        /* column address control set */
        ILI9341_Write_Cmd(CMD_SetCoordinateX);
        ILI9341_Write_Data(0x00);
        ILI9341_Write_Data(0x00);
        ILI9341_Write_Data(0x00);
        ILI9341_Write_Data(0xEF);

        /* page address control set */
        DEBUG_DELAY();
        ILI9341_Write_Cmd(CMD_SetCoordinateY);
        ILI9341_Write_Data(0x00);
        ILI9341_Write_Data(0x00);
        ILI9341_Write_Data(0x01);
        ILI9341_Write_Data(0x3F);

        /*  Pixel Format Set (3Ah)  */
        DEBUG_DELAY();
        ILI9341_Write_Cmd(0x3a);
        ILI9341_Write_Data(0x55);

        /* Sleep Out (11h)  */
        ILI9341_Write_Cmd(0x11);
        ILI9341_Delay(0xAFFf << 2);
        DEBUG_DELAY();

        /* Display ON (29h) */
        ILI9341_Write_Cmd(0x29);
    }
}

总体初始化

也就是把上述的一些初始化集合在一起。

cpp 复制代码
void ILI9341_Init(void)
{
    ILI9341_GPIO_Config();				// FSMC_GPIO的配置
    ILI9341_FSMC_Config();				// FSMC相关配置

    ILI9341_BackLed_Control(ENABLE);	// 点亮LCD背光灯
    ILI9341_Rst();						// 复位
    ILI9341_REG_Config();				// 初始化ILI9341寄存器
    
    // 其中0、3、5、6 模式适合从左至右显示文字,
    // 不推荐使用其它模式显示文字	其它模式显示文字会有镜像效果
    // 其中 6 模式为大部分液晶例程的默认显示方向
    ILI9341_GramScan(LCD_SCAN_MODE);
}

设置液晶显示窗口和光标位置

根据液晶屏的要求,在发送显示数据前需要先设置窗口,确定发送像素数据的显示区域。

cpp 复制代码
/**
 * @brief  在ILI9341显示器上开辟一个窗口
 * @param  usX :在特定扫描方向下窗口的起点X坐标
 * @param  usY :在特定扫描方向下窗口的起点Y坐标
 * @param  usWidth :窗口的宽度
 * @param  usHeight :窗口的高度
 * @retval 无
 */
void ILI9341_OpenWindow(uint16_t usX, uint16_t usY, uint16_t usWidth, uint16_t usHeight)
{
	/* 设置X坐标,设置起始点和结束点 */
    ILI9341_Write_Cmd(CMD_SetCoordinateX); 				
    ILI9341_Write_Data(usX >> 8);
    ILI9341_Write_Data(usX & 0xff);
    ILI9341_Write_Data((usX + usWidth - 1) >> 8);
    ILI9341_Write_Data((usX + usWidth - 1) & 0xff);

	/* 设置Y坐标,设置起始点和结束点 */
    ILI9341_Write_Cmd(CMD_SetCoordinateY);
    ILI9341_Write_Data(usY >> 8);
    ILI9341_Write_Data(usY & 0xff);
    ILI9341_Write_Data((usY + usHeight - 1) >> 8);
    ILI9341_Write_Data((usY + usHeight - 1) & 0xff);
}

/**
 * @brief  设定ILI9341的光标坐标
 * @param  usX :在特定扫描方向下光标的X坐标
 * @param  usY :在特定扫描方向下光标的Y坐标
 * @retval 无
 */
static void ILI9341_SetCursor(uint16_t usX, uint16_t usY)
{
    ILI9341_OpenWindow(usX, usY, 1, 1);
}

填充像素点和清窗口屏

cpp 复制代码
/**
 * @brief  在ILI9341显示器上以某一颜色填充像素点
 * @param  ulAmout_Point :要填充颜色的像素点的总数目
 * @param  usColor :颜色
 * @retval 无
 */
static __inline void ILI9341_FillColor(uint32_t ulAmout_Point, uint16_t usColor)
{
    uint32_t i = 0;

    /* memory write */
    ILI9341_Write_Cmd(CMD_SetPixel);

    for (i = 0; i < ulAmout_Point; i ++)
    {
        ILI9341_Write_Data(usColor);
    }
}

static uint16_t CurrentTextColor = BLACK;	// 前景色
static uint16_t CurrentBackColor = WHITE;	// 背景色

/**
 * @brief  对ILI9341显示器的某一窗口以某种颜色进行清屏
 * @param  usX :在特定扫描方向下窗口的起点X坐标
 * @param  usY :在特定扫描方向下窗口的起点Y坐标
 * @param  usWidth :窗口的宽度
 * @param  usHeight :窗口的高度
 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
 * @retval 无
 */
void ILI9341_Clear(uint16_t usX, uint16_t usY, uint16_t usWidth, uint16_t usHeight)
{
    ILI9341_OpenWindow(usX, usY, usWidth, usHeight);
    ILI9341_FillColor(usWidth * usHeight, CurrentBackColor);
}

/**
 * @brief  对ILI9341显示器的某一点以某种颜色进行填充
 * @param  usX :在特定扫描方向下该点的X坐标
 * @param  usY :在特定扫描方向下该点的Y坐标
 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
 * @retval 无
 */
void ILI9341_SetPointPixel(uint16_t usX, uint16_t usY)
{
    if ((usX < LCD_X_LENGTH) && (usY < LCD_Y_LENGTH))
    {
        ILI9341_SetCursor(usX, usY);
        ILI9341_FillColor(1, CurrentTextColor);
    }
}

获取某一个坐标点的像素数据

cpp 复制代码
/**
 * @brief  读取 GRAM 的一个像素数据
 * @param  无
 * @retval 像素数据
 */
static uint16_t ILI9341_Read_PixelData(void)
{
    uint16_t usRG = 0, usB = 0 ;

    ILI9341_Write_Cmd(0x2E); /* 读数据 */
    // 去掉前一次读取结果
    ILI9341_Read_Data(); 	 /* FIRST READ OUT DUMMY DATA */

    // 获取红色通道与绿色通道的值
    usRG = ILI9341_Read_Data();  	/*READ OUT RED AND GREEN DATA  */
    usB = ILI9341_Read_Data();  	/*READ OUT BLUE DATA*/

    return ((usRG & 0xF800) | ((usRG << 3) & 0x7E0) | (usB >> 11));
}

/**
 * @brief  获取 ILI9341 显示器上某一个坐标点的像素数据
 * @param  usX :在特定扫描方向下该点的X坐标
 * @param  usY :在特定扫描方向下该点的Y坐标
 * @retval 像素数据
 */
uint16_t ILI9341_GetPointPixel(uint16_t usX, uint16_t usY)
{
    uint16_t usPixelData;
	
    ILI9341_SetCursor(usX, usY);
    usPixelData = ILI9341_Read_PixelData();

    return usPixelData;
}

画线

cpp 复制代码
/**
 * @brief  在 ILI9341 显示器上使用 Bresenham 算法画线段
 * @param  usX1 :在特定扫描方向下线段的一个端点X坐标
 * @param  usY1 :在特定扫描方向下线段的一个端点Y坐标
 * @param  usX2 :在特定扫描方向下线段的另一个端点X坐标
 * @param  usY2 :在特定扫描方向下线段的另一个端点Y坐标
 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
 * @retval 无
 */
void ILI9341_DrawLine(uint16_t usX1, uint16_t usY1, uint16_t usX2, uint16_t usY2)
{
    uint16_t us;
    uint16_t usX_Current, usY_Current;

    int32_t lError_X = 0, lError_Y = 0, lDelta_X, lDelta_Y, lDistance;
    int32_t lIncrease_X, lIncrease_Y;

    //计算坐标增量
    lDelta_X = usX2 - usX1;
    lDelta_Y = usY2 - usY1;

    usX_Current = usX1;
    usY_Current = usY1;

    if (lDelta_X > 0)
    {
        lIncrease_X = 1;    //设置单步方向
    }
    else if (lDelta_X == 0)
    {
        lIncrease_X = 0;    //垂直线
    }
    else
    {
        lIncrease_X = -1;
        lDelta_X = - lDelta_X;
    }

    if (lDelta_Y > 0)
    {
        lIncrease_Y = 1;
    }
    else if (lDelta_Y == 0)
    {
        lIncrease_Y = 0;    //水平线
    }
    else
    {
        lIncrease_Y = -1;
        lDelta_Y = - lDelta_Y;
    }

    if (lDelta_X > lDelta_Y)
    {
        lDistance = lDelta_X;    //选取基本增量坐标轴
    }
    else
    {
        lDistance = lDelta_Y;
    }

    for (us = 0; us <= lDistance + 1; us ++)  //画线输出
    {
        ILI9341_SetPointPixel(usX_Current, usY_Current);   //画点

        lError_X += lDelta_X ;
        lError_Y += lDelta_Y ;

        if (lError_X > lDistance)
        {
            lError_X -= lDistance;
            usX_Current += lIncrease_X;
        }

        if (lError_Y > lDistance)
        {
            lError_Y -= lDistance;
            usY_Current += lIncrease_Y;
        }
    }
}

画矩形

cpp 复制代码
/**
 * @brief  在 ILI9341 显示器上画一个矩形
 * @param  usX_Start :在特定扫描方向下矩形的起始点X坐标
 * @param  usY_Start :在特定扫描方向下矩形的起始点Y坐标
 * @param  usWidth:矩形的宽度(单位:像素)
 * @param  usHeight:矩形的高度(单位:像素)
 * @param  ucFilled :选择是否填充该矩形
  *   该参数为以下值之一:
  *     @arg 0 :空心矩形
  *     @arg 1 :实心矩形
 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
 * @retval 无
 */
void ILI9341_DrawRectangle(uint16_t usX_Start, uint16_t usY_Start, uint16_t usWidth, uint16_t usHeight,
                           uint8_t ucFilled)
{
    if (ucFilled)
    {
        ILI9341_OpenWindow(usX_Start, usY_Start, usWidth, usHeight);
        ILI9341_FillColor(usWidth * usHeight, CurrentTextColor);
    }
    else
    {
        ILI9341_DrawLine(usX_Start, usY_Start, usX_Start + usWidth - 1, usY_Start);
        ILI9341_DrawLine(usX_Start, usY_Start + usHeight - 1, usX_Start + usWidth - 1, usY_Start + usHeight - 1);
        ILI9341_DrawLine(usX_Start, usY_Start, usX_Start, usY_Start + usHeight - 1);
        ILI9341_DrawLine(usX_Start + usWidth - 1, usY_Start, usX_Start + usWidth - 1, usY_Start + usHeight - 1);
    }
}

画圆形

cpp 复制代码
/**
 * @brief  在 ILI9341 显示器上使用 Bresenham 算法画圆
 * @param  usX_Center :在特定扫描方向下圆心的X坐标
 * @param  usY_Center :在特定扫描方向下圆心的Y坐标
 * @param  usRadius:圆的半径(单位:像素)
 * @param  ucFilled :选择是否填充该圆
  *   该参数为以下值之一:
  *     @arg 0 :空心圆
  *     @arg 1 :实心圆
 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
 * @retval 无
 */
void ILI9341_DrawCircle(uint16_t usX_Center, uint16_t usY_Center, uint16_t usRadius, uint8_t ucFilled)
{
    int16_t sCurrentX, sCurrentY;
    int16_t sError;

    sCurrentX = 0;
    sCurrentY = usRadius;

    sError = 3 - (usRadius << 1);       //判断下个点位置的标志

    while (sCurrentX <= sCurrentY)
    {
        int16_t sCountY;

        if (ucFilled)
		{
            for (sCountY = sCurrentX; sCountY <= sCurrentY; sCountY ++)
            {
                ILI9341_SetPointPixel(usX_Center + sCurrentX, usY_Center + sCountY);              //1,研究对象
                ILI9341_SetPointPixel(usX_Center - sCurrentX, usY_Center + sCountY);              //2
                ILI9341_SetPointPixel(usX_Center - sCountY,   usY_Center + sCurrentX);            //3
                ILI9341_SetPointPixel(usX_Center - sCountY,   usY_Center - sCurrentX);            //4
                ILI9341_SetPointPixel(usX_Center - sCurrentX, usY_Center - sCountY);              //5
                ILI9341_SetPointPixel(usX_Center + sCurrentX, usY_Center - sCountY);              //6
                ILI9341_SetPointPixel(usX_Center + sCountY,   usY_Center - sCurrentX);            //7
                ILI9341_SetPointPixel(usX_Center + sCountY,   usY_Center + sCurrentX);            //0
            }
		}
        else
        {
            ILI9341_SetPointPixel(usX_Center + sCurrentX, usY_Center + sCurrentY);                //1,研究对象
            ILI9341_SetPointPixel(usX_Center - sCurrentX, usY_Center + sCurrentY);                //2
            ILI9341_SetPointPixel(usX_Center - sCurrentY, usY_Center + sCurrentX);                //3
            ILI9341_SetPointPixel(usX_Center - sCurrentY, usY_Center - sCurrentX);                //4
            ILI9341_SetPointPixel(usX_Center - sCurrentX, usY_Center - sCurrentY);                //5
            ILI9341_SetPointPixel(usX_Center + sCurrentX, usY_Center - sCurrentY);                //6
            ILI9341_SetPointPixel(usX_Center + sCurrentY, usY_Center - sCurrentX);                //7
            ILI9341_SetPointPixel(usX_Center + sCurrentY, usY_Center + sCurrentX);                //0
        }

        sCurrentX ++;

        if (sError < 0)
        {
            sError += 4 * sCurrentX + 6;
        }
        else
        {
            sError += 10 + 4 * (sCurrentX - sCurrentY);
            sCurrentY --;
        }
    }
}

显示字符和字符串

cpp 复制代码
/**
 * @brief  在 ILI9341 显示器上显示一个英文字符
 * @param  usX :在特定扫描方向下字符的起始X坐标
 * @param  usY :在特定扫描方向下该点的起始Y坐标
 * @param  cChar :要显示的英文字符
 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
 * @retval 无
 */
void ILI9341_DispChar_EN(uint16_t usX, uint16_t usY, const char cChar)
{
    uint8_t  byteCount, bitCount, fontLength;
    uint16_t ucRelativePositon;
    uint8_t *Pfont;

    //对ascii码表偏移(字模表不包含ASCII表的前32个非图形符号)
    ucRelativePositon = cChar - ' ';

    //每个字模的字节数
    fontLength = (LCD_Currentfonts->Width * LCD_Currentfonts->Height) / 8;

    //字模首地址
    /*ascii码表偏移值乘以每个字模的字节数,求出字模的偏移位置*/
    Pfont = (uint8_t *)&LCD_Currentfonts->table[ucRelativePositon * fontLength];

    //设置显示窗口
    ILI9341_OpenWindow(usX, usY, LCD_Currentfonts->Width, LCD_Currentfonts->Height);

    ILI9341_Write_Cmd(CMD_SetPixel);

    //按字节读取字模数据
    //由于前面直接设置了显示窗口,显示数据会自动换行
    for (byteCount = 0; byteCount < fontLength; byteCount++)
    {
        //一位一位处理要显示的颜色
        for (bitCount = 0; bitCount < 8; bitCount++)
        {
            if (Pfont[byteCount] & (0x80 >> bitCount))
            {
                ILI9341_Write_Data(CurrentTextColor);
            }
            else
            {
                ILI9341_Write_Data(CurrentBackColor);
            }
        }
    }
}

/**
 * @brief  在 ILI9341 显示器上显示英文字符串
 * @param  line :在特定扫描方向下字符串的起始Y坐标
  *   本参数可使用宏LINE(0)、LINE(1)等方式指定文字坐标,
  *   宏LINE(x)会根据当前选择的字体来计算Y坐标值。
	*		显示中文且使用LINE宏时,需要把英文字体设置成Font8x16
 * @param  pStr :要显示的英文字符串的首地址
 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
 * @retval 无
 */
void ILI9341_DispStringLine_EN(uint16_t line,  char *pStr)
{
    uint16_t usX = 0;

    while (* pStr != '\0')
    {
        if ((usX - ILI9341_DispWindow_X_Star + LCD_Currentfonts->Width) > LCD_X_LENGTH)
        {
            usX = ILI9341_DispWindow_X_Star;
            line += LCD_Currentfonts->Height;
        }

        if ((line - ILI9341_DispWindow_Y_Star + LCD_Currentfonts->Height) > LCD_Y_LENGTH)
        {
            usX = ILI9341_DispWindow_X_Star;
            line = ILI9341_DispWindow_Y_Star;
        }

        ILI9341_DispChar_EN(usX, line, * pStr);
        pStr ++;
        usX += LCD_Currentfonts->Width;
    }
}

/**
 * @brief  在 ILI9341 显示器上显示英文字符串
 * @param  usX :在特定扫描方向下字符的起始X坐标
 * @param  usY :在特定扫描方向下字符的起始Y坐标
 * @param  pStr :要显示的英文字符串的首地址
 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
 * @retval 无
 */
void ILI9341_DispString_EN(uint16_t usX, uint16_t usY,  char *pStr)
{
    while (* pStr != '\0')
    {
        if ((usX - ILI9341_DispWindow_X_Star + LCD_Currentfonts->Width) > LCD_X_LENGTH)
        {
            usX = ILI9341_DispWindow_X_Star;
            usY += LCD_Currentfonts->Height;
        }

        if ((usY - ILI9341_DispWindow_Y_Star + LCD_Currentfonts->Height) > LCD_Y_LENGTH)
        {
            usX = ILI9341_DispWindow_X_Star;
            usY = ILI9341_DispWindow_Y_Star;
        }

        ILI9341_DispChar_EN(usX, usY, * pStr);
        pStr ++;
        usX += LCD_Currentfonts->Width;
    }
}

/**
 * @brief  在 ILI9341 显示器上显示英文字符串(沿Y轴方向)
 * @param  usX :在特定扫描方向下字符的起始X坐标
 * @param  usY :在特定扫描方向下字符的起始Y坐标
 * @param  pStr :要显示的英文字符串的首地址
 * @note 可使用LCD_SetBackColor、LCD_SetTextColor、LCD_SetColors函数设置颜色
 * @retval 无
 */
void ILI9341_DispString_EN_YDir(uint16_t usX, uint16_t usY,  char *pStr)
{
    while (* pStr != '\0')
    {
        if ((usY - ILI9341_DispWindow_Y_Star + LCD_Currentfonts->Height) > LCD_Y_LENGTH)
        {
            usY = ILI9341_DispWindow_Y_Star;
            usX += LCD_Currentfonts->Width;
        }

        if ((usX - ILI9341_DispWindow_X_Star + LCD_Currentfonts->Width) >  LCD_X_LENGTH)
        {
            usX = ILI9341_DispWindow_X_Star;
            usY = ILI9341_DispWindow_Y_Star;
        }

        ILI9341_DispChar_EN(usX, usY, * pStr);
        pStr ++;
        usY += LCD_Currentfonts->Height;
    }
}

设置或获取当前字体类型

cpp 复制代码
/**
  * @brief  设置英文字体类型
  * @param  fonts: 指定要选择的字体
	*		参数为以下值之一
  * 	@arg:Font24x32;
  * 	@arg:Font16x24;
  * 	@arg:Font8x16;
  * @retval None
  */
void LCD_SetFont(sFONT *fonts)
{
    LCD_Currentfonts = fonts;
}

/**
  * @brief  获取当前字体类型
  * @param  None.
  * @retval 返回当前字体类型
  */
sFONT *LCD_GetFont(void)
{
    return LCD_Currentfonts;
}

设置或获取LCD前景和背景颜色

cpp 复制代码
/**
  * @brief  设置LCD的前景(字体)及背景颜色,RGB565
  * @param  TextColor: 指定前景(字体)颜色
  * @param  BackColor: 指定背景颜色
  * @retval None
  */
void LCD_SetColors(uint16_t TextColor, uint16_t BackColor)
{
    CurrentTextColor = TextColor;
    CurrentBackColor = BackColor;
}

/**
  * @brief  获取LCD的前景(字体)及背景颜色,RGB565
  * @param  TextColor: 用来存储前景(字体)颜色的指针变量
  * @param  BackColor: 用来存储背景颜色的指针变量
  * @retval None
  */
void LCD_GetColors(uint16_t *TextColor, uint16_t *BackColor)
{
    *TextColor = CurrentTextColor;
    *BackColor = CurrentBackColor;
}

/**
  * @brief  设置LCD的前景(字体)颜色,RGB565
  * @param  Color: 指定前景(字体)颜色
  * @retval None
  */
void LCD_SetTextColor(uint16_t Color)
{
    CurrentTextColor = Color;
}

/**
  * @brief  设置LCD的背景颜色,RGB565
  * @param  Color: 指定背景颜色
  * @retval None
  */
void LCD_SetBackColor(uint16_t Color)
{
    CurrentBackColor = Color;
}

清除某行文字

cpp 复制代码
/**
  * @brief  清除某行文字
  * @param  Line: 指定要删除的行
  *   本参数可使用宏LINE(0)、LINE(1)等方式指定要删除的行,
  *   宏LINE(x)会根据当前选择的字体来计算Y坐标值,并删除当前字体高度的第x行。
  * @retval None
  */
void LCD_ClearLine(uint16_t Line)
{
    ILI9341_Clear(0, Line, LCD_X_LENGTH, ((sFONT *)LCD_GetFont())->Height);	/* 清屏,显示全黑 */
}

测试代码1:液晶屏幕显示

cpp 复制代码
void LCD_Test(void)
{
    /*演示显示变量*/
    static uint8_t testCNT = 0;
    char dispBuff[100];

    testCNT++;

    LCD_SetFont(&Font8x16);								// 设置字体类型:8*16,16*24,24*32
    LCD_SetColors(RED, BLACK);							// 设置前景和背景色
    ILI9341_Clear(0, 0, LCD_X_LENGTH, LCD_Y_LENGTH);	// 清屏,显示全黑
	
    /********显示字符串示例*******/
	ILI9341_DispString_EN(24, 0,  "  ____           _____");
	ILI9341_DispString_EN(24, 16, " ______         _______");
	ILI9341_DispString_EN(24, 32, "_________      _________");
	ILI9341_DispString_EN(24, 48, "  _____   Love   _____");
	ILI9341_DispString_EN(24, 64, "    _____      _____");
	ILI9341_DispString_EN(24, 80, "      _____  _____");
	ILI9341_DispString_EN(24, 96, "        ________");
	ILI9341_DispString_EN(24, 112, "          ____");

	/********显示变量示例*******/
	sprintf(dispBuff, "%04d ", testCNT);
	ILI9341_DispString_EN(104, 32, dispBuff);

    /*******显示图形示例******/
    LCD_SetFont(&Font24x32);
	
    /* 画直线 */
    LCD_ClearLine(LINE(4));		/* LINE4 = 32*4 = 128,清除单行文字 */
    LCD_SetTextColor(BLUE);
    ILI9341_DispStringLine_EN(LINE(4), "Draw line:");

    LCD_SetTextColor(RED);
    ILI9341_DrawLine(0, 160, 239, 319);
    ILI9341_DrawLine(239, 160, 0, 319);

    LCD_SetTextColor(YELLOW);
    ILI9341_DrawLine(0, 180, 239, 180);
    ILI9341_DrawLine(0, 300, 239, 300);

    LCD_SetTextColor(BLUE);
    ILI9341_DrawLine(20, 160, 20, 319);
    ILI9341_DrawLine(220, 160, 220, 319);

    HAL_Delay(2000);
    ILI9341_Clear(0, 16 * 8, LCD_X_LENGTH, LCD_Y_LENGTH - 16 * 8);	/* 清中下屏,显示全黑 */

    /*画矩形*/
    LCD_ClearLine(LINE(4));		/* LINE4 = 32*4 = 128,清除单行文字 */
    LCD_SetTextColor(BLUE);
    ILI9341_DispStringLine_EN(LINE(4), "Draw Rect:");

    LCD_SetTextColor(RED);
    ILI9341_DrawRectangle(0, 160, 240, 160, 1);

    LCD_SetTextColor(YELLOW);
    ILI9341_DrawRectangle(80, 200, 120, 100, 0);

    LCD_SetTextColor(BLUE);
    ILI9341_DrawRectangle(120, 170, 100, 50, 1);

	HAL_Delay(2000);
    ILI9341_Clear(0, 16 * 8, LCD_X_LENGTH, LCD_Y_LENGTH - 16 * 8);	/* 清中下屏,显示全黑 */

    /* 画圆 */
    LCD_ClearLine(LINE(4));		/* LINE4 = 32*4 = 128,清除单行文字 */
    LCD_SetTextColor(BLUE);
    ILI9341_DispStringLine_EN(LINE(4), "Draw Cir:");

    LCD_SetTextColor(RED);
    ILI9341_DrawCircle(120, 240, 78, 0);

    LCD_SetTextColor(YELLOW);
    ILI9341_DrawCircle(120, 240, 50, 1);

    LCD_SetTextColor(BLUE);
    ILI9341_DrawCircle(120, 240, 20, 1);

    HAL_Delay(2000);
    ILI9341_Clear(0, 16 * 8, LCD_X_LENGTH, LCD_Y_LENGTH - 16 * 8);	/* 清屏,显示全黑 */
}

实验现象

测试代码2:液晶坐标方向

液晶屏幕显示的功能例程中,使用了ILI9341_GramScan(6)固定了X和Y轴的方向。在该例城,主要对ILI9341_GramScan函数参数取不同值,改变X和Y轴的方向。

cpp 复制代码
/*用于展示LCD的八种方向模式*/
void LCD_Direction_Show(void)
{
    uint8_t i = 0;
    char dispBuff[100];

    //轮流展示各个方向模式
    for (i = 0; i < 8; i++)
    {
        LCD_SetFont(&Font16x24);
        LCD_SetColors(RED, BLACK);

        ILI9341_Clear(0, 0, LCD_X_LENGTH, LCD_Y_LENGTH);	/* 清屏,显示全黑 */

        //其中0、3、5、6 模式适合从左至右显示文字,
        //不推荐使用其它模式显示文字	其它模式显示文字会有镜像效果
        //其中 6 模式为大部分液晶例程的默认显示方向
        ILI9341_GramScan(i);

        sprintf(dispBuff, "o --->X");
        ILI9341_DispStringLine_EN(LINE(0), dispBuff); // 沿X方向显示文字
														 
        sprintf(dispBuff, "o ||VY");                     
        ILI9341_DispString_EN_YDir(0, 0, dispBuff);   // 沿Y方向显示文字

		LCD_SetFont(&Font24x32);
		LCD_SetTextColor(BLUE);
		sprintf(dispBuff, "%d", i);
		ILI9341_DispString_EN(50, 50, dispBuff);
		
        HAL_Delay(2000);
    }
}

实验现象

由此看出,参数为6时,观看最佳。

相关推荐
赵大仁8 分钟前
在 CentOS 7 上安装 Node.js 20 并升级 GCC、make 和 glibc
linux·运维·服务器·ide·ubuntu·centos·计算机基础
vvw&13 分钟前
Docker Build 命令详解:在 Ubuntu 上构建 Docker 镜像教程
linux·运维·服务器·ubuntu·docker·容器·开源
冷曦_sole39 分钟前
linux-21 目录管理(一)mkdir命令,创建空目录
linux·运维·服务器
最后一个bug41 分钟前
STM32MP1linux根文件系统目录作用
linux·c语言·arm开发·单片机·嵌入式硬件
dessler1 小时前
Docker-Dockerfile讲解(二)
linux·运维·docker
卫生纸不够用1 小时前
子Shell及Shell嵌套模式
linux·bash
world=hello1 小时前
关于科研中使用linux服务器的集锦
linux·服务器
soragui2 小时前
【ChatGPT】OpenAI 如何使用流模式进行回答
linux·运维·游戏
白云coy3 小时前
Redis 安装部署[主从、哨兵、集群](linux版)
linux·redis