Rt-Thread 操作系统 memheap 管理多块内存的使用方法

  在开发中由于单片机自带的 RAM 空间比较小,有时候需要扩展片外的 RAM 以供使用,RT-Thread 提供了 memheap 管理算法来管理多块不相邻的内存空间,本文以正点原子的 STM32F429 阿波罗开发板为例,讲解使用 memheap 内存管理算法对内部 RAM 和片外的 SDRAM 进行管理的方法,使用的软件包为基于开发板的 STM32F429-ATK-APOLLO v0.1.0 软件包。

1 memheap 管理算法简介

  本部分来源于 RT-Thread 的 memheap 管理算法官方文档
  memheap 管理算法适用于系统含有多个地址可不连续的内存堆。使用 memheap 内存管理可以简化系统存在多个内存堆时的使用:当系统中存在多个内存堆的时候,用户只需要在系统初始化时将多个所需的 memheap 初始化,并开启 memheap 功能就可以很方便地把多个 memheap(地址可不连续)粘合起来用于系统的 heap 分配。
注:在开启 memheap 之后原来的 heap 功能将被关闭,两者只可以通过打开或关闭 RT_USING_MEMHEAP_AS_HEAP 来选择其一
  memheap 工作机制如下图所示,首先将多块内存加入 memheap_item 链表进行粘合。当分配内存块时,会先从默认内存堆去分配内存,当分配不到时会查找 memheap_item 链表,尝试从其他的内存堆上分配内存块。应用程序不用关心当前分配的内存块位于哪个内存堆上,就像是在操作一个内存堆。
image.png

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 Operating 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 Studio RGB屏幕之 SDRAM 配置
image.png

  配置好了片外的 SDRAM 后,我们还需要选择相应的内存管理算法,同样在 RT-Thread Settings 里面进行配置,配置界面如下图所示。
image.png

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) 
}

  根据上面的两段代码,分析后我们可以看出 RT-Thread 自动初始化的代码首先将未使用的片内 RAM 的空间都当做系统的内存堆空间进行初始化,对应的控制块的名称为 _heap,所以在使用 rt_malloc 进行空间的申请时会先申请片内的 RAM。
  除此之外分析 rt_malloc 的源码我们还可以得到申请空间实际调用的是 rt_memheap_alloc,该函数的第一个参数决定了是从哪里申请的空间,所以我们可以直接使用该函数来确定从哪里来申请空间。如果我们想有先从片外的 SDRAM 申请,然后再从片内的 RAM 申请,也可以修改 rt_malloc 的源码,将 ptr = rt_memheap_alloc(&_heap, size); 的第一个参数修改为自己定义的外部 SDRAM 的控制块的名称,对应的将 if (heap == &_heap) 中的 _heap 也修改为自己定义的外部 SDRAM 的控制块的名称。

7 完整代码

  在基于芯片创建的工程的技术上将 drv_sdram.c 的代码进行了部分的修改,修改后的完整代码如下

#include <board.h>

#ifdef BSP_USING_SDRAM
#include <sdram_port.h>

#define DRV_DEBUG
#define LOG_TAG             "drv.sdram"
#include <drv_log.h>

static SDRAM_HandleTypeDef hsdram1;
static FMC_SDRAM_CommandTypeDef command;
#ifdef RT_USING_MEMHEAP_AS_HEAP
static struct rt_memheap sdram_heap;
#endif

/**
  * @brief  Perform the SDRAM exernal memory inialization sequence
  * @param  hsdram: SDRAM handle
  * @param  Command: Pointer to SDRAM command structure
  * @retval None
  */
static void SDRAM_Initialization_Sequence(SDRAM_HandleTypeDef *hsdram, FMC_SDRAM_CommandTypeDef *Command)
{
    __IO uint32_t tmpmrd = 0;
    uint32_t target_bank = 0;

#if SDRAM_TARGET_BANK == 1
    target_bank = FMC_SDRAM_CMD_TARGET_BANK1;
#else
    target_bank = FMC_SDRAM_CMD_TARGET_BANK2;
#endif

    /* Configure a clock configuration enable command */
    Command->CommandMode           = FMC_SDRAM_CMD_CLK_ENABLE;
    Command->CommandTarget         = target_bank;
    Command->AutoRefreshNumber     = 1;
    Command->ModeRegisterDefinition = 0;

    /* Send the command */
    HAL_SDRAM_SendCommand(hsdram, Command, 0x1000);

    /* Insert 100 ms delay */
    /* interrupt is not enable, just to delay some time. */
    for (tmpmrd = 0; tmpmrd < 0xffffff; tmpmrd ++)
        ;

    /* Configure a PALL (precharge all) command */
    Command->CommandMode            = FMC_SDRAM_CMD_PALL;
    Command->CommandTarget          = target_bank;
    Command->AutoRefreshNumber      = 1;
    Command->ModeRegisterDefinition = 0;

    /* Send the command */
    HAL_SDRAM_SendCommand(hsdram, Command, 0x1000);

    /* Configure a Auto-Refresh command */
    Command->CommandMode            = FMC_SDRAM_CMD_AUTOREFRESH_MODE;
    Command->CommandTarget          = target_bank;
    Command->AutoRefreshNumber      = 8;
    Command->ModeRegisterDefinition = 0;

    /* Send the command */
    HAL_SDRAM_SendCommand(hsdram, Command, 0x1000);

    /* Program the external memory mode register */
#if SDRAM_DATA_WIDTH == 8
    tmpmrd = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_1     |
#elif SDRAM_DATA_WIDTH == 16
    tmpmrd = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_2     |
#else
    tmpmrd = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_4     |
#endif
             SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL        |
#if SDRAM_CAS_LATENCY == 3
             SDRAM_MODEREG_CAS_LATENCY_3                |
#else
             SDRAM_MODEREG_CAS_LATENCY_2                |
#endif
             SDRAM_MODEREG_OPERATING_MODE_STANDARD      |
             SDRAM_MODEREG_WRITEBURST_MODE_SINGLE;

    Command->CommandMode            = FMC_SDRAM_CMD_LOAD_MODE;
    Command->CommandTarget          = target_bank;
    Command->AutoRefreshNumber      = 1;
    Command->ModeRegisterDefinition = tmpmrd;

    /* Send the command */
    HAL_SDRAM_SendCommand(hsdram, Command, 0x1000);

    /* Set the device refresh counter */
    HAL_SDRAM_ProgramRefreshRate(hsdram, SDRAM_REFRESH_COUNT);
}

static int SDRAM_Init(void)
{
    int result = RT_EOK;
    FMC_SDRAM_TimingTypeDef SDRAM_Timing;

    /* SDRAM device configuration */
    hsdram1.Instance = FMC_SDRAM_DEVICE;
    SDRAM_Timing.LoadToActiveDelay    = LOADTOACTIVEDELAY;
    SDRAM_Timing.ExitSelfRefreshDelay = EXITSELFREFRESHDELAY;
    SDRAM_Timing.SelfRefreshTime      = SELFREFRESHTIME;
    SDRAM_Timing.RowCycleDelay        = ROWCYCLEDELAY;
    SDRAM_Timing.WriteRecoveryTime    = WRITERECOVERYTIME;
    SDRAM_Timing.RPDelay              = RPDELAY;
    SDRAM_Timing.RCDDelay             = RCDDELAY;

#if SDRAM_TARGET_BANK == 1
    hsdram1.Init.SDBank             = FMC_SDRAM_BANK1;
#else
    hsdram1.Init.SDBank             = FMC_SDRAM_BANK2;
#endif
#if SDRAM_COLUMN_BITS == 8
    hsdram1.Init.ColumnBitsNumber   = FMC_SDRAM_COLUMN_BITS_NUM_8;
#elif SDRAM_COLUMN_BITS == 9
    hsdram1.Init.ColumnBitsNumber   = FMC_SDRAM_COLUMN_BITS_NUM_9;
#elif SDRAM_COLUMN_BITS == 10
    hsdram1.Init.ColumnBitsNumber   = FMC_SDRAM_COLUMN_BITS_NUM_10;
#else
    hsdram1.Init.ColumnBitsNumber   = FMC_SDRAM_COLUMN_BITS_NUM_11;
#endif
#if SDRAM_ROW_BITS == 11
    hsdram1.Init.RowBitsNumber      = FMC_SDRAM_ROW_BITS_NUM_11;
#elif SDRAM_ROW_BITS == 12
    hsdram1.Init.RowBitsNumber      = FMC_SDRAM_ROW_BITS_NUM_12;
#else
    hsdram1.Init.RowBitsNumber      = FMC_SDRAM_ROW_BITS_NUM_13;
#endif

#if SDRAM_DATA_WIDTH == 8
    hsdram1.Init.MemoryDataWidth    = FMC_SDRAM_MEM_BUS_WIDTH_8;
#elif SDRAM_DATA_WIDTH == 16
    hsdram1.Init.MemoryDataWidth    = FMC_SDRAM_MEM_BUS_WIDTH_16;
#else
    hsdram1.Init.MemoryDataWidth    = FMC_SDRAM_MEM_BUS_WIDTH_32;
#endif
    hsdram1.Init.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4;
#if SDRAM_CAS_LATENCY == 1
    hsdram1.Init.CASLatency         = FMC_SDRAM_CAS_LATENCY_1;
#elif SDRAM_CAS_LATENCY == 2
    hsdram1.Init.CASLatency         = FMC_SDRAM_CAS_LATENCY_2;
#else
    hsdram1.Init.CASLatency         = FMC_SDRAM_CAS_LATENCY_3;
#endif
    hsdram1.Init.WriteProtection    = FMC_SDRAM_WRITE_PROTECTION_DISABLE;
#if SDCLOCK_PERIOD == 2
    hsdram1.Init.SDClockPeriod      = FMC_SDRAM_CLOCK_PERIOD_2;
#else
    hsdram1.Init.SDClockPeriod      = FMC_SDRAM_CLOCK_PERIOD_3;
#endif
    hsdram1.Init.ReadBurst          = FMC_SDRAM_RBURST_ENABLE;
#if SDRAM_RPIPE_DELAY == 0
    hsdram1.Init.ReadPipeDelay      = FMC_SDRAM_RPIPE_DELAY_0;
#elif SDRAM_RPIPE_DELAY == 1
    hsdram1.Init.ReadPipeDelay      = FMC_SDRAM_RPIPE_DELAY_1;
#else
    hsdram1.Init.ReadPipeDelay      = FMC_SDRAM_RPIPE_DELAY_2;
#endif

    /* Initialize the SDRAM controller */
    if (HAL_SDRAM_Init(&hsdram1, &SDRAM_Timing) != HAL_OK)
    {
        LOG_E("SDRAM init failed!");
        result = -RT_ERROR;
    }
    else
    {
        /* Program the SDRAM external device */
        SDRAM_Initialization_Sequence(&hsdram1, &command);
        LOG_D("sdram init success, mapped at 0x%X, size is %d bytes, data width is %d", SDRAM_BANK_ADDR, SDRAM_SIZE, SDRAM_DATA_WIDTH);
#ifdef RT_USING_MEMHEAP_AS_HEAP
        /* If RT_USING_MEMHEAP_AS_HEAP is enabled, SDRAM is initialized to the heap */
        rt_memheap_init(&sdram_heap, "sdram", (void *)SDRAM_BANK_ADDR, SDRAM_SIZE);
#endif
    }

    return result;
}
INIT_BOARD_EXPORT(SDRAM_Init);

#ifdef DRV_DEBUG
#ifdef FINSH_USING_MSH
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 申请空间测试 */
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("malloc failed");
    }
}
MSH_CMD_EXPORT(sdram_malloc_test, sdram malloc test)

/* 从片内 RAM 和 片外 SDRAM 顺序申请测试 */
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)
#endif /* FINSH_USING_MSH */
#endif /* DRV_DEBUG */
#endif /* BSP_USING_SDRAM */

sdram_port.h

#ifndef __SDRAM_PORT_H__
#define __SDRAM_PORT_H__

/* parameters for sdram peripheral */
/* Bank1 or Bank2 */
#define SDRAM_TARGET_BANK               1
/* stm32f4 Bank1:0XC0000000  Bank2:0XD0000000 */
#define SDRAM_BANK_ADDR                 ((uint32_t)0XC0000000)
/* data width: 8, 16, 32 */
#define SDRAM_DATA_WIDTH                16
/* column bit numbers: 8, 9, 10, 11 */
#define SDRAM_COLUMN_BITS               9
/* row bit numbers: 11, 12, 13 */
#define SDRAM_ROW_BITS                  13
/* cas latency clock number: 1, 2, 3 */
#define SDRAM_CAS_LATENCY               3
/* read pipe delay: 0, 1, 2 */
#define SDRAM_RPIPE_DELAY               1
/* clock divid: 2, 3 */
#define SDCLOCK_PERIOD                  2
/* refresh rate counter */
#define SDRAM_REFRESH_COUNT             ((uint32_t)0x02AB)
#define SDRAM_SIZE                      ((uint32_t)0x2000000)

/* Timing configuration for W9825G6KH-6 */
/* 90 MHz of SD clock frequency (180MHz/2) */
/* TMRD: 2 Clock cycles */
#define LOADTOACTIVEDELAY               2
/* TXSR: 7x11.90ns */
#define EXITSELFREFRESHDELAY            8
/* TRAS: 4x11.90ns */
#define SELFREFRESHTIME                 6
/* TRC:  7x11.90ns */
#define ROWCYCLEDELAY                   6
/* TWR:  2 Clock cycles */
#define WRITERECOVERYTIME               2
/* TRP:  2x11.90ns */
#define RPDELAY                         2
/* TRCD: 2x11.90ns */
#define RCDDELAY                        2

/* memory mode register */
#define SDRAM_MODEREG_BURST_LENGTH_1             ((uint16_t)0x0000)
#define SDRAM_MODEREG_BURST_LENGTH_2             ((uint16_t)0x0001)
#define SDRAM_MODEREG_BURST_LENGTH_4             ((uint16_t)0x0002)
#define SDRAM_MODEREG_BURST_LENGTH_8             ((uint16_t)0x0004)
#define SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL      ((uint16_t)0x0000)
#define SDRAM_MODEREG_BURST_TYPE_INTERLEAVED     ((uint16_t)0x0008)
#define SDRAM_MODEREG_CAS_LATENCY_2              ((uint16_t)0x0020)
#define SDRAM_MODEREG_CAS_LATENCY_3              ((uint16_t)0x0030)
#define SDRAM_MODEREG_OPERATING_MODE_STANDARD    ((uint16_t)0x0000)
#define SDRAM_MODEREG_WRITEBURST_MODE_PROGRAMMED ((uint16_t)0x0000)
#define SDRAM_MODEREG_WRITEBURST_MODE_SINGLE     ((uint16_t)0x0200)

#endif
  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
RT-Thread诞生于2006年,是一款以开源、中立、社区化发展起来的物联网操作系统RT-Thread主要采用 C 语言编写,浅显易懂,且具有方便移植的特性(可快速移植到多种主流 MCU 及模组芯片上)。RT-Thread把面向对象的设计方法应用到实时系统设计中,使得代码风格优雅、架构清晰、系统模块化并且可裁剪性非常好。 RT-Thread有完整版和Nano版,对于资源受限的微控制器(MCU)系统,可通过简单易用的工具,裁剪出仅需要 3KB Flash、1.2KB RAM 内存资源的 NANO 内核版本;而相对资源丰富的物联网设备,可使用RT-Thread完整版,通过在线的软件包管理工具,配合系统配置工具实现直观快速的模块化裁剪,并且可以无缝地导入丰富的软件功能包,实现类似 Android 的图形界面及触摸滑动效果、智能语音交互效果等复杂功能。 RT-Thread架构 RT-Thread是一个集实时操作系统RTOS)内核、中间件组件的物联网操作系统,架构如下: 内核层:RT-Thread内核,是 RT-Thread的核心部分,包括了内核系统中对象的实现,例如多线程及其调度、信号量、邮箱、消息队列、内存管理、定时器等;libcpu/BSP(芯片移植相关文件 / 板级支持包)与硬件密切相关,由外设驱动和 CPU 移植构成。 组件与服务层:组件是基于 RT-Thread内核之上的上层软件,例如虚拟文件系统、FinSH命令行界面、网络框架、设备框架等。采用模块化设计,做到组件内部高内聚,组件之间低耦合。 RT-Thread软件包:运行于 RT-Thread物联网操作系统平台上,面向不同应用领域的通用软件组件,由描述信息、源代码或库文件组成。RT-Thread提供了开放的软件包平台,这里存放了官方提供或开发者提供的软件包,该平台为开发者提供了众多可重用软件包的选择,这也是 RT-Thread生态的重要组成部分。软件包生态对于一个操作系统的选择至关重要,因为这些软件包具有很强的可重用性,模块化程度很高,极大的方便应用开发者在最短时间内,打造出自己想要的系统。RT-Thread已经支持的软件包数量已经达到 180+。 RT-Thread的特点: 资源占用极低,超低功耗设计,最小内核(Nano版本)仅需1.2KB RAM,3KB Flash。 组件丰富,繁荣发展的软件包生态 。 简单易用 ,优雅的代码风格,易于阅读、掌握。 高度可伸缩,优质的可伸缩的软件架构,松耦合,模块化,易于裁剪和扩展。 强大,支持高性能应用。 跨平台、芯片支持广泛。
RT-Thread操作系统资料包是为了方便开发者了解和使用RT-Thread操作系统而提供的一系列资料资源。 首先,RT-Thread操作系统资料包包含了操作系统的详细介绍和架构说明,让开发者能够了解RT-Thread的基本原理和设计思想。这对于初次接触RT-Thread的开发者来说是非常有价值的,可以帮助他们更好地理解操作系统的工作机制。 其次,RT-Thread操作系统资料包还提供了丰富的应用实例和案例分析,这些案例涵盖了不同领域的项目,如嵌入式系统、物联网设备等。这些实例可以帮助开发者更加直观地了解RT-Thread的功能和特性,以及如何在实际项目中使用RT-Thread进行开发。 此外,RT-Thread操作系统资料包还包含了详细的开发文档和API参考手册,这些文档可以为开发者提供操作系统使用方法和开发技巧。开发者可以根据文档中的指导来进行基于RT-Thread的应用开发,从而提高开发效率和减少开发难度。 最后,RT-Thread操作系统资料包还提供了相关的开发工具和驱动程序,这些工具和驱动可以帮助开发者更好地进行开发和调试工作。开发者可以通过这些工具和驱动来实现硬件与操作系统的有效交互,加快项目的开发进度。 总的来说,RT-Thread操作系统资料包是一套全面而实用的开发资源,可以帮助开发者更好地理解和使用RT-Thread操作系统,提高开发效率和项目的成功率。无论是新手还是有经验的开发者,都能从中获得帮助和指导。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值