⚠️裸机仓库:https://gitee.com/simonchina_carel_li/mini2440-bare-metal.git
⚠️Tag: 11-sdram

1. 目的

目前我们的固件运行还是使用SOC内部的SRAM,

它只有4KB的空间,

现在我们想解锁开发板上的64MB的SDRAM空间,从而跑更复杂的程序

2. 方案分析

再次贴出原理图,

请添加图片描述

目前的信息,

  • 地址线错位2bits连接,访问粒度为字访问(32bit)

  • SDRAM接在BANK6上,SDRAM的并联容量是64MB,那么可用的地址空间为0x30000000 ~ 0x0x34000000

  • LADDR24~25位段用来确定SDRAM片内的四层BANK之一

  • LnWBE0/1/2/3用来进行字节访问

我们需要配置好内存控制器,然后内存控制器就用使用我们期望的行为和参数进行匹配的SDRAM访问,

那么,怎么设置内存控制器呢?

3. 要配置哪些参数?

设置内存控制器,就是配置相关的寄存器,

我们要设置哪些关键行为和参数呢?

以我这个开发板上的EM63A165-6G(166MHz版本)为例,查询芯片手册,填写参数表,

参数 要设置成多少
CAS Latency (列选通潜伏期,简称 CL) 2/3 clocks
是否使用UB/LB,就是字节访问 使用
是否使用nWATT引脚(硬件等待) 不使用,没有此引脚,我们采用软件等待(Tacc)
BANK6总线宽度 32bit
BANK6储存器类型 MT 同步SDRAM
Trcd(RAS 到 CAS 的延迟) ≥ 18 ns
SCAN(列地址数) 9 bits
行地址数 13 bits
REFEN(SDRAM 刷新使能) 使能
TREFMD(SDRAM 刷新模式) CBR/自动刷新
Trp(SDRAM RAS 预充电时间) ≥ 18 ns
Trc (SDRAM 半行周期时间) ≥ 60 ns
刷新周期 8192 次 / 64ms, 7.8125us
BURST_EN(ARM 核突发(Burst)操作使能) 使能
SCKE_EN(SDRAM 掉电模式使能 SCKE 控制) 填0,不休眠
SCLK_EN(只在 SDRAM 访问周期期间 SCLK使能,以降低功耗) 填 1,访问时再使能时钟(为了省电)
BK76MAP(Bank6/7 存储器映射) 64M/64M
WBL(写突发长度) 0 代表 “Programmed Burst Length”(写突发长度跟着读突发长度走)。1 代表单字写。正常工作一律填 0
TM(测试模式) 正常使用绝对不能改,必须填 00
CL(CAS 等待时间(latency)) 该芯片支持 CL=2 或 3, 100MHz的hclk下强烈建议 (CL=3)
BT(突发类型) Sequential(线性顺序突发)
BL(突发长度) 此位必须固定填 000。因为 S3C2440 的内存控制器会自己通过硬件逻辑来连续发地址控制 Cache Line 填充,不需要开启 SDRAM 内部的突发计数器

不需要全部知道这些参数究竟什么意思,只要知道它们是内存控制器操作SDAM的行为模式和参数就行了,

4. 代码实现

4.1 SDRAM初始化

我们使用汇编实现,

其实C代码实现sdram的初始化可以,只不过如果用C实现,我们需要放在.data段复制和.bss段清零之后

这样的话sdram初始化之后,我们还需要重新进行新的.data段复制和.bss段清零, 显得复杂和臃肿

因此我们使用汇编实现,这样直接放在.data段复制和.bss段清零之前就好了。

创建汇编文件common/sdram.s,

查阅手册,对照上述的方案配置项表,配置这几个寄存器,具体的配置项见注释,


    .equ BWSCON,    0x48000000
    .equ BANKCON6,  0x4800001C
    .equ REFRESH,   0x48000024
    .equ BANKSIZE,  0x48000028
    .equ MRSRB6,    0x4800002C

.section .text
.global sdram_setup
sdram_setup:
    @ 初始化BWSCON
    @ 对 Bank 6 使用 UB/LB
    @ WAIT 禁止
    @ 数据总线宽度32bits
    ldr     r0, =BWSCON
    ldr     r1, [r0]
    bic     r1, r1, #(0b1111 << 24)
    orr     r1, r1, #((1 << 27) | (0 << 26) | (0b10 << 24))
    str     r1, [r0]
    
    @ 初始化BANKCON6
    ldr     r0, =BANKCON6
    ldr     r1, [r0]
    ldr     r2, =((0b11 << 15) | (0b1111 << 0))
    bic     r1, r1, r2
    @ 同步 DRAM
    @ Trcd >= 18ns, HCLK = 101.25MHz(周期9.88ns), 所以设置值为2个周期的延迟
    @ SCAN = 9bits
    ldr     r2, =((0b11 << 15) | (0b00 << 2) | (0b01 << 0))
    orr     r1, r1, r2
    str     r1, [r0]

    @ 初始化REFRESH
    @ SDRAM 刷新使能
    @ SDRAM 刷新模式 - CBR/自动刷新
    @ Trp >= 18ns, HCLK = 101.25MHz(周期9.88ns), 所以设置值为2个周期的延迟
    @ Trc = Trp + Tsrc >= 60ns, 所以Tsrc >= 42ns, Tsrc设置值为5个周期的延迟
    @ 刷新周期 7.8us, 对应值1260
    ldr     r0, =REFRESH
    ldr     r1, =((1 << 23) | (0 << 22) | (0 << 20) | (0b01 << 18) | (1260 << 0))
    str     r1, [r0]

    @ 初始化BANKSIZE
    @ ARM 核突发(Burst)操作使能
    @ 禁止 SDRAM 掉电模式
    @ 只在 SDRAM 访问周期期间 SCLK使能,以降低功耗
    @ Bank6/7 存储器映射 -  64MB/64MB
    ldr     r0, =BANKSIZE
    ldr     r1, =((1 << 7) | (0 << 5) | (1 << 4) | (0b001 << 0))
    str     r1, [r0]

    @ 初始化MRSRB6
    @ WBL写突发长度 - 突发(固定)
    @ TM测试模式 -  模式寄存器组(固定)
    @ CL CAS 等待时间(latency) - 3个时钟
    @ BT 突发类型 - Sequential(线性顺序突发/连续(固定))
    @ BL 突发长度 - S3C2440 的内存控制器会自己通过硬件逻辑来连续发地址控制 Cache Line 填充,不需要开启 SDRAM 内部的突发计数器
    ldr     r0, =MRSRB6
    ldr     r1, =((0 << 9) | (0 << 7) | (0b11 << 4) | (0 << 3) | (0 << 0))
    str     r1, [r0]

    bx lr

4.2 SDRAM测试验证

我们要验证什么东西?

  • 接线是否短路或虚焊

  • UB/LB 字节掩码是否生效

  • 读写性能评估

话不多说,上代码,具体细节看注释,

创建C文件sdram/main.c,

#include "s3c2440a.h"

// 宏定义 SDRAM 的基地址和容量
#define SDRAM_BASE          0x30000000
#define SDRAM_SIZE_BYTES    (64 * 1024 * 1024) // 64MB

#define printf              uart0_printf

/**
 * @brief SDRAM 综合测试函数
 */
void sdram_test()
{
    volatile uint32_t *pWord;
    volatile uint8_t  *pByte;
    uint32_t i, read_val;
    
    printf("\r\n===================================\r\n");
    printf("      SDRAM 诊断与性能测试开始      \r\n");
    printf("===================================\r\n");

    /*-------------------------------------------------------------------------
     * 测试一:地址线与数据线综合测试 (Address-in-Address Test)
     * 原理:将当前的物理地址作为数据,写入该物理地址中。
     * 作用:这是排查硬件布线短路最有效的算法。如果 A1 和 A2 短路,或者
     * D5 虚焊,读回来的数据绝对不等于它的物理地址。
     *------------------------------------------------------------------------*/
    printf("[1/3] 正在进行全盘 32-bit 地址映射与数据校验 (64MB)...\r\n");
    
    // 1. 填入数据
    for (i = 0; i < SDRAM_SIZE_BYTES; i += 4) {
        pWord = (volatile uint32_t *)(SDRAM_BASE + i);
        *pWord = (uint32_t)pWord; // 把指针本身的值(地址)当做数据写进去
        if (i % 0x100000 == 0) {
            printf("    已填入 %d MB\n", (i + 1) / (1024 * 1024));
        }
    }

    // 2. 读出校验
    for (i = 0; i < SDRAM_SIZE_BYTES; i += 4) {
        pWord = (volatile uint32_t *)(SDRAM_BASE + i);
        read_val = *pWord;
        if (read_val != (uint32_t)pWord) {
            printf("\r\n[!] 严重硬件错误: 32-bit 读写校验失败!\r\n");
            printf("    地址: 0x%x\r\n", (uint32_t)pWord);
            printf("    期望: 0x%x\r\n", (uint32_t)pWord);
            printf("    实际: 0x%x\r\n", read_val);
            printf("    异或: 0x%x (可用于排查哪根引脚短路)\r\n", (uint32_t)pWord ^ read_val);
            return;
        }
        if (i % 0x100000 == 0) {
            printf("    已校验 %d MB\n", (i + 1) / (1024 * 1024));
        }
    }
    printf("      -> 校验通过!数据线与地址线无短路/断路。\r\n");


    /*-------------------------------------------------------------------------
     * 测试二:UB/LB 字节掩码测试 (Byte Enable Test)
     * 作用:验证你的 BWSCON 寄存器中 UB/LB 设定是否生效,硬件 DQM 引脚是否正常。
     * 如果不正常,写 8-bit 数据会把其他 24-bit 冲刷掉。
     *------------------------------------------------------------------------*/
    printf("[2/3] 正在进行 UB/LB 字节掩码 (8-bit) 读写保护测试...\r\n");
    
    // 1. 先写一个 32-bit 的背景数据
    pWord = (volatile uint32_t *)SDRAM_BASE;
    *pWord = 0x00000000; 

    // 2. 用 8-bit 指针,只修改其中几个字节
    pByte = (volatile uint8_t *)SDRAM_BASE;
    pByte[0] = 0xAA; // 写入最低字节 (小端模式)
    pByte[2] = 0xCC; // 写入次高字节
    
    // 3. 再次用 32-bit 读出,验证未被修改的字节是否依然是 0
    read_val = *pWord;
    // 期望结果:0x00CC00AA (小端模式:高地址存高字节)
    if (read_val != 0x00CC00AA) {
        printf("\r\n[!] 严重硬件错误: UB/LB 字节掩码测试失败!\r\n");
        printf("    期望: 0x00CC00AA\r\n");
        printf("    实际: 0x%x\r\n", read_val);
        printf("    请检查 BWSCON 的 WS 位以及硬件 DQM 连线!\r\n");
        return;
    }
    printf("      -> 校验通过!字节掩码功能正常。\r\n");


    /*-------------------------------------------------------------------------
     * 测试三:读写性能评估 (带 Cache 提示)
     * 说明:在裸机中精确测速需要使用硬件定时器 (Timer)。
     * 这里提供一个 1MB 块拷贝的循环框架,如果你开启了 Timer,可以加上时间戳。
     *------------------------------------------------------------------------*/
    printf("[3/3] 正在进行 1MB 块顺序读写性能测试...\r\n");
    
    // 我们测试将 SDRAM 前 1MB 的数据拷贝到第 2 个 1MB 的区域
    volatile uint32_t *pSrc = (volatile uint32_t *)SDRAM_BASE;
    volatile uint32_t *pDst = (volatile uint32_t *)(SDRAM_BASE + 0x100000); // 偏移 1MB
    uint32_t words_1MB = (1024 * 1024) / 4;
    
    // -- TODO: 如果你有定时器驱动,在这里记录 start_time --
    // uint32_t start_time = timer_get_ticks();
    
    for (i = 0; i < words_1MB; i++) {
        pDst[i] = pSrc[i];
    }
    
    // -- TODO: 在这里记录 end_time,计算 MB/s --
    // uint32_t end_time = timer_get_ticks();
    // printf("      -> 耗时: %d ticks\r\n", end_time - start_time);
    
    printf("      -> 读写循环完成!\r\n");
    printf("===================================\r\n");
    printf(" SDRAM 测试全部通过!你的硬件像岩石一样稳定。\r\n");
    printf("===================================\r\n");
}

int main()
{
    sdram_test();
    for (;;);
    return 0;
}

4.3 运行

编译,make sdram,烧录,运行,

(为了方便示例,仅拿4M区域做验证)
请添加图片描述

其实,现阶段我们是没有开启MMU和Cache的,而且编译优化也未开启,其实SDRAM的性能测试结果肯定不尽人意,目前1MB块读写大概是2S~
先挖个坑,等后面开启MMU和Cache的测试,再来比较体验差异~

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐