STM32F4-内存管理

1 SRAM的使用

        STM32F407自带 192K SRAM,一般应用足够,但对内存要求高时,如算法或GUI,可能不够用。因此,通常在开发板上增加 1M字节SRAM芯片,例如XM8A51216,满足大内存需求。

        XM8A51216 是星忆存储科技公司生产的一颗 16 位宽 512K(512*16bit,即 1M 字节)容量的CMOS 静态内存芯片。

XM8A51216功能框图

        图中 A0~18 为地址线,总共 19 根地址线(即 2^19=512K,1K=1024);

        DQ0~15 为数据线,总共 16 根数据线。

        CEn 是芯片使能信号,低电平有效;

        OEn 是输出使能信号,低电平有效;

        WEn 是写使能信号,低电平有效;

        BLEn 和 BHEn 分别是高字节控制和低字节控制信号。

  • FSMC_NE3:这是STM32 FSMC的第三个片选信号线。在FSMC中,每个片选信号线对应一个外部存储器区域,通过配置FSMC的相关寄存器,可以定义每个区域的地址范围、数据宽度、访问时序等参数。

根据FSMC的NEx的接线,可知SRAM起始地址

        由于 外部SRAM接的是 FSMC的 NE3,

片外SRAM

        一个 Bank为256M,FSMC支持4个Bank。

        FSMC 只支持 BANK1~4。一个BANK 256M。

        0x6000 0000~0x6FFF FFFF       BANK1     SRAM使用

        0x7000 0000~0x7FFF FFFF       BANK2    

        0x8000 0000~0x8FFF FFFF       BANK3     FLASH使用

        0x9000 0000~0x9FFF FFFF       BANK4     PC卡使用

        每个 BANK 又被分为 4 个区,一个区 64 M。 

        要实现XM8A51216访问,需配置FSMC:

        1) 使能FSMC时钟,配置相关IO为复用输出;

RCC_AHB3PeriphClockCmd(RCC_AHB3Periph_FSMC,ENABLE);//使能 FSMC 时钟 
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用输出
void GPIO_PinAFConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_PinSource, uint8_t GPIO_AF);
 GPIO_PinAFConfig(GPIOD,GPIO_PinSource0,GPIO_AF_FSMC);//PD0,AF12

        2) 设置FSMC BANK1区域3的工作模式、位宽和读写时序;

void FSMC_NORSRAMInit(FSMC_NORSRAMInitTypeDef* FSMC_NORSRAMInitStruct);

        3) 使能BANK1区域3。//一个BANK256M,故BANK1_AREA3偏移地址128M

FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM3, ENABLE); // 使能 BANK1的区域3

        配置完成后,可通过地址0X68000000访问XM8A51216。 

        因为 Bank1从0x6000 0000开始,一个BANK 256M,至 0x6FFF FFFF结束。

        注意,我们说内存地址大小 xxM的时候,指的是MB,因为1映射地址可寻址到一个字节的存储单元。

        一个地址通常对应一个字节(8位)的存储单元。

        这意味着,如果你有一个32位的地址,它可以指向4GB(2^32字节)内存空间中的任何一个字节。

        一个地址可以寻址到一个8位的存储单元(即一个字节)。

2 内存管理简介

        内存管理旨在高效、快速分配、释放和回收内存资源,核心实现两个函数:

        malloc申请内存

        free释放内存

分块式内存管理原理

        分块式内存管理内存池内存管理表组成,

        内存池等分为n块,管理表记录每块状态。

        表项为0表示未占用,非零表示连续占用块数。分配从顶到底,初始化时表项全零。

        分配原理

        malloc从内存管理表末端开始,找m块连续空闲内存,将其标记为m并返回起始地址;若不足m块,返回NULL。

        释放原理

        free根据指针p找到对应内存块管理表项,将其及连续m-1项清零,标记释放。

3 正点原子malloc.c流程解析

// 内存池 (32 字节对齐)   
__align(32) u8 mem1base[MEM1_MAX_SIZE];                                // 内部 SRAM 内存池 
__align(32) u8 mem2base[MEM2_MAX_SIZE] __attribute__((at(0X68000000))); // 外部 SRAM 内存池 
__align(32) u8 mem3base[MEM3_MAX_SIZE] __attribute__((at(0X10000000))); // 内部 CCM 内存池  
  
// 内存管理表  
u16 mem1mapbase[MEM1_ALLOC_TABLE_SIZE]; // 内部 SRAM 内存池映射表  
u16 mem2mapbase[MEM2_ALLOC_TABLE_SIZE] __attribute__((at(0X68000000 + MEM2_MAX_SIZE))); // 外部 SRAM 内存池映射表  
u16 mem3mapbase[MEM3_ALLOC_TABLE_SIZE] __attribute__((at(0X10000000 + MEM3_MAX_SIZE))); // 内部 CCM 内存池映射表

        __align(xx) 指定了变量 mem1base 应该按照 xx 字节对齐

         __attribute__((at(0Xxx))) 是 __attribute__ 的一个标准用法,它指定了变量应该被放置在特定的内存地址 0Xxx。

        之所以将内存定义为三个内存池,是因为STM32F4有三个不同地址和访问特性的内存区域:

        内部普通SRAM(地址从0X2000 0000开始,共128KB),所有外设均可访问。

        内部CCM内存(地址从0X1000 0000开始,共64KB),仅CPU可访问,DMA等外设不可直接访问。

        外部SRAM(地址从0X6800 0000开始,共1024KB)。

        由于内存池需要连续的内存空间,因此这三个内存区域各自形成一个内存池,总共三个内存池进行管理。内存池的大小(MEM1_MAX_SIZE、MEM2_MAX_SIZE、MEM3_MAX_SIZE)在malloc.h中定义,外部SRAM和CCM内存池从各自的首地址开始,而内部SRAM内存池的首地址由编译器自动分配。所有内存池均使用__align(32)进行32字节对齐,以满足不同场合的需求。

// 内存管理参数定义  
const u32 memtblsize[SRAMBANK] = {  // 内存表大小  
    MEM1_ALLOC_TABLE_SIZE,  
    MEM2_ALLOC_TABLE_SIZE,  
    MEM3_ALLOC_TABLE_SIZE  
};  
  
const u32 memblksize[SRAMBANK] = {  // 内存分块大小  
    MEM1_BLOCK_SIZE,  
    MEM2_BLOCK_SIZE,  
    MEM3_BLOCK_SIZE  
};  
  
const u32 memsize[SRAMBANK] = {  // 内存总大小  
    MEM1_MAX_SIZE,  
    MEM2_MAX_SIZE,  
    MEM3_MAX_SIZE  
};  
  
// 内存管理控制器定义  
struct _m_mallco_dev mallco_dev = {  
    .mem_init = my_mem_init,         // 内存初始化函数  
    .mem_perused = my_mem_perused,   // 内存使用率函数  
    .mem_pools = {                   // 内存池  
        mem1base,  
        mem2base,  
        mem3base  
    },  
    .mem_map_bases = {               // 内存管理状态表  
        mem1mapbase,  
        mem2mapbase,  
        mem3mapbase  
    },  
    .mem_ready = {0, 0, 0}           // 内存管理未就绪标志  
};
// 内存分配函数(内部调用)  
// memx: 所属内存块  
// size: 要分配的内存大小(字节)  
// 返回值: 0XFFFFFFFF 代表错误;其他为内存偏移地址  
u32 my_mem_malloc(u8 memx, u32 size) {  
    signed long offset = 0;  
    u32 nmemb = (size + memblksize[memx] - 1) / memblksize[memx]; // 计算所需内存块数  
    u32 cmemb = 0; // 连续空内存块数  
  
    if (!mallco_dev.memrdy[memx]) mallco_dev.init(memx); // 未初始化则先初始化  
    if (size == 0) return 0XFFFFFFFF; // 不需要分配内存  
  
    for (offset = memtblsize[memx] - 1; offset >= 0; offset--) { // 从后向前搜索空内存块  
        if (!mallco_dev.memmap[memx][offset]) cmemb++; // 连续空内存块数增加  
        else cmemb = 0; // 重置连续空内存块数  
        if (cmemb == nmemb) { // 找到足够的连续空内存块  
            for (u32 i = 0; i < nmemb; i++) mallco_dev.memmap[memx][offset + i] = nmemb; // 标记为已占用  
            return offset * memblksize[memx]; // 返回内存偏移地址  
        }  
    }  
    return 0XFFFFFFFF; // 未找到足够的连续空内存块  
}  
  
// 释放内存函数(内部调用)  
// memx: 所属内存块  
// offset: 内存地址偏移  
// 返回值: 0 代表释放成功;1 代表未初始化;2 代表偏移超区  
u8 my_mem_free(u8 memx, u32 offset) {  
    if (!mallco_dev.memrdy[memx]) {  
        mallco_dev.init(memx); // 未初始化则先初始化  
        return 1; // 返回未初始化状态  
    }  
    if (offset >= memsize[memx]) return 2; // 偏移超区  
  
    int index = offset / memblksize[memx]; // 计算内存块索引  
    int nmemb = mallco_dev.memmap[memx][index]; // 获取内存块数量  
    for (int i = 0; i < nmemb; i++) mallco_dev.memmap[memx][index + i] = 0; // 释放内存块  
    return 0; // 释放成功  
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大象荒野

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值