2 只使用片内 RAM 的示例
平时用的就是从片内申请内存,写这部分主要是为了和从片外 SDRAN 申请内存进行对比。选择以开发板创建工程后,选择 STM32F429-ATK-APOLLO 开发板,工程创建后默认是没有开启片外的 SDRAM 的,此时工程中只配置了片内的 RAM 作为内存堆,我们编写一个 sram 内存堆的申请测试函数进行内存堆的测试,测试代码如下所示(示例仅作为测试使用,目的是了解原理,均没有写内存堆的释放函数,下面的测试函数一样)。
void sram_test(void)
{
int size = 50 * 1024; // 50KBytes
rt_uint8_t * ptr = RT_NULL;
ptr = rt_malloc(size);
if(ptr != RT_NULL)
{
LOG_D("ptr = %p", ptr); // 打印申请到的空间的首地址
}
else
{
LOG_E("malloc failed");
}
}
MSH_CMD_EXPORT(sram_test, sram test)
编译烧写后我们使用定义的 sram_test 命令来进行内存堆的申请测试,测试结果如下。根据测试的日志信息我们可以看出系统复位后内存堆的空间为 183400 字节,我们设定的是每次申请 50KB = 51200 字节的空间,每次申请后打印出剩余的内存堆空间的大小。从结果可以看出,每次申请的内存空间的地址都是 0x2000**,这是因为 STM32 的内部 RAM 空间的起始地址为 0x20000000,等到第四次申请时内部 RAM 的剩余空间大小不够导致申请失败。
| /
- RT - Thread Opera
ting System
/ | 4.0.4 build May 10 2022 21:04:03
2006 - 2021 Copyright by rt-thread team
msh >list_memheap /* 初始时,打印内存堆空间的信息 */
memheap pool size max used size available size
-------- ---------- ------------- --------------
heap 190756 7356 183400
msh >sram_test
[D/main] ptr = 20003380
msh >list_memheap /* 第 1 次申请后,打印内存堆空间的信息 */
memheap pool size max used size available size
-------- ---------- ------------- --------------
heap 190756 58580 132176
msh >sram_test
[D/main] ptr = 2000fb98
msh >list_memheap /* 第 2 次申请后,打印内存堆空间的信息 */
memheap pool size max used size available size
-------- ---------- ------------- --------------
heap 190756 109804 80952
msh >sram_test
[D/main] ptr = 2001c3b0
msh >list_memheap /* 第 3 次申请后,打印内存堆空间的信息 */
memheap pool size max used size available size
-------- ---------- ------------- --------------
heap 190756 161028 29728
msh >sram_test
[E/main] malloc failed
msh >sram_test
[E/main] malloc failed
msh >list_memheap /* 申请失败后,打印内存堆空间的信息 */
memheap pool size max used size available size
-------- ---------- ------------- --------------
heap 190756 161028 29728
3 配置片外 SDRAM 和 内存管理算法
在 RT-Thread Settings 里面可以配置使能片外的 SDRAM,配置方式如下图所示,配置后 SDRAM 的驱动代码位于路径 libraries/HAL_Drivers/drv_sdram.c 下。
配置好了片外的 SDRAM 后,我们还需要选择相应的内存管理算法,同样在 RT-Thread Settings 里面进行配置,配置界面如下图所示。
4 SDRAM 的读写测试
配置完 SDRAM 和内存管理算法后,我们需要将片外的 SDRAM 加入到 memheap_item 链表中进行管理,添加的方法如下:
struct rt_memheap sdram_heap; // memheap 控制块
#define SDRAM_BANK_ADDR ((uint32_t)0XC0000000) // SDRAM 的起始地址
#define SDRAM_SIZE ((uint32_t)0x2000000) // SDRAM 的大小
/* SDRAM 内存堆的初始化 */
rt_memheap_init(&sdram_heap, "sdram", (void *)SDRAM_BANK_ADDR, SDRAM_SIZE);
将 SDRAM 内存堆进行初始化后,编译下载置开发板可以后,使用 list_memheap 可以看到新增加的 sdram 内存堆,如下所示。我们可以看到片外的 SDRAM 初始化之后我们并没有使用,但是在 max used size 字段中确显示已经使用了 48 字节的空间,这部分空间是内存堆的数据头,用于 magic、used 信息及链表节点使用。
| /
- RT - Thread Operating System
/ | 4.0.4 build May 10 2022 21:24:46
2006 - 2021 Copyright by rt-thread team
msh >list_memheap
memheap pool size max used size available size
-------- ---------- ------------- --------------
sdram 33554432 48 33554384 /* 新增加的 SDRAM */
heap 190584 7356 183228 /* 片内的 RAM */
为了测试我们初始化的 SDRAM 是否可以正常使用,通常我们会首先写一个 SDRAM 的读写测试函数,对 SDRAM 的每个字节进行读写测试,根据写入和读出的结果是否一致来判断 SDRAM 是否配置正确,读写测试代码如下。正点原子 F429 阿波罗开发板的 SDRAM 使用的是 16 根地址线,因此读写测试时数据位的宽度定义为 16。
#define SDRAM_DATA_WIDTH 16 // 数据位的宽度
#define SDRAM_BANK_ADDR ((uint32_t)0XC0000000) // SDRAM 的起始地址
int sdram_test(void)
{
int i = 0;
uint32_t start_time = 0, time_cast = 0;
#if SDRAM_DATA_WIDTH == 8
char data_width = 1;
uint8_t data = 0;
#elif SDRAM_DATA_WIDTH == 16
char data_width = 2;
uint16_t data = 0;
#else
char data_width = 4;
uint32_t data = 0;
#endif
/* write data */
LOG_D("Writing the %ld bytes data, waiting....", SDRAM_SIZE);
start_time = rt_tick_get();
for (i = 0; i < SDRAM_SIZE / data_width; i++)
{
#if SDRAM_DATA_WIDTH == 8
*(__IO uint8_t *)(SDRAM_BANK_ADDR + i * data_width) = (uint8_t)0x55;
#elif SDRAM_DATA_WIDTH == 16
*(__IO uint16_t *)(SDRAM_BANK_ADDR + i * data_width) = (uint16_t)(i % 65535);
#else
*(__IO uint32_t *)(SDRAM_BANK_ADDR + i * data_width) = (uint32_t)0x55555555;
#endif
}
time_cast = rt_tick_get() - start_time;
LOG_D("Write data success, total time: %d.%03dS.", time_cast / RT_TICK_PER_SECOND,
time_cast % RT_TICK_PER_SECOND / ((RT_TICK_PER_SECOND * 1 + 999) / 1000));
/* read data */
LOG_D("start Reading and verifying data, waiting....");
for (i = 0; i < SDRAM_SIZE / data_width; i++)
{
#if SDRAM_DATA_WIDTH == 8
data = *(__IO uint8_t *)(SDRAM_BANK_ADDR + i * data_width);
if (data != 0x55)
{
LOG_E("SDRAM test failed!");
break;
}
#elif SDRAM_DATA_WIDTH == 16
data = *(__IO uint16_t *)(SDRAM_BANK_ADDR + i * data_width);
if (data != (i % 65535))
{
LOG_E("SDRAM test failed!");
break;
}
#else
data = *(__IO uint32_t *)(SDRAM_BANK_ADDR + i * data_width);
if (data != 0x55555555)
{
LOG_E("SDRAM test failed!");
break;
}
#endif
}
if (i >= SDRAM_SIZE / data_width)
{
LOG_D("SDRAM test success!");
}
return RT_EOK;
}
MSH_CMD_EXPORT(sdram_test, sdram test)
执行读写测试函数后,如果测试成功,日志信息如下。需要注意的是,读写测试函数是对片外 SDRAM 的整片的测试,执行完读写测试代码后,如果申请片外 SDRAM 的空间会直接导致硬件错误,因为我们对 SDRAM 整片的读写测试破坏了 SDRAM 中保存的数据头的信息,所以申请会出错。测试 SDRAM 的读写没有问题后我们应该重启开发板进行内存的申请测试。
| /
- RT - Thread Operating System
/ | 4.0.4 build May 10 2022 21:24:46
2006 - 2021 Copyright by rt-thread team
msh >list_memheap
memheap pool size max used size available size
-------- ---------- ------------- --------------
sdram 33554432 48 33554384
heap 190584 7356 183228
msh >sdram_test
[D/drv.sdram] Writing the 33554432 bytes data, waiting....
[D/drv.sdram] Write data success, total time: 4.393S.
[D/drv.sdram] start Reading and verifying data, waiting....
[D/drv.sdram] SDRAM test success!
5 内存堆申请测试
5.1 内部 RAM 和 片外 SDRAM 顺序申请测试
同样的,我们编写一个函数对添加 SDRAM 后 menheap 管理的内存堆进行测试,测试代码如下
void malloc_test(void)
{
int size = 50 * 1024; // 50KBytes
rt_uint8_t * ptr = RT_NULL;
ptr = rt_malloc(size);
if(ptr != RT_NULL)
{
LOG_D("ptr = %p", ptr); // 打印申请到的空间的首地址
}
else
{
LOG_E("malloc failed");
}
}
MSH_CMD_EXPORT(malloc_test, malloc test)
下载程序到开发板后,根据测试结果我们可以看到使用 rt_malloc 函数进行申请是首先申请的是片内的 RAM 的空间,等到片内 RAM 的剩余空间不够时系统会去另一块内存堆(SDRAM)上申请空间。
| /
- RT - Thread Operating System
/ | 4.0.4 build May 10 2022 20:11:53
2006 - 2021 Copyright by rt-thread team
msh >list_memheap /* 初始时,打印内存堆空间的信息 */
memheap pool size max used size available size
-------- ---------- ------------- --------------
sdram 33554432 48 33554384
heap 190584 7356 183228
msh >sdram_malloc_test
[D/drv.sdram] ptr = 2000342c /* 申请到的是片内 RAM 的空间 */
msh >list_memheap /* 第 1 次申请后,打印内存堆空间的信息 */
memheap pool size max used size available size
-------- ---------- ------------- --------------
sdram 33554432 48 33554384
heap 190584 58580 132004
msh >sdram_malloc_test
[D/drv.sdram] ptr = 2000fc44 /* 申请到的是片内 RAM 的空间 */
msh >list_memheap /* 第 2 次申请后,打印内存堆空间的信息 */
memheap pool size max used size available size
-------- ---------- ------------- --------------
sdram 33554432 48 33554384
heap 190584 109804 80780
msh >sdram_malloc_test
[D/drv.sdram] ptr = 2001c45c /* 申请到的是片内 RAM 的空间 */
msh >list_memheap /* 第 3 次申请后,打印内存堆空间的信息 */
memheap pool size max used size available size
-------- ---------- ------------- --------------
sdram 33554432 48 33554384
heap 190584 161028 29556
msh >sdram_malloc_test
[D/drv.sdram] ptr = c0000018 /* 片内 RAM 剩余空间不够,申请到的是片外 SDRAM 的空间 */
msh >list_memheap /* 第 4 次申请后,打印内存堆空间的信息 */
memheap pool size max used size available size
-------- ---------- ------------- --------------
sdram 33554432 51272 33503160
heap 190584 161028 29556
msh >sdram_malloc_test
[D/drv.sdram] ptr = c000c830 /* 片内 RAM 剩余空间不够,申请到的是片外 SDRAM 的空间 */
5.2 直接申请片外 SDRAM 内存测试
如果想直接从片外的 SDRAM 内存空间进行申请时,我们可以使用 rt_memheap_alloc 进行操作,同样我们也编写一个直接从片外 SDRAM 申请空间的测试函数,如下所示。其中 sdram_heap 控制块需要和上文对 SDRAM 初始化 rt_memheap_init(&sdram_heap, ...) 时的控制块的变量保持一致。
void sdram_malloc_test(void)
{
int size = 50 * 1024; // 50KBytes
uint8_t *ptr;
ptr = rt_memheap_alloc(&sdram_heap, size);
if(ptr != RT_NULL)
{
LOG_D("ptr = %p", ptr); // 打印申请到的空间的首地址
}
else
{
LOG_E("sdram malloc failed");
}
}
MSH_CMD_EXPORT(sdram_malloc_test, sdram malloc test)
直接从片外 SDRAM 申请空间的测试结果如下,从结果中可以看出每次申请的都是片外 SDRAM 中的空间,且此时片内 RAM 的剩余空间大于要申请的空间的大小。
| /
- RT - Thread Operating System
/ | 4.0.4 build May 10 2022 20:11:53
2006 - 2021 Copyright by rt-thread team
msh >list_memheap /* 初始时,打印内存堆空间的信息 */
memheap pool size max used size available size
-------- ---------- ------------- --------------
sdram 33554432 48 33554384
heap 190584 7356 183228
msh >sdram_malloc_test
[D/drv.sdram] ptr = c0000018 /* 申请到的是片外 SDRAM 的空间 */
msh >list_memheap /* 第 1 次申请后,打印内存堆空间的信息 */
memheap pool size max used size available size
-------- ---------- ------------- --------------
sdram 33554432 51272 33503160
heap 190584 7356 183228
msh >sdram_malloc_test
[D/drv.sdram] ptr = c000c830 /* 申请到的是片外 SDRAM 的空间 */
msh >list_memheap /* 第 2 次申请后,打印内存堆空间的信息 */
memheap pool size max used size available size
-------- ---------- ------------- --------------
sdram 33554432 102496 33451936
heap 190584 7356 183228
msh >sdram_malloc_test
[D/drv.sdram] ptr = c0019048 /* 申请到的是片外 SDRAM 的空间 */
msh >list_memheap /* 第 3 次申请后,打印内存堆空间的信息 */
memheap pool size max used size available size
-------- ---------- ------------- --------------
sdram 33554432 153720 33400712
heap 190584 7356 183228
6 补充
6.1 为什么 rt_malloc 优先申请片内 RAM 的内存
rt_malloc() 的源码(rt-thread/src/memheap.c)如下所示。
void *rt_malloc(rt_size_t size)
{
void *ptr;
/* try to allocate in system heap */
ptr = rt_memheap_alloc(&_heap, size); // 先从 _heap 控制块中申请内存
if (ptr == RT_NULL) // _heap 控制块申请失败,查找其他的 memheap 控制块
{
struct rt_object *object;
struct rt_list_node *node;
struct rt_memheap *heap;
struct rt_object_information *information;
/* try to allocate on other memory heap 尝试从其他的内存堆中进行申请 */
information = rt_object_get_information(RT_Object_Class_MemHeap); // 获取类型为内存堆的对象信息
RT_ASSERT(information != RT_NULL);
for (node = information->object_list.next;
node != &(information->object_list);
node = node->next) // 遍历 memheap_item 链表
{
object = rt_list_entry(node, struct rt_object, list); // 获取结构体的首地址 container_of
heap = (struct rt_memheap *)object;
RT_ASSERT(heap);
RT_ASSERT(rt_object_get_type(&heap->parent) == RT_Object_Class_MemHeap);
/* not allocate in the default system heap */
if (heap == &_heap) // 如果找到的控制块和 _heap 相同则继续查找其他控制块
continue;
ptr = rt_memheap_alloc(heap, size); // 找到了其他的内存堆,就从该内存堆上申请空间
if (ptr != RT_NULL)
break;
}
}
... ... // 省去分析无关代码
return ptr;
}
分析上述源码我们可以看到首先调用了 rt_memheap_alloc(&_heap, size) 从 _heap 控制块中申请内存,如果从 _heap 控制块中申请失败的话,就从 memheap_list 链表中查找其他的内存堆,如果有其他的内存堆就从找到的内存堆中申请空间,如果没有其他的内存堆则返回 RT_NULL。
那么 _heap 是在哪里定义和初始化的呢,继续分析源码我们可以发现,在文件 rt-thread/src/memheap.c 中对 _heap 进行了定义和初始化,代码如下。
static struct rt_memheap _heap;
void rt_system_heap_init(void *begin_addr, void *end_addr)
{
RT_ASSERT((rt_uint32_t)end_addr > (rt_uint32_t)begin_addr);
/* initialize a default heap in the system */
rt_memheap_init(&_heap,
"heap",
begin_addr,
(rt_uint32_t)end_addr - (rt_uint32_t)begin_addr);
}
在 RT-Thread 自动初始化的代码中对片内的内存堆进行了初始化,代码如下
void rt_hw_board_init()
{
rt_system_heap_init((void *)HEAP_BEGIN, (void *)HEAP_END); // 初始化内部 RAM 的内存堆
// #define HEAP_BEGIN (&__bss_end)
// #define HEAP_END STM32_SRAM_END
// #define STM32_SRAM_END (0x20000000 + STM32_SRAM_SIZE * 1024)
// #define STM32_SRAM_SIZE (192)
}
原作者:crystal266