STM32
直播中

贾虎世

7年用户 1671经验值
私信 关注
[问答]

什么是FSMC?

什么是FSMC?

回帖(1)

张凯

2021-11-23 14:21:10
  1 概述

    STM32控制器芯片内部有一定大小的SRAM及FLASH作为内存和程序存储空间,代码存放在FLASH里,而代码运行过程的一些变量及缓存会放在SRAM里(可以把SRAM理解成电脑的内存条,内存条实质是由多个内存颗粒(即SDRAM芯片)组成的通用标准模块),当程序较大、比如跑算法或者跑 GUI 等,内存和程序空间不足时,就需要在STM32芯片的外部扩展存储器了。STM32F103ZE系列芯片可以扩展外部SRAM用作内存。

  1.1 自带的IS62WV51216芯片

    STM32F103ZET6 自带了 64K 字节的 SRAM(在芯片里),战舰 STM32 开发板板载了一颗 1M 字节容量的外部 SRAM 芯片:IS62WV51216,它是一颗 16 位宽 512K(16 * 512K,即 1M 字节;ps:16=2个字节)容量的 CMOS 静态内存芯片。“16”表示16位数据总线,即表示每个地址上的数据是16位(2个字节)。PS:1M字节 = 1024 * 1024字节 = 512K * 2字节 = (512 * 1024)字节 * 2字节
  1.2 FSMC概述

    FSMC是Flexible StaticMemory Controller的缩写,就是灵活的静态存储控制器。它可以用于驱动包括SRAM、NOR FLASH(如W25Q128)以及NAND FLSAH类型的存储器。其他我们不用管,我们只要知道,stm32雇佣FSMC这个管家来管理我们的IS62WV51216。
    介绍之前,先描述一下用FSMC来管理IS62WV51216后的美好样子:已知C代码在定义变量时,编译器最终会将变量名替换成内存的实际地址,以后每次访问该变量,就相当于直接访问了该地址里的数据,还有比如指针的解引用。这个也一样,配置好FSMC一些相关的参数后(相当于让外部SRAM与它进行绑定),然后我们就可以直接使用外部SRAM上的内存了,以前的变量对应的是芯片内部内存的地址,而现在则对应的是外部SRAM里的地址,并且和对内部内存地址操作的方式都一样,有两种方式:
  



使用指针对SRAM进行读写:定义一个指针直接指向SRAM里的地址即可
#define SRAM_BASE_ADDR                                 (0x68000000)
uint8_t *p;
p = (uint8_t *)(SRAM_BASE_ADDR+0x40);
*p = 0xAB; //写入数据到外部SRAM,同样也可读出
1
2
3
4
使用绝对地址的方式访问SRAM:直接访问到SRAM里的实际地址
#define SRAM_BASE_ADDR                                 (0x68000000)
//使用__attribute__定义变量时,需要定义为全局变量(MDK提供的)
//指定一个变量的地址
uint8_t testValue __attribute__ ((at (SRAM_BASE_ADDR+0x40))) ;
testValue = 0xAB; //写入数据到外部SRAM,同样也可读出
 看到这里惊呆了,居然还能这样子方便,即只要你知道这个外部SRAM里的内存地址了,你就可以很方便去访问了。这里应该有个疑问,使用内部内存的地址和这个外部的内存的地址会不会有冲突?不会。下面是cotex-m3内核的地址映射和FSMC相关的地址映射:可以知道芯片自带的片上SRAM的地址在0x20000000到0x3FFFFFFF,而划分给FSMC管理的地址从0x60000000到0x9FFFFFFF,即供外部SRAM拓展的最大空间有1G这么大。

       
      

      

      

      
        code区访问片上SRAM的数据和访问外部SRAM的数据一摸一样,只要有了内存地址,并且你的内存芯片已经做好了准备(比如初始化FSMC),那么就可以直接读写了。
        
      
      2 IS62WV51216芯片介绍

      先介绍IS62WV51216芯片的相关知识:
      
           
  

  
    芯片图
           
  

  
    内部功能框架
    图中 A0 ~ 18 为地址线,总共 19 根地址线(即 2^19bit=512K,1K=1024bit);IO0 ~ 15 为数据线,总共 16 根数据线(分高低两部分)。CS2 和 CS1 都是片选信号,不过 CS2 是高电平有效 CS1 是低电平有效;OE是输出使能信号(读信号,如果要读取该芯片,则需要使能该引脚);WE 为写使能信号(写入数据时,则使能该引脚);UB 和 LB 分别是高字节控制和低字节控制信号(数据掩码信号);
    实际的内存大小为1024 * 1204个字节,但是19根地址线只能访问到512K(512*1024字节)的范围,那怎么办呢?注意IS62WV51216里的”16“代表的是16bit,也就是只要给一个地址,其实就能够访问到16bit的数据(即2字节,比如输入地址0,实际可以访问到第0和第1个字节),因此19根地址线实际可寻址的范围是1024个字节这么大。只不过有时候如果我只想读写一个字节的时候怎么做呢?那就用到了上一段的UB 和 LB数据掩码信号。
    比如,我要往第0字节写入数据,而不影响第1字节里原有的数据,则我需要将UB线拉高,LB线拉低(低位字节有效),数据通过16根数据线传过来,IS62WV51216就知道自己只要接收 IO0 ~ 7的数据,自动屏蔽忽略了IO8 ~ 15的数据。
  
  2.1 SRAM通信时序

    外部SRAM(IS62WV51216)的读写时序介绍,采用的是异步方式:
  下面是STM32读取SRAM时的时序:

   
  

  
  


  • 先使能片选引脚指明设备,然后开始发送地址;
  • 一段时间后使能OE引脚,再经过tDOE时间后开始采集数据;
  • 数据是内存芯片通过16个IO引脚传回STM32的,在整个读取的过程地址线要保持不变tRC时间(即每根地址线上的高低电平要保持住);
  • 三个时间比较重要:tRC,tAA,tODE。

   
  

  
   STM32写入数据到SRAM的时序如下:
   
  

  
  读写时序的流程很类似,过程如下:
 (1) 主机使用地址信号线发出要访问的存储器目标地址;
 (2) 控制片选信号CS1#及CS2#使能存储器芯片;
 (3) 若是要进行读操作,则控制读使能信号OE#表示要读数据,若进行写操作则控制写使能信号WE#表示要写数据;
 (4) 使用掩码信号LB#与UB#指示要访问目标地址的高、低字节部分;
 (5) 若是读取过程,存储器会通过数据线向主机输出目标数据,若是写入过程,主要使用数据线向存储器传输目标数据。
   
  3 FSMC的介绍

    关于FSMC的介绍,如前面提到的,FSMC是Flexible StaticMemory Controller的缩写,就是灵活的静态存储控制器。它是STM32集成的一个外设,顾名思义它只能控制静态存储器,类似于SDRAM这样的动态内存它无法控制,可以把它看成是一个衔接CPU与外部存储的桥梁,它的功能呢就是你往相应的地址里写数据时候,你不需用软件来模拟外部存储芯片的读写时序,而只需配置好FSMC相关的时序寄存器,配置好相关寄存器之后,你只管往相应存储块中的地址里写数据就可以了。
    下面是FSMC的控制框图,本文讲到的IS62WV51216这个外部SRAM归属于NOR存储控制器来管理,因此我们暂时先学习这部分即可。
   
  

  
    然后接着是FSMC NOR存储控制器的信号线,CLK即HCLK,FSMC这个外设挂载在HCLK时钟线下(默认72MHz,用于内核与FSMC的时钟同步,也可用于输出时钟信号),接着是地址总线共有26个,但是这次的SRAM只用到19个,数据线和SRAM一样都是16个,NE是个很有趣的东西,它决定了FSMC可以控制多个存储器,下文再接着介绍,先继续往下看,NOE和NWE分别为读/写使能,NL和NWAIT没有用到,暂时不理;最后的NBL则是掩码信号引脚,对应的是SRAM的LB#和UB#
   
  

  
    上面讲到的FSMC引脚与SRAM的引脚相匹配总结如下,发现其高度统一:
   
  

  
    接着将上面的FSMC_NE[1:4],是用于控制SRAM芯片的控制信号线,STM32具有FSMC_NE1/2/3/4号引脚,不同的引脚对应STM32内部不同的地址区域。啥不同的地址区域呢?
  
           
  

  

  

  
    内核地址映射
           
  

  
    FSMC地址映射
    cotex-m3将4GB的地址空间中的0x60000000到0x9FFFFFFF共1GB的空间分给外部内存,这1GB的空间供用户自由使用,然后强势的FSMC就接管了这1GB的空间。FSMC把整个External RAM存储区域分成了4个Bank区域,并分配了地址范围及适用的存储器类型,如NOR及SRAM存储器只能使用Bank1的地址(0x60000000到0x6FFFFFFF)。
    Bank内部的256MB空间又被分成4个小块,每块64M,各自有相应的控制引脚用于连接片选信号,如FSMC_NE[4:1]信号线可用于选择BANK1内部的4小块地址区域,当STM32访问0x68000000-0x6BFFFFFF地址空间时,会访问到Bank1的第3小块区域,相应的FSMC_NE3信号线会输出控制信号。
   
  

  
    例如,当STM32访问0x68000000-0x6BFFFFFF地址空间时,FSMC_NE3引脚会自动设置为低电平,由于它连接到SRAM的CE#引脚,所以SRAM的片选被使能,而访问0x60000000-0x63FFFFFF地址时,FSMC_NE1会输出低电平。当使用不同的FSMC_NE引脚连接外部存储器时,STM32访问SRAM的地址不一样,从而达到控制多块SRAM芯片的目的。

  3.1 FSMC时序

    另外,FSMC外设支持输出多种不同的时序以便于控制不同的存储器,它具有ABCD四种模式,以模式A为例,当内核发出访问某个指向外部存储器地址时,FSMC外设会根据配置控制信号线产生时序(由硬件产生时序)访问存储器,下图是访问外部SRAM时FSMC外设的读写时序:
   
  

  
    以读时序为例,该图表示一个存储器操作周期由地址建立周期(ADDSET)、数据建立周期(DATAST)以及2个HCLK周期组成。在地址建立周期中,地址线发出要访问的地址,数据掩码信号线指示出要读取地址的高、低字节部分,片选信号使能存储器芯片;地址建立周期结束后读使能信号线发出读使能信号,接着存储器通过数据信号线把目标数据传输给FSMC,FSMC把它交给内核。
   
  

  
    写时序类似,区别是它的一个存储器操作周期仅由地址建立周期(ADDSET)和数据建立周期(DATAST)组成,且在数据建立周期期间写使能信号线发出写信号,接着FSMC把数据通过数据线传输到存储器中。

  
3.2 FSMC相关的配置结构体
IS62WV51216为异步类型的SRAM存储器


时序结构体: FSMC_NORSRAMTimingInitTypeDef,定义SRAM读写时序中的各项时间参数,与FSMC_BRT及FSMC_BWTR寄存器配置对应。


//HCLK的时钟频率为72MHz,即一个HCLK周期为1/72微秒
typedef struct{
        uint32_t FSMC_AddressSetupTime;/*ADDSET 地址建立时间,0-0xF个HCLK周期*/
        uint32_t FSMC_AddressHoldTime ;/*ADDHLD 地址保持时间,0-0xF个HCLK周期*/
        uint32_t FSMC_DataSetupTime;/*DATAST 地址建立时间,0-0xF 个HCLK周期*/
        uint32_t FSMC_BusTurnAroundDuration; /*BUSTURN 总线转换周期, 0-0xE个HCLK周期,在NOR FLASH */
        uint32_t FSMC_CLKDivision; /*CLKDIV 时钟分频因子, 1-0xF,若控制异步存储器,本参数无效*/
        uint32_t FSMC_DataLatency; /*数据延迟/保持时间,若控制异步存储器,本参数无效*/
        uint32_t FSMC_AccessMode;/*设置访问模式,可选FSMC_AccessMode_A/B/C/D模式。一般来说控制SRAM时使用A模式。
*/
} FSMC_NORSRAMTimingInitTypeDef;


//对于本示例中只用到以下三个参数:
//FSMC_AddressSetupTime、FSMC_DataSetupTime、FSMC_AccessMode


初始化结构体: FSMC_NORSRAMInitTypeDef,对应到FSMC_BCR中的寄存器位。


typedef struct{
        uint32_t FSMC_Bank;/*设置要控制的Bank区域(1~4)*/
        uint32_t FSMC_DataAddressMux; /*设置地址总线与数据总线是否复用,nor flash使用*/
        uint32_t FSMC_MemoryType;/*设置存储器的类型,本示例SRAM*/
        uint32_t FSMC_MemoryDatawidth;/*设置存储器的数据宽度,可选8/16*/
        uint32_t FSMC_BurstAccessMode; /*设置是否支持突发访问模式(访问地址自增加),只支持同步类型的存储器*/
        uint32_t FSMC_AsynchronousWait;/*设置是否使能在同步传输时的等待信号,*/
        uint32_t FSMC_WaitsignalPolarity; /* 设置等待信号的极性*/
        uint32_t FSMC_WrapMode;/*设置是否支持对齐的突发模式*/
        uint32_t FSMC_WaitsignalActive; /* 配置等待信号在等待前有效还是等待期间有效*/
        uint32_t FSMC_Writeoperation;/*设置是否写使能(保护存储器,禁止写使能的话FSMC只能从存储器中读取数据,不能写入)*/
        uint32_t FSMC_Waitsignal;/*设置是否使能等待状态插入*/
        uint32_t FSMC_ExtendedMode ;/*设置是否使能扩展模式*/
        uint32_t FSMC_WriteBurst;/*设置是否使能写突发操作*/
        /* 当不使用扩展模式时,本参数用于配置读写时序,否则用于配置读时序*/
        FSMC_NORSRAMTimingInitTypeDef* FSMC_ReadwriteTimingStruct;
        /*当使用扩展模式时,本参数用于配置写时序*/
        FSMC_NORSRAMTimingInitTypeDef* FSMC_WriteTimingStruct;
} FSMC_NORSRAMInitTypeDef;


/*
在非扩展模式下,对存储器读写的时序都只使用FSMC_BCR寄存器中的配置,即下面的
FSMC_ReadWriteTimingStruct结构体成员;在扩展模式下,对存储器的读写时序可以分开配置,
读时序使用FSMC_BCR寄存器,写时序使用FSMC_BWTR寄存器的配置,即下面的FSMC_WriteTimingStruct结构体。
*/


//对于SRAM,只要关注这几个:
//FSMC_Bank、FSMC_DataAddressMux、FSMC_MemoryType、FSMC_MemoryDatawidth
//FSMC_Writeoperation、FSMC_ExtendedMode
//FSMC_ReadwriteTimingStruct、FSMC_WriteTimingStruct


   4 扩展外部SRAM硬件部分

    SRAM与FSMC的硬件连接图如下,用到的引脚很多很多,,我就不一一列出来了,反正有DEFG引脚,直接在代码里看吧。
  

           
  

  
    SRAM连接的引脚
           
  

  
    芯片FSMC引脚
  
5 扩展外部SRAM代码部分
 使用 FSMC 的 BANK1 区域 3 来控制 IS62WV51216


sram.c


#include "sram.h"          
#include "usart.h"


/*
        A[0:18]接FMSC_A[0:18]
        D[0:15]接FSMC_D[0:15]
        UB接FSMC_NBL1 引脚PE1
        LB接FSMC_NBL0 引脚PE0
        OE接FSMC_NOE  引脚PD4
        WE接FSMC_NWE  引脚PD5
        CS接FSMC_NE3  引脚PG10
*/
                                       
#define Bank1_SRAM3_ADDR    ((u32)(0x68000000))       
                                                    
//初始化外部SRAM
void FSMC_SRAM_Init(void)
{       
        FSMC_NORSRAMInitTypeDef  FSMC_NORSRAMInitStructure;
        FSMC_NORSRAMTimingInitTypeDef  readWriteTiming;
        GPIO_InitTypeDef  GPIO_InitStructure;

        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOE|RCC_APB2Periph_GPIOF|RCC_APB2Periph_GPIOG,ENABLE);
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC,ENABLE);//开启外设时钟

        //所有引脚都配置成复用推挽输出、50MHz
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;                  
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;


       
//        /*写法一:将所用到的引脚的地址相或,得到最终的地址,这样做可以简化代码*/
       
//        /*---------------------------------------------------------------*/
//        GPIO_InitStructure.GPIO_Pin = 0xFF33;                                  //PORTD
//         GPIO_Init(GPIOD, &GPIO_InitStructure);
//  /*---------------------------------------------------------------*/
//        GPIO_InitStructure.GPIO_Pin = 0xFF83;                                  //PORTE
//         GPIO_Init(GPIOE, &GPIO_InitStructure);
//  /*---------------------------------------------------------------*/
//         GPIO_InitStructure.GPIO_Pin = 0xF03F;                                  //PORTF
//         GPIO_Init(GPIOF, &GPIO_InitStructure);
//  /*---------------------------------------------------------------*/
//        GPIO_InitStructure.GPIO_Pin = 0x043F;                                  //PORTG
//         GPIO_Init(GPIOG, &GPIO_InitStructure);
//  /*---------------------------------------------------------------*/
                                   
                                       
  /*写法二:常规写法*/
       
        /*---------------------------------------------------------------*/
        /*A地址信号线 针对引脚配置*/
        //A0 ~ 9 分别对应 PF0 ~ PF5 和 PF12 ~ PF15
        GPIO_InitStructure.GPIO_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;
  GPIO_Init(GPIOF, &GPIO_InitStructure);
        //A10 ~ 15 分别对应 PG0 ~ PG5
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;
        GPIO_Init(GPIOG, &GPIO_InitStructure);
        //A16 ~ 18 分别对应 PD11 ~ PD13
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13;
        GPIO_Init(GPIOD, &GPIO_InitStructure);
        /*---------------------------------------------------------------*/
        /*DQ数据信号线 针对引脚配置*/
        //D0 ~ 15对应的引脚分别为 D14 D15 D0 D1 E7 E8 E9 E10 E11 E12 E13 E14 E15 D8 D9 D10
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14|GPIO_Pin_15|GPIO_Pin_0|GPIO_Pin_1
                                                                                                                                |GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10;
        GPIO_Init(GPIOD, &GPIO_InitStructure);
        GPIO_InitStructure.GPIO_Pin = 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;
        GPIO_Init(GPIOE, &GPIO_InitStructure);
        /*---------------------------------------------------------------*/
        /*控制信号线*/
        //UB LB OE WE CS 分别对应 PE0 PE1 PD4 PD5 PG10
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;
        GPIO_Init(GPIOE, &GPIO_InitStructure);
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5;
        GPIO_Init(GPIOD, &GPIO_InitStructure);
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
        GPIO_Init(GPIOG, &GPIO_InitStructure);
       
       
        /*
                        读时序的要求
                        //1.  ADDSET+1+DATAST+1 +2 >55ns
                        //2.  DATAST+1  > 25ns
                        //3.  ADDSET+1 > 0ns
                       
                        写时序的要求
                        //1.  ADDSET+1+DATAST+1  >55ns
                        //2.  DATAST+1  > 40ns
                        //3.  ADDSET+1 > 0ns
                       
                        //理论上ADDSET=0 DATAST=1也可以了,但是实际不行,故分别设置成0,2
        */
        readWriteTiming.FSMC_AccessMode = FSMC_AccessMode_A;         //模式A
        readWriteTiming.FSMC_AddressSetupTime = 0x00;         //地址建立时间(ADDSET)为1个HCLK 1/72M=13.8 ns,理论值
        readWriteTiming.FSMC_DataSetupTime = 0x02;                 //数据保持时间(DATAST)为3个HCLK 3/72M=54.9ns(DATAST设为2,在寄存器会加1,因此为3)
        //SRAM模式A未用到以下的参数,全都设为0
  readWriteTiming.FSMC_AddressHoldTime = 0x00;         //地址保持时间(ADDHLD)       
  readWriteTiming.FSMC_BusTurnAroundDuration = 0x00;
  readWriteTiming.FSMC_CLKDivision = 0x00;
  readWriteTiming.FSMC_DataLatency = 0x00;
       
        /*初始化参数*/
  FSMC_NORSRAMInitStructure.FSMC_Bank = FSMC_Bank1_NORSRAM3;//  这里我们使用NE3 ,也就对应BTCR[4],[5]
        FSMC_NORSRAMInitStructure.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable; // 非扩展模式,读写使用相同的时序
  FSMC_NORSRAMInitStructure.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_16b;//存储器数据宽度为16bit
        FSMC_NORSRAMInitStructure.FSMC_MemoryType =FSMC_MemoryType_SRAM;// FSMC_MemoryType_SRAM;  //SRAM
        FSMC_NORSRAMInitStructure.FSMC_WriteOperation = FSMC_WriteOperation_Enable;        //存储器写使能
        //初始化结构体SRAM下用不到,配置为disable
  FSMC_NORSRAMInitStructure.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable;
  FSMC_NORSRAMInitStructure.FSMC_BurstAccessMode =FSMC_BurstAccessMode_Disable;// FSMC_BurstAccessMode_Disable;
  FSMC_NORSRAMInitStructure.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
        FSMC_NORSRAMInitStructure.FSMC_AsynchronousWait=FSMC_AsynchronousWait_Disable;
  FSMC_NORSRAMInitStructure.FSMC_WrapMode = FSMC_WrapMode_Disable;   
  FSMC_NORSRAMInitStructure.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWaitState;  
  FSMC_NORSRAMInitStructure.FSMC_WaitSignal = FSMC_WaitSignal_Disable;  
  FSMC_NORSRAMInitStructure.FSMC_WriteBurst = FSMC_WriteBurst_Disable;  
        //读写时序结构体
  FSMC_NORSRAMInitStructure.FSMC_ReadWriteTimingStruct = &readWriteTiming;
  FSMC_NORSRAMInitStructure.FSMC_WriteTimingStruct = &readWriteTiming; //读写同样时序


  FSMC_NORSRAMInit(&FSMC_NORSRAMInitStructure);  //初始化FSMC配置
  FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM3, ENABLE);  // 使能BANK3                                                                                                                                                         
}


main.c:SRAM掉电不保存数据,因为要先按key0写入数据,然后key1访问0字节地址的数据时才有意义(值应该为0)


#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "usart.h"         
#include "sram.h"


//两种方式访问SRAM
//1.使用指针对SRAM进行读写
//2.使用绝对地址的方式访问SRAM


//使用__attribute__定义变量时,需要定义为全局变量
u32 testValue __attribute__((at(SRAM_BASE_ADDR+0x40)));//该变量存的数值放在外部SRAM中


//外部内存测试(最大支持1M字节内存测试)            
void fsmc_sram_test(void)
{  
        u32 i=0;            
        u8 temp=0;          
        u8 sval=0;        //在地址0读到的数据                                            
  printf("正在测试外部SRAM内存...rn");
        //每隔4K字节,写入一个数据,总共写入256个数据,刚好是1M字节
        for(i=0;i<1024*1024;i+=4096) //1M字节 = 1024*1024字节 = 512K * 2字节 = (512*1024)字节 * 2字节 = 256 * 4 K
        {
          //第一次将数值0写入到“0”地址(1字节),第二次将1写入“4094”地址(1字节),...(总共有1024*1024个地址)
                *(uint8_t*)(SRAM_BASE_ADDR+i) = temp;//一个字节为单位进行写入
                temp++;
        }
        //依次读出之前写入的数据,进行校验                  
        for(i=0;i<1024*1024;i+=4096)
        {
                temp = *(uint8_t*)(SRAM_BASE_ADDR+i);//一个字节为单位进行读出
                if(i==0) sval=temp;
                else if(temp<=sval) break;//后面读出的数据一定要比第一次读到的数据大.
        }
  printf("经测试读出的外部SRAM内存大小为:%dKrnrn",(u16)(temp-sval+1)*4); //最终temp=255,sval=0       
}          


int main(void)
{         
        //指针方式测试
        uint8_t *p8 = (uint8_t *)SRAM_BASE_ADDR; //SRAM的"0"地址,只访问一个字节
        uint16_t *p16;  //访问第0和第1个字节
        uint8_t testTemp_1;
        uint16_t testTemp_2;
        //float *pf = (float *)(SRAM_BASE_ADDR+20);
       
        u8 key;                 
        u8 i=0;             
        delay_init();                     //延时函数初始化          
        NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
        uart_init(115200);                 //串口初始化为115200
        LED_Init();                                          //初始化与LED连接的硬件接口
        KEY_Init();                                        //初始化按键
        FSMC_SRAM_Init();                        //初始化外部SRAM  
         
        printf("key0:测试SRAM容量tkey1:指针方式访问SRAMtkey2:绝对地址方式访问SRAMrnrn");
  while(1)
        {       
                key=KEY_Scan(0);//不支持连按       
                if(key==KEY0_PRES)fsmc_sram_test();//测试SRAM容量
                else if(key==KEY1_PRES){
                        printf("(8bit时) 读取到外部SRAM 0x%p 地址的内容为: %drn",p8,*p8);
                        p16 = (uint16_t *)(SRAM_BASE_ADDR);//同时对两个字节操作
                        *p16 = 0xabcd; //将第0和第1字节的内容写为0x1234
                        testTemp_2 = *(uint16_t*)(SRAM_BASE_ADDR); //读出“0”地址和“1”地址里的数据
                        printf("(16bit时)读取到外部SRAM 0x%p 地址的内容为: 0x%xrnrn",p16,testTemp_2);
                }
                else if(key==KEY2_PRES){
                        testValue = 10;
                        printf("将 %d 的值写入到外部SRAM 0x%p 地址rn",testValue,&testValue);
                        testTemp_1 = *(uint8_t*)(SRAM_BASE_ADDR+0x40);
                        printf("从外部SRAM 0x%p 地址读取到的数值为 %drnrn",&testValue,testTemp_1);
                }
                else delay_ms(10);   
                i++;
                if(i==20)//DS0闪烁
                {
                        i=0;
                        LED0=!LED0;
                }
        }             
}




  运行结果如下:
   
  

  
   
  6 内存管理内、外部SRAM

    运用内存管理技术,实现对内存的动态管理。比如定义一个u32 testsram[250000]的数组放在外部SRAM中,U32表示一个数占4个字节(B),这个数组一共包涵250000个数,也就是说这个数组的大小为4250000=110^7=100 0000=100万B=1000KB=1MB,这个数组比我们实际用的SRAM较小,1M=1024KB,小了24KB。在实际运用中当有多个这样的数组需求,则需要用到动态内存管理。
    内存管理,是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如何高效、快速的分配,并且在适当的时候释放和回收内存资源。内存管理的实现方法有很多种,他们其实最终都是要实现 2 个函数:malloc 和 free;malloc 函数用于内存申请,free 函数用于内存释放。
    针对于单片机 RAM 如此紧缺的设备来讲,使用 C 标准库中的内存管理函数是不恰当的,存在着许多弊端,主要有以下几点:
  


  • 他们的实现可能非常大,占据了相当大的一块代码空间
  • 这两个函数会使得链接器配置得复杂
  • 如果允许堆空间的生长方向覆盖其他变量的内存,他们会成为 debug 的灾难

  这里用正点原子教程的分块式内存管理方法,在其中文参考手册的572页。
   
  

  
    从上图可以看出,分块式内存管理由内存池和内存管理表两部分组成。内存池被等分为 n块,对应的内存管理表,大小也为 n,内存管理表的每一个项对应内存池的一块内存。
    内存管理表的项值代表的意义为:当该项值为 0 的时候,代表对应的内存块未被占用,当该项值非零的时候,代表该项对应的内存块已经被占用,其数值则代表被连续占用的内存块数。比如某项值为 10,那么说明包括本项对应的内存块在内,总共分配了 10 个内存块给外部的某个指针。
  内寸分配方向如图所示,是从顶到底的分配方向。即首先从最末端开始找空内存。当内存管理刚初始化的时候,内存表全部清零,表示没有任何内存块被占用。
  6.1 分配原理

    当指针p调用malloc申请内存的时候,先判断p要分配的内存块数(m),然后从第n项开始向下查找,直到找到m块连续的空内存块(即对应内存管理表项为0),然后将这m个内存管理表项的值都设置为m(标记被占用),最后把最后的这个空内存块的地址返回指针p,完成一次分配。注意,如果当内存不够的时候(找到最后也没找到连续的m块空闲内存),则返回NULL给p,表示分配失败。(首先确定要开辟的内存大小n,从表的最后向前找n个空单元,找到后将其对应的内存地址返回,那么[p,p+n]就是空闲的n个单元。)
  6.2 释放原理

    当指针p申请的内存用完,需要释放的时候,调用free函数实现。free函数先判断p指向的内存地址所对应的内存块,然后找到对应的内存管理表项目,得到p所占用的内存块数目m(内存管理表项目的值就是所分配内存块的数目),将这m个内存管理表项目的值都清零,标记释放,完成一次内存释放。
   
  
6.3 代码部分
malloc.h


#ifndef __MALLOC_H
#define __MALLOC_H
#include "stm32f10x.h"

#ifndef NULL
#define NULL 0
#endif


//定义两个内存池
#define SRAMIN         0                //内部内存池
#define SRAMEX   1                //外部内存池
#define SRAMBANK         2        //定义支持的SRAM块数



//内存管理控制器
struct _m_mallco_dev
{
        void (*init)(u8);                                        //初始化
        u8 (*perused)(u8);                                      //内存使用率
        u8         *membase[SRAMBANK];                                //内存池 管理SRAMBANK个区域的内存
        u16 *memmap[SRAMBANK];                                 //内存管理状态表
        u8  memrdy[SRAMBANK];                                 //内存管理是否就绪
};
extern struct _m_mallco_dev mallco_dev;         //在mallco.c里面定义


void mymemset(void *s,u8 c,u32 count);        //设置内存
void mymemcpy(void *des,void *src,u32 n);//复制内存     
void my_mem_init(u8 memx);                                //内存管理初始化函数(外/内部调用)
u32 my_mem_malloc(u8 memx,u32 size);        //内存分配(内部调用)
u8 my_mem_free(u8 memx,u32 offset);                //内存释放(内部调用)
u8 my_mem_perused(u8 memx);                                //获得内存使用率(外/内部调用)


//用户调用函数
void myfree(u8 memx,void *ptr);                          //内存释放(外部调用)
void *mymalloc(u8 memx,u32 size);                        //内存分配(外部调用)
void *myrealloc(u8 memx,void *ptr,u32 size);//重新分配内存(外部调用)
#endif


malloc.c


#include "malloc.h"            


//mem1内存参数设定.mem1完全处于内部SRAM里面.
#define MEM1_BLOCK_SIZE                        32                                                            //内存块大小为32字节
#define MEM1_MAX_SIZE                        40*1024                                                  //最大管理内存 40K //STM32内部SRAM为64K
#define MEM1_ALLOC_TABLE_SIZE        MEM1_MAX_SIZE/MEM1_BLOCK_SIZE         //内存表大小


//mem2内存参数设定.mem2的内存池处于外部SRAM里面
#define MEM2_BLOCK_SIZE                        32                                                            //内存块大小为32字节
#define MEM2_MAX_SIZE                        960 *1024                                                  //最大管理内存960K
#define MEM2_ALLOC_TABLE_SIZE        MEM2_MAX_SIZE/MEM2_BLOCK_SIZE         //内存表大小


//因为内存块的大小是 32 字节,而且我们从这里也可以看到我们所定义的内存池本质
//就是一个全局变量的数组,这个数组在编译时,就被分配了一个固定大小的内存


//内存池(32字节对齐)
__align(32) u8 mem1base[MEM1_MAX_SIZE];                                                                                                        //内部SRAM内存池,数组个数40K,类型U8
__align(32) u8 mem2base[MEM2_MAX_SIZE] __attribute__((at(0X68000000)));                                        //外部SRAM内存池
//内存管理表
u16 mem1mapbase[MEM1_ALLOC_TABLE_SIZE];                                                                                                        //内部SRAM内存池MAP
u16 mem2mapbase[MEM2_ALLOC_TABLE_SIZE] __attribute__((at(0X68000000+MEM2_MAX_SIZE)));        //外部SRAM内存池MAP
//内存管理参数          
const u32 memtblsize[SRAMBANK]={MEM1_ALLOC_TABLE_SIZE,MEM2_ALLOC_TABLE_SIZE};                        //内存表大小
const u32 memblksize[SRAMBANK]={MEM1_BLOCK_SIZE,MEM2_BLOCK_SIZE};                                                //内存分块大小
const u32 memsize[SRAMBANK]={MEM1_MAX_SIZE,MEM2_MAX_SIZE};                                                                //内存总大小




//内存管理控制器
struct _m_mallco_dev mallco_dev=
{
        my_mem_init,                                //内存初始化
        my_mem_perused,                                //内存使用率
        mem1base,mem2base,                        //内存池
        mem1mapbase,mem2mapbase,        //内存管理状态表
        0,0,                                                   //内存管理未就绪
};


//复制内存
//*des:目的地址
//*src:源地址
//n:需要复制的内存长度(字节为单位)
void mymemcpy(void *des,void *src,u32 n)  
{  
    u8 *xdes=des;
          u8 *xsrc=src;
    while(n--)*xdes++=*xsrc++;  
}  
//设置内存
//*s:内存首地址
//c :要设置的值
//count:需要设置的内存大小(字节为单位)
void mymemset(void *s,u8 c,u32 count)  
{  
    u8 *xs = s;  
    while(count--)*xs++=c;  
}          
//内存管理初始化  
//memx:所属内存块
void my_mem_init(u8 memx)  
{  
    mymemset(mallco_dev.memmap[memx], 0,memtblsize[memx]*2);//内存状态表数据清零  
          mymemset(mallco_dev.membase[memx], 0,memsize[memx]);        //内存池所有数据清零  
          mallco_dev.memrdy[memx]=1;                                                                //内存管理初始化OK  
}  
//获取内存使用率
//memx:所属内存块
//返回值:使用率(0~100)
u8 my_mem_perused(u8 memx)  
{  
    u32 used=0;  
    u32 i;  
    for(i=0;i     {  
        if(mallco_dev.memmap[memx])used++;
    }
    return (used*100)/(memtblsize[memx]);  
}  
//内存分配(内部调用)
//memx:所属内存块
//size:要分配的内存大小(字节)
//返回值:0XFFFFFFFF,代表错误;其他,内存偏移地址
u32 my_mem_malloc(u8 memx,u32 size)  
{  
    signed long offset=0;  
    u32 nmemb;        //需要的内存块数  
          u32 cmemb=0;//连续空内存块数
    u32 i;  
    if(!mallco_dev.memrdy[memx])mallco_dev.init(memx);//未初始化,先执行初始化
    if(size==0)return 0XFFFFFFFF;//不需要分配
    nmemb=size/memblksize[memx];          //获取需要分配的连续内存块数
    if(size%memblksize[memx])nmemb++;  
    for(offset=memtblsize[memx]-1;offset>=0;offset--)//搜索整个内存控制区  
    {     
                if(!mallco_dev.memmap[memx][offset])cmemb++;//连续空内存块数增加
                else cmemb=0;                                                                //连续内存块清零
                if(cmemb==nmemb)                                                        //找到了连续nmemb个空内存块
                {
            for(i=0;i             {  
                mallco_dev.memmap[memx][offset+i]=nmemb;  
            }  
            return (offset*memblksize[memx]);//返回偏移地址  
                }
    }  
    return 0XFFFFFFFF;//未找到符合分配条件的内存块  
}  
//释放内存(内部调用)
//memx:所属内存块
//offset:内存地址偏移
//返回值:0,释放成功;1,释放失败;  
u8 my_mem_free(u8 memx,u32 offset)  
{  
    int i;  
    if(!mallco_dev.memrdy[memx])//未初始化,先执行初始化
        {
                mallco_dev.init(memx);   
        return 1;//未初始化  
    }  
    if(offset     {  
        int index=offset/memblksize[memx];                        //偏移所在内存块号码  
        int nmemb=mallco_dev.memmap[memx][index];        //内存块数量
        for(i=0;i         {  
            mallco_dev.memmap[memx][index+i]=0;  
        }  
        return 0;  
    }else return 2;//偏移超区了.  
}  
//释放内存(外部调用)
//memx:所属内存块
//ptr:内存首地址
void myfree(u8 memx,void *ptr)  
{  
        u32 offset;   
        if(ptr==NULL)return;//地址为0.  
        offset=(u32)ptr-(u32)mallco_dev.membase[memx];     
    my_mem_free(memx,offset);        //释放内存      
}  
//分配内存(外部调用)
//memx:所属内存块
//size:内存大小(字节)
//返回值:分配到的内存首地址.
void *mymalloc(u8 memx,u32 size)  
{  
    u32 offset;   
        offset=my_mem_malloc(memx,size);                                
    if(offset==0XFFFFFFFF)return NULL;  
    else return (void*)((u32)mallco_dev.membase[memx]+offset);  
}  
//重新分配内存(外部调用)
//memx:所属内存块
//*ptr:旧内存首地址
//size:要分配的内存大小(字节)
//返回值:新分配到的内存首地址.
void *myrealloc(u8 memx,void *ptr,u32 size)  
{  
    u32 offset;   
    offset=my_mem_malloc(memx,size);          
    if(offset==0XFFFFFFFF)return NULL;     
    else  
    {                                                                            
            mymemcpy((void*)((u32)mallco_dev.membase[memx]+offset),ptr,size);        //拷贝旧内存内容到新内存   
        myfree(memx,ptr);                                                                                                            //释放旧内存
        return (void*)((u32)mallco_dev.membase[memx]+offset);                                  //返回新内存首地址
    }  
}


main.c


#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "usart.h"          
#include "sram.h"
#include "malloc.h"
#include "string.h"

int main(void)
{         
        u8 key;                 
        u8 i=0;            
        u8 *p[10]={0};
        u8 sramx=0;                                //默认为内部sram
        char* nowSram[2] = {"内部SRAM","外部SRAM"};
        int num=0;
  
        delay_init();                     //延时函数初始化          
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
        uart_init(115200);                 //串口初始化为115200
        LED_Init();                                          //初始化与LED连接的硬件接口
        KEY_Init();                                        //初始化按键
        FSMC_SRAM_Init();                        //初始化外部SRAM  
        my_mem_init(SRAMIN);                //初始化内部内存池
        my_mem_init(SRAMEX);                //初始化外部内存池
       
        printf("》》 %s 使用率:%d%%rn",nowSram[sramx],my_mem_perused(sramx));
        printf("key0:动态申请内存tkey1:写入新数据tkey2:释放内存tkey_up:切换内/外部SRAMrnrn");


  while(1)
        {       
                key=KEY_Scan(0);                //不支持连按       
                switch(key)
                {
                        case 0:                                //没有按键按下       
                                break;
                        case KEY0_PRES:                //KEY0按下
                                if(p[num]) num++;
                          if(num>10)
                                {
                                          num = 10;
                                                printf("%s: 申请失败!已满!rnrn",nowSram[sramx]);
                                          break;
                                }
                                p[num] = mymalloc(sramx,2048);//申请2K字节
                          // int sprintf(char *string, char *format [,argument,...]);
                          //把格式化的数据写入某个字符串中,即发送格式化输出到 string 所指向的字符串
                                if(p[num]!=NULL)
                                {
                                        sprintf((char*)p[num],"Memory Malloc Test: %s",nowSram[sramx]);//向p写入一些内容
                                        printf("%s: 申请内存成功!rn内容:%srn地址:0x%prn",nowSram[sramx],(char*)p[num],p[num]);
                                        printf("》》 %s 使用率:%d%%rnrn",nowSram[sramx],my_mem_perused(sramx));
                                }
                                else printf("%s: 申请失败!地址错误!rnrn",nowSram[sramx]);
                                break;
                        case KEY1_PRES:                //KEY1按下          
                                if(p[num]!=NULL)
                                {
                                        sprintf((char*)p[num],"Write Test New: Memory Malloc Test: %s",nowSram[sramx]);//更新显示内容          
                                        printf("%s: 已更新数据!rn内容:%srn地址:0x%prnrn",nowSram[sramx],(char*)p[num],p[num]);
                                        printf("》》 %s 使用率:%d%%rnrn",nowSram[sramx],my_mem_perused(sramx));
                                }
                                break;
                        case KEY2_PRES:                //KEY2按下
        printf("%s: 释放内存成功!rn地址:0x%prnrn",nowSram[sramx],p[num]);                               
                                myfree(sramx,p[num]);//释放内存
                                p[num]=0;                        //指向空地址
                          printf("》》 %s 使用率:%d%%rnrn",nowSram[sramx],my_mem_perused(sramx));
                          num--;
                          if(num<0) num=0;
                                break;
                        case WKUP_PRES:                //KEY UP按下
                                if(p[num])
                                {
                                                printf("切换失败!请先释放 %s 的内存!rnrn",nowSram[sramx]);
                                                break;
                                }
                                sramx =! sramx;        //切换当前malloc/free操作对象
                                printf("切换成功!现在为:%srnrn",nowSram[sramx]);
                                printf("》》 %s 使用率:%d%%rnrn",nowSram[sramx],my_mem_perused(sramx));
                                break;
                }
                delay_ms(10);   
                i++;
                if((i%20)==0)//DS0闪烁.
                {
                        LED0=!LED0;
                }
        }          
}


运行结果为:
   
  

  
   

  这里提醒大家,如果对一个指针进行多次内存申请,而之前的申请又没释放,那么将造成“内存泄露”,这是内存管理所不希望发生的,久而久之,可能导致无内存可用的情况!所以,在使用的时候,请大家一定记得,申请的内存在用完以后,一定要释放。
举报

更多回帖

×
20
完善资料,
赚取积分