FSMC简述

  • 大容量,且引脚数在 100 脚以上的 STM32F103 芯片都带有 FSMC 接口。

  • FSMC,即灵活的静态存储控制器flexible static memory controller。

扩展内存

  • STM32 的 FSMC 接口支持包括 SRAM、NAND FLASH、NOR FLASH 和 PSRAM 等存储器。

image-20230215142551415

  • FSMC就是一个MCU与外部存储器(SRAM,FLASH等)读写数据的一个接口

FSMC内部原理

FSMC 的框图

image-20230215142930470

  • NE[4-1] 片选,用来区分不同设备
  • NWE为写。
  • NOE为读
    • n低电平有效
    • o output
    • e 使能
      image-20230215181515867

驱动SRAM

  • FSMC驱动外部SRAM时,外部SRAM的控制一般有
    • 地址线(如A0-A25)
    • 数据线(如D0-D15)
    • 写信号(WE,即WR)
    • 读信号(OE,即RD)
    • 片选信号(CS)
    • 如果SRAM支持字节控制,那么还有UB/LB信号。

驱动TFTLCD

  • 真正在操作LCD的时候需要用到的就只有:
    • RS、D0~D15、WR、RD和CS。
  • 其操作时序和SRAM的控制完全类似,唯一不同就是TFTLCD有RS信号,但是没有地址信号。(所以将RS当做地址线来用)

image-20230215143936618

image-20230215143952237

(该表格为F407的,与103不太一样,仅看个大概意思)

  • TFTLCD通过RS信号来决定传送的数据是数据还是命令,本质上可以理解为一个地址信号
    • 比如我们把*RS接在A0上面
    • 那么当FSMC控制器写地址0的时候,会使得A0变为0,对TFTLCD来说,就是写命令。而FSMC写地址1的时候,A0将会变为1,对TFTLCD来说,就是写数据了
    • 当然RS也可以接在其他地址线上
  • 因此,可以把TFTLCD当成一个SRAM来用,只不过这个SRAM有2个地址,这就是FSMC可以驱动LCD的原理。

FSMC内存划分

FSMC的分块(存储块一的介绍)

​ STM32的 FSMC将外部存储器划分为固定大小为 256M 字节的四个存储块(Bank),FSMC 总共管理 1GB 空间.如下为FSMC在CPU中的地址映射划分。

image-20230215144724020

​ 各个模块的控制范围:

image-20230215144816108

NOR和PSRAM地址映像

​ HADDR[27:26]位用于选择四个存储块之一:

HADDR[27:26]选择的存储块
00存储块1 NOR/PSRAM 1
01存储块1 NOR/PSRAM 2
10存储块1 NOR/PSRAM 3
11存储块1 NOR/PSRAM 3

不同数据宽度对存储块1寻址的影响

HADDR是需要转换到外部存储器的内部AHB地址线。HADDR[25:0]包含外部存储器地址。HADDR是字节地址,而存储器访问不都是按字节访问,因此接到存储器的地址线依存储器的数据宽度有所不同,如下表:

image-20230215145845505

需要或者可以访问的地址空间大小。

  • 当Bank1接的是16位宽度存储器的时候:HADDR[25:1],FSMC_A[24:0]
  • 当Bank1接的是8位宽度存储器的时候:HADDR[25:0],FSMC_A[25:0]

不论外部接8位/16位宽设备,FSMC_A[0]永远接在外部设备地址A[0]

这里需要注意的是:

这个地址映射,指的是你选择的数据宽度是哪种,然后对应会映射到哪种

  • 数据宽度为8位时:比如HADDR映射的基地址是0x68000000,那么对于FSMC来说,也就是外部存储器就是0x00000000的地址,若HADDR内部地址+1变成0x68000001,那么外部地址也随着+1,变成0x00000001
  • 数据宽度16位:比如HADDR映射的基地址是0x68000000,那么对于FSMC来说,也就是外部存储器就是0x00000000的地址,若HADDR内部地址+1变成0x68000001,但是由于HADDR的HADDR[1]与FSMC[0]相邻,反而右移一位,映射到外部存储器的地址依旧是0x00000000,同理,若内部+2,则外部就映射到0x00000002,这就是为什么64要除以2

FSMC中地址与外设地址的对应关系

​ FSMC中的1G空间存储的是外设地址,当我们在存储块中的访问单元序号+1,对应的外设存储单元的访问地址就自加8(如果外设数据存储的数据宽度为8b的话)

image-20230215150536729

STM32 的FSMC 模拟8080 接口时序

​ ILI9341 的 8080 通讯接口时序可以由 STM32 使用普通 I/O 接口进行模拟,但这样效率太低,STM32 提供了一种特别的控制方法——使用 FSMC 接口实现 8080 时序。

FSMC控制异步NORFLASH的时序

​ FSMC 外设支持输出多种不同的时序以便于控制不同的存储器,它具有 ABCD 四种模式,下面我们仅针对控制异步 NOR FLASH 使用的模式 B 进行讲解,见图FSMCNOR_FLASH的时序图 。

  • 读时序

image-20230215181408000

  • 写时序

image-20230215181808866

​ 当内核发出访问某个指向外部存储器地址时,FSMC 外设会根据配置控制信号线产生时序访问存储器,上图中的是访问外部异步 NOR FLASH(模式 B)时 FSMC 外设的读写时序。

​ 以读时序为例,该图表示一个存储器操作周期由地址建立周期 (ADDSET)、数据建立周期(DATAST) 以及 2 个 HCLK 周期组成。在地址建立周期中,地址线发出要访问的地址,数据掩码信号线指示出要读取地址的高、低字节部分,片选信号使能存储器芯片;地址建立周期结束后读使能信号线发出读使能信号,接着存储器通过数据信号线把目标数据传输给 FSMC,FSMC 把它交给内核。

​ 写时序类似,区别是它的一个存储器操作周期仅由地址建立周期 (ADDSET) 和数据建立周期(DATAST) 组成,且在数据建立周期期间写使能信号线发出写信号,接着 FSMC 把数据通过数据线传输到存储器中。

​ 根据 STM32 对寻址空间的地址映射,地址 0x6000 0000 ~0x9FFF FFFF 是映射到外部存储器的,而其中的0x6000 0000 ~0x6FFF FFFF 则是分配给 NOR FLASH、PSRAM 这类可直接寻址的器件。

​ 当 FSMC 外设被配置成正常工作,并且外部接了 NOR FLASH 时,若向 0x60000000 地址写入数据如 0xABCD,FSMC 会自动在各信号线上产生相应的电平信号,写入数据。FSMC 会控制片选信号 NE1 选择相应的 NOR 芯片,然后使用地址线 A[25:0] 输出 0x60000000,在 NWE 写使能信号线上发出低电平的写使能信号,而要写入的数据信号 0xABCD 则从数据线 D[15:0] 输出,然后数据就被保存到 NOR FLASH 中了。

用FSMC模拟8080时序

image-20230215182302111

​ 对比 FSMC NOR/PSRAM 中的模式 B 时序与 ILI9341液晶控制器芯片使用的 8080 时序可发现,这两个时序是十分相似的 (除了 FSMC 的地址线 A 和8080 的 D/CX 线,可以说是完全一样)。

image-20230215183402410

​ 对于 FSMC 和 8080 接口,前四种信号线都是完全一样的,仅仅是 FSMC 的地址信号线 A[25:0] 与8080 的数据/命令选择线 D/CX 有区别。而对于 D/CX 线,它为高电平的时候表示数值,为低电平的时候表示命令,如果能使用 FSMC 的 A 地址线根据不同的情况产生对应的电平,那么就完全可以使用 FSMC 来产生 8080 接口需要的时序了。

​ 为了模拟出 8080 时序,我们可以把 FSMC 的 A0 地址线 (也可以使用其它 A1/A2 等地址线) 与ILI9341 芯片 8080 接口的 D/CX 信号线连接,那么当 A0 为高电平时 (即 D/CX 为高电平),数据线 D[15:0] 的信号会被 ILI9341 理解为数值,若 A0 为低电平时 (即 D/CX 为低电平),传输的信号则会被理解为命令。

  • 当使用 FSMC 向 0x6xxx xxx1、0x6xxx xxx3、0x6xxx xxx5…这些奇数地址写入数据时,地址最低位的值均为 1,所以它会控制地址线 A0(D/CX) 输出高电平,那么这时通过数据线传输的信号会被理解为数值;
  • 若向 0x6xxx xxx0 、0x6xxx xxx2、0x6xxx xxx4…这些偶数地址写入数据时,地址最低位的值均为 0,所以它会控制地址线 A0(D/CX) 输出低电平,因此这时通过数据线传输的信号会被理解为命令

见表使用 *FSMC* 输出地址示例 :

image-20230215183740083

注意:在实际控制时,以上地址计算方式还不完整,还需要注意 HADDR 内部地址与 FSMC 地址信号线的转换。

FSMC结构体

NORFLASH时序结构体

控制 FSMC 使用 NOR FLASH 存储器时主要是配置时序寄存器以及控制寄存器,利用 ST 标准库的时序结构体以及初始化结构体可以很方便地写入参数。

1
2
3
4
5
6
7
8
9
10
typedef struct
{
uint32_t FSMC_AddressSetupTime; /* 地址建立时间,0-0xF 个 HCLK 周期 */
uint32_t FSMC_AddressHoldTime; /* 地址保持时间,0-0xF 个 HCLK 周期 */
uint32_t FSMC_DataSetupTime; /* 地址建立时间,0-0xF 个 HCLK 周期 */
uint32_t FSMC_BusTurnAroundDuration; /* 总线转换周期,0-0xF 个 HCLK 周期,在 NOR*/
uint32_t FSMC_CLKDivision; /* 时钟分频因子,1-0xF,若控制异步存储器,本参数无效 */
uint32_t FSMC_DataLatency; /* 数据延迟时间,若控制异步存储器,本参数无效 */
uint32_t FSMC_AccessMode; /* 设置访问模式 */
} FSMC_NORSRAMTimingInitTypeDef;

FSMC初始化结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typedef struct
{
uint32_t FSMC_Bank; /* 设置要控制的 Bank 区域 */
uint32_t FSMC_DataAddressMux; /* 设置地址总线与数据总线是否复用 */
uint32_t FSMC_MemoryType; /* 设置存储器的类型 */
uint32_t FSMC_MemoryDataWidth; /* 设置存储器的数据宽度 */
uint32_t FSMC_BurstAccessMode; /* 设置是否支持突发访问模式,只支持同步类型的存储器*/
uint32_t FSMC_AsynchronousWait; /* 设置是否使能在同步传输时的等待信号,*/
uint32_t FSMC_WaitSignalPolarity; /* 设置等待信号的极性 */
uint32_t FSMC_WrapMode; /* 设置是否支持对齐的突发模式 */
uint32_t FSMC_WaitSignalActive; /* 配置等待信号在等待前有效还是等待期间有效 */
uint32_t FSMC_WriteOperation; /* 设置是否写使能 */
uint32_t FSMC_WaitSignal; /* 设置是否使能等待状态插入 */
uint32_t FSMC_ExtendedMode; /* 设置是否使能扩展模式 */
uint32_t FSMC_WriteBurst; /* 设置是否使能写突发操作 */
/* 当不使用扩展模式时,本参数用于配置读写时序,否则用于配置读时序 */
FSMC_NORSRAMTimingInitTypeDef *FSMC_ReadWriteTimingStruct;
/* 当使用扩展模式时,本参数用于配置写时序 */
FSMC_NORSRAMTimingInitTypeDef *FSMC_WriteTimingStruct;
} FSMC_NORSRAMInitTypeDef;

(1) FSMC_Bank

​ 本成员用于选择 FSMC 映射的存储区域,它的可选参数以及相应的内核地址映射范围见表可以选择的存储器区域及区域对应的地址范围

image-20230215184555289

(2)FSMC_ExtendedMode本成员用于设置是否使用扩展模式(FSMC_ExtendedMode_Enable/Disable),在非扩展模式下,对存储器读写的时序都只使用 FSMC_BCR 寄存器中的配置,即下面的FSMC_ReadWriteTimingStruct 结构体成员;在扩展模式下,对存储器的读写时序可以分开配置,读时序使用 FSMC_BCR 寄存器,写时序使用 FSMC_BWTR 寄存器的配置,即后面的 FSMC_WriteTimingStruct 结构体成员。

FSMC—-液晶显示(野火板子)

使用 FSMC 外设控制实验板配套的 3.2 寸 ILI9341 液晶屏,见图 27-21,该液晶屏的分辨率为 320x240,支持 RGB565 格式。

image-20230218112904812

硬件设计

图 27-21液晶屏背面的 PCB 电路对应图 27-22、图 27-23、图 27-24 中的原理图,分别是屏幕 PCB 底板原理图、触摸部分原理图、液晶排针接口线序图。

image-20230218113317003

图 27-24 表示的是 PCB 底板引出的排针线序,屏幕整体通过这些引出的排针与开发板或其它控制器连接。

image-20230218113530204

注意:其中请着重关注图中液晶屏 LCD_CS 及 LCD_RS(即 DC 引脚)与 FSMC 存储区选择引脚 FSMC_NE 及地址信号 FSMC_A 的编号,它们会决定 STM32 要使用什么内存地址来控制与液晶屏的通讯。

软件设计

编程要点

(1) 初始化通讯使用的目标引脚及端口时钟;

(2) 使能 FSMC 外设的时钟;

(3) 配置 FSMC 为异步 NOR FLASH 模式以仿真 8080 时序;

(4) 建立机制使用 FSMC 向液晶屏发送命令及数据;

(5) 发送控制命令初始化液晶屏;

(6) 编写液晶屏的绘制像素点函数;

(7) 利用描点函数制作各种不同的液晶显示应用。

配置GPIO引脚和FSMC的配置同SRAM一样,唯一不同的只有以下几条:

1
2
3
readWriteTiming.FSMC_AccessMode = FSMC_AccessMode_B;//配置成模式B
readWriteTiming.FSMC_AddressSetupTime = 0x01;
readWriteTiming.FSMC_DataSetupTime = 0x04;

这里的建立时间和保持时间是通过经验值,具体计算也可以看ili9341的数据手册配置,如图27-26和27-27。

image-20230218121604509

image-20230218121615534

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

初始化完 FSMC 后,即可使用类似扩展外部 SRAM 中的读取方式:通过访问某个地址,由 FSMC 产生时序与外部存储器通讯,进行读写。

image-20230218121848397

可以往上看原理不同数据宽度寻址影响,只要访问对应的地址,那么就能模拟出8080时序,从而对液晶屏进行访问,但是由于选择是十六位数据通信,就会存在地址偏移的问题,只要多左移动一位即可,:

例如液晶屏的CS引脚连接的是FSMC_NE1,而RS引脚是连接FSMC_A[16],那么只需要将基地址(0X6000 0000 |(1<<16+1))就可以在FSMC_A[16]输出高电平,从而表示数据,若(0X6000 0000 &(~(1<<16+1))),则表示清零,FSMC_A[16]输出低电平,从而表示命令

这样就能完成对ili9341的读写:

1
2
3
4
5
/*************** ILI9341 显示屏的 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
11
12
13
14
15
16
17
18
19
20
21
/**
* @brief 向 ILI9341 写入命令
* @param usCmd :要写入的命令(表寄存器地址)
* @retval 无
*/
__inline void ILI9341_Write_Cmd ( uint16_t usCmd )
{
* ( __IO uint16_t * ) ( FSMC_Addr_ILI9341_CMD ) = usCmd;

}

/**
* @brief 向 ILI9341 写入数据
* @param usData :要写入的数据
* @retval 无
*/
__inline void ILI9341_Write_Data ( uint16_t usData )
{
* ( __IO uint16_t * ) ( FSMC_Addr_ILI9341_DATA ) = usData;

}

ILI9341重要命令

  • 画图的流程图

image-20230218123849076

  • 0x2A命令
    • 前两个字节表示X轴起始位置的地方,后两个字节表示X轴末尾位置的地方

image-20230218123941605

  • 0x2B命令
    • 前两个字节表示Y轴起始位置的地方,后两个字节表示Y轴末尾位置的地方

image-20230218124308025

  • 0x2C命令
    • 发送n个数据,每个数据都是16位,表示一个格子的填充内容,用的是RGB565格式

image-20230218124323331

颜色转换

红色 -> RGB888 -> 0xFF 00 00

红色 -> RBG565 -> 0x1F 00 00

计算公式为:

RGB565 = (R &(0x1F)<<11)|(G &(0x3F)<<5)|(B &(0x1F))

其中,R,G,B,分别为RBG888格式的数据,带入即可得到转换的数据

设置液晶的扫描方向

image-20230218181616988

通过0x36命令就能设置液晶的扫描方向,分别设置三个位,MY,MX,MV。其中MY指的就是Y轴扫描方向,MX就是X轴扫描方向,MV就是XY轴兑换,一共有8种组合方式,如下图:

image-20230218181931083

代码:

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
#define ILI9341_LESS_PIXEL 240 // 液晶屏较短方向的像素宽度
#define ILI9341_MORE_PIXEL 320 // 液晶屏较长方向的像素宽度

// 根据液晶扫描方向而变化的 XY 像素宽度
// 调用 ILI9341_GramScan 函数设置方向时会自动更改
uint16_t LCD_X_LENGTH = ILI9341_LESS_PIXEL;
uint16_t LCD_Y_LENGTH = ILI9341_MORE_PIXEL;

// 液晶屏扫描模式,本变量主要用于方便选择触摸屏的计算参数
// 参数可选值为 0-7
// 调用 ILI9341_GramScan 函数设置方向时会自动更改
// LCD 刚初始化完成时会使用本默认值
uint8_t LCD_SCAN_MODE = 6;
void ILI9341_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 方向像素宽度为 240,Y 方向为 320
LCD_X_LENGTH = ILI9341_LESS_PIXEL;
LCD_Y_LENGTH = ILI9341_MORE_PIXEL;
}
else
{
// 1 3 5 7 模式下 X 方向像素宽度为 320,Y 方向为 240
LCD_X_LENGTH = ILI9341_MORE_PIXEL;
LCD_Y_LENGTH = ILI9341_LESS_PIXEL;
}

// 0x36 命令参数的高 3 位可用于设置 GRAM 扫描方向
ILI9341_Write_Cmd(0x36);
ILI9341_Write_Data(0x08 | (ucOption << 5)); // 根据 ucOption 的值设置 LCD 参数,共 0-7 种模式
//重新设置开窗大小
ILI9341_Write_Cmd(CMD_SetCoordinateX);
ILI9341_Write_Data(0x00); /* x 起始坐标高 8 位 */
ILI9341_Write_Data(0x00); /* x 起始坐标低 8 位 */
ILI9341_Write_Data(((LCD_X_LENGTH - 1) >> 8) & 0xFF); /* x 结束坐标高 8 位 */
ILI9341_Write_Data((LCD_X_LENGTH - 1) & 0xFF); /* x 结束坐标低 8 位 */

ILI9341_Write_Cmd(CMD_SetCoordinateY);
ILI9341_Write_Data(0x00); /* y 起始坐标高 8 位 */
ILI9341_Write_Data(0x00); /* y 起始坐标低 8 位 */
ILI9341_Write_Data(((LCD_Y_LENGTH - 1) >> 8) & 0xFF); /*y 结束坐标高 8 位 */
ILI9341_Write_Data((LCD_Y_LENGTH - 1) & 0xFF); /*y 结束坐标低 8 位 */

/* write gram start */
ILI9341_Write_Cmd(CMD_SetPixel);
}

到这里,ILI9341驱动LCD就结束啦,可能还有一些没讲好哈,多多包涵。

参考文献

野火开发指南

ILI9341数据手册

stm32f10x数据手册