STM32外扩SRAM实现方法详解—FSMC
实现效果:使用外部SRAM像使用内部RAM一样(1.只需简单的变量定义 2.可像正常变量一样使用指针访问)
使用开发板为正点原子探索者和正点原子例程串口的通讯的例程
这里直接忽略FSMC的讲解,对应讲解内容可看正点原子开发手册TFT屏幕使用那张对FSMC的讲解即可
通过查看探索者原理图可以知道,探索者自带的SRAM被映射到FSMC上的地址0x6800 0000–0x680F FFFF 大小为1M 0x10 0000 (请记住这里的地址和大小后面会用上的)
1如何使编译器,将地址安排到外部SRAM上面去?
这里一keil软件为例,如果使用IAR对应的修改会有不同
这里有两种方法:
第一种直接在工程配置里修改如图
第二种使直接修改链接文件(这里建议在工程同路径下,定义一个.sct后缀的文件,这里我定义的文件为user.sct,并将这个文件配置到工程上面)
取消1处复选框的复选框勾选,并在2处添加刚才定义的文件,并点击编辑,将以下代码复制到这个文件里面即可
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
LR_IROM1 0x08000000 0x00100000 { ; load region size_region
ER_IROM1 0x08000000 0x00100000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
}
RW_IRAM1 0x20000000 0x00020000 { ; RW data
.ANY (+RW +ZI)
}
RW_IRAM2 0x68000000 0x00100000 { ; RW data
.ANY (+RW +ZI)
}
}
此时我们点击编译并查看map文件
这里我们看到,很多全局变量和数组都分配到了外部SRAM对应的地址上了
如果你此时并不能达到这种效果,你可以在你的代码里面自定义一个很大数组,并在程序里面使用一下这个数组(如果不使用这个数组在编译的时候会被优化)
FSMC初始化
如果仅完成了上一步,下载程序并不会出错,只是你的变量的数据全是错的,不能改也不能读。原因就是因为你没有初始化FSMC
FSMC初始化代码如下(使用的是正点原子的SRAM初始化)
这个初始化函数,必须放在执行main函数前运行完,即放在启动文件面执行(这里我是放在了启动文件系统初始化函数(SystemInit)里面完成的)
//SRAM初始化
SRAM_HandleTypeDef SRAM_Handler; //SRAM句柄
void SRAM_Init(void)
{
GPIO_InitTypeDef GPIO_Initure;
FSMC_NORSRAM_TimingTypeDef FSMC_ReadWriteTim;
__HAL_RCC_FSMC_CLK_ENABLE(); //使能FSMC时钟
__HAL_RCC_GPIOD_CLK_ENABLE(); //使能GPIOD时钟
__HAL_RCC_GPIOE_CLK_ENABLE(); //使能GPIOE时钟
__HAL_RCC_GPIOF_CLK_ENABLE(); //使能GPIOF时钟
__HAL_RCC_GPIOG_CLK_ENABLE(); //使能GPIOG时钟
//PD0,1,4,5,8~15
GPIO_Initure.Pin=GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_8|\
GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|\
GPIO_PIN_14|GPIO_PIN_15;
GPIO_Initure.Mode=GPIO_MODE_AF_PP; //推挽复用
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_HIGH; //高速
GPIO_Initure.Alternate=GPIO_AF12_FSMC; //复用为FSMC
HAL_GPIO_Init(GPIOD,&GPIO_Initure);
//PE0,1,7~15
GPIO_Initure.Pin=GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9|\
GPIO_PIN_10| GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|\
GPIO_PIN_15;
HAL_GPIO_Init(GPIOE,&GPIO_Initure);
//PF0~5,12~15
GPIO_Initure.Pin=GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4|\
GPIO_PIN_5|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;
HAL_GPIO_Init(GPIOF,&GPIO_Initure);
//PG0~5,10
GPIO_Initure.Pin=GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_10;
HAL_GPIO_Init(GPIOG,&GPIO_Initure);
SRAM_Handler.Instance=FSMC_NORSRAM_DEVICE;
SRAM_Handler.Extended=FSMC_NORSRAM_EXTENDED_DEVICE;
SRAM_Handler.Init.NSBank=FSMC_NORSRAM_BANK3; //使用NE3
SRAM_Handler.Init.DataAddressMux=FSMC_DATA_ADDRESS_MUX_DISABLE; //地址/数据线不复用
SRAM_Handler.Init.MemoryType=FSMC_MEMORY_TYPE_SRAM; //SRAM
SRAM_Handler.Init.MemoryDataWidth=FSMC_NORSRAM_MEM_BUS_WIDTH_16; //16位数据宽度
SRAM_Handler.Init.BurstAccessMode=FSMC_BURST_ACCESS_MODE_DISABLE; //是否使能突发访问,仅对同步突发存储器有效,此处未用到
SRAM_Handler.Init.WaitSignalPolarity=FSMC_WAIT_SIGNAL_POLARITY_LOW; //等待信号的极性,仅在突发模式访问下有用
SRAM_Handler.Init.WaitSignalActive=FSMC_WAIT_TIMING_BEFORE_WS; //存储器是在等待周期之前的一个时钟周期还是等待周期期间使能NWAIT
SRAM_Handler.Init.WriteOperation=FSMC_WRITE_OPERATION_ENABLE; //存储器写使能
SRAM_Handler.Init.WaitSignal=FSMC_WAIT_SIGNAL_DISABLE; //等待使能位,此处未用到
SRAM_Handler.Init.ExtendedMode=FSMC_EXTENDED_MODE_DISABLE; //读写使用相同的时序
SRAM_Handler.Init.AsynchronousWait=FSMC_ASYNCHRONOUS_WAIT_DISABLE; //是否使能同步传输模式下的等待信号,此处未用到
SRAM_Handler.Init.WriteBurst=FSMC_WRITE_BURST_DISABLE; //禁止突发写
SRAM_Handler.Init.ContinuousClock=FSMC_CONTINUOUS_CLOCK_SYNC_ASYNC;
//FMC读时序控制寄存器
FSMC_ReadWriteTim.AddressSetupTime=0x00; //地址建立时间(ADDSET)为1个HCLK 1/168M=6ns*16=96ns
FSMC_ReadWriteTim.AddressHoldTime=0x00; //地址保持时间(ADDHLD)模式A未用到
FSMC_ReadWriteTim.DataSetupTime=0x08; //数据保存时间为8个HCLK =6*1=6ns
FSMC_ReadWriteTim.BusTurnAroundDuration=0X00;
FSMC_ReadWriteTim.AccessMode=FSMC_ACCESS_MODE_A;//模式A
HAL_SRAM_Init(&SRAM_Handler,&FSMC_ReadWriteTim,&FSMC_ReadWriteTim);
}
使用此初始化函数,可能会存在由于库文件缺失而报错,这里按要求添加即可(如图是我在使用添加的两个库文件 )
这个初始化函数,如果放在main函数里面执行,也能正常运行
但是变量的初始值将不是正确的
例如:int a =2;
放在mian函数里面,a的初始值就不一定是2,而是一个随机数
这里我们可以在程序里面对这些分配到外部SRAM变量进行操作。并通过串口打印对应值,或者直接仿真查看变量对应地址信息是否随着我们意愿在改变
至此我们就完成了使用FSMC外扩SRAM的程序修改,这个修改和正点原子SRAM例程的最本质的差距是,我们变量的地址是编译器分配的,而正点原子是使用内存管理的方式完成的
其他
看到这里我们会不会有这种想法,我不想把所有的变量地址都分配到外部,我可不可以自由控制,让他去外面才去外面?
这种方法当然是可以的,我们只需要告诉编译器,你这某些section编译外部就可以了
实现方法很简答
1.自定义一个section
2.修改链接配置文件即可
自定义section
如下图代码,自定义了.fzcdate的块,并定义变量让其编译的时候放在这个块里
#define FZCDATA __attribute__((section(".fzcdata")))
FZCDATA int ccc=2; //自定义块
修改链接配置文件
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************
LR_IROM1 0x08000000 0x00100000 { ; load region size_region
ER_IROM1 0x08000000 0x00100000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
}
RW_IRAM1 0x20000000 0x00020000 { ; RW data
.ANY (+RW +ZI)
}
RW_IRAM2 0x68000000 0x00100000 { ; RW data
.ANY (.fzcdata)
}
}
此时点击编译,并查看map文件
由此可以看到只有ccc被分配到了外部SRAM里面
作者:黑小胖