LV16-24-FSMC-03-读写外部SRAM实例
本文主要是STM32开发——通过FSMC驱动外部SRAM的一些相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
Windows | windows11 |
Ubuntu | Ubuntu16.04的64位版本 |
VMware® Workstation 16 Pro | 16.2.3 build-19376536 |
SecureCRT | Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日 |
开发板 | 正点原子 i.MX6ULL Linux阿尔法开发板 |
uboot | NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03) |
linux内核 | linux-4.15(NXP官方提供) |
STM32开发板 | 正点原子战舰V3(STM32F103ZET6) |
点击查看本文参考资料
- 通用
分类 | 网址 | 说明 |
官方网站 | https://www.arm.com/ | ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档 |
https://www.st.com/content/st_com/zh.html | ST官方网站,在这里我们可以找到STM32的相关文档 | |
https://www.stmcu.com.cn/ | 意法半导体ST中文官方网站,在这里我们可以找到STM32的相关中文参考文档 | |
http://elm-chan.org/fsw/ff/00index_e.html | FatFs文件系统官网 | |
教程书籍 | 《ARM Cortex-M3权威指南》 | ARM公司专家Joseph Yiu(姚文祥)的力作,中文翻译是NXP的宋岩 |
《ARM Cortex-M0权威指南》 | ||
《ARM Cortex-M3与Cortex-M4权威指南》 | ||
开发论坛 | http://47.111.11.73/forum.php | 开源电子网,正点原子的资料下载及问题讨论论坛 |
https://www.firebbs.cn/forum.php | 国内Kinetis开发板-野火/秉火(刘火良)主持的论坛,现也做STM32和i.MX RT | |
https://www.amobbs.com/index.php | 阿莫(莫进明)主持的论坛,号称国内最早最火的电子论坛,以交流Atmel AVR系列单片机起家,现已拓展到嵌入式全平台,其STM32系列帖子有70W+。 | |
http://download.100ask.net/index.html | 韦东山嵌入式资料中心,有些STM32和linux的相关资料也可以来这里找。 | |
博客参考 | http://www.openedv.com/ | 开源网-原子哥个人博客 |
http://blog.chinaaet.com/jihceng0622 | 博主是原Freescale现NXP的现场应用工程师 | |
cortex-m-resources | 这其实并不算是一个博客,这是ARM公司专家Joseph Yiu收集整理的所有对开发者有用的官方Cortex-M资料链接(也包含极少数外部资源链接) |
- STM32
STM32 | STM32 HAL库开发实战指南——基于F103系列开发板 | 野火STM32开发教程在线文档 |
STM32库开发实战指南——基于野火霸道开发板 | 野火STM32开发教程在线文档 |
- SD卡
SD Association | 提供了SD存储卡和SDIO卡系统规范 |
点击查看相关文件下载
STM32F103xx英文数据手册 | STM32F103xC/D/E系列的英文数据手册 |
STM32F103xx中文数据手册 | STM32F103xC/D/E系列的中文数据手册 |
STM32F10xxx英文参考手册(RM0008) | STM32F10xxx系列的英文参考手册 |
STM32F10xxx中文参考手册(RM0008) | STM32F10xxx系列的中文参考手册 |
Arm Cortex-M3 处理器技术参考手册-英文版 | Cortex-M3技术参考手册-英文版 |
STM32F10xxx Cortex-M3编程手册-英文版(PM0056) | STM32F10xxx/20xxx/21xxx/L1xxxx系列Cortex-M3编程手册-英文版 |
SD卡相关资料——最新版本 | 有关SD卡的一些资料可以从这里下载 |
SD卡相关资料——历史版本 | 有关SD卡的一些历史版本资料可以从这里下载,比如后边看的SD卡2.0协议 |
SD 2.0 协议标准完整版 | 这是一篇关于SD卡2.0协议的中文文档,还是比较有参考价值的,可以一看 |
一、硬件设计
我使用的是正点原子战舰V3的板子,硬件连接如下图:
二、STM32CubeMX配置
这里只关心FSMC的配置,其他的可以看前边的笔记。
1. FSMC配置
1.1 模式选择
①、选择FSMC外设;
②、Bank1的四个区域,刚开始没看懂,后来发现就是这里有4个可选的,每一个应该是对应Bank1的一个区域,每个区域可以选择自己的NE引脚,但是这四个是不能选择相同的NE线,这应该就是为了让我们方便配置多个设备。所以对于上边的SRAM接线,我们只要选择了NE3线,在Mode选项卡中使用NOR Flash/PSRAM/SRAM/ROM/LCD x这四个中哪一个都没有问题,主要是NE线要选对。
FSMC_NE 是用于控制存储器芯片的片选控制信号线,STM32 具有 FSMC_NE1/2/3/4 号引脚,不同的引脚对应 STM32 内部不同的地址区域。例如,当 STM32 访问 0x68000000-0x6BFFFFFF 地址空间时,FSMC_NE3 引脚会自动设置为低电平,由于它一般连接到外部存储器的片选引脚且低电平有效,所以外部存储器的片选被使能,而访问 0x60000000-0x63FFFFFF 地址时,FSMC_NE1 会输出低电平。当使用不同的 FSMC_NE 引脚连接外部存储器时,STM32 访问外部存储的地址不一样,从而达到控制多个外部存储器芯片的目的。
③、这里应该就是Bank2区域,我们没有用到;
④、这里应该是Bank3区域,我们也没有用到;
⑤、这里是Bank4区域,我们没有用到。
上边的Bank1我们选择使用 NOR Flash/PSRAM/SRAM/ROM/LCD 1,并使用NE3线来驱动我们的SRAM,这样使用的就是Bank1的第3区域了。
1.2 参数配置
①、片选位,我们选择NE3,这是硬件电路决定的;
②、内存类型,这里我们使用的SRAM,就选择SRAM就可以啦;
③、地址位数,我们的SRAM有A[18:0]共19根线,虽说18根就能寻完1M空间,但是硬件电路接了,我们这里就配置成19位,也更符合逻辑,我试过配置成18也是没问题的。
④、数据位数,可选8位或者16位,我们使用的SRAM有I/O[15:0]共16位,所以这里选择16位即可。
⑤、是否使能同步模式,我们使用异步模式来驱动FSMC,这里禁止掉。
⑥、字节位使能,在网上查了一下,是什么高八位允许,我们上边选的16位数据的话,这里就需要勾选上。
⑦、Bank,这里不需要选,这是上面我们配置后显示的,可以看一下我们使用的区域是否正确。
⑧、写保护,我们要写数据到SRAM的话,这里需要设置为Enable。
⑨、扩展模式,我们驱动的SRAM可以使用FSMC读写相同的时序,这里我们不需要扩展模式,默认关闭即可。注意这里我们要是使能了扩展模式,就可以单独去配置读写时序参数,在配置界面也会多出一些参数配置选项,如下图:
⑩、地址建立时间,看1.3时间参数配置一小节。
⑪、数据建立时间,看1.3时间参数配置一小节。
⑫、总线转化时间,这个其实我们并不需要太怎么关注,我看网上的例程大多数都直接设置为0就可以了。
1.3 时间参数配置
上边我们需要配置地址建立时间,数据建立时间和总线转化时间,总线转化时间不用管,主要是地址建立时间和数据建立时间,我们下边来看一下这两个参数如何确定。我们配置时间参数的时候,要满足条件的情况下尽可能短。我们先计算一下HCLK是多长时间:
1 | HCLK = 1/72M (s) = 1/(72*1000*1000) S |
1.3.1 读时序
FSMC模式A和SRAM时序图对应如下图所示:
SRAM读时序各部分时间要求如下图(我们使用的是55ns的):
①处表示整个读的过程:
1 | (ADDSET+1)*HCLK + (DATAST+1)*HCLK + 2*HCLK ≥ tRC(55ns) |
②处表示OE#使能信号拉低多久后DOUT上出现有效数据:
1 | (DATAST+1)*HCLK ≥ tDOE(25ns,最长在25ns后会给一个有效数据) |
③处表示地址建立时间,就是地址建立之后多久使能信号OE#被拉低:
1 | (ADDSET+1)*HCLK ≥ 0ns |
这个对应的是SRAM的从地址给出之后到OE#拉低的时间,SRAM中没有给出具体时间,我们可以看一个比它长的时间,比如tAA,手册上写tAA控制在55ns以内即可,没看到它有最小值,所以这里我们可以配置为0ns
所以对于读时序,我们可以确定如下参数:
1 | DATAST=1 ===> (DATAST+1)*HCLK=27.6ns ≥ 25ns(测试过这样配置成1的话,是无法正常读的) |
1.3.2 写时序
FSMC模式A和SRAM时序图对应如下图所示:
SRAM写时序各部分时间要求如下图(我们使用的是55ns的):
①处表示整个写入的过程:
1 | (ADDSET+1)*HCLK + (DATAST+1)*HCLK ≥ tWC(55ns) |
②处表示WE#使能信号拉低持续的时间,在这个时间内SRAM才会将数据写到存储矩阵中:
1 | (DATAST+1)*HCLK ≥ tPWE(40ns) |
这样才能保证SRAM写入的时候DIN上的数据依然是有效的。
③处表示地址建立时间,就是地址建立之后多久使能信号WE#被拉低:
1 | (ADDSET+1)*HCLK ≥ tSA(0ns) |
这个对应的是SRAM的从地址给出之后到WE#拉低的时间,但是SRAM的时序图中那一小块没看明白,不过问题不大。
所以对于写时序,我们可以确定如下参数:
1 | DATAST=1 ===> (DATAST+1)*HCLK=27.6ns 无法满足≥40ns的条件 |
1.3.3 参数的确定
我们经过上边的分析,得到如下参数:
1 | 读时序: |
我们发现,读写时序的参数是一样的,这样我们就可以不使用扩展模式,读写时序共用一套寄存器即可。
2. 时钟配置
我们来看一下时钟的配置情况:
可以看到我们的时钟里边HCLK为72MHz,这样一个时钟周期就是13.8ns。
三、读写外部SRAM
1. SRAM基地址
1 | /* 使用NOR/SRAM的 Bank1.sector3,地址位HADDR[27,26]=10 |
2. 初始化FSMC
1 | SRAM_HandleTypeDef hsram1; |
这里需要注意的是当我们启用扩展模式的时候,这样才能使用前面设置的模式A,否则其实我们使用的是模式1。
(1)NSBank :设置存储区域 FSMC_Bank,FSMC_Bank 成 员 设 置 FSMC 的 SRAM存储区域映射选择为FSMC_Bank1_NORSRAM3,这是由于我们的 SRAM 硬件连接到 FSMC_NE3 和NOR/PSRAM 相关引脚,所以对应到存储区域 Bank1 SRAM3,对应的基地址为0x68000000;
(2)MemoryType :由于我们控制的是 SRAM 类型存储器,所以 FSMC_MemoryType 成员要选择相应的FSMC_MEMORY_TYPE_SRAM;
(3)MemoryDataWidth :根据硬件的数据线连接,数据线宽度被配置为 16 位宽 FSMC_NORSRAM_MEM_BUS_WIDTH_16;
(4)WriteOperation :WriteOperation 用于设置写使能,只有使能了才能正常使用 FSMC 向外部存储器写入数据;
(5)ExtendedMode :在 FSMC_ExtendedMode 成员中可以配置是否使用扩展模式,当设置扩展模式时,我们可以使用FSMC_NORSRAM_TimingTypeDef定义两个结构体变量,分别配置读写时序,两种配置互相独立,可以赋值为不同的读写时序结构体。在本实例中不使用扩展模式,所以就只定义了一个时序结构体变量;
3. 指针读写数据
完成初始化 SRAM 后,我们就可以利用它存储数据了,由于 SRAM 的存储空间是被映射到内核的寻址区域的,我们可以通过映射的地址直接访问 SRAM,访问这些地址时, FSMC 外设自动读写 SRAM,程序上无需额外操作。
3.1 按8位读写指定地址
1 | uint8_t ubWritedata_8b = 0, ubReaddata_8b = 0; |
(1)Bank1_SRAM3_ADDR :SRAM映射到内核后的基地址。
(2)offset :相对于基地址的偏移,我们使用的是 1M字节的SRAM,所以范围为0~0x100000(1MB)。
(3)ubWritedata_8b : 要写入的8位数据。
(4)ubReaddata_8b : 读取的8位数据存放在这里。
3.2 按16位读写指定地址
1 | uint16_t uhWritedata_16b = 0, uhReaddata_16b = 0; |
(1)Bank1_SRAM3_ADDR :SRAM映射到内核后的基地址。
(2)offset :相对于基地址的偏移,我们使用的是 1M字节的SRAM,所以范围为0~0x80000(512KB)。
(3)ubWritedata_16b : 要写入的16位数据。
(4)ubReaddata_16b : 读取的16位数据存放在这里。
3.3 8位数据指针读写测试
1 | int rw_8bit_test(void) |
3.4 16位数据指针读写测试
1 | int rw_16bit_test(void) |
4. 读写指定字节数据
4.1 向SRAM写入n个字节数据
1 | void FSMC_SRAM_WriteBuffer(uint8_t *pBuffer,uint32_t WriteAddr,uint32_t n) |
【函数说明】在指定地址(WriteAddr+Bank1_SRAM3_ADDR)开始,连续写入n个字节。
【参数说明】
- pBuffer :字节指针
- WriteAddr :要写入的地址
- n :要写入的字节数
4.2 从SRAM读取n个字节数据
1 | void FSMC_SRAM_ReadBuffer(uint8_t *pBuffer,uint32_t ReadAddr,uint32_t n) |
【函数说明】在指定地址(WriteAddr+Bank1_SRAM3_ADDR)开始,连续读取n个字节。
【参数说明】
- pBuffer :字节指针
- WriteAddr :要读的起始地址
- n :要读取的字节数
4.3 读写n个字节数据测试
1 | void rw_nByte_test(void) |
5. MDK预存数据?
注意这一部分的测试代码是在MDK工程中进行的。后边会说为什么一起这样区分。
5.1 预存数据语法
在C语言中,有一个 __attribute__,通过它,我们可以直接定义数据到指定的地址去:
1 | uint8_t testRram[500] __attribute__((at(0X68000000)));// 测试用数组 |
testsram将会被定义到0X68000000为起始地址的空间中去。要注意使用这种方法定义变量时,必须在函数外把它定义成全局变量,才可以存储到指定地址上。
5.2 预存数据测试函数
1 | void SRAM_Test(void) |
5.3 测试效果
有没有发现,最后读取出来的字符串数据是有问题的,按理来说应该是输出 This is my sram!!! 才对,可以最终输出数据有问题,我们会发现SRAM的每个地址的数据也和预想的不一样。此时我们就要考虑数据建立时间问题了,我们往上加1(将DATAST配置为3),然后重新编译下载,会发现数据正常了:
由此可知,我们计算出来的时间与临界值太过于接近,可能也会有问题,有问题的时候我们可以增加一些时间,然后再测试。
6. Makefile工程?
我后来使用VScode+STM32CubeMX生成的Makefile文件来开发,所以这里遇到了一个坑。
6.1 预存数据测试
我们先按上边的方式预存数据到0X68000000地址,测一下效果,我们对测试函数进行一下精简:
1 | uint8_t testSram[500] __attribute__((at(0X68000000)));// 测试用数组 |
然后我们进行编译,这个时候我们发现,报警告啦:
1 | warning: 'at' attribute directive ignored [-Wattributes] |
6.2 测试效果
我们会看到有如下打印:
1 | This is my stm32f103zet6 test! |
咦?我们不是定义到0x68000000去了吗?为什么跑到0x20000084去了?这就是为什么上边要区分MDK工程和Makefile工程的原因啦。问题就出在上边的警告,arm-gcc中不支持at直接指定变量位置的写法,所以会警告,但是具体MDK为什么支持,倒是不是很清楚,可能是它会自动去修改链接脚本文件吧,我们这里看一下要是使用Makefile的话,如何定义变量到外部SRAM中去。
6.3 修改链接文件
arm-gcc同样支持指定变量地址,只不过语法是下面这样的:
1 | __attribute__ ((section ("SECTIONNAME"))) |
换句话说,在链接文件中划分一个新的段,将这个变量放到这个段内就可以解决了。与scatter文件(MDK使用的链接文件格式)不同,由cubemx生成的Makefile工程使用的是ld文件,我们修改如下:
1 | /* Specify the memory areas */ |
- 结尾的 >SRAM指上面花括号内的内容都放在第二部分中定义的SRAM空间中。如果没有 AT> FLASH ,那么编译bin文件时地址是连续的。
6.4 测试数组定义
1 | uint8_t testSram[500] __attribute__((section(".sram")));// 测试用数组 |
6.5 测试效果
这一次我们重新编译,下载,执行结果如下:
1 | This is my stm32f103zet6 test! |
可以看到,这样我们就将全局数组定义到了0x68000000起始的位置去。