目录
[FSMC控制异步Nor Flash的时序](#FSMC控制异步Nor Flash的时序)
[NOR FLASH时序结构体](#NOR FLASH时序结构体)
[FSMC初始化结构体(针对NOR FLASH)](#FSMC初始化结构体(针对NOR FLASH))
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时,观看最佳。