利用FSMC模拟8080时序控制LCD
2019-07-25 10:12:43

注:本文属博主学习时所作笔记,内容源大参考于野火的《零死角玩转STM32F103》以及部分网络资料,笔记内容仅作为自己参考,免去频繁查询参考手册的麻烦,如有错误,还请指出!

ILI9341 液晶控制器

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模式B时序与8080时序对比(写过程)
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 的内部寻址空间的,这里也涉及到使用地址线某个引脚时,需要计算对应的地址,以达到控制输出高低电平。

FSMC 的地址映射

代码分析

液晶LCD硬件相关宏定义

根据液晶屏的原理图,将FSMC 控制液晶屏硬件相关的配置都以宏的形式定义:

开发板与屏幕的连接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*****************	LCD控制信号	******************/
//片选引脚
#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;

/* 使能 FSMC 对应相应管脚时钟*/
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);
/* 省略部分信号线 */

/* 配置 FSMC 相对应的数据线,FSMC-D0~D15 */
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);
/* 省略WR, CS, DC 控制引脚 */

//设置普通推挽输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
/* 配置 LCD 复位 RST 控制管脚 */
GPIO_InitStructure.GPIO_Pin = ILI9341_RST_PIN;
GPIO_Init (ILI9341_RST_PORT, &GPIO_InitStructure);
/* 配置 LCD 背光控制管脚 BK */
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模式初始化结构体 */
FSMC_NORSRAMInitTypeDef FSMC_NORSRAMInitStructure;
/* 读写时序结构体 */
FSMC_NORSRAMTimingInitTypeDef readWriteTiming;

/* 使能 FSMC 时钟*/
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC, ENABLE);

//地址建立时间(ADDSET)为 1 个 HCLK 2/72M=28ns
readWriteTiming.FSMC_AddressSetupTime = 0x01; //地址建立时间
//数据保持时间(DATAST) + 1 个 HCLK = 5/72M=70ns
readWriteTiming.FSMC_DataSetupTime = 0x04; //数据建立时间
//选择控制的模式
//模式 B, 异步 NOR FLASH 模式,与 ILI9341 的 8080 时序匹配
readWriteTiming.FSMC_AccessMode = FSMC_AccessMode_B;

/* 以下配置与模式 B 无关 */
readWriteTiming.FSMC_AddressHoldTime = 0x00; //地址保持时间,模式 B未用到
//设置总线转换周期,仅用于复用模式的 NOR 操作
readWriteTiming.FSMC_BusTurnAroundDuration = 0x00;
//设置时钟分频,仅用于同步类型的存储器
readWriteTiming.FSMC_CLKDivision = 0x00;
//数据保持时间,仅用于同步型的 NOR
readWriteTiming.FSMC_DataLatency = 0x00;

//选择使用的bank, 本次实例中使用的是FSMC_NE1
//需要在宏定义中定义:#define FSMC_Bank1_NORSRAMx FSMC_Bank1_NORSRAM1
FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAMx;
//设置地址总线是否与数据总线是否复用,仅用于NOR
FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable;
//设置要控制的存储器类型:本实例使用的是异步NOR
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;

/* 设置是否使能等待状态插入,用于设置当存储器处于突发传输模式时,
* 是否允许通过 NWAIT 信号插入等待状态,不使用
*/
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的配置
FSMC_NORSRAMInit (&FSMC_NORSRAMInitStructure);
/* 使能 FSMC_Bank1_NORSRAMx */
FSMC_NORSRAMCmd (FSMC_Bank1_NORSRAMx, ENABLE);
}

计算控制液晶屏时使用的地址

  • 首先确定板子NEx引脚的连接,本次实例使用的是FSMC_NE1

  • 在 数据手册 –> 存储器映像 中查询FSMC_bnak1 NOR/PSRAM 1的地址:

  • 使用的是FSMS_NE1也就是当访问到0x6000000-0x63FFFFFF这个地址范围内时,FSMC都会产生有效的访问时序;本实例中使用的是FSMC_A16地址线作为命令/数据选择线,即:

    • 使FSMC_A16为高电平,即第十六位为 1 ,可用地址范围内任意地址做一下运算:

      1
      0X6000 0000 |= (1<<16) = 0x6001 0000;
    • 同样的,使FSMC_A16为低电平,即:

      1
      0X6000 0000 &= ~ (1<<16) = 0x6000 0000;
  • 由于地址线转换的问题,这里不细说,需要知道的是,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
/*********** 定义 FSMC 参数宏 **********/
//FSMC_Bank1_NORSRAM 用于 LCD 命令操作的地址
#define FSMC_Addr_ILI9341_CMD ( ( uint32_t ) 0x60020000 )

//FSMC_Bank1_NORSRAM 用于 LCD 数据操作的地址
#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(); //返回第二个读取参数
}
Prev
2019-07-25 10:12:43
Next