STM32CubeMX实战教程(九)——外部SRAM+内存管理

外部SRAM+内存管理

  • 前言
  • 材料
  • 运行内存RAM
    • SRAM
    • 内部CCRAM
  • IS62WV51216
  • 内存管理
  • 工程配置
    • 基础配置
    • FSMC
  • 进入代码
    • __attribute__((at()))
    • 动态内存分配
      • malloc.h
      • malloc.c
    • 实验代码
      • main
      • gpio
  • 下载验证
  • 结语

前言

内存管理作为STM32及其他单片机非常重要的知识,可以说是单片机学习中必须要学到的,它不是像其他知识一样基于外设展开,而是基于自身内部的内存或是外部内存出发的;是学习较高级复杂的外设或功能如:USB,emWin,以及操作系统的基础,因为这是当单片机功能越来越复杂时绕不开的问题
简单举个栗子:有时候需要申请一个很大的内存时,虽然现在STM32具备的内存已经非常大了,但是还是会出现不够用的情况,但是通过内存管理,可以根据所需内存是否长期需要,以及其他条件去判断是请外部内存,还是释放部分当前不用的内存来进行分配。

材料

  • STM32F4正点原子探索者
  • 开发板原理图
  • HAL库函数手册

运行内存RAM

STM32F4的RAM主要由内部SRAM,内部CCRAM构成,如果有增加外部SRAM,那就由这三部分构成,下面简单说明一下三者区别

SRAM

静态随机存取存储器(Static Random-Access Memory,SRAM)是随机存取存储器的一种。所谓的“静态”,是指这种存储器只要保持通电,里面储存的数据就可以恒常保持。相对之下,动态随机存取存储器(DRAM)里面所储存的数据就需要周期性地更新。然而,当电力供应停止时,SRAM储存的数据还是会消失(被称为volatile memory),这与在断电后还能储存资料的ROM或闪存是不同的。
内部SRAM则是单片机本身自带的SRAM,其大小都在STM32CubeMX选型中就可以看到,STM32F407ZG是自带192K的RAM,其中包括了SRAM和CCRAM。
在这里插入图片描述
外部SRAM则是有外部RAM芯片提供,内部空间不够时使用。

内部CCRAM

相比内部SRAM,内部CCRAM的读写速度会更加快,但它只受CPU直接控制,而不受外设读写控制。其他与内部SRAM无异,地址也在RAM中。

IS62WV51216

外部SRAM芯片,512K,顾名思义作为内部SRAM的补充和扩展,我们可以直接把变量定义在外部SRAM中,具体性能这里不详细介绍,感兴趣的话百度都能查,这里主要详细看看它的时序及工作方式。数据手册里面也是写得非常清楚了。
STM32CubeMX实战教程(九)——外部SRAM+内存管理_第1张图片

  • 图中A0~18为地址线,总共19根地址线(即2^19=512K,1K=1024);
  • I/O0~15为数据线,总共16根数据线
  • CS2和CS1都是片选信号,不过CS2是高电平有效CS1是低电平有效;OE是输出使能信号(读信号);WE为写使能信号;UB和LB分别是高字节控制和低字节控制信号,即允许单独进行高8位或低8位的数据写入操作;

STM32CubeMX实战教程(九)——外部SRAM+内存管理_第2张图片
STM32CubeMX实战教程(九)——外部SRAM+内存管理_第3张图片
然后再根据各个时序图和时序表,一顿操作后得到几组读写时序数据,懒得计算的小伙伴也可以直接上正点原子的例程抄结果,结果和抄的姿势后面将列出。

内存管理

在面对有限的内存时,合理运用好内存管理可以使得MCU的资源分配更高效且快速,正如C语言中,当我们面对长度未知的变量时,往往采用动态内存分配的方法,利用malloc函数和指针,进行内存的动态申请和释放,STM32也可以用同样的原理进行内存的分配和调用,具体原理本文暂不深究,重在用法的掌握。

工程配置

基础配置

基础配置与前面其他文章完全一致,时钟主频为168M,使能一个LED灯,以及三个按键,便于现象观察
晶振

时钟树
LED
在这里插入图片描述

另外把LCD液晶使能一下,方便观察现象,具体配置如下,不懂的参考《STM32CubeMX实战教程(七)——TFT_LCD液晶显示》,当然也可以用串口输出。

STM32CubeMX实战教程(九)——外部SRAM+内存管理_第4张图片
STM32CubeMX实战教程(九)——外部SRAM+内存管理_第5张图片

FSMC

对于FSMC外设的作用和原理,已经在《STM32CubeMX实战教程(七)——TFT_LCD液晶显示》介绍过了,这里也不再重复,这里直接开始配置。
STM32CubeMX实战教程(九)——外部SRAM+内存管理_第6张图片

由于NE4已经使能给LCD显示了,这里就使用NE3作为SRAM,其对应的地址范围为0x68000000~6BFFFFFF当然也可以用NE1或者NE2,但是在代码中的基地址则需要根据下表相对应修改,具体在生成代码后将进行解释。
STM32CubeMX实战教程(九)——外部SRAM+内存管理_第7张图片

根据IS62WV51216芯片介绍,需要19位地址信号及6位数据信号,于是对应了这里的Address和Data,同时由于该芯片支持字节控制,我们需要另外提供高低字节信号,即使能Byte enable。不使能等待信号。
以下是参数配置,
STM32CubeMX实战教程(九)——外部SRAM+内存管理_第8张图片
Wirte Operation

  • Enable 允许向存储器写数据
  • Disabled 只允许从存储器中读数据

Extended

  • Disable 读写时序相同
  • Enable 读写时序不同(分别配置)

然后是信号时序,直接对着抄就完事。

进入代码

生成代码后,先在Options for target中添加外部SRAM的地址和大小,此时编译器在变量的地址自动分配时也会把外部SRAM考虑在内了,如果想修改内存自动分配的优先级的话可以编写sct文件的方法实现,这不是本文重点,暂不深究。
STM32CubeMX实战教程(九)——外部SRAM+内存管理_第9张图片
default上的选项框不建议勾上,因为勾上即代表所选内存由编译器管理分配,即这时候不允许用户在此内存定义变量,所以也就不能用__attribute__关键字把变量定义在这个地方,否则出现类似以下的报错
在这里插入图片描述

attribute((at()))

uint32_t test[250000] __attribute__((at(0X68000000)));

在MDK中__attribute__是一个可以指定所定义变量地址的关键字,例如上述代码,可以利用这个关键字将数组test直接定义到指定地址0X68000000,非常灵活。但是需要注意的是,用这种方法定义变量时必须将变量定义成全局变量,即函数外。

动态内存分配

要实现内存的动态分配,就要有动态分配函数,对应C语言中的malloc函数,但是STM32本身并不自带这个功能,需要用户自己编写对应接口函数,这里正点原子也已经有成熟的malloc文件了,我对其稍作修改以适配HAL库版本开发,大家可以自行下载,下载链接,提取码为jmnj,下面我将为大家讲解基本函数及其用法。

malloc.h


#define MEM1_BLOCK_SIZE			32  	  						
#define MEM1_MAX_SIZE			110*1024  						
#define MEM1_ALLOC_TABLE_SIZE	MEM1_MAX_SIZE/MEM1_BLOCK_SIZE 	

#define MEM2_BLOCK_SIZE			32  	  						
#define MEM2_MAX_SIZE			800 *1024  						
#define MEM2_ALLOC_TABLE_SIZE	MEM2_MAX_SIZE/MEM2_BLOCK_SIZE 	
		 

#define MEM3_BLOCK_SIZE			32  	  						
#define MEM3_MAX_SIZE			60 *1024  						
#define MEM3_ALLOC_TABLE_SIZE	MEM3_MAX_SIZE/MEM3_BLOCK_SIZE 	

首先进入malloc.h代码,在第21行到第34行定义了各部分内存的内存大小,如上的参数MEM~_MAX_SIZE是可以根据实际灵活调整的,这里是指需要管理的内存大小,并不一定要等于实际,可以偏小,但是不能偏大,例如根据实际STM32型号不同或是外部SRAM芯片不同可以将如上的110,800,60几个参数对应修改。

void my_mem_init(uint8_t memx);		
uint8_t my_mem_perused(uint8_t memx);		

void myfree(uint8_t memx,void *ptr);  		
void *mymalloc(uint8_t memx,uint32_t size);			
void *myrealloc(uint8_t memx,void *ptr,uint32_t size);

往下可以看到有几个函数,这里只需要关心以上几个函数就可以了,其他都是供内部调用使用的

  • my_mem_init,这个函数是负责初始化内存块的,只有一个参数,填SRAMIN表示内部内存池;SRAMEX表示外部内存池;SRAMCCM表示CCM内存池
  • my_mem_perused,这个函数可以用来获得内存的使用率,同样只有一个参数,填入数据同上,返回参数为内存使用率,即0~100
  • myfree,内存释放函数,同C语言的free函数功能,输入参数为内存种类和首地址,那么所指定的内存块中指定地址之后的内存都会被释放
  • mymalloc,内存申请函数,参数为内存块种类即需要申请的大小,单位为字节,返回申请到的内存首地址
  • myrealloc,重新分配内存函数,输入参数为内存块种类,旧首地址和需要分配的内存大小,作用是将指定内存里的内容拷贝至新内存中,并返回新内存的首地址

malloc.c

在这个文件中我们需要关注的只有一处,也就是SRAM内存地址,在这个文件中是0X68000000,也就是对应FSMC配置时所选的NE3,如果选的是NE2,那么这里则改成0x64000000,按照FSMC一节表中列出的参数,以此类推,当然CCM内存的地址也可以对应实际情况进行修改。
这里再次强调,由于这里用到了关键字__attribute__,在Options for target中的target栏,如果填了对应的内存地址,那么在default选项框内千万不能打勾,否则编译时报错!!!

实验代码

由于内存管理和外部SRAM的实验本身是没有实验现象的,为了能够看到初始化成功的现象,这里参考正点原子的代码另外加入了实验现象的代码以便观察。具体原理不多解释了,直接抄代码就行。如果是用串口输出现象的话就把LCD相关输出函数改成串口打印。

main

首先,在main.c文件中引用

#include "ILI93xx.h"
#include "malloc.h" 

不用液晶的话只包含malloc.h就行
其次,在main函数中定义新变量

	uint8_t key;
	uint8_t sramx=0;	
	uint8_t i=0;	    
	uint8_t *p=0;
	uint8_t *tp=0;
	uint8_t paddr[18];

在while循环前添加

	TFTLCD_Init();
	LCD_Clear(WHITE);
	my_mem_init(SRAMIN);			//³õʼ»¯ÄÚ²¿ÄÚ´æ³Ø
	my_mem_init(SRAMEX);			//³õʼ»¯ÍⲿÄÚ´æ³Ø
	my_mem_init(SRAMCCM);			//³õʼ»¯CCMÄÚ´æ³Ø
		POINT_COLOR=RED;//ÉèÖÃ×ÖÌåΪºìÉ« 
	LCD_ShowString(30,50,200,16,16,"Explorer STM32F4");	
	LCD_ShowString(30,70,200,16,16,"MALLOC TEST");	
	LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
	LCD_ShowString(30,110,200,16,16,"2014/5/15");   
	LCD_ShowString(30,130,200,16,16,"KEY0:Malloc  KEY2:Free");
	LCD_ShowString(30,150,200,16,16,"KEY_UP:SRAMx KEY1:Read"); 
 	POINT_COLOR=BLUE;//ÉèÖÃ×ÖÌåΪÀ¶É« 
	LCD_ShowString(30,170,200,16,16,"SRAMIN");
	LCD_ShowString(30,190,200,16,16,"SRAMIN  USED:   %");
	LCD_ShowString(30,210,200,16,16,"SRAMEX  USED:   %");
	LCD_ShowString(30,230,200,16,16,"SRAMCCM USED:   %");

在while循环中添加

				key=KEY_Scan(0);
				switch(key)
		{
			case 0:	
				break;
			case KEY0_PRES:
				p=mymalloc(sramx,2048);
				if(p!=NULL)sprintf((char*)p,"Memory Malloc Test%03d",i);
				break;
			case KEY1_PRES:	   
				if(p!=NULL)
				{
					sprintf((char*)p,"Memory Malloc Test%03d",i);
					LCD_ShowString(30,270,200,16,16,p);			
				}
				break;
			case KEY2_PRES:	  
				myfree(sramx,p);
				p=0;			
				break;
			case WKUP_PRES:
				sramx++; 
				if(sramx>2)sramx=0;
				if(sramx==0)LCD_ShowString(30,170,200,16,16,"SRAMIN ");
				else if(sramx==1)LCD_ShowString(30,170,200,16,16,"SRAMEX ");
				else LCD_ShowString(30,170,200,16,16,"SRAMCCM");
				break;
		}
				if(tp!=p)
		{
			tp=p;
			sprintf((char*)paddr,"P Addr:0X%08X",(uint32_t)tp);
			LCD_ShowString(30,250,200,16,16,paddr);	
			if(p)LCD_ShowString(30,270,200,16,16,p);
		    else LCD_Fill(30,270,239,266,WHITE);	
		}
		HAL_Delay(10);   
		i++;
		if((i%20)==0)//DS0ÉÁ˸.
		{
			LCD_ShowNum(30+104,190,my_mem_perused(SRAMIN),3,16);
			LCD_ShowNum(30+104,210,my_mem_perused(SRAMEX),3,16);
			LCD_ShowNum(30+104,230,my_mem_perused(SRAMCCM),3,16);
			HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);
		}

在main.h添加下面代码用于位带操作

#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum)) 

#define GPIOA_ODR_Addr    (GPIOA_BASE+20) 
#define GPIOB_ODR_Addr    (GPIOB_BASE+20)
#define GPIOC_ODR_Addr    (GPIOC_BASE+20) 
#define GPIOD_ODR_Addr    (GPIOD_BASE+20) 
#define GPIOE_ODR_Addr    (GPIOE_BASE+20)
#define GPIOF_ODR_Addr    (GPIOF_BASE+20)
#define GPIOG_ODR_Addr    (GPIOG_BASE+20) 
#define GPIOH_ODR_Addr    (GPIOH_BASE+20) 
#define GPIOI_ODR_Addr    (GPIOI_BASE+20)
#define GPIOJ_ODR_ADDr    (GPIOJ_BASE+20)
#define GPIOK_ODR_ADDr    (GPIOK_BASE+20) 

#define GPIOA_IDR_Addr    (GPIOA_BASE+16)
#define GPIOB_IDR_Addr    (GPIOB_BASE+16)
#define GPIOC_IDR_Addr    (GPIOC_BASE+16) 
#define GPIOD_IDR_Addr    (GPIOD_BASE+16) 
#define GPIOE_IDR_Addr    (GPIOE_BASE+16) 
#define GPIOF_IDR_Addr    (GPIOF_BASE+16) 
#define GPIOG_IDR_Addr    (GPIOG_BASE+16) 
#define GPIOH_IDR_Addr    (GPIOH_BASE+16) 
#define GPIOI_IDR_Addr    (GPIOI_BASE+16) 
#define GPIOJ_IDR_Addr    (GPIOJ_BASE+16) 
#define GPIOK_IDR_Addr    (GPIOK_BASE+16) 

#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n) 
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  

#define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n) 
#define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n) 

#define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n) 
#define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)

#define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n) 
#define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n) 

#define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)
#define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n)  

#define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n)  
#define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n)  

#define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n) 
#define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)  

#define PHout(n)   BIT_ADDR(GPIOH_ODR_Addr,n)
#define PHin(n)    BIT_ADDR(GPIOH_IDR_Addr,n)

#define PIout(n)   BIT_ADDR(GPIOI_ODR_Addr,n)
#define PIin(n)    BIT_ADDR(GPIOI_IDR_Addr,n) 

#define PJout(n)   BIT_ADDR(GPIOJ_ODR_Addr,n)  
#define PJin(n)    BIT_ADDR(GPIOJ_IDR_Addr,n)  

#define PKout(n)   BIT_ADDR(GPIOK_ODR_Addr,n)  
#define PKin(n)    BIT_ADDR(GPIOK_IDR_Addr,n)  

gpio

gpio.c定义函数

uint8_t KEY_Scan(uint8_t mode)
{
    static uint8_t key_up=1;     //按键松开标志
    if(mode==1)key_up=1;  //支持连按
    if(key_up&&(KEY0==0||KEY1==0||KEY2==0||WK_UP==1))
    {
        HAL_Delay(10);
        key_up=0;
        if(KEY0==0)       return KEY0_PRES;
        else if(KEY1==0)  return KEY1_PRES;
        else if(KEY2==0)  return KEY2_PRES;
        else if(WK_UP==1) return WKUP_PRES;          
    }else if(KEY0==1&&KEY1==1&&KEY2==1&&WK_UP==0)key_up=1;
    return 0; //无按键按下
}

这是正点原子的按键算法,比普通延时消抖可靠性更高,且支持连按,很好看懂,这里不多解释。在gpio.h中添加

#define KEY0        HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_4)  //KEY0按键PE4
#define KEY1        HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_3)  //KEY1按键PE3
#define KEY2        HAL_GPIO_ReadPin(GPIOE,GPIO_PIN_2) 	//KEY2按键PE2
#define WK_UP       HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)  //WKUP按键PA0

#define KEY0_PRES 	1
#define KEY1_PRES	2
#define KEY2_PRES	3
#define WKUP_PRES   4
uint8_t KEY_Scan(uint8_t mode);

以上就是所有代码,接下来就可以下载至板子中看现象了。

下载验证

下载进开发板后可以根据屏幕上的提示进行操作了,KEY0为申请内存,KEY2为释放内存,KEY1为读内存,KEY_UP为切换内存块
STM32CubeMX实战教程(九)——外部SRAM+内存管理_第10张图片
具体下载方法这里不再重复,可查看《STM32CubeMX实战教程(一)——软件入门》,工程源文件我已经上传,

结语

本章就是STM32CubeMX的最后一篇实战教程了,考虑到其他外设的配置其实也可以依葫芦画瓢,以及更高阶的外设和STM32功能其实并不常用,而且也更加复杂,没办法一篇文章就能够讲清楚,所以教程数量并不多,但是我会把我做的例程都放在资源处,有需要的自行下载,没积分的私我邮箱,感谢大家支持,以后如果有其他教程或者学习笔记可能也会另外记录讲解~
祝大家事业蒸蒸日上!
奥里给~

你可能感兴趣的:(stm32,单片机,arm)