011.SDAM配置实践|千篇笔记实现嵌入式全栈/裸机篇
⚠️裸机仓库: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的测试,再来比较体验差异~
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)