目录
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外设会自动完成数据访问过程,读写命令的操作不需要程序控制。
如:
#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时序结构体
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)
/** * @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初始化(结构体)
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输出低电平,使得液晶屏会把传输过程理解为命令传输。
//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寄存器
厂家写好的,直接用。
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); } }
总体初始化
也就是把上述的一些初始化集合在一起。
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); }
设置液晶显示窗口和光标位置
根据液晶屏的要求,在发送显示数据前需要先设置窗口,确定发送像素数据的显示区域。
/** * @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); }
填充像素点和清窗口屏
/** * @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); } }
获取某一个坐标点的像素数据
/** * @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; }
画线
/** * @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; } } }
画矩形
/** * @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); } }
画圆形
/** * @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 --; } } }
显示字符和字符串
/** * @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; } }
设置或获取当前字体类型
/** * @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前景和背景颜色
/** * @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; }
清除某行文字
/** * @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:液晶屏幕显示
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轴的方向。
/*用于展示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时,观看最佳。