FSMC称为灵活的静态存储器,它能够与同步或异步存储器和16位PC存储器卡连接,STM32F4的FSMC接口支持包括SRAM、NAND FLASH、NOR FLASH和PSRAM等存储器。
FSMC框图
从FSMC框图可以看到,FSMC将外部设备分为2类:NOR/PSRAM设备和NAND/PC卡设备。所有外部存储器共享地址、数据和控制信号,但有各自的片选信号。FSMC 一次只能访问一个外部器件。
这里将LCD的片选接口与FSMC_NE4相连,即利用FSMC_NE4实现对LCD的片选;另外SRAM芯片的片选接口与FSMC_NE3相连,即利用FSMC_NE3实现对SRAM芯片的片选。FSMC本身就是静态存储器控制器,通过FSMC接口访问SRAM是理所当然的事,这里能将LCD也连接到FSMC,显然说明LCD在操作上与SRAM有相似之处。
SRAM的控制一般有:地址线(如A18A0)、数据线(如D15D0)、写信号(WE)、读信号(OE)、片选信号(CS),如果SRAM支持字节控制,那么还有UB/LB信号。
LCD的信号则包括:寄存器选择(RS)、数据线(D15-D0)、写信号(WE)、读信号(OE)、片选信号(CS)、复位信号RST和背光BL。
除去与访问过程无关的信号RST、BL,则两者的控制信号是极度的一致,区别仅在于SRAM有地址线(A18-A0),而LCD有RS信号线,从作用上看,两者也是一致的,都决定访问数据的位置。若假定SRAM仅一根地址线A0,则说明数据位置仅有两个,通过A0取0和取1,区分访问的数据到底在哪个地址空间;而LCD的RS取0和取1,也说明有两个存储空间,即ILI9341的寄存器的GRAM。显然,当把RS理解成一根地址线时,LCD就等效成SRAM了。RS与地址线A6进行相连,因此通过把地址线中的A6置0可以访问ILI9341的寄存器,而把A6置1则可以访问GRAM。
STM32F4xx的FSMC支持 8/16/32 位数据宽度,这里用到的 LCD 是 16 位宽度的,所以在设置数据宽度时,用户应选择16位宽。使用FSMC的目的,是把STM32F4XX的片外外设映射到片内ARM核可寻址的地址空间当中,这样通过访问片内对应的地址空间,就可以操作片外外设了。显然,这样的操作等同于把“片外外设”变成了“片内外设”,在访问方式上,和访问片内外设一模一样。
FSMC存储区域
FSMC 的外部存储器被划分为 4 个固定大小的存储区域,每个存储区域的大小为256 MB。
● 存储区域 1 可连接多达 4 个 NOR Flash 或 PSRAM 存储器器件。此存储区域被划分为 4 个
NOR/PSRAM 区域,带 4 个专用片选信号。
● 存储区域 2 和 3 用于连接 NAND Flash 器件(每个存储区域一个器件)
● 存储区域 4 用于连接 PC 卡设备
对于每个存储区域,所要使用的存储器类型由用户在配置寄存器中定义。
Bank1的256M字节空间由 28 根地址线(HADDR[27:0])(注意:这里的HADDR是内部AHB的地址线,它的地址是按字节进行“编号”的)寻址。这里HADDR[25:0]来自外部存储器地址FSMC_A[25:0],而HADDR[27:26]对4个区进行寻址。
NOR/PSRAM 存储区域选择
由于我的LCD片选信号与FSMC的FSMX_NE4连接,所以LCD对应寻址空间的首地址为0x60000000+0xC000000=0x6C000000。LCD的RS接到了地址A6,而没有其他地址线,因此按照道理,其他的地址线可取任意值。
HADDR[25:0] 包含外部存储器地址。由于 HADDR 为字节地址,而存储器按字寻址,所以根据存储器数据宽度不同,实际向存储器发送的地址也将有所不同,如下表所示。
LCD配置为16位数据宽度,因此内部地址的偏移量是外部地址2倍,即假定FSMC_A的地址值取x,则映射到内部地址,则实际地址首地址应为0x6C000000+2x。由于此次仅用到A6,即FSMC_A[6],若该位取0时,其他地址线也取0,则FSMC_A的值为0,此时其映射的AHB地址为0x6C000000;若FSMC_A[6]取1,且其他地址线都取0,则FSMC_A的值为0x40,此时映射的AHB地址即为0x6C000000+2*0x40,即0x6C000080。找到了地址,就可以像访问片内外设那样去访问片外外设了。
这里直接宏定义地址,方便调用。
//未使用的地址线全部取0(注意:取任意值均可,这里取0)
#define LCD_CMD_ADD (volatile u16 *)0X6C000000
#define LCD_DATA_ADD (volatile u16 *)(0X6C000000 + 0X80)
为进一步简化操作,可为以上两个地址宏定义加地址访问符“*”,以便于访问这两个地址空间。
#define LCD_CMD *LCD_CMD_ADD
#define LCD_DATA *LCD_DATA_ADD
定义了这两个宏,以后写命令和写数据就方便多了。若要发命令,只需给LCD_CMD赋值即可,若要发数据,只需给LCD_DATA发数据就行了。
得到了片外设备(LCD)映射到片内AHB空间的地址,就方便用户对ILI9341的寄存器和GRAM进行访问了,但访问要有合适的时序才可以。SRAM的可编程访问参数如下表所示:
由于ILI9341在写入和读取时,速度差异较大,为合理地利用读写速度,这里采用异步模式A,如此以来,只需关注上表的前三个参数,即:地址建立时间、地址保持时间、数据建立时间。
ILI9341的8080-II读写时序
上图中所对应电气参数的取值如表所示:
从上图表可以得到ILI9341在8080-II时序下的读写控制信号电平状态为:写控制脉冲高电平持续的时间为:twrh<15ns(HCLK=168MHz,因此twrh<3个HCLK);写控制脉冲低电平持续的时间为:twrl<15ns(同理,twrl<3个HCLK)。读GRAM控制脉冲高电平持续的时间为:trdhfm<90ns(即:trdhfm<15个HCLK); 读GRAM控制脉冲低电平持续的时间为:trdlfm<355ns(即:trdlfm<60个HCLK)。读ID号控制脉冲高电平持续的时间为:trdh<90ns(即:trdh<15个HCLK); 读ID号控制脉冲低电平持续的时间为:trdl<45ns(即:trdl<8个HCLK)。
ILI9341的写操作周期相对于读操作周期要短很多,即写的速度要明显快于读的速度。另外,为保证读GRAM和读ID号可共用同一读时序,应保证读控制脉冲的低电平持续时间trd至少为60个HCLK。这里,为充分发挥写入速度快的优势,这里分开配置读时序和写时序。
FSMC在模式A下的读/写时序
读时序中地址建立时间ADDSET,即为读控制脉冲的高电平时间,由以上结论可知,ADDSET(RD)的值为90ns(15个HCLK周期);读时序中数据保持时间DATAST,即读控制脉冲的低电平时间,由以上结论可知,DATAST(RD)的值为355ns(60个HCLK周期);写时序对应的建立时间ADDSET,即为写控制信号的高电平,由以上结论可知,此时的ADDSET(WR)的值为15ns(3个HCLK周期);写时序对应的数据保持时间DATAST,即写控制脉冲的低电平时间,由以上结论可知,此时的DATAST(WR)的值为15ns(3个HCLK)。
有了以上的时序参数,就可以配置FSMC相关寄存器了。
下面对寄存器进行简要说明:
SRAM/NOR-Flash片选控制寄存器1..4(FSMC_BCR1..4):
bit19:写入突发使能。对于CRAM(PSRAM),该位可在写操作时使能同步突发协议。读取访问期间同步突发协议的使能位为FSMC_BCRx寄存器中的BURSTEN位。0:始终在异步模式下进行写入操作;1:在同步模式下进行写入操作。操作LCD时,始终在异步模式下进行操作,因此这里设置为0。
bit15:异步传输期间的等待信号。该位可使能/禁止FSMC使用等待信号,即使是在异步协议期间该位也有作用。0:运行异步协议时不考虑NWAIT信号(复位后的默认值);1:运行异步协议时考虑NWAIT信号。这里不考虑NWAIT信号。
bit14:扩展模式使能。FSMC可对FSMC_BWTR寄存器中的写入时间进行配置,此配置由EXTMOD位使能,进而使读取和写入操作采用不同的时序。0:不考虑FSMC_BWTR寄存器中的值;1:考虑FSMC_BWTR寄存器中的值。由于此次采用的时序是模式A,因此采用扩展模式,并分开设置读写时序。这里将EXTMOD设置为1。
bit13:等待使能位。该位可使能/禁止在同步模式下访问FLASH时通过NWAIT信号插入等待周期。0:禁止NWAIT信号;1:使能NWAIT信号。这里禁止即可。
bit12:写入使能。用于指示FSMC是否在存储区域内使能/禁止写入操作。0:FSMC在存储区域内禁止写入操作;1:FSMC在存储区内使能写入操作。因此就是想利用FSMC读写ILI9341,因此,这里需使能存储区域的写入操作。
bit11:等待时序配置。NWAIT信号指示存储器中的数据是否有效,或者在同步模式下访问FLASH时是否必须插入等待周期。该配置位决定存储器是在等待周期之前的一个时钟周期还是等待周期期间使能NWAIT。0:NWAIT信号在等待周期之前的一个数据周期有效;1:NWAIT信号在等待周期期间有效。这里无需设置。
bit10:环回突发模式支持。定义控制器是否将一个AHB突发环回访问分割成两个线性访问。仅在突发模式下访问存储器时有效。0:未使能直接环回突发;1:使能直接环回突发。这里不采用环回突发模式。
bit9:等待信号极性位。定义存储器的等待信号极性。仅在突发模式下访问存储器时有效。0:NWAIT低电平有效;1:NWAIT高电平有效。该位无需设置。
bit8:BURSTEN,突发使能位。用于使能/禁止读取操作期间的同步突发访问。仅对同步突发存储器有效。0:禁止;1:有效。此用于同步模式,因此这里无需设置。
bit6:FLASH访问使能。用于使能NOR FLASH访问操作。0:禁止访问;1:使能访问。这里是把LCD等效成SRAM,因此禁止即可。
bit[5:4]:存储器数据总线宽度。00:8位;01:16位;10和11:保留,不使用。显然,这里需设置为16位。
bit[3:2]:存储器类型。定义与相应存储区域相连的外部存储器类型;00:SRAM、ROM;01:PSRAM;10:NOR FLASH/OneNAND FLASH。这里配置为SRAM。
bit1:地址/数据复用使能功能。该位置1时,仅对NOR和PSRAM存储器有效;0:地址/数据非复用;1:地址/数据在数据总线上复用。无关项,无需设置。
bit0:存储区域使能。0:禁止相应的存储区域;1:使能相应的存储区域。这里一定要使能的。
SRAM/NOR-Flash片选时序寄存器1..4(FSMC_BTR1..4):
bit[29:28]:访问模式。00:模式A;01:模式B;10:模式C:11:模式D。显然,此次选择模式A。
bit[27:24]:DATLAT,同步突发NOR FLASH的数据延迟。这里不需要进行设置。
bit[23:20]:CLKDIV,CLK信号的时钟分频比。在异步模式上,该值为无关值,因此这里无需关注。
bit[19:16]:这里采用默认值即可。
bit[15:8]:DATST,数据阶段的持续时间。该寄存器是针对读操作的,由前面时序分析可知,这里取为60个HCLK。
bit[7:4]:ADDHLD,地址保持阶段持续的时间。在异步模式,该字段是无关项,因此无需设置。
bit[3:0]:ADDSET,地址建立时间。由前面时序分析可知,读操作时ADDSET可取15个HCLK。
SRAM/NOR-FLASH写入时序寄存器1..4(FSMC_BWTR1..4):
bit[29:28]:ACCMOD,访问模式。这里同读时序,配置为模式A。
bit[27:24]:DATLAT,数据延迟。期用于同步突发NOR FLASH,因此这里可以不设置。
bit[23:20]:CLKDIV,CLK信号时钟分频比。这里仍是无关项,不设置。
bit[19:16]:总线周转阶段的持续时间。这里按默认值设置即可。
bit[15:8]:数据阶段的持续时间。由前面的时序分析可得,写操作时,DATST取3个HCLK。
bit[7:4]:地址阶段的保持时间。在异步模式,该字段是无关项,无需设置。
bit[3:0]:地址阶段的建立时间。由前面的时序分析可得,写操作时,ADDSET取3个HCLK。
分析完如何配置FSMC寄存器,开始GPIO引脚并复用为FSMC,并按上述分析配置寄存器,即可让FSMC模块为用户提供正确的时序。
但在stm32f4xx.h文件当中,ST公司并没有按照中文手册中那样,去命名FSMC的寄存器。
FSMC寄存器映射表
stm32f4xx.h当中定义FSMC的数据结构为:
typedef struct
{
__IO uint32_t BTCR[8]; /*!< NOR/PSRAM chip-select control register(BCR) and chip-select timing register(BTR) */
}FSMC_Bank1_TypeDef;
#define FSMC_Bank1 ((FSMC_Bank1_TypeDef *) FSMC_Bank1_R_BASE)
上述结构体当中成员为一个8元素的数组。由C数组知识易知,数组成员在内存当中地址是相邻的。宏定义FSMC_Bank1为FSMC_Bank1_TypeDef类型的地址,而FSMC_Bank1_R_BASE即为0xA0000000。FSMC_Bank1所指向的第一个成员即为FSMC_BCR1。由地址连续性,显然可得如下对应关系:
BTCR[0]对应FSMC_BCR1,BTCR[1]对应FSMC_BTR1;
BTCR[2]对应FSMC_BCR2,BTCR[3]对应FSMC_BTR2;
BTCR[4]对应FSMC_BCR3,BTCR[5]对应FSMC_BTR3;
BTCR[6]对应FSMC_BCR4,BTCR[7]对应FSMC_BTR4。
同理,写入时序寄存器对应的数据结构为:
BWTR[0]对应FSMC_BWTR1,BWTR[2]对应FSMC_BWTR2;
BWTR[4]对应FSMC_BWTR3,BWTR[6]对应FSMC_BWTR2;
这里元素存在跳跃性,原因是寄存器地址间隔为8,因此,为保证地址上的一致性,这里必须这样定义数据结构。
配置FSMC
static void ILI9341_GpioInit()
{
//1. 开时钟PB/PD/PE/PF/PG
RCC- >AHB1ENR |= 1< < 1 | 0XF< < 3;
//2. 背光引脚:通用推挽输出
//端口设置(pb15)
GPIOB- >MODER &= ~(0X3< < 30);
GPIOB- >MODER |= 1< < 30; //普通输出
GPIOB- >OTYPER &= ~(1< < 15); //推挽
GPIOB- >OSPEEDR |= 0X3< < 30;
GPIOB- >PUPDR &= ~(0X3< < 30); //无上下拉
//其他所有引脚复用为FSMC
/*
LCD_CS:PG12
RS:PF12 = >FSMC_A[6]
WR:PD5
RD:PD4
D0-D1:PD14/PD15
D2-D3:PD0/PD1
D4-D12:PE7-PE15
D13-D15:PD8-PD10
*/
//2. PD(配置为复用)
GPIOD- >MODER &= ~(0XF< < 0 | 0XF< < 8 | 0X3F< < 16 | 0xf< < 28);
GPIOD- >MODER |= 0X0a< < 0 | 0xa< < 8 | 0x2a < < 16 |0xa< < 28; //PD口复用
GPIOD- >OTYPER &= ~(0X3< < 0 | 0X3< < 4 | 0X7< < 8 | 0X3< < 14); //推挽
GPIOD- >OSPEEDR |= (0XF< < 0 | 0XF< < 8 | 0X3F< < 16 | 0xf< < 28); //速度100Mhz
GPIOD- >PUPDR &= ~(0XF< < 0 | 0XF< < 8 | 0X3F< < 16 | 0xf< < 28); //无上下拉
//PE口配置
GPIOE- >MODER &= 0X00003FFF;
GPIOE- >MODER |= 0Xaaaa8000; //PE复用
GPIOE- >OTYPER &= 0X007F; //PE7-15推挽
GPIOE- >OSPEEDR |= 0XFFFFC000; //PE7-15速度为100Mhz
GPIOE- >PUPDR &= 0X00003FFF; //PE7-15无上下拉
//FP12
GPIOF- >MODER &= ~(0X3< < 24);
GPIOF- >MODER |= 2< < 24;
GPIOF- >OTYPER &= ~(1< < 12); //推挽
GPIOF- >OSPEEDR |= 0X3< < 24; //100mHZ
GPIOF- >PUPDR &= ~(0X3< < 24); //无上下拉
//FG12
GPIOG- >MODER &= ~(0X3< < 24);
GPIOG- >MODER |= 2< < 24;
GPIOG- >OTYPER &= ~(1< < 12); //推挽
GPIOG- >OSPEEDR |= 0X3< < 24; //100mHZ
GPIOG- >PUPDR &= ~(0X3< < 24); //无上下拉
//选择复用的功能:复用为FSMC
//复用功能选择
//PD:
GPIOD- >AFR[0] &= 0XFF00FF00;
GPIOD- >AFR[0] |= 0x00cc00cc; //PD0/1/4/5复用为FSMC
GPIOD- >AFR[1] &= 0X00FFF000;
GPIOD- >AFR[1] |= 0XCC000CCC; //PD8/9/10/14/15复用为FSMC
//PE:
GPIOE- >AFR[0] &= 0X0FFFFFFF;
GPIOE- >AFR[0] |= 0XC0000000; //PE7复用为FSMC
GPIOE- >AFR[1] &= 0x00000000;
GPIOE- >AFR[1] |= 0XCCCCCCCC; //PE8-15复用,可以直接往AFR[1]中赋值
//PF:
GPIOF- >AFR[1] &= 0xfff0ffff;
GPIOF- >AFR[1] |= 0X000C0000; //PF12复用为FSMC
//PG:
GPIOG- >AFR[1] &= 0xfff0ffff;
GPIOG- >AFR[1] |= 0X000C0000; //PG12复用为FSMC
//3. 开FSMC时钟
RCC- >AHB3ENR |= 1< < 0;
//4. 配置FSMC寄存器
//BCR4
FSMC_Bank1- >BTCR[6] &= ~(1< < 19); //始终在异步模式下操作
FSMC_Bank1- >BTCR[6] &= ~(1< < 15); //不考虑等待信号
FSMC_Bank1- >BTCR[6] |= 1< < 14; //使能扩展功能,即读写时序分开
FSMC_Bank1- >BTCR[6] &= ~(1< < 13); //禁止等待nWait信号
FSMC_Bank1- >BTCR[6] |= 1< < 12; //使能写操作
FSMC_Bank1- >BTCR[6] &= ~(0x3< < 4);
FSMC_Bank1- >BTCR[6] |= 1< < 4; //16位数据宽度
FSMC_Bank1- >BTCR[6] &= ~(0x3< < 2); //存储器类型为:SRAM
//BTR4:
//BTR4(读时序)
FSMC_Bank1- >BTCR[7] &= ~(0x3< < 28); //异步模式A
FSMC_Bank1- >BTCR[7] |= 0xf< < 16; //总线周转阶段持续时间为默认值
FSMC_Bank1- >BTCR[7] &= 0xffff00ff;
FSMC_Bank1- >BTCR[7] |= 60< < 8; //DATAST为60HCLK
FSMC_Bank1- >BTCR[7] |= 0xf< < 0; //ADDSET为15HCLK
//BWTR(写时序)
FSMC_Bank1E- >BWTR[6] = 0;
FSMC_Bank1E- >BWTR[6] &= ~(0x3< < 28); //异步模式A
FSMC_Bank1E- >BWTR[6] |= 0xf< < 16; //总线周转阶段持续时间为默认值
FSMC_Bank1E- >BWTR[6] |= 3< < 8; //DATAST为3个HCLK
FSMC_Bank1E- >BWTR[6] |= 3< < 0; //ADDSET为3个HCLK
//使能存储块
FSMC_Bank1- >BTCR[6] |= 1< < 0;
GPIOB- >BSRRH = 0X1< < 15; //关背光
}
配置完FSMC,替代之前用IO口模拟8080时序的ILI9341GPIO初始化函数,并放入之前的图片显示和碰撞小球实验中,可以发现刷屏速度得到了很大的提高。
可以看到,FSMC操作LCD比GPIO模拟8080时序会快很多,可能有人会说硬件实现就是比软件实现快。但其实不是这样的,用软件模拟时序,给出的延时与标准时序可能不是完全一致,导致速度比较慢,如果用软件模拟时序时使用逻辑分析仪对时序的延时时间进行优化,GPIO模拟8080时序也是可以达到FSMC的速度的,有兴趣的可以自己尝试一下。
评论
查看更多