【转】STM32的FSMC详解
STM32的FSMC真是一个万能的总线控制器,不仅可以控制SRAM,NOR FLASH,NAND FLASH,PC Card,还能控制LCD,TFT.
一般越是复杂的东西,理解起来就很困难,但是使用上却很方便,如USB。
不过FSMC也有很诡异的地方.如
// 实际地址A21=1,而非A22.[注:0x60400000=0x60000000|(1UL<<22) ]
*(volatile uint16_t *)0x60800000=0x0;
// 实际地址A22=1,而非A23 [注:0x60800000=0x60000000|(1UL<<23) ]
为什么呢?那时我还以为软件或硬件还是芯片有BUG,
我就是从上面的不解中开始研究FSMC的…..
1.FSMC信号引脚
STM32的管脚排列很没有规律,而且分布在多个不同端口上,初始化要十分小心.需要用到的引脚都要先初始化成”复用功能推挽输出”模式.(GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP )
并且开启时钟
(RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOx, ENABLE); )
像STM32F103Z(144脚)芯片有独立的地址和数据总线,而STM32F103V(100脚)就没有,
地址和数据总线要像51单片机一样分时复用,而在STM32F103R系列(64脚)就没有FSMC模块.
复用总线时管脚:
PD14,//FSMC_DA0
PD15,//FSMC_DA1
PD0 ,//FSMC_DA2
PD1 ,//FSMC_DA3
PE7 ,//FSMC_DA4
PE8 ,//FSMC_DA5
PE9 ,//FSMC_DA6
PE10,//FSMC_DA7
PE11,//FSMC_DA8
PE12,//FSMC_DA9
PE13,//FSMC_DA10
PE14,//FSMC_DA11
PE15,//FSMC_DA12
PD8 ,//FSMC_DA13
PD9 ,//FSMC_DA14
PD10,//FSMC_DA15
PD11,//FSMC_A16
PD12,//FSMC_A17
PD13,//FSMC_A18
PE3 ,//FSMC_A19
PE4 ,//FSMC_A20
PE5 ,//FSMC_A21
PE6 ,//FSMC_A22
PE2 ,//FSMC_A23
PG13,//FSMC_A24//STM32F103Z
PG14,//FSMC_A25//STM32F103Z
独立的地址总线管脚:
[注:总线是16Bit情况下,FSMC通过FSMC_NBL1,FSMC_NBL0,区分高低字节.下面W代表WORD,即16BIT字.]
PF0 ,//FSMC_A0 //2^1=2W =4 Bytes //144PIN STM32F103Z
PF1 ,//FSMC_A1 //2^2=4W =8 Bytes//144PIN STM32F103Z
PF2 ,//FSMC_A2 //2^3=8W= 16 Bytes //144PIN STM32F103Z
PF3 ,//FSMC_A3 //2^4=16W =32 Bytes//144PIN STM32F103Z
PF4 ,//FSMC_A4 //2^5=32W =64 Bytes//144PIN STM32F103Z
PF5 ,//FSMC_A5 //2^6=64W =128 Bytes//144PIN STM32F103Z
PF12,//FSMC_A6 //2^7=128W =256 Bytes //144PIN STM32F103Z
PF13,//FSMC_A7 //2^8=256W =512 Bytes //144PIN STM32F103Z
PF14,//FSMC_A8 //2^9= 512W =1k Bytes//144PIN STM32F103Z
PF15,//FSMC_A9 //2^10=1kW =2k Bytes//144PIN STM32F103Z
PG0 ,//FSMC_A10 //2^11=2kW =4k Bytes//144PIN STM32F103Z
PG1 ,//FSMC_A11 //2^12=4kW =8k Bytes//144PIN STM32F103Z
PG2 ,//FSMC_A12 //2^13=8kW =16k Bytes//144PIN STM32F103Z
PG3 ,//FSMC_A13 //2^14=16kW =32k Bytes//144PIN STM32F103Z
PG4 ,//FSMC_A14 //2^15=32kW =64k Bytes//144PIN STM32F103Z
PG5 ,//FSMC_A15 //2^16=64kW =128k Bytes//144PIN STM32F103Z
PD11,//FSMC_A16 //2^17=128kW =256k Bytes
PD12,//FSMC_A17 //2^18=256kW =512k Bytes
PD13,//FSMC_A18 //2^19=512kW =1M Bytes
PE3 ,//FSMC_A19 //2^20=1MW =2M Bytes
PE4 ,//FSMC_A20 //2^21=2MW =4M Bytes
PE5 ,//FSMC_A21 //2^22=4MW =8M Bytes
PE6 ,//FSMC_A22 //2^23=8MW =16M Bytes
PE2 ,//FSMC_A23 //2^24=16MW =32M Bytes //100PIN STM32F103V MAX
PG13,//FSMC_A24 //2^25=32MW =64M Bytes //144PIN STM32F103Z
PG14,//FSMC_A25 //2^26=64MW =128M Bytes //144PIN STM32F103Z
独立的数据总线管脚:
PD14,//FSMC_D0
PD15,//FSMC_D1
PD0 ,//FSMC_D2
PD1 ,//FSMC_D3
PE7 ,//FSMC_D4
PE8 ,//FSMC_D5
PE9 ,//FSMC_D6
PE10,//FSMC_D7
PE11,//FSMC_D8
PE12,//FSMC_D9
PE13,//FSMC_D10
PE14,//FSMC_D11
PE15,//FSMC_D12
PD8 ,//FSMC_D13
PD9 ,//FSMC_D14
PD10,//FSMC_D15
控制信号
PD4,//FSMC_NOE,/RD
PD5,//FSMC_NWE,/WR
PB7,//FSMC_NADV,/ALE
PE1,//FSMC_NBL1,/UB
PE0,//FSMC_NBL0,/LB
PD7,//FSMC_NE1,/CS1
PG9,//FSMC_NE2,/CS2
PG10,//FSMC_NE3,/CS3
PG12,//FSMC_NE4,/CS4
//PD3,//FSMC_CLK
//PD6,//FSMC_NWAIT
2.地址的分配
地址与片选是挂勾的,也就是说器件挂载在哪个片选引脚上,就固定了访问地址范围和FsmcInitStructure.FSMC_Bank
- //地址范围:0x60000000~0x63FFFFFF,片选引脚PD7(FSMC_NE1),最大支持容量64MB,
- //[在STM32F103V(100脚)上地址范围为A0~A23,最大容量16MB]
- FsmcInitStructure.FSMC_Bank =FSMC_Bank1_NORSRAM1;
- //地址范围:0x64000000~0x67FFFFFF, 片选引脚PG9(FSMC_NE2),最大支持容量64MB
- FsmcInitStructure.FSMC_Bank =FSMC_Bank1_NORSRAM2;
- //地址范围:0x68000000~0x6BFFFFFF,片选引脚PG10(FSMC_NE3),最大支持容量64MB
- FsmcInitStructure.FSMC_Bank =FSMC_Bank1_NORSRAM3;
- //地址范围:0x6C000000~0x6FFFFFFF,片选引脚(PG12 FSMC_NE4),最大支持容量64MB
- FsmcInitStructure.FSMC_Bank =FSMC_Bank1_NORSRAM4;
3.时序测量
简单原理草图
写数据的时序
读数据的时序
1.数据总线设定为16位宽情况下测量FSMC时序,即
FsmcInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;
使用逻辑分析仪测量(循环执行下面这条语句,下同)的波形
*(volatile uint16_t *)(0x60002468UL)=0xABCD;
可以看出NADV下降沿瞬间DATABUS上的数据被锁存器锁存,接着NWE低电平,总线输出0xABCD,数据0xABCD被写入0x1234这个地址.
*(volatile uint16_t*)(0x60002469UL )=0xABCD;
what?向这个地址写出现了两次总线操作.
为了一探究竟,我引出了控制线.
*(volatile uint16_t*)(0x60000468UL )=0xABCD;
向0x60000468UL写入0xABCD到底会发什么?
从时序图中我们可以看到,
向0x60000468UL在地址(在范围:0x60000000~0x63FFFFFF内)写入数据,片选引脚PD7(FSMC_NE1)被拉低.而在这之前,数据总线上先产生0x234,于是在NADV下降沿瞬间,数据被锁存在地址锁存器上(A0~A15),与A16~A25(如果有配置的话,会在NE1下降沿同时送出)组合成完整的地址信号.然而有人会问这个0x234是哪来的,你是否注意到它正好等于0x468/2,难道是巧合吗?不是的,在16位数据总线情况下(NORSRAMInitStrc.FSMC_MemoryDataWidth=FSMC_MemoryDataWidth_16b;),
像这样
*(volatile uint16_t*)(0x60000000|addr)=0xABCD;
写入一个值,实际在地址线上产生的值是addr/2(即addr>>2),
所以如果我们一定要向addrx写入0xABCD则我们要这样写
*(volatile uint16_t*)(0x60000000|addrx<<1)=0xABCD;
NADV为高电平时, NEW被拉低,NOE为高,且NBL1,NBL0为低,随后数据总线线上产生0xABCD于是0xABCD被写进SRAM的地址0x234中
那如果我们向一个奇数地址像这样
*(volatile uint16_t*)(0x60000469UL )=0xABCD;写入值会发生什么呢?
从图中我们可以看到,STM32其实分成了两次字节写的过程,第一次向0x469/2写入0xCD,第二次向0x469/2+1写入0xAB,
有人会问你为什么这样说,NWE为低时总线上不是0xCDAB吗?没错,但是注意NBL1,NBL0的电平组合,NBL1连接到SRAM的nUB,NBL0连接到SRAM的nLB.第一次NEW为低时NBL1为低,NBL0为高,0xCDAB的高位被写入SRAM的0x234,第二次NWE为低时NBL1为高,NBL0为低,0xCDAB的低位被写入SRAM的0x235.
当我们查看反汇编时发现,指令是相同的
- 0x080036C4 0468 DCW 0x0468
- 0x080036C6 6000 DCW 0x6000
- MOVW r0,#0xABCD
- LDR r1,[pc,#420] ; @0x080036C4//r1=0x60000468
- STRH r0,[r1,#0x00]
- 0x080036C4 0469 DCW 0x0469
- 0x080036C6 6000 DCW 0x6000
- MOVW r0,#0xABCD
- LDR r1,[pc,#420] ; @0x080036C4//r1=0x60000469
- STRH r0,[r1,#0x00]
以上是写入的时序,下面测量读取的时序
首先我们向SRAM的真实地址0x234,0x235分别写入0x8824,0x6507
- *(volatile uint16_t*)(0x60000000UL |0x234 <<1 )=0x8824;
- *(volatile uint16_t*)(0x60000000UL |0x235 <<1 )=0x6507;
- *(volatile uint16_t*)(0x60000000UL |0x236 <<1 )=0x6735;
- *(volatile uint16_t*)(0x60000000UL |0x237 <<1 )=0x2003;
- *(volatile uint16_t*)(0x60000000UL |0x238 <<1 )=0x6219;
然后读取:
tmp=*(volatile uint16_t*)(0x60000468UL );
如图tmp结果为0x8824
再试
tmp=*(volatile uint16_t*)(0x60000469UL );
- 1
- 2
nUB=nLB=0;按16bit读
从0x234读得0X8824取高字节”88”作tmp低8位
从0x235读得0X6507取低字节”07”作tmp高8位
最终tmp=0x0788
接下来验证更特殊的
*(volatile uint8_t*)(0x60000469UL )=0xABCD;
由于NBL1=0,NBL0=1,0xCD被写入0x234的高地址,
数据总线上出现的值是0xCDNN, NN是随机数据,不过一般是和高位一样的值
*(volatile uint8_t*)(0x60000468UL )=0xABCD;
由于NBL1=1,NBL0=0,0xCD被写入0x234的低地址,
数据总线上出现的值是0xNNCD,NN是随机数据
验证字节读取的
首先我们向SRAM的真实地址0x234,0x235分别写入0x8824,0x6507
- *(volatile uint16_t*)(0x60000000UL |0x234 <<1 )=0x8824;
- *(volatile uint16_t*)(0x60000000UL |0x235 <<1 )=0x6507;
然后这样读取
tmp=*(volatile uint8_t*)(0x60000469UL );//对奇地址的单字节读取,数据总线的高8位被返回 tmp=0x88
tmp=*(volatile uint8_t*)(0x60000468UL );//对偶地址的单字节读取,数据总线的低8位被返回 tmp=0x24
还有更特殊的没有,有!
*(volatile int64_t*)(0x60000468UL)=0XABCDEF1234567890;//0XABCD EF12 3456 7890,如图,分别进行了4次操作才写完:
*(volatile int64_t*)(0x60000469UL)=0XABCDEF1234567890;//0XABCD EF12 3456 7890,如图,对奇地址写比偶地址多一次操作:
- *(volatile uint16_t*)(0x60000000UL |0x234 <<1 )=0x8824;
- *(volatile uint16_t*)(0x60000000UL |0x235 <<1 )=0x6507;
- *(volatile uint16_t*)(0x60000000UL |0x236 <<1 )=0x6735;
- *(volatile uint16_t*)(0x60000000UL |0x237 <<1 )=0x2003;
- *(volatile uint16_t*)(0x60000000UL |0x238 <<1 )=0x6219;
tmp=*(volatile int64_t*)(0x60000469UL);// tmp=0x1920036735650788
tmp=*(volatile int64_t*)(0x60000468UL); //tmp=0x2003673565078824
1.数据总线设定为8位宽情况下测量FSMC时序,即
FsmcInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_8b;
*(volatile uint16_t*)(0x60000468UL )=0xABCD;
*(volatile uint16_t*)(0x60000469UL )=0xABCD;
- *(volatile uint16_t*)(0x60000468UL )=0x3344;
- *(volatile uint16_t*)(0x60000469UL )=0xABCD;
tmp=(volatile uint16_t)(0x60000469UL ); //tmp=0xabcd
tmp=*(volatile uint16_t*)(0x60000468UL );
tmp=0xcd44
tmp=*(volatile uint8_t*)(0x60000468UL );
tmp=0x44
- tmp=*(volatile uint8_t*)(0x60000469UL );
- tmp=0xcd
*(volatile uint8_t*)(0x60000469UL )=0xABCD;
*(volatile uint8_t*)(0x60000468UL )=0xABCD;
- tmp=*(volatile uint64_t*)(0x60000468UL );
- tmp=0x2003673565ABCD44
tmp=*(volatile uint64_t*)(0x60000469UL );//tmp=0x192003673565ABCD
*(volatile uint64_t*)(0x60000469UL )=0XABCDEF1234567890;
*(volatile uint64_t*)(0x60000468UL )=0XABCDEF1234567890;
【转】STM32的FSMC详解的更多相关文章
- STM32固件库详解
STM32固件库详解 emouse原创文章,转载请注明出处http://www.cnblogs.com/emouse/ 应部分网友要求,最新加入固件库以及开发环境使用入门视频教程,同时提供例程模板 ...
- STM32 HAL库详解 及 手动移植
源: STM32 HAL库详解 及 手动移植
- (转)stm32启动文件详解
在<<STM32不完全手册里面>>,用的是STM32F103RBT6,所有的例程都采用了一个叫STM32F10x.s的启动文件,里面定义了STM32的堆栈大小以及各种中断的名字 ...
- (一)STM32固件库详解(转载)
本篇博文是转载自emouse,因为不能直接转载,所以是复制过来再发布的. emouse原创文章,转载请注明出处http://www.cnblogs.com/emouse/ 1.1 基于标准外设库的 ...
- STM32启动文件详解及SystemInit函数分析(转)
;先在RAM中分配系统使用的栈,RAM的起始地址为0x2000_0000 ;然后在RAM中分配变量使用的堆 ;然后在CODE区(flash)分配中断向量表,flash的起始地址为0x0800_0000 ...
- STM32—串口通讯详解
串口通讯目录 物理层 协议层 USART简介 开发板与上位机的连接 代码讲解: 一.初始化结构体 二.NVIC配置中断优先级 三.USART配置函数讲解 四.传输数据的函数: 1.发送一个字节 2.发 ...
- STM32 NVIC配置详解
例程: /* Configure one bit for preemption priority */ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1) ...
- STM32 DMA使用详解
DMA部分我用到的相对简单,当然,可能这是新东西,我暂时还用不到它的复杂功能吧.下面用问答的形式表达我的思路. DMA有什么用? 直接存储器存取用来提供在外设和存储器之间或者存储器和存储器之间的高速数 ...
- STM32硬件调试详解
STM32的基本系统主要涉及下面几个部分: 一.电源 1).无论是否使用模拟部分和AD部分,MCU外围出去VCC和GND,VDDA.VSSA.Vref(如果封装有该引脚)都必需要连接,不可悬空: 2) ...
随机推荐
- oracle中查询某个库中所有的表以及所占的表空间大小
1. 查某一用户下的表select SEGMENT_NAME,TABLESPACE_NAME,sum(BYTES/1024/1024)||'M' from USER_extents where SEG ...
- pwn学习日记Day7 基础知识积累
知识杂项 strncpy(char s1,const char s2,int n); 其中有三个参数分别表示目标字符串s1,源字符串s2,拷贝长度.意思是将s2指向的字符串的前n个长度的字符放到s1指 ...
- git常用操作方法
在实际开发,测试中,会使用git作为版本控制工具来完成团队协作.因此,对基本的git操作指令进行总结是十分有必要的,本文对一些术语或者理论基础,不重新码字,可以参考廖雪峰老师的博文,本文只对命令做归纳 ...
- linux下编辑VI窗口插入与编辑命令
前言 在嵌入式linux开发中,进行需要修改一下配置文件之类的,必须使用vi,因此,熟悉 vi 的一些基本操作,有助于提高工作效率. 一,模式 vi编辑器有3种模式:命令模式.输入模式.末行模式.掌握 ...
- 阶段5 3.微服务项目【学成在线】_day02 CMS前端开发_06-vuejs研究-vuejs基础-v-on指令
3.v-on绑定一个按钮的单击事件 计算的按钮上加事件 点击计算的按钮,弹出的事件 定义一个Result的变量
- CodeIgniter问题:Unable to load the requested file: .php
调试时出现 Unable to load the requested file: .php, 后来排查到是模板渲染的问题,view函数的参数没接收到,修改后就好了.
- JAVA 基础编程练习题17 【程序 17 猴子吃桃问题】
17 [程序 17 猴子吃桃问题] 题目:猴子吃桃问题:猴子第一天摘下若干个桃子,当即吃了一半,还不瘾,又多吃了一个 第二天早上又 将剩下的桃子吃掉一半,又多吃了一个.以后每天早上都吃了前一天剩下的一 ...
- 查看oracle数据库的链接数和用户
http://www.cnblogs.com/is1988/archive/2012/11/21/2780067.html 1.查询oracle的连接数select count(*) from v$s ...
- nginx反向代理本地 单台wed -使用域名代理
环境: 本地外网ip:123.58.251.166 .配置index.html网页 [root@host---- conf.d]# cat /web/sing/index.html <h1> ...
- selenium+python自动化框架
流程: 环境准备 eclipse :需安装pydev.testng插件 python :安装python完成后,需 pip下安装selenium:命令: pip install selenium 我现 ...