跳到主要内容

第 11 课:编写 SDRAM 测试应用

Board 已经具备 SDRAM 初始化能力,现在用一个独立 Zephyr 应用选择这项能力,并验证地址线、数据线、字节通道和全部 32 MiB 空间。

SDRAM 测试的三个软件层次

先认识这个独立应用​

应用位于:

apps/sdram_test/
├─ CMakeLists.txt
├─ prj.conf
├─ sample.yaml
├─ README.md
├─ boards/
│ └─ dshanmcu_hpm6e70.overlay
└─ src/
└─ main.c

它与 LED 应用 互不读取源文件或配置。构建时选择哪个应用目录,就只编译哪个应用。

打开 apps/sdram_test/CMakeLists.txt,其中先登记本工程的 Board 搜索根目录:

# 当前文件位于 apps/sdram_test/,向上两级就是工程根目录。
list(APPEND BOARD_ROOT ${CMAKE_CURRENT_LIST_DIR}/../..)

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})

因此这个独立应用可以直接选择 dshanmcu_hpm6e70,不要求使用者在每次构建时重复填写板卡目录。

第一步:在本应用中启用 SDRAM​

打开:

apps/sdram_test/prj.conf

加入:

# 编译本板的 FEMC 与 SDRAM 初始化代码。
CONFIG_DSHANMCU_HPM6E70_SDRAM=y

# 在 Zephyr 进入 main() 前完成 SDRAM 初始化。
CONFIG_INIT_EXT_RAM=y

# 输出测试过程,并为完整测试留出足够的主线程栈。
CONFIG_PRINTK=y
CONFIG_MAIN_STACK_SIZE=4096

Board 中的 SDRAM 节点默认关闭,所以还要用本应用的 Overlay 启用它。打开:

apps/sdram_test/boards/dshanmcu_hpm6e70.overlay

写入:

/* 只有这个应用需要使用板载 SDRAM。 */
&dram {
status = "okay";
};

prj.conf 选择软件能力,Overlay 改变本次构建使用的硬件状态。两项缺少任何一项,都会在编译阶段被 Board 中的检查发现。

第二步:从设备树取得测试范围​

打开:

apps/sdram_test/src/main.c

文件开头加入:

#include <zephyr/kernel.h>
#include <zephyr/devicetree.h>
#include <zephyr/sys/printk.h>

#include <hpm_clock_drv.h>
#include <hpm_l1c_drv.h>

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

/* 地址和容量来自 Board DTS,不在应用中重复写死。 */
#define SDRAM_NODE DT_NODELABEL(dram)
#define SDRAM_BASE DT_REG_ADDR(SDRAM_NODE)
#define SDRAM_SIZE_BYTES DT_REG_SIZE(SDRAM_NODE)
#define SDRAM_WORDS (SDRAM_SIZE_BYTES / sizeof(uint32_t))

/* 取得 main() 之前执行的板级初始化结果。 */
extern int dshanmcu_hpm6e70_get_sdram_init_status(void);

如果以后修改设备树中的容量,测试范围会随构建结果一起改变,应用不需要同步维护另一个常量。

第三步:先确认初始化成功​

main() 开始时先检查 Board 初始化返回值:

int init_status;

printk("\nHPM6E70 SDRAM test\n");
printk("Step 1/6: check the pre-kernel SDRAM initialization...\n");

init_status = dshanmcu_hpm6e70_get_sdram_init_status();
if (init_status != 0) {
printk("FAIL [pre-kernel init] status=%d\n", init_status);
printk("SDRAM TEST FAILED\n");
return 0;
}

这样可以区分“FEMC 没有初始化成功”和“初始化成功但某根存储器信号异常”。

第四步:物理内存测试期间绕过 D-cache​

如果 CPU 从缓存中读回刚写入的数据,即使 SDRAM 没有正确保存,也可能得到相同结果。完整测试开始前保存当前状态并关闭 D-cache:

bool dcache_was_enabled = l1c_dc_is_enabled();

if (dcache_was_enabled) {
l1c_dc_writeback_all();
l1c_dc_disable();
}

/* 所有物理 SDRAM 测试在这里执行。 */

if (dcache_was_enabled) {
l1c_dc_invalidate_all();
l1c_dc_enable();
}

这不是为了提高测试强度,而是为了确保每次读写都真正到达 FEMC 和 U9。

第五步:逐层编写测试​

测试不是简单重复写入同一个数。每种模式用于暴露不同类型的连接问题:

测试写入方式主要检查对象
快速探测四个互补 32 位数基本读写通路
Walking 1/0单个 1 或 0 逐位移动数据线短路、断路、固定电平
字节通道使用 uint8_t 连续读写DQM0/DQM1 与两个字节通道
全空间固定模式四种模式覆盖 32 MiB全部存储单元的基本稳定性
地址作为数据每个地址写入不同值地址镜像和地址线问题

Walking 1/0 的实现如下:

static bool walking_data_test(void)
{
volatile uint32_t *cell = (volatile uint32_t *)SDRAM_BASE;

/* 让 1 和 0 依次经过每一位。 */
for (uint32_t bit = 0; bit < 32U; ++bit) {
uint32_t walking_one = 1UL << bit;

*cell = walking_one;
if (*cell != walking_one) {
return false;
}

*cell = ~walking_one;
if (*cell != ~walking_one) {
return false;
}
}
return true;
}

字节通道测试必须使用 8 位指针:

static bool byte_lane_test(void)
{
/* 8 位访问会让 FEMC 使用 DQM0/DQM1 控制相应字节。 */
volatile uint8_t *mem = (volatile uint8_t *)SDRAM_BASE;

for (size_t i = 0; i < 1024U; ++i) {
mem[i] = (uint8_t)(i ^ (i >> 3));
}
for (size_t i = 0; i < 1024U; ++i) {
if (mem[i] != (uint8_t)(i ^ (i >> 3))) {
return false;
}
}
return true;
}

工程中的 main.c 还会在失败时打印阶段、地址、期望值和实际值,便于把现象对应到具体连接。

第六步:使用标准 west 命令编译​

在 HPM6E70 工程根目录执行:

west build -p always `
-b dshanmcu_hpm6e70/hpm6e70 `
-d build-sdram `
apps/sdram_test

本工程已经用该命令完成编译,构建目录中应出现:

build-sdram/zephyr/zephyr.elf
build-sdram/zephyr/zephyr.bin
build-sdram/zephyr/zephyr.hex
build-sdram/zephyr/zephyr.dts
build-sdram/zephyr/.config

烧录当前构建:

west flash -d build-sdram --skip-rebuild

第七步:根据串口输出判断结果​

测试通过时,串口会依次出现每个阶段的 PASS,最后一行应为:

SDRAM TEST PASSED: 32 MiB verified

完整 SDRAM 测试通过时的真机输出

真机实测输出:固件由本课流程构建并经 CH347F 烧录,启动横幅显示构建分支 zsg_v0.7.0,Step 1 可见 FEMC 时钟 133333333 Hz 与 SDRAM 映射范围 0x40000000..0x41ffffff。

如果失败,不要只记录“SDRAM 不工作”。先看方括号中的阶段:

  • pre-kernel init:检查 FEMC 时钟、引脚复用和时序;
  • data-lines:重点检查 DQ 数据线;
  • byte-lanes:重点检查 DQM0/DQM1;
  • address-lines:重点检查地址线、Bank 线和容量描述。

本课完成结果​

完成后,已经把一项板载硬件能力完整接入 Zephyr:应用通过 Kconfig 选择能力,通过 Overlay 启用硬件节点,通过设备树宏取得资源,并用可定位故障的程序验证真实存储器,而不是依赖应用中的固定地址和单次读写。