大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家介绍的是在i.MXRT1060-EVK上利用memtester程序给SDRAM做压力测试。

 

我们知道恩智浦i.MXRT1xxx系列是高性能MCU的代表,对于这个超高主频(>=500MHz)的怪兽,不做点人机交互图形显示或者跑点算法方面的高阶应用实在有点浪费它的能力,高阶应用往往需要大容量缓存(RAM),i.MXRT1xxx系列内部RAM最大也就1-2MB,不是特别宽裕,因此我们在板级设计时往往会为i.MXRT1xxx再搭配一块外部RAM。外部RAM的种类选择很多,可通过FlexSPI外设挂HyperRAM/PSRAM或者通过SEMC外设挂SDRAM/SRAM,其中SDRAM是最常见的选择,官方EVK上默认也是选了SDRAM。

 

有了外部SDRAM虽然解决了系统缓存容量危机,但也同时可能引入潜在的系统稳定性问题,毕竟SDRAM是外部器件,容易受干扰(比如PCB走线或者环境温度等),如果SDRAM的读写访问不可靠,那么系统就会有不可预知的奇怪问题发生,因此我们需要在板子出厂时对SDRAM做一次压力测试(既测试了SDRAM芯片和PCB设计,也测试了对应的SEMC外设配置),今天痞子衡就选用memtester程序给SDRAM上一次强度。

 

关于memtester程序的基本知识,痞子衡之前专门写过一篇文章 《内存读写正确性压力测试程序(memtester)》 ,本篇就是基于了解memtester基本知识之后的一次实践。来,让我们开始吧。

 

一、准备工作

1.1 硬件平台NXP i.MX RT1060 EVK

要开始给SDRAM做压力测试,首先你得有一块开发板,恩智浦官网上有i.MXRT1060评估板,板载SDRAM芯片型号为镁光的MT48LC16M16A2,我们今天就来测试它。

 

 

1.2 集成开发环境IAR EWARM

ARM Cortex-M微控制器的集成开发环境有很多,其中IAR EWARM凭借优良的特性备受广大工程师青睐,今天痞子衡就选用IAR作为软件环境,具体版本为IAR EWARM v8.50.6。

 

1.3 官方软件开发包NXP MCUXpresso SDK

在开始移植memtester程序到i.MXRT1062上之前,我们需要先有一个i.MXRT1062的基本hello world的例程,当然我们可以对着数据手册自己从头写一个,但是这里痞子衡推荐使用官方软件开发包。

 

注册并登录恩智浦官网,来到 MCUXpresso SDK Builder 页面,在"Select Development Board"里选择EVK-MIMXRT1060后点击Build MCUXpresso SDK后跳转到下一个页面,点击Download SDK后便可得到SDK_2.9.1_EVK-MIMXRT1060.zip,下面是痞子衡下载的开发包具体版本信息:

 

 

二、开始实测

2.1 跑通hello world

给i.MXRT1060 EVK板子供电(J2口接5V输出的电源),并且使用USB线连接电脑与板子的J41 USB口,此时在设备管理器应该可以看到USB虚拟的串口(EVK板载LPC-LINK2调试器内含USB转串口功能,如果看不到串口,请自行安装LPC-LINK2驱动)。

 

打开前一步下载的开发包里的\SDK_2.9.1_EVK-MIMXRT1060\boards\evkimxrt1060\demo_apps\hello_world\iar\hello_world.eww工程,确认工程option里linker文件选择的是MIMXRT1062xxxxx_ram.icf,然后J21 JTAG口连接上Jlink Plus直接将工程下载进主芯片的RAM运行。

 

如果工程运行正常,你在串口调试助手(115200,8N1)里应该能看到"hello world."打印输出。

 

2.2 移植memtester程序

以hello_world工程为基础,将从官方网站下载到的memtester-4.5.0.tar包解压,将\memtester-4.5.0\路径下的如下源文件(.c或.h)全部拷贝到hello world工程目录下:

 

\memtester-4.5.0
                \memtester.h
                \memtester.c        --主程序入口
                \sizes.h            --关于系统位数(32/64bit)的一些定义
                \types.h            --所用数据类型的定义
                \tests.h
                \tests.c            --测试算法子程序

 

将上面所有memtester源文件全部添加进hello_world工程并将工程更名为memtester,然后再将工程中原主函数入口文件hello_world.c更名为main.c,此时基本memtester工程就完成了。但注意此时工程无法编译,因为memtester源文件还需要进一步修改。

 

2.2.1 适配嵌入式平台

我们下载的memtester源码本用作在Unix-like系统上运行的,所以源码里面有一些仅适用于Unix-like系统上运行的代码,需要将这些代码全部删除以适合在嵌入式平台运行。

 

关于头文件引用部分,需要删除memtester.c和tests.c文件里的一些不适用的#include语句,改用SDK里的标准头:

 

// 下述Unix系统头需删除
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

// 下述SDK标准头需添加
#include "fsl_common.h"
#include "fsl_debug_console.h"

 

整个memtester源码其实最主要(唯一)的改动就是memtester.c里的原main()函数,本来main函数是接受控制台传来的用户选项完成解析获取必要的参数(内存起始地址,测试长度,压力测试循环次数),然后运行tests.c里各种算法子程序。我们需要做的就是将main函数里代码各种删减,把不适用于MCU上运行的代码全部拿掉,并且将main函数原型改为如下:

 

/*******************************************************************************
* Input parameters:
* ---- phystestbase : memory base address
* ---- wantraw      : memory size
* ---- memsuffix    : memory unit, B,K,M,G for B, KB, MB, GB
* ---- loop         : memory test code loop times
* ---- pagesize     : memory pase size (Bytes)
*/
int memtester_main(ul phystestbase, ul wantraw, char *memsuffix, ul loops, ul pagesize) 
{}

 

最后memtester_main里跑tests.c里具体算法子程序时,如果遇到失败的情况,默认是继续执行下一个算法子程序,我们可以加一个fail_stop控制变量,决定遇到失败是直接结束整个测试还是继续往下跑。

 

2.2.2 板级初始化

上一节里已经将memtester.c里面的main()函数改成了memtester_main(),所以我们直接在原hello_world.c改成的main.c里的main函数中增加memtester_main()的调用即可,注意按需设置测试参数(下述代码里的设置就是一个示例,测试0x8000_0000地址开始的64KB内存,循环一次即可)。此外Cache是否使能对测试影响很大,建议关掉DCache测试。

 

typedef struct _semc_test_config {
  uint32_t baseAddr;
  uint32_t testSize;
  uint32_t loopNum;
  uint32_t dramFreq;
  uint32_t enableCache;
} semc_test_config_t;

int fail_stop = 1;

int main(void)
{
    char memsuffix = 'B';
    /* Init board hardware. */
    BOARD_InitHardware();

    /* --------------- stress test --------------- */
    semc_test_config_t testConfig;
    testConfig.baseAddr = 0x80000000;
    testConfig.testSize = 64 * 1024;
    testConfig.loopNum = 1;
    testConfig.dramFreq = CLOCK_GetFreq(kCLOCK_SemcClk);
    testConfig.enableCache = 0;
    
    if (!testConfig.enableCache) {
        /* Disable D cache */
        SCB_DisableDCache();
    }

    PRINTF("\r\n########## Print out from target board ##########\r\n");
    PRINTF("\r\nSDRAM r/w test settings:\r\n");
    PRINTF("      Base Addr: 0x%x;\r\n", testConfig.baseAddr);
    PRINTF("      Test Size: %d Bytes;\r\n", testConfig.testSize);
    PRINTF("      Test Loop: %d;\r\n", testConfig.loopNum);    
    PRINTF("      SDRAM Freq: %d Hz;\r\n",testConfig.dramFreq);
    PRINTF("      Enable Cache: %d;\r\n\r\n", testConfig.enableCache);

    /* Run memory stress test: 64KByte, loop=1, page_size = 1kbyte */
    memtester_main(testConfig.baseAddr, testConfig.testSize, &memsuffix, testConfig.loopNum, (1*1024));

    while (1)
    {
    }
}
 
2.2.3 串口打印功能

串口打印功能的改动比较简单,直接把原memtester.c和tests.c文件里的fprintf()全部替换成PRINTF()即可,PRINTF函数在原hello world工程里已经实现了。

 

2.3 memtester参数配置

痞子衡在memtester程序的基本知识介绍里说过,memtester几乎没有参数配置,就是需要在sizes.h文件里把如下两个宏加进去:

 

#define ULONG_MAX  (4294967295UL)
#define TEST_NARROW_WRITES 

 

2.4 输出memtester结果

到这里memtester的移植工作就完全结束了,此时memtester工程(代码和变量都链接在TCM里)也应该能正常编译了。你可能会疑问,SEMC外设的初始化代码在哪里啊?别急,工程选项调试器设置里提供了两种SDRAM初始化脚本,我们随便选用一种即可:

 

 

另外需要注意的是,在工程预编译选项里需要加上SKIP_SYSCLK_INIT宏,因为工程时钟初始化函数BOARD_BootClockRUN()会根据这个宏来决定要不要重配SEMC,我们已经有SDRAM初始化脚本了,不需要在工程里再配SEMC时钟了。

 

 

现在还等什么?将memtester工程赶紧下载进芯片并打开串口调试助手看memtester结果啊。痞子衡为了尽快得到结果,仅测试了64KB的空间,按说应该测试全部32MB空间才对,但这个测试时间是很漫长的,如果循环次数loopNum大于1,那等待时间就更长了。如果测试中遇到了失败,除了要检查板级硬件外,还可以调整SDRAM初始化脚本里的配置再次测试,直至通过压力测试。

 

 

想偷懒的朋友直接移步痞子衡的github https://github.com/JayHeng/cortex-m-apps 去下载移植好的工程,工程在\cortex-m-apps\apps\memtester_imxrt1062\bsp\下面。

 

至此,在i.MXRT1060-EVK上利用memtester程序给SDRAM做压力测试痞子衡便介绍完毕了,掌声在哪里~~~