FSMC(Flexible Static Memeoy Controller)灵活的静态存储控制器,它可以用于驱动包括SRAM、NOR FLASH以及NAND FLSAH类型的存储器,不能驱动如SDRAM这种动态的存储器。FMC外设支持控制SDRAM存储器。硬件框架如下图所示:

FSMC支持8/16/32位数据宽度(32位宽数据将被划分成多个连续的16或8位访问),并将外部存储器划分为固定大小为256(4 * 64)MB的四个存储块:

Bank1被用于驱动NOR Flash/SRAM/PSRAM,被分为4个区,每个区为64MB。FSMC_NE[1:4]片选引脚对应的不同的Bank所选区,并且有唯一确定的HADDR[27:26]值与之对应,如下表所示:

HADDR 是 AHB 内部地址线,但也会参与对外部存储器的寻址。
以下图为例,LCD的CS片选引脚拉低时,会将与之相连的FSMC_NE3拉低,即选择Bank1的第3区。因此,一般地址与器件挂载的片选引脚相关,当不同的FSMC_NE引脚连接不同的外部存储器时,访问的存储区域不一样,达到控制多块存储器的目的。

注意:Bank1每个存储区域片选信号NE[4:1]是唯一的,其它信号(地址、数据和控制)均为共享信号。
根据存储器数据宽度不同,实际向存储器发送的地址也将有所不同,如下表所示:

当Bank1接16位宽度存储器时:HADDR[25:1] --> FSMC_A[24:0]
当Bank1接8位宽度存储器时: HADDR[25:0] --> FSMC_A[25:0]
即当存储器数据宽度为16位时,FSMC将使用内部的HADDR[25:1] 地址来作为对外部存储器的寻址地址,因此实际向存储器写入的地址会向右移1位。下面举例说明:
*(volatile uint16_t*)(0x60000468UL)=0xABCD;
向0x60000468(Bank1的第1区)写入0xABCD,观察写入时的时序图(先阅读后面的接口信号&通讯时序章节后再来看此处分析):

数据总线上先产生0x234(0x468 >> 1),然后NE1被拉低,在NADV下降沿瞬间,数据被锁存在地址锁存器上(A0~A15)和A16~A25(实验暂未使用,若使用会在NE1下降沿同时送出)组合成完整的地址信号。
注意:不论外接8位/16位宽设备,FSMC_A[0]永远接在外部设备地址A[0]。
在此情况下,有三个存储区域:

对于NAND Flash存储器,通用区和特性区存储空间分为三个部分,均位于低位256KB中:

软件通过操作这3个区域访问NAND Flash的流程:
由于 NAND Flash 存储器会自动递增地址,所以在访问连续存储器位置时,无需递增数据区域的地址。
对于NOR Flash/PSRAM,对于是否将FSMC的地址与数据线复用时的接口信号不一样,注意前缀“N”表示相关的信号为低电平有效。
| FSMC信号名称 | I/O | 说明 |
|---|---|---|
| CLK | O | 时钟(用于同步突发) |
| A[25:0] | O | 地址总线 |
| D[15:0] | I/O | 双向数据总线 |
| NE[1:4] | O | 片选信号 |
| NOE | O | 输出使能(读使能) |
| NWE | O | 写入使能 |
| NL(NADV) | O | 锁存使能 |
| NWAIT | I | FSMC 的 NOR Flash 等待输入信号 |
A[25:0] 26根地址线一共可以表示226=64MB行存储单元,可访问的空间大小与数据宽度有关。
| FSMC信号名称 | I/O | 说明 |
|---|---|---|
| CLK | O | 时钟(用于同步突发) |
| A[25:16] | O | 地址总线 |
| AD[15:0] | I/O | 16位复用双向地址/数据总线 |
| NE[1:4] | O | 片选信号 |
| NOE | O | 输出使能(读使能) |
| NWE | O | 写入使能 |
| NL(NADV) | O | 锁存使能 |
| NWAIT | I | FSMC 的 NOR Flash 等待输入信号 |
| FSMC信号名称 | I/O | 说明 |
|---|---|---|
| CLK | O | 时钟(用于同步突发) |
| A[25:0] | O | 地址总线 |
| D[15:0] | I/O | 双向数据总线 |
| NE[1:4] | O | 片选信号 |
| NOE | O | 输出使能(读使能) |
| NWE | O | 写入使能 |
| NL(NADV) | O | 仅用于PSRAM输入的地址有效信号 |
| NWAIT | I | FSMC 的 NOR Flash 等待输入信号 |
| NBL[1] | O | 高字节使能 |
| NBL[0] | O | 低字节使能 |
NBL[1:0] 在进行读取访问时为低电平
| FSMC信号名称 | I/O | 说明 |
|---|---|---|
| CLK | O | 时钟(用于同步突发) |
| A[25:16] | O | 地址总线 |
| AD[15:0] | I/O | 16位复用双向地址/数据总线 |
| NE[1:4] | O | 片选信号 |
| NOE | O | 输出使能(读使能) |
| NWE | O | 写入使能 |
| NL(NADV) | O | 仅用于PSRAM输入的地址有效信号 |
| NWAIT | I | FSMC 的 NOR Flash 等待输入信号 |
| NBL[1] | O | 高字节使能 |
| NBL[0] | O | 低字节使能 |
「FSMC模式」
驱动SRAM时一般使用模式1或者模式A。
下面分析模式A(SRAM/PSRAM)的读/写时序:
读时序

存储器操作周期由地址建立周期(ADDSET)、**数据建立周期(**DATAST)组成。在地址建立周期中,地址线发出要访问的地址,NBL[1:0] 在进行读取访问时为低电平,片选信号使能存储器芯片;地址建立周期结束后读使能信号线发出读使能信号,接着存储器通过数据信号线把目标数据传输给FSMC,FSMC把它交给内核。
写时序

写时序类似,区别是在数据建立周期期间写使能信号线发出写信号。
注意:STM32F1与F4的D[15:0]时序有些差异,详见各自手册。
对于NOR FLASH/PSRAM控制器(存储块1),通过FSMC_BCRx、FSMC_BTRx和FSMC_BWTRx寄存器(其中x=1~4,对应bank的4个区)设置FSMC访问外部存储器的时序参数,拓宽了可选用的外部存储器的速度范围。模式A下的寄存器配置参数如下图所示:
FSMC_BCRx寄存器:

FSMC_BTRx寄存器:

FSMC_BWTRx寄存器:

若没有设置FSMC_BCRx的EXTMOD位,则读写共用FSMC_BTRx寄存器。
若设置了EXTMOD位,则FSMC_BTRx将和FSMC_BWTRx配合来配置写入和读取的时序参数。即FSMC_BTRx用于配置读取访问,FSMC_BWTRx用于配置写入访问。
在ST官方库提供的的寄存器定义里面,并没有定义FSMC_BCRx、FSMC_BTRx、FSMC_BWTRx等这个单独的寄存器,而是将他们进行了一些组合。规律如下:
FSMC_BCRx和FSMC_BTRx,组合成BTCR[8]寄存器组,他们的对应关系如下:
BTCR数组偶数位(从0开始)对应FSMC_BCRx(从1开始),BTCR数组奇数位则对应FSMC_BTRx
FSMC_BWTRx则组合成BWTR[7],他们的对应关系如下:
BWTR[0]对应FSMC_BWTR1,BWTR[2]对应FSMC_BWTR2,
BWTR[4]对应FSMC_BWTR3,BWTR[6]对应FSMC_BWTR4,
BWTR[1]、BWTR[3]和BWTR[5]保留。
typedef struct
{
/*!< 地址建立时间,0-0xF个HCLK周期 @note 此参数与同步NOR Flash无关 */
uint32_t FSMC_AddressSetupTime;
/*!< 地址保持时间,0-0xF个HCLK周期 @note 此参数与同步NOR Flash无关 */
uint32_t FSMC_AddressHoldTime;
/*!< 数据建立时间,0-0xF个HCLK周期 @note 此参数用于SRAM, ROM 和 异步复用NOR Flash */
uint32_t FSMC_DataSetupTime;
/*!< 总线转换周期,0-0xF个HCLK周期。在复用NOR FLASH存储器中,地址线与数据线可分时复用,总线转换周期就是指总线在这两种状态间切换需要的延时,防止冲突。控制其它存储器时这个参数无效,配置为0即可。*/
uint32_t FSMC_BusTurnAroundDuration;
/*!< 时钟分频因子,范围1-0xF,@note 此参数与异步存储器无关*/
uint32_t FSMC_CLKDivision;
/*!< 数据保持时间 @note 此参数与异步存储器无关*/
uint32_t FSMC_DataLatency;
/*!< 存储器访问模式,控制LCD时一般使用B模式 */
uint32_t FSMC_AccessMode;
}FSMC_NORSRAMTimingInitTypeDef;
LCD通信时的地址&数据建立周期
typedef struct
{
/*!< 设置要控制的Bank区域(BANK1的分区) value of @ref FSMC_NORSRAM_Bank */
uint32_t FSMC_Bank;
/*!< 设置地址总线与数据总线是否复用,在控制NOR FLASH时,可以地址总线与数据总线可以分时复用,以减少使用STM32信号线的数量,但通信速度会下降 */
uint32_t FSMC_DataAddressMux;
/*!< 设置要控制的存储器类型 */
uint32_t FSMC_MemoryType;
/*!< 设置要控制的存储器的数据宽度 */
uint32_t FSMC_MemoryDataWidth;
/*!< 设置是否使用突发访问模式 */
uint32_t FSMC_BurstAccessMode;
/*!< 设置是否使能在同步传输时使用的等待信号,在控制同步类型的NOR或PSRAM时,存储器可以使用FSMC_NWAIT引脚通知STM32需要等待。 */
uint32_t FSMC_AsynchronousWait;
/*!< 设置等待信号的有效极性,即要求等待时,使用高电平还是低电平 */
uint32_t FSMC_WaitSignalPolarity;
/*!< 设置是否支持把非对齐(如访问奇数地址)的AHB突发操作分割成2次线性操作(FSMC_WrapMode_Enable/Disable),该配置仅在突发模式下有效 */
uint32_t FSMC_WrapMode;
/*!< 配置在突发传输模式时,决定存储器是在等待状态之前的一个数据周期有效还是在等待状态期间有效 */
uint32_t FSMC_WaitSignalActive;
/*!< 设置是否写使能,禁止写使能的话FSMC只能从存储器中读取数据 */
uint32_t FSMC_WriteOperation;
/*!< 设置当存储器牌突发传输模式时,是否允许通过NWAIT信号引脚插入等待状态 */
uint32_t FSMC_WaitSignal;
/*!< 是否使用扩展模式 */
uint32_t FSMC_ExtendedMode;
/*!< 是否使能写突发操作 */
uint32_t FSMC_WriteBurst;
/*!< 写时序初始化结构体 */
FSMC_NORSRAMTimingInitTypeDef* FSMC_ReadWriteTimingStruct;
/*!< 读时序初始化结构体 */
FSMC_NORSRAMTimingInitTypeDef* FSMC_WriteTimingStruct;
}FSMC_NORSRAMInitTypeDef;
突发访问模式:指发送一个地址后连续访问多个数据(存储器地址自增),非突发模式下每访问一个数据都需要输入一个地址,仅在控制同步类型的存储器时才能使用突发模式。(LCD每次访问固定地址,因此配置为非突发模式)
控制LCD需要关注的配置(黄色mark部分):

LCD分为带控制器与不带控制器的,本文主要分析带有ILI9806G液晶控制器芯片的LCD,其内部结构如下图所示:



ILI9806G控制器根据自身的IM[3:0]信号线电平决定它与MCU的通讯方式,DBI[2:0]配置RGB格式,本文使用的LCD被配置8080接口通讯,且使用16根数据线的RGB565格式。引出的排针信号线如下图:

| 信号线 | ILI9806G对应的信号线 | 说明 |
|---|---|---|
| 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 | - | 背光信号,低电平点亮 |
| GPIO23 | - | 空引脚,不需要连接 |
| RST | 与触摸IC相连 | 触摸IC的复位引脚 |
| INT | 与触摸IC相连 | 触摸IC的中断信号引脚 |
| SCL | 与触摸IC相连 | 触摸IC的IIC总线的时钟信号 |
| SDA | 与触摸IC相连 | 触摸IC的IIC总线的数据信号 |
带X的表示低电平有效
向ILI9806写命令的时序图(8080接口写命令时序):

CSX拉低,片选使能,可写入数据。
格式为RGB565的一个像素数据传输过程:

MSB = DB15, LSB = DB0
控制LCD时,控制LCD时使用与NOR FLASH一样的模式B,NOR FLASH控制信号线:
| FSMC信号名称 | 信号方向 | 功能 |
|---|---|---|
| CLK | 输出 | 时钟(同步突发模式使用) |
A[25:0] | 输出 | 地址总线 |
D[15:0] | 输入/输出 | 双向数据总线 |
NE[x] | 输出 | 片选,x = 1…4 |
NOE | 输出 | 输出使能 |
NWE | 输出 | 写使能 |
| NWAIT | 输入 | NOR闪存要求FSMC等待的信号 |
| NADV | 输出 | 地址、数据线复用时作锁存信号 |
表中高亮部分为控制LCD需要用到的引脚,因采用异步模式控制,因此无需同步时钟CLK。
「FSMC模式B时序与8080时序/引脚对比」

| FSMC-NOR信号线 | 功能 | 8080信号线 | 功能 |
|---|---|---|---|
| NEx | 片选信号 | CSX | 片选信号 |
| NWE | 写使能 | WRX | 写使能 |
| NOE | 读使能 | RDX | 读使能 |
| D[15:0] | 数据信号 | D[15:0] | 数据信号 |
| A[25:0] | 地址信号 | D/CX | 数据/命令选择 |
通过对比发现区别看起来较大的即为A[25:0]地址信号与LCD控制器的D/C数据/命令选择信号。因此,为模拟8080时序,可将D/CX与A0(其他地址引脚也可以)连接,当A0拉低时表示传输的信号为命令,拉高则表示数据。即当使用FSMC向奇数地址写入数据时表示传数据,偶数地址表示传命令:
| 地址 | 地址的二进制值**(仅列出低四位)** | A0(D/CX)的电平 | 控制ILI9341时的意义 |
|---|---|---|---|
| 0x6xxx xxx1 | 0001 | 1高电平 | D数值 |
| 0x6xxx xxx3 | 0011 | 1高电平 | D数值 |
| 0x6xxx xxx0 | 0000 | 0低电平 | C命令 |
| 0x6xxx xxx2 | 0010 | 0低电平 | C命令 |

注意:开发板中LCD与SRAM共用同一个FSMC,但是在不同时刻下操作的片选引脚不一样,SRAM为NE4,因此向LCD写入时会拉低NE3片选,选择bank1的第3区,不会干扰到SRAM。
D0~D15、CS、RD、WE、RS(命令/数据选择)全部初始化为复用推挽输出。BL、RST初始化为普通推挽输出。
static void FSMC_LCD_Init(void)
{
FSMC_NORSRAMInitTypeDef FSMC_NORSRAMInitStructure;
FSMC_NORSRAMTimingInitTypeDef readWriteTiming;
/*使能FSMC外设时钟*/
RCC_AHB3PeriphClockCmd(RCC_AHB3Periph_FSMC, ENABLE);
//地址建立时间(ADDSET)为5个HCLK, 5/168M = 30ns
readWriteTiming.FSMC_AddressSetupTime = 0x04;
//地址保持时间(ADDHLD)模式B未用到
readWriteTiming.FSMC_AddressHoldTime = 0x00;
//数据建立时间(DATAST)+ 1个HCLK = 5/168M=30ns
readWriteTiming.FSMC_DataSetupTime = 0x04;
//设置总线转换周期,仅用于复用模式的NOR操作
readWriteTiming.FSMC_BusTurnAroundDuration = 0x00;
//设置时钟分频,仅用于同步类型的存储器
readWriteTiming.FSMC_CLKDivision = 0x00;
//数据保持时间,仅用于同步型的NOR
readWriteTiming.FSMC_DataLatency = 0x00;
//选择匹配为异步NOR FLASH的模式(模拟8080)
readWriteTiming.FSMC_AccessMode = FSMC_AccessMode_B;
// 选择FSMC映射的存储区域: Bank1 sram3
FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM3;
//设置地址总线与数据总线是否复用,仅用于NOR
FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable;
//设置要控制的存储器类型:NOR FLASH类型
FSMC_NORSRAMInitStructure.FSMC_MemoryType = FSMC_MemoryType_NOR;
//存储器数据宽度:16位
FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;
//设置是否使用突发访问模式,仅用于同步类型的存储器
FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode = FSMC_BurstAccessMode_Disable;
//设置是否使能等待信号,仅用于同步类型的存储器
FSMC_NORSRAMInitStructure.FSMC_AsynchronousWait = FSMC_AsynchronousWait_Disable;
//设置等待信号的有效极性,仅用于同步类型的存储器
FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
//设置是否支持把非对齐的突发操作,仅用于同步类型的存储器
FSMC_NORSRAMInitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable;
//设置等待信号插入的时间,仅用于同步类型的存储器
FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;
//存储器写使能
FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable;
//不使用等待信号
FSMC_NORSRAMInitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable;
//不使用扩展模式,读写使用相同的时序
FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable;
//突发写操作
FSMC_NORSRAMInitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable;
//读写时序配置
FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &readWriteTiming;
//读写同样时序,使用扩展模式时这个配置才有效
FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &readWriteTiming;
FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure); //初始化FSMC配置
FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM3, ENABLE); // 使能BANK
}
其中,地址建立时间ADDSET和数据建立时间DATAST需要参考ILI9806G手册时序图计算:


对于STM32F4其HCLK频率为168M,即1个HCLK为6ns,但是根据模式B的时序图实际写入的ADDSET和DATAST均会+1。同时,由于读数据比写数据慢许多,一般需要使能扩展模式,即读写使用不同的时序。However, 本文使用的「野火」LCD屏幕经过他们测试ADDSET和DATAST都设为30ns,且读写使用相同时序(不用扩展模式),即可正常通信。
BANK1第3区的基地址为0x68000000,使用A0地址信号控制LCD的D/C信号,即向HADDR的0x68000000 | (1 << 0)表示传输数据,而0x68000000 & ~(1 << 0)表示传输命令。
不过经前文分析,当存储器数据宽度为16位时,FSMC使用HADDR[25:1] 地址来作为对外部存储器的寻址地址,即HADDR1对应A0,实际向存储器写入的地址会向右移1位。因此,要先让A0左移1位,即0x68000000 | (1 << 1)表示传输数据,而0x68000000 & ~(1 << 1)表示传输命令。
注意:对于其他地址引脚,如传输命令,第0位需置0,其他小于该引脚对应的位需置1。如配置A17位地址引脚,则写数据地址为
0x68000000 + (1<<18) = 0x68040000,则写命令地址为0x6803FFFE
#define Bank1_NOR_SRAM3_ADDR ((uint32_t)(0x68000000))
//使用A0连接到D/CX引脚
#define FSMC_Addr_ILI9806G_DATA ((uint32_t)(Bank1_NOR_SRAM3_ADDR | (1 << (0 + 1)))) //0x68000002
#define FSMC_Addr_ILI9806G_CMD ((uint32_t)(Bank1_NOR_SRAM3_ADDR & ~(1 << (0 + 1)))) //0x68000000
/**
* @brief 向ILI9806G写入命令
* @param usCmd :要写入的命令(表寄存器地址)
* @retval 无
*/
__inline void ILI9806G_Write_Cmd ( uint16_t usCmd )
{
* ( __IO uint16_t * ) ( FSMC_Addr_ILI9806G_CMD ) = usCmd;
}
/**
* @brief 向ILI9806G写入数据
* @param usData :要写入的数据
* @retval 无
*/
__inline void ILI9806G_Write_Data ( uint16_t usData )
{
* ( __IO uint16_t * ) ( FSMC_Addr_ILI9806G_DATA ) = usData;
}
/**
* @brief 从ILI9806G读取数据
* @param 无
* @retval 读取到的数据
*/
__inline uint16_t ILI9806G_Read_Data ( void )
{
return ( * ( __IO uint16_t * ) ( FSMC_Addr_ILI9806G_DATA ) );
}
读取LCD控制芯片ID:

需要先复位LCD
//读取液晶ID
uint16_t LCD_GetID(void)
{
uint16_t id = 0;
uint16_t dummy;
LCD_ReadReg(0xD3, &dummy);
dummy = LCD_RD_DATA(); // 实测需要空读三次才行
dummy = LCD_RD_DATA();
id |= (uint16_t)(LCD_RD_DATA() << 8);
id |= LCD_RD_DATA();
return id;
}
寄存器配置由厂家提供,我们只需将写数据函数、写命令函数、延时函数均换成自己封装好的FSMC读写函数和延时函数即可。
「液晶扫描方向设置」


void ILI9806G_GramScan(uint8_t ucOption)
{
//参数检查,只可输入0-7
if (ucOption > 7)
return;
//根据模式更新LCD_SCAN_MODE的值,主要用于触摸屏选择计算参数
LCD_SCAN_MODE = ucOption;
//根据模式更新XY方向的像素宽度
if (ucOption % 2 == 0)
{
// 0 2 4 6模式下X方向像素宽度为480,Y方向为854
LCD_X_LENGTH = ILI9806G_LESS_PIXEL;
LCD_Y_LENGTH = ILI9806G_MORE_PIXEL;
}
else
{
// 1 3 5 7模式下X方向像素宽度为854,Y方向为480
LCD_X_LENGTH = ILI9806G_MORE_PIXEL;
LCD_Y_LENGTH = ILI9806G_LESS_PIXEL;
}
// 0x36命令参数的高3位可用于设置GRAM扫描方向
ILI9806G_Write_Cmd(0x36);
ILI9806G_Write_Data(0x00 | (ucOption << 5)); //根据ucOption的值设置LCD参数,共0-7种模式
ILI9806G_Write_Cmd(CMD_SetCoordinateX);
ILI9806G_Write_Data(0x00); /* x 起始坐标高8位 */
ILI9806G_Write_Data(0x00); /* x 起始坐标低8位 */
ILI9806G_Write_Data(((LCD_X_LENGTH - 1) >> 8) & 0xFF); /* x 结束坐标高8位 */
ILI9806G_Write_Data((LCD_X_LENGTH - 1) & 0xFF); /* x 结束坐标低8位 */
ILI9806G_Write_Cmd(CMD_SetCoordinateY);
ILI9806G_Write_Data(0x00); /* y 起始坐标高8位 */
ILI9806G_Write_Data(0x00); /* y 起始坐标低8位 */
ILI9806G_Write_Data(((LCD_Y_LENGTH - 1) >> 8) & 0xFF); /* y 结束坐标高8位 */
ILI9806G_Write_Data((LCD_Y_LENGTH - 1) & 0xFF); /* y 结束坐标低8位 */
/* write gram start */
ILI9806G_Write_Cmd(CMD_SetPixel);
}
注意:0x36寄存器bit3是配置数据为RGB还是BGR,若实际使用过程中R与B颜色错位,则需将此位置1,即使用ILI9806G_Write_Data(0x0f | (ucOption << 5));

「绘制矩形」
绘制矩形步骤:

//绘制矩形 RGB565
void LCD_Draw_Rect(uint16_t x0, uint16_t x1, uint16_t y0, uint16_t y1, uint16_t color)
{
ILI9806G_Write_Cmd(0x2a);
ILI9806G_Write_Data((x0 >> 8) & 0xFF); // x0高8位
ILI9806G_Write_Data(x0 & 0xFF);
ILI9806G_Write_Data((x1 >> 8) & 0xFF); // x1高8位
ILI9806G_Write_Data(x1 & 0xFF);
ILI9806G_Write_Cmd(0x2b);
ILI9806G_Write_Data((y0 >> 8) & 0xFF); // y0高8位
ILI9806G_Write_Data(y0 & 0xFF);
ILI9806G_Write_Data((y1 >> 8) & 0xFF); // y1高8位
ILI9806G_Write_Data(y1 & 0xFF);
ILI9806G_Write_Cmd(0x2c); // 写入像素命令(往定义的矩形框内按扫描方向进行写入)
for (uint32_t i = 0; i < (x1 - x0 + 1) * (y1 - y0 + 1); i++){
ILI9806G_Write_Data(color);
}
}
References
END