STM32F103_外部RAM⽤作运存---IS62WV51216
概述
SRAM的简介
折腾过电脑的朋友都知道,当电脑运⾏⽐较卡的时候,我们可以通过给电脑加装内存条来改善电脑的性能。那么号称微型计算机的单⽚机能不能像电脑⼀样加装内存条呢?装内存条倒是不⾏,但是我们可以给单⽚机外加和内存条效果⼀样的SRAM来提升单⽚机的性能。下⾯以STM32F407ZGT6单⽚机来讲解⼀下来扩展外部SRAM。
原理:给STM32芯⽚扩展内存与给PC扩展内存的原理是⼀样的,只是PC上⼀般以内存条的形式扩展,内存条实质是由多个内存颗粒(即SRAM芯⽚)组成的通⽤标准模块,⽽STM32直接与SRAM芯⽚连接。
SRAM,型号IS62WV51216,管脚图如下:
IS62WV51216的管脚总的来说⼤致分为:电源线、地线、地址线、数据线、⽚选线、写使能端、读使能端和数据掩码信号线。
从这个图中我们可以看出IS62WV51216有19根地址线和16根数据线,从这些数据中我们可以分析出IS62WV51216的存储⼤⼩为1M,那么这个1M是怎么分析出来的呢?
我们得来说说IS62WV51216的存储原理。⾸先,我们来谈⼀谈⼀般的SRAM的存储原理:
sram的存储模型我们可以⽤矩阵来说明:
SRAM内部包含的存储阵列,可以把它理解成⼀张表格,数据就填在这张表格上。和表格查⼀样,指定⼀个⾏地址和列地址,就可以精确地到⽬标单元格,这是SRAM芯⽚寻址的基本原理。这样的每个单元格被称为存储单元,⽽这样的表则被称为存储矩阵。地址译码器把N 根地址线转换成2的N次⽅根信号线,每根信号线对应⼀⾏或⼀列存储单元,通过地址线到具体的存储单元,实现寻址。如果存储阵列⽐较⼤,地址线会分成⾏和列地址,或者⾏、列分时复⽤同⼀地址总线,访问数据寻址时先⽤地址线传输⾏地址再传输列地址。
但是呢?你会发现,这个原理好像不太适⽤于IS62WV51216,为什么呢?
其实不然,因为我们使⽤的SRAM⽐较⼩,IS62WV51216没有列地址线。它只有19根⾏地址线,那么,我们就可以这么来解释:
IS62WV51216有16根数据线,也就是说它的数据宽度为16位,⼀个⾏地址也就对应16位,即2字节空间。好,那现在来计算⼀下
IS62WV51216有多少个⾏地址。2的19次⽅等于512K,在512K的基础之上在乘我们之前计算的2字节,
不正好是1024K,也就是1M吗?1M 后⾯的单位是B,即Byte,⽽不是Bit哦。这样的话你就会发现IS62WV51216这个名字中本⾝就包含了⼤量的信息:IS62WV51216共有512K 个⾏地址,数据宽度为16位,再加以计算就可以得到它的存储⼤⼩为1M啦,有趣吧!
FSMC的简介
FSMC是Flexible StaticMemory Controller的缩写,就是灵活的静态存储控制器。它可以⽤于驱动包括SRAM、NOR FLASH以及NAND FLSAH类型的存储器。其他我们不⽤管,从上⾯我们可以总结的是,stm32雇佣FSMC这个管家来管理我们的IS62WV51216。来来来,我们来看看FSMC的庐⼭真⾯⽬:
蒙了吧!⼜是这么多信号线,不要怕,我们还是来总结归纳⼀下。我们FSMC控制SRAM为例来说明:通过查看STM32F103系列的参考⼿册:
你会发现居然和SRAM中的线居然⾼度统⼀(那是当然喏,我们就是讲的FSMC嘛!)
1. FSMC_NBL[1:0]分别对应于LB#、UB#,有什么⽤呢?提供数据掩码信号。具体是怎么回事呢?还记得前⾯提到的⾏地址线吗?
⼀根⾏地址线对应16位的数据,我们可以把16位的数据分为⾼字节和低字节。当要访问宽度为16位的数据时,使⽤⾏地址线指出地址,然后把UB#和LB#线都设置为低电平(FSMC_NBL0和FSMC_NBL1为
低电平),那么I/O0-I/O15线(FSMC_D0到FSMC_D15)都有效,它们⼀起输出该地址的16位数据(或者接收16位数据到该地址);当要访问宽度为8位的数据时,使⽤⾏地址线指出地址,然后把UB#(FSMC_NBL0)设置为低电平,I/O8-I/O15(FSMC_D8到FSMC_D15)会对应输出该地址的⾼8位,I/O0-I/O7的信号⽆效(或者把LB#(FSMC_NBL1)设置为低电平,I/O0-I/O7(FSMC_D0到FSMC_D7)会对应输出该地址的低8位,I/O8-I/O15的信号⽆效。这样是不是有⼀部分信号没有⽤呢?好像被掩盖了。因此它们被称为数据掩码信号。
2. FSMC_NE[1:4]是个很有趣的东西,它决定了FSMC可以控制多个存储器。这⾥就要提及FSMC的地址映射啦!
⾸先,有⼀点我们必须明⽩,对于32位的stm32单⽚机来说,它能够管理的地址⼤⼩为4GB,⽽stm32将4GB的地址空间中的0x60000000到0x9FFFFFFF共1GB的空间分给外部内存,所以这1GB的空间就成了我们的⼩天地,供我们⾃由玩耍。然后强势的FSMC就接管了这1GB的空间,FSMC将图中的1GB⼤⼩的External RAM存储区域分成了4个Bank区域,每个Bank对应于stm32内部寻址空间的不同地址范围。那么为什么要分为不同的Bank区域呢?因为不同的Bank可以来管理不同的外部存储设备,⽐如NOR Flash及SRAM存储器只能使⽤Bank1的地址,NAND Flash存储器只能使⽤Bank2和Bank3的地址,等。
细⼼的你肯定还会发现,每个Bank中居然还有4x64MB这种⽂字,这是什么意思呢?
Bank内部的256MB空间⼜被分成4个⼩块,每块64M,各⾃有相应的控制引脚⽤于连接⽚选信号。FSMC_NE[4:1]信号线就分别对应图中的FSMC bank1 NOR/PSRAM4到FSMC bank1 NOR/PSRAM1。当STM32访问0x6C000000-0x6FFFFFFF地址空间时,会访问到Bank1的第3⼩块区域:FSMC bank1 NOR/PSRAM3相应的FSMC_NE3信号线会输出控制信号(即⽚选信号),如果这个时候FSMC_NE3处刚好接上IS62WV51216的CS端,那么IS62WV51216就可以任由我们摆布啦。因此,对于你使IS62WV51216来说,⼀定要注意你的CS端是接的FSMC的哪个FSMC_NE端,这决定你在程序访问哪个地址范围。
下⾯来说⼀下在stm32F407中SRAM的硬件连接:对于FSMC来说,它已经集成到了单⽚机内部,它的提供给的管脚已经确定了,是不能改动的,这个可以参考STM32对应芯⽚的Datasheet。唯⼀具有灵活性的就是FSMC_NE,具体⽤哪个FSMC_NE管脚来和你的SRAM相连,当然是你的⾃由,但是不要忘了,你要到你选的FSMC_NE所对应的地址范围,不然写程序的时候就搞不清喏!
⼀、配置启动⽂件
我们使⽤官⽅标准库,拷贝标准库FSMC例程⾥⾯的"startup_stm32f10x_hd.s"⽂件(⼯程使⽤103ZE,若使⽤互联型芯⽚拷贝对应⽂件),替换掉我们之前⼯程的启动⽂件,如下图:
⼆、配置FSMC
我们使⽤官⽅标准库⾥⾯"system_stm32f10x.c"⽂件⾥⾯现成的函数接⼝(使⽤寄存器配置)来配置FSMC,只需要打
开"system_stm32f10x.c"⽂件⾥⾯第122⾏的宏"DATA_IN_ExtSRAM",见下图:
单片机printf函数三、分配RAM
RAM地址的分配是由编译器完成的,因此需要对⼯程进⾏相应配置,就是使⽤外部RAM,见下图:
四、测试函数说明
该函数位于main.c⽂件下⾯;
这个函数主要就是对上⾯配置及整改⼯程的测试。定义⼀个全局变量和⼀个局部变量,通过串⼝打印出他们的地址就可以判断运⾏内存是使⽤外部还是内部。
五、打印(测试)结果
看了测试函数就知道依次打印出来的数据是什么,这⾥我们很明显的可以看到打印出的地址是0x6800xxxx,这⾥的0x6800xxxx地址数据就是外部SRAM地址(不懂的话,请看昨天的讲解),说明运⾏内存确实是外部SRAM.
六、变量定位定义
对于⼀个使⽤单⽚机内部 RAM 的访问相当容易,基本上定义变量是不需要思考其定位问题的,当把外部 SRAM 考虑进来时,则需要考虑内部及外部的问题;⽐如,如何让⼀个变量定位在内部或者外部 RAM;定位于内部是如何访问,定位于外部时⼜如何访问。这⾥说的是⼀个变量,或者⼀个数组的定位问题,当涉及到⼀个⽂件或者多个⽂件其内部所有变量的定位问题就复杂得多了。变量定位定义的⼀般⽅法(使⽤__attribute__)。
⼀般的定义⽅法如下图:
图1
定义了 3 数组(属于公共变量),现在检查下对应的 map ⽂件如下图所⽰:
图2
如上图的 2998 ⾏ 3012 ⾏ 3013 ⾏可见与上⾯定义的位置是对应的,所以这样实现了变量定义的定位功能;当内部 RAM 不⾜时或者有意定义⼀个变量定位到外部 RAM 中就可以采⽤这种⽅法(使⽤外部 RAM 有前提条件这⾥就不说了)。
七、批量定义变量到外部 SRAM
如何实现批量的变量定义到外部 RAM 呢?除了批量地使⽤__attribute__定义变量,还是有更快捷的⽅法的。
配置外部 SRAM 可⽤起始地址及⼤⼩—如下图:
图3
图4
如上图所⽰,最左最右边的⼩⽅框不要打勾…千万不要打勾,开始地址及⼤⼩必须如实填写(Size 的值可以⼩于但绝对不能⼤于实际外置芯⽚值),开始地址安装原理图连线确定其值。
⼋、定义⼀个⽂件内的所有变量于外部SRAM
⾸先定⼀个⼩⽬标:确定你要⼀个所有变量需要定位于外部 SRAM 的⽂件,接着按照下图来配置(这⾥让 main.c 这个⽂件,让⾥⾯定义的所有变量均定位于外部SRAM 中)在⼯程窗⼝选择 main.c 点⿏标右键如下图:
图5
菜单选择第⼀⾏"Options for File 'main.c' "之后显⽰如下图:
图6
如上图我们仅需关注"Memory Assignment"组,Code/Const 定义代码及 const 的定位,Zero Initialized Data 及 Other Data 是变量的定位,此处我们关⼼的仅数据(变量)部分的定位。定位设置如下图:
图7
⾄此,main.c ⽂件内部的所有变量均已定位到外部 SRAM 中(前提是没忘记点 OK 按钮),到这⾥应该会发现⼀个问题如图 7 每⼀项都有⼀个<default>选项;在 keil 的⼯程⾥每个⽂件的变量安排都会有⼀个默认选项,当这⾥选择<default>时则会启⽤如图3 所⽰的默认选项,可以看到图 4 那⾥,说到千万不要打勾的那⾥。打勾的话那⾥就变成了第⼀默认选项,那么图 7 的配置就多余了。这个是可以验证的。那么再回头验证⼀下图 7 的配置是否实现了将 main.c ⽂件中的变量定位到外部 SRAM…..同样查看MAP ⽂件验证⼀下。⾸先在 main.c 中定义变量如下图:
图8
如上图所⽰定义了⼀个结构体实例,两个 16 的数组,对应 map ⽂件如下图:
图9
如上图所⽰, 3009 ⾏ 3010 ⾏使⽤__attribute__定义占⽤了外部 SRAM的部分地址,《main.c》⽂件中并没有使⽤__attribute__来定义变量,其定位也处于外部 SRAM 中,其数组的⼤⼩定义与 map
⽂件是⼀致的,在此证明经过配置之后,在 main.c ⽂件中定义的变量均会被定位到外部 SRAM 中。再验证 GraphicsDac_t 的⼤⼩。printf(" GraphicsDac_t size=%d \r\n",sizeof(GraphicsDac_t));
图 10(可见 printf 的⼤⼩与 map 的⼤⼩是⼀致的)
九、将变量定义到内部SRAM
参考图 3,将内部 SRAM 的《default》打勾,之后将图 7 的下两个选项配置为《default》即可,这样实现⼀个⽂件其变量定位的切换。多个外部 SRAM 芯⽚时适当参考配置。
⼗、定位到外部 SRAM的变量的访问⽅法
⽅法⼀、⼀般访问外部 SRAM 的⽅法
⾸先使⽤ SRAM_Init();之后使⽤下⾯两个函数读写外部 SRAM:
SRAM_ReadBuffer(uint16_t* pBuffer, uint32_t ReadAddr, uint32_t NumHalfwordToRead)
SRAM_WriteBuffer(uint16_t* pBuffer, uint32_t WriteAddr, uint32_t NumHalfwordToWrite)
⽅法⼆、编译器
如第 3 节变量定位定义⽅法,变量的访问就由编译器⾃⼰搞定了(关于这⼀点还没有实际硬件验证---这⾥仅是理所当然的推测,⾄于还要使
⽤⽅法⼀是不可思议的),变量的读写就和内部变量⼀样操作。
备注:⽂件内部函数内部的变量被定义在堆栈⾥,同时说明上⾯所说的定位到外部 SRAM 的变量均为全局变量,其⽂件内的局部变量还是在堆栈⾥(此处为我的推测,没经过验证)。