1 SRAM的使用
STM32F407自带 192K SRAM,一般应用足够,但对内存要求高时,如算法或GUI,可能不够用。因此,通常在开发板上增加 1M字节SRAM芯片,例如XM8A51216,满足大内存需求。
XM8A51216 是星忆存储科技公司生产的一颗 16 位宽 512K(512*16bit,即 1M 字节)容量的CMOS 静态内存芯片。
图中 A0~18 为地址线,总共 19 根地址线(即 2^19=512K,1K=1024);
DQ0~15 为数据线,总共 16 根数据线。
CEn 是芯片使能信号,低电平有效;
OEn 是输出使能信号,低电平有效;
WEn 是写使能信号,低电平有效;
BLEn 和 BHEn 分别是高字节控制和低字节控制信号。
-
FSMC_NE3:这是STM32 FSMC的第三个片选信号线。在FSMC中,每个片选信号线对应一个外部存储器区域,通过配置FSMC的相关寄存器,可以定义每个区域的地址范围、数据宽度、访问时序等参数。
由于 外部SRAM接的是 FSMC的 NE3,
一个 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; // 释放成功
}