内存管理作为STM32及其他单片机非常重要的知识,可以说是单片机学习中必须要学到的,它不是像其他知识一样基于外设展开,而是基于自身内部的内存或是外部内存出发的;是学习较高级复杂的外设或功能如:USB,emWin,以及操作系统的基础,因为这是当单片机功能越来越复杂时绕不开的问题。
简单举个栗子:有时候需要申请一个很大的内存时,虽然现在STM32具备的内存已经非常大了,但是还是会出现不够用的情况,但是通过内存管理,可以根据所需内存是否长期需要,以及其他条件去判断是请外部内存,还是释放部分当前不用的内存来进行分配。
STM32F4的RAM主要由内部SRAM,内部CCRAM构成,如果有增加外部SRAM,那就由这三部分构成,下面简单说明一下三者区别
静态随机存取存储器(Static Random-Access Memory,SRAM)是随机存取存储器的一种。所谓的“静态”,是指这种存储器只要保持通电,里面储存的数据就可以恒常保持。相对之下,动态随机存取存储器(DRAM)里面所储存的数据就需要周期性地更新。然而,当电力供应停止时,SRAM储存的数据还是会消失(被称为volatile memory),这与在断电后还能储存资料的ROM或闪存是不同的。
内部SRAM则是单片机本身自带的SRAM,其大小都在STM32CubeMX选型中就可以看到,STM32F407ZG是自带192K的RAM,其中包括了SRAM和CCRAM。
外部SRAM则是有外部RAM芯片提供,内部空间不够时使用。
相比内部SRAM,内部CCRAM的读写速度会更加快,但它只受CPU直接控制,而不受外设读写控制。其他与内部SRAM无异,地址也在RAM中。
外部SRAM芯片,512K,顾名思义作为内部SRAM的补充和扩展,我们可以直接把变量定义在外部SRAM中,具体性能这里不详细介绍,感兴趣的话百度都能查,这里主要详细看看它的时序及工作方式。数据手册里面也是写得非常清楚了。
然后再根据各个时序图和时序表,一顿操作后得到几组读写时序数据,懒得计算的小伙伴也可以直接上正点原子的例程抄结果,结果和抄的姿势后面将列出。
在面对有限的内存时,合理运用好内存管理可以使得MCU的资源分配更高效且快速,正如C语言中,当我们面对长度未知的变量时,往往采用动态内存分配的方法,利用malloc函数和指针,进行内存的动态申请和释放,STM32也可以用同样的原理进行内存的分配和调用,具体原理本文暂不深究,重在用法的掌握。
基础配置与前面其他文章完全一致,时钟主频为168M,使能一个LED灯,以及三个按键,便于现象观察
另外把LCD液晶使能一下,方便观察现象,具体配置如下,不懂的参考《STM32CubeMX实战教程(七)——TFT_LCD液晶显示》,当然也可以用串口输出。
对于FSMC外设的作用和原理,已经在《STM32CubeMX实战教程(七)——TFT_LCD液晶显示》介绍过了,这里也不再重复,这里直接开始配置。
由于NE4已经使能给LCD显示了,这里就使用NE3作为SRAM,其对应的地址范围为0x68000000~6BFFFFFF,当然也可以用NE1或者NE2,但是在代码中的基地址则需要根据下表相对应修改,具体在生成代码后将进行解释。
根据IS62WV51216芯片介绍,需要19位地址信号及6位数据信号,于是对应了这里的Address和Data,同时由于该芯片支持字节控制,我们需要另外提供高低字节信号,即使能Byte enable。不使能等待信号。
以下是参数配置,
Wirte Operation
Extended
然后是信号时序,直接对着抄就完事。
生成代码后,先在Options for target中添加外部SRAM的地址和大小,此时编译器在变量的地址自动分配时也会把外部SRAM考虑在内了,如果想修改内存自动分配的优先级的话可以编写sct文件的方法实现,这不是本文重点,暂不深究。
default上的选项框不建议勾上,因为勾上即代表所选内存由编译器管理分配,即这时候不允许用户在此内存定义变量,所以也就不能用__attribute__关键字把变量定义在这个地方,否则出现类似以下的报错
uint32_t test[250000] __attribute__((at(0X68000000)));
在MDK中__attribute__是一个可以指定所定义变量地址的关键字,例如上述代码,可以利用这个关键字将数组test直接定义到指定地址0X68000000,非常灵活。但是需要注意的是,用这种方法定义变量时必须将变量定义成全局变量,即函数外。
要实现内存的动态分配,就要有动态分配函数,对应C语言中的malloc函数,但是STM32本身并不自带这个功能,需要用户自己编写对应接口函数,这里正点原子也已经有成熟的malloc文件了,我对其稍作修改以适配HAL库版本开发,大家可以自行下载,下载链接,提取码为jmnj,下面我将为大家讲解基本函数及其用法。
#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);
往下可以看到有几个函数,这里只需要关心以上几个函数就可以了,其他都是供内部调用使用的
在这个文件中我们需要关注的只有一处,也就是SRAM内存地址,在这个文件中是0X68000000,也就是对应FSMC配置时所选的NE3,如果选的是NE2,那么这里则改成0x64000000,按照FSMC一节表中列出的参数,以此类推,当然CCM内存的地址也可以对应实际情况进行修改。
这里再次强调,由于这里用到了关键字__attribute__,在Options for target中的target栏,如果填了对应的内存地址,那么在default选项框内千万不能打勾,否则编译时报错!!!
由于内存管理和外部SRAM的实验本身是没有实验现象的,为了能够看到初始化成功的现象,这里参考正点原子的代码另外加入了实验现象的代码以便观察。具体原理不多解释了,直接抄代码就行。如果是用串口输出现象的话就把LCD相关输出函数改成串口打印。
首先,在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.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实战教程(一)——软件入门》,工程源文件我已经上传,
本章就是STM32CubeMX的最后一篇实战教程了,考虑到其他外设的配置其实也可以依葫芦画瓢,以及更高阶的外设和STM32功能其实并不常用,而且也更加复杂,没办法一篇文章就能够讲清楚,所以教程数量并不多,但是我会把我做的例程都放在资源处,有需要的自行下载,没积分的私我邮箱,感谢大家支持,以后如果有其他教程或者学习笔记可能也会另外记录讲解~
祝大家事业蒸蒸日上!
奥里给~