内存管理
1.内存管理简介
STM32F103ZET6内部有64KB的SRAM内存,加上外扩的128KB容量的SRAM后,可使用的内存还是比较多的。本例程将介绍一种简单的内存管理方式(即分块内存管理)来有效管理这些内存,类似于C语言中通过malloc函数和free函数来申请和释放内存
内存管理是指软件运行时对计算机内存资源的分配和使用的技术,其最主要的目的是如何高效、快速的分配,并且在适当的时候释放和回收内存资源。这里介绍一种比较简单的内存管理方法:分块式内存管理,其实现原理如下图示:
从上图可以看出,分块式内存管理由内存池和内存管理表两部分组成。内存池被等分为n块,对应的内存管理表,大小也为n,内存管理表的每一个项对应内存池的一块内存。
内存管理表的项值代表的意义为:当该项值为0的时候,代表对应的内存块未被占用,当该项值非零的时候,代表该项对应的内存块已经被占用,其数值则代表被连续占用的内存块数。比如某项值为10,那么说明包括本项对应的内存块在内,总共分配了 10 个内存块给外部的某个指针。
内寸分配方向如图所示,是从顶→底的分配方向。即首先从最末端开始找空内存。当内存管理刚初始化的时候,内存表全部清零,表示没有任何内存块被占用。
- 分配原理:当指针p调用malloc申请内存的时候,先判断p要分配的内存块数(m),然后从第n项开始,向下查找,直到找到m块连续的空内存块(即对应内存管理表项为0),然后将这m个内存管理表项的值都设置为m(标记被占用),最后,把最后的这个空内存块的地址返回指针p,完成一次分配。注意,如果当内存不够的时候(找到最后也没找到连续的m块空闲内存),则返回NULL给 p,表示分配失败。
- 释放原理:当p申请的内存用完,需要释放的时候,调用free函数实现。free函数先判断p指向的内存地址所对应的内存块,然后找到对应的内存管理表项目,得到p所占用的内存块数目m(内存管理表项目的值就是所分配内存块的数目),将这m个内存管理表项目的值都清零,标记释放,完成一次内存释放。
2. 硬件设计
D1指示灯用来提示系统运行状态,K_UP用来申请内存,K_DOWN用来释放内存,K_RIGHT用来选择所操作的内存,TFTLCD和串口1用来显示内存状态及内存使用率
- D1指示灯
- K_UP/K_DOWN/K_RIGHT
- USART1
- TFTLCD模块
- IS62WV12816
以上硬件的电路图,在之前的例程中都有介绍过,请参考相应的例程
3. 软件设计
3.1 STM32CubeMX设置
- RCC设置外接HSE,时钟设置为72M
- PC0设置为GPIO推挽输出模式、上拉、高速、默认输出电平为高电平
- PA0设置为GPIO输入模式、下拉模式;PE3/PE4设置为GPIO输入模式、上拉模式
- USART1选择为异步通讯方式,波特率设置为115200Bits/s,传输数据长度为8Bit,无奇偶校验,1位停止位
- 激活FSMC,详细请参考TFTLCD显示章节的设置
- 输入工程名,选择工程路径(不要有中文),选择MDK-ARM V5;勾选Generated periphera initialization as a pair of ‘.c/.h’ files per IP ;点击GENERATE CODE,生成工程代码
3.2 MDK-ARM编程
- 添加按键驱动文件key.c和key.h,参考按键输入例程
- 添加TFTLCD驱动文件tftlcd.c 和tftlcd.h,参考TFTLCD显示例程
- 添加IS62WV12816芯片驱动文件sram.c和sram.h,参考外部SRAM例程
- 添加内存管理驱动文件malloc.c和malloc.h
几个重要函数:
/*释放内存*/
void myfree(u8 memx,void *paddr){
u32 offset;
if(paddr==NULL)return; //内存首地址
offset=(u32)paddr-(u32)malloc_cortol.membase[memx]; //memx为所属内存块
my_mem_free(memx,offset); //释放内存
}
/*分配内存*/
void *mymalloc(u8 memx,u32 size){
u32 offset;
offset=my_mem_malloc(memx,size); //size表示内存大小(字节)
if(offset==0XFFFFFFFF)
return NULL;
else
return (void*)((u32)malloc_cortol.membase[memx]+offset); //返回分配到的内存首地址
}
/*重新分配内存*/
void *myrealloc(u8 memx,void *paddr,u32 size){
u32 offset;
offset=my_mem_malloc(memx,size); //memx表示所属内存块;size为要分配的内存大小(字节)
if(offset==0XFFFFFFFF)return NULL;
else{
my_mem_cpy((void*)((u32)malloc_cortol.membase[memx]+offset),paddr,size); //拷贝旧内存内容到新内存
myfree(memx,paddr); //paddr表示旧内存首地址
return (void*)((u32)malloc_cortol.membase[memx]+offset); //返回新内存首地址
}
}
- 在main.c文件下编写内存管理测试代码
int main(void){
/* USER CODE BEGIN 1 */
uint8_t key, i=0, *p=0,*tp=0;
uint8_t paddr[18];
uint8_t sramx=0;
/* USER CODE END 1 */
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_FSMC_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
TFTLCD_Init();
FSMC_SRAM_Init();
my_mem_init(SRAMIN);
my_mem_init(SRAMEX);
FRONT_COLOR=BROWN;
LCD_DrawRectangle(25,25,215,135);
FRONT_COLOR=RED;
LCD_ShowString(30,30,200,16,16,"ANDYXI STM32");
LCD_ShowString(30,50,200,16,16,"STM32CubeMX");
LCD_ShowString(30,70,200,16,16,"MALLOC TEST");
FRONT_COLOR=BLACK;
LCD_ShowString(30,90,200,16,16, "K_R:Malloc K_L:Free");
LCD_ShowString(30,110,200,16,16,"K_U:SRAMx K_D:Write");
FRONT_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: %");
/* USER CODE END 2 */
while (1){
key=KEY_Scan(0);
switch(key){
case 0:
break;
case KEY_RIGHT_PRES:
p=mymalloc(sramx,2048);
if(p!=NULL)sprintf((char*)p,"Memory Malloc Test%03d",i);
break;
case KEY_DOWN_PRES:
if(p!=NULL){
sprintf((char*)p,"Memory Malloc Test%03d",i);
LCD_ShowString(30,250,200,16,16,p);
}
break;
case KEY_LEFT_PRES:
myfree(sramx,p); p=0;
break;
case KEY_UP_PRES:
sramx=!sramx;
if(sramx)LCD_ShowString(30,170,200,16,16,"SRAMEX");
else LCD_ShowString(30,170,200,16,16,"SRAMIN");
break;
}
if(tp!=p){
tp=p;
sprintf((char*)paddr,"P Addr:0X%08X",(uint32_t)tp);
LCD_ShowString(30,230,200,16,16,paddr);
if(p)LCD_ShowString(30,250,200,16,16,p);
else LCD_Fill(30,250,239,266,WHITE);
}
HAL_Delay(10);
i++;
if((i%20)==0){
LCD_ShowNum(30+96,190,my_mem_perused(SRAMIN),3,16);
LCD_ShowNum(30+96,210,my_mem_perused(SRAMEX),3,16);
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_0);
}
}
}
4. 下载验证
编译无误下载到开发板后,可以看到D1指示灯不断闪烁,KEY_UP可以切换当前操作内存(内部SRAM/外部SRAM),KEY_DOWN用于更新p的内容,KEY_LEFT用于释放内存,更新的数据都会显示在LCD屏上