注:本文属博主学习时所作笔记,内容源大参考于野火的《零死角玩转STM32F103》以及部分网络资料,笔记内容仅作为自己参考,免去频繁查询参考手册的麻烦,如有错误,还请指出!
ILI9341 液晶控制器
ILI9341 控制器内部电路连接完后,其余信号线引出到排针
液晶屏引出的信号线说明
信号线 |
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] |
- |
触摸屏的控制信号线,下一章再介绍 |
FSMC简介
STM32F1 系列芯片使用 FSMC 外设来管理扩展的存储器, FSMC 是 Flexible Static
Memory Controller 的缩写,译为灵活的静态存储控制器。它可以用于驱动包括 SRAM、
NOR FLASH 以及 NAND FLSAH 类型的存储器,不能驱动如 SDRAM 这种动态的存储器而
在 STM32F429 系列的控制器中,它具有 FMC 外设,支持控制 SDRAM 存储器。
MCU 对液晶屏的操作实际上就是把显示数据写入到显存中,与控制存储器非常类似,且8080 接口的通讯时序完全可以使用 FSMC 外设产生。
模拟 8080 接口时序
在模拟控制 LCD 时,是使用 FSMC 的 NOR\PSRAM 模式的, 而且使用的是类似异步、 地址与数据线独立的 NOR FLASH 类型的模式 B,实际上 CLK、 NWAIT、 NADV 引脚并没有使用到。
FSMC 信 号 名 |
信号方向 |
功能 |
CLK |
输出 |
时钟(同步突发模式使用) |
A[25:0] |
输出 |
地址总线 |
D[15:0] |
输入/输出 |
双向数据总线 |
NE[x] |
输出 |
片选, x = 1…4 |
NOE |
输出 |
输出使能 |
NWE |
输出 |
写使能 |
NWAIT |
输入 |
NOR 闪存要求 FSMC 等待的信号 |
NADV |
输出 |
地址、数据线复用时作锁存信号 |
对比 FSMC NOR/PSRAM 中的模式 B 时序与 ILI9341 液晶控制器芯片使用的 8080 时序可发现,这两个时序是十分相似的(除了 FSMC 的地址线 A 和 8080 的 D/CX 线,可以说是完全一样)
FSMC-NOR信号线 |
功能 |
8080 信号线 |
功能 |
NEx |
片选信号 |
CSX |
片选信号 |
NWR |
写使能 |
WRX |
写使能 |
NOE D[15:0] |
读使能 数据信号 |
RDX D[15:0] |
读使能 数据信号 |
A[25:0] |
地址信号 |
D/CX |
数据/命令选择 |
为了模拟出 8080 时序,我们可以把 FSMC 的 A0 地址线(也可以使用其它 A1/A2 等地
址线)与 ILI9341 芯片 8080 接口的 D/CX 信号线连接,那么当 A0 为高电平时(即 D/CX 为高
电平),数据线 D[15:0]的信号会被 ILI9341 理解为数值,若 A0 为低电平时(即 D/CX 为低电
平),传输的信号则会被理解为命令。
FSMC的地址映射
使用 FSMC 外接存储器时,其存储单元是映射到 STM32 的内部寻址空间的,这里也涉及到使用地址线某个引脚时,需要计算对应的地址,以达到控制输出高低电平。
代码分析
液晶LCD硬件相关宏定义
根据液晶屏的原理图,将FSMC 控制液晶屏硬件相关的配置都以宏的形式定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
#define ILI9341_CS_CLK RCC_APB2Periph_GPIOD #define ILI9341_CS_PORT GPIOD #define ILI9341_CS_PIN GPIO_Pin_7
#define ILI9341_DC_CLK RCC_APB2Periph_GPIOD #define ILI9341_DC_PORT GPIOD #define ILI9341_DC_PIN GPIO_Pin_11
#define ILI9341_D0_CLK RCC_APB2Periph_GPIOD #define ILI9341_D0_PORT GPIOD #define ILI9341_D0_PIN GPIO_Pin_14
|
初始化 FSMC 的 GPIO
利用上面的宏,编写 FSMC 的 GPIO 引脚初始化函数,对于 FSMC 引脚,全部直接初始化为复用推挽输出模式即可,而背光 BK 引脚及液晶复信 RST 信号则被初始化成普通的推挽输出模式,这两个液晶控制信号直接输出普通的电平控制即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| static void ILI9341_GPIO_Config (void) { GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd ( ILI9341_CS_CLK|ILI9341_DC_CLK|ILI9341_WR_CLK| ILI9341_RD_CLK |ILI9341_BK_CLK|ILI9341_RST_CLK| ILI9341_D0_CLK|ILI9341_D1_CLK, ENABLE);
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Pin = ILI9341_D0_PIN; GPIO_Init(ILI9341_D0_PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Pin = ILI9341_RD_PIN; GPIO_Init (ILI9341_RD_PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Pin = ILI9341_RST_PIN; GPIO_Init (ILI9341_RST_PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = ILI9341_BK_PIN; GPIO_Init (ILI9341_BK_PORT, &GPIO_InitStructure); }
|
配置 FSMC 的模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| static void ILI9341_FSMC_Config (void) { FSMC_NORSRAMInitTypeDef FSMC_NORSRAMInitStructure; FSMC_NORSRAMTimingInitTypeDef readWriteTiming; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC, ENABLE); readWriteTiming.FSMC_AddressSetupTime = 0x01; readWriteTiming.FSMC_DataSetupTime = 0x04; readWriteTiming.FSMC_AccessMode = FSMC_AccessMode_B; readWriteTiming.FSMC_AddressHoldTime = 0x00; readWriteTiming.FSMC_BusTurnAroundDuration = 0x00; readWriteTiming.FSMC_CLKDivision = 0x00; readWriteTiming.FSMC_DataLatency = 0x00; FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAMx; FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable; FSMC_NORSRAMInitStructure.FSMC_MemoryType = FSMC_MemoryType_NOR; FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b; FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode = FSMC_BurstAccessMode_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_NORSRAMCmd (FSMC_Bank1_NORSRAMx, ENABLE); }
|
计算控制液晶屏时使用的地址
首先确定板子NEx引脚的连接,本次实例使用的是FSMC_NE1
在 数据手册 –> 存储器映像 中查询FSMC_bnak1 NOR/PSRAM 1
的地址:
使用的是FSMS_NE1
也就是当访问到0x6000000-0x63FFFFFF
这个地址范围内时,FSMC都会产生有效的访问时序;本实例中使用的是FSMC_A16
地址线作为命令/数据选择线,即:
由于地址线转换的问题,这里不细说,需要知道的是,STM32 内部的 HADDR 与 FSMC 的连接关系会左移一位,计算关系也会改变:
使FSMC_A16
地址线为高电平:
1
| 0X6000 0000 |= (1<<(16+1)) = 0x6002 0000;
|
使FSMC_A16
地址线为低电平:
1
| 0X6000 0000 &= ~(1<<(16+1)) = 0x6000 0000;
|
封装函数
因为在实例后面会频繁对LCD进行操作,所以把发送命令及发送数据的操作封装成了内联函数,会更方便调用:
1 2 3 4 5 6
|
#define FSMC_Addr_ILI9341_CMD ( ( uint32_t ) 0x60020000 )
#define FSMC_Addr_ILI9341_DATA ( ( uint32_t ) 0x60000000 )
|
1 2 3 4 5 6 7 8 9 10
| __inline void ILI9341_Write_Cmd (uint16_t usCmd) { (__IO uint16_t *)(FSMC_Addr_ILI9341_CMD) = usCmd; }
__inline void ILI9341_Write_Data (uint16_t usData) { (__IO uint16_t *)(FSMC_Addr_ILI9341_DATA) = usData; }
|
需要写操作时,只要把要发送的命令代码或数据作为参数输入到函数然后调用即可,对于液晶屏的读操作,把向指针赋值的过程改为读取指针内容即可。
1 2 3 4 5
| __inline uint16_t ILI9341_Read_Data (void) { return (*FSMC_Addr_ILI9341_DATA); }
|
检验封装
在上述函数封装好之后,进行一些测试检验使很有必要,我们可以查询ILI9341
的数据文档,选择合适的命令进行测试:
当发送命令0x0C
,第二个参数会返回 LCD 的像素信息,可以封装一个检验函数如下:
1 2 3 4 5 6
| uint16_t Read_Pixel_Format(void) { ILI9341_Write_Cmd(0x0C); ILI9341_Read_Data(); return ILI9341_Read_Data(); }
|