注:以下内容有参考朱老师物联网大讲堂裸机部分课件
DRAM介绍
同步动态随机存取内存(synchronous dynamic random-access memory,简称SDRAM),有一个同步接口的动态随机存取内存。
DDR:DDR就是DDR SDRAM,是SDRAM的升级版。(DDR:double rate,双倍速度的SDRAM)
DDR有好多代:DDR1 DDR2 DDR3 DDR4 LPDDR
1、中央处理器(CPU)与主机板上的主存储器(SDRAM)存取资料的的时钟(Clock)相同,所以称为同步。
2、容量大、价格低、掉电易失性、随机读写、总线式访问
3、SDRAM/DDR都属于动态内存(相对于静态内存SRAM),都需要先运行一段初始化代码来初始化才能使用
SDRAM在系统中属于SoC外接设备(外部外设。以前说过随着半导体技术发展,很多东西都逐渐集成到SoC内部去了。现在还长期在外部的一般有:Flash、SDRAM/DDR、网卡芯片如DM9000、音频Codec。现在有一些高集成度的芯片也试图把这几个集成进去,做成真正的单芯片解决方案。)
SDRAM通过地址总线和数据总线接口(总线接口)与SoC通信。
单片内存框图
开发板原理图上使用的是K4T1G164QQ,但是实际开发板上贴的不是这个,是另一款。但是这两款是完全兼容的,进行软件编程分析的时候完全可以参考K4T1G164QQ的文档。
全球做SDRAM的厂商不多,二线厂家做的产品参数都是向一线厂家(三星、KingSton)看齐,目的是兼容一线厂家的设计,然后让在意成本的厂商选择它的内存芯片替代一线厂家的内存芯片。SDRAM的这个市场特征就导致这个东西比较标准化,大部分时候细节参数官方(芯片原厂家)都会给你一个参考值。
内存命名规则解读
K4T1G164QE:
K表示三星产品,4表示是DRAM,T表示产品号码,1G表示容量(1Gb,等于128MB,我们开发板X210上一共用了4片相同的内存,所以总容量是128×4=512MB)16表示单芯片是16位宽的,4表示是4bank。
1、一块内存的数据总线是16位,两块并联得到32位,我们共有4块
2、从芯片名称(K4T1G164QQ)中的1G可知每一块的大小为1Gb,等于128MB,所以我们总共的内存大小为512MB
3、两个内存端口分别连接了两块内存所以各自端口的大小为256MB
由数据手册可知:
DRAM0:内存地址范围:0x20000000~0x3FFFFFFF(512MB),对应引脚是Xm1xxxx
DRAM1: 内存地址范围:0x40000000~0x7FFFFFFF(1024MB),对应引脚是Xm2xxxx
所以内存合法地址是:0x20000000~0x2FFFFFFF(256MB) + 0x40000000~0x4FFFFFFF(256MB)
DDR2内存类型的初始化顺序(共28步):
我们以DMC0为例,DMC1同理
1.为了给控制器和内存设备提供稳定的电源,控制器必须断言并保持CKE在逻辑低电平。
然后提供稳定的时钟。注意:XDDR2SEL应该在高位,以保持CKE在低位。
我们的驱动强度设为2X,所以我们就需要将0x0000AAAA写入对应的寄存器
.global sdram_asm_init
sdram_asm_init:
ldr r0, =0xf1e00000
ldr r1, =0x0
str r1, [r0, #0x0] //这部分无相关解释,我们暂且保留
/* DMC0 Drive Strength (Setting 2X) */
ldr r0, =ELFIN_GPIO_BASE // 基地址:0xE0200000
// MP1_0DRV_SR_OFFSET=0x3CC 寄存器中对应0b10,就是2X
ldr r1, =0x0000AAAA
str r1, [r0, #MP1_0DRV_SR_OFFSET]
ldr r1, =0x0000AAAA
str r1, [r0, #MP1_1DRV_SR_OFFSET]
ldr r1, =0x0000AAAA
str r1, [r0, #MP1_2DRV_SR_OFFSET]
ldr r1, =0x0000AAAA
str r1, [r0, #MP1_3DRV_SR_OFFSET]
ldr r1, =0x0000AAAA
str r1, [r0, #MP1_4DRV_SR_OFFSET]
ldr r1, =0x0000AAAA
str r1, [r0, #MP1_5DRV_SR_OFFSET]
ldr r1, =0x0000AAAA
str r1, [r0, #MP1_6DRV_SR_OFFSET]
ldr r1, =0x0000AAAA
str r1, [r0, #MP1_7DRV_SR_OFFSET]
ldr r1, =0x00002AAA //根据数据手册,因为n的取值范围变为0~6导致的
str r1, [r0, #MP1_8DRV_SR_OFFSET]
2.设置PhyControl0.ctrl_start_point 和 PhyControl0.ctrl_inc位域根据时钟频率校正值。设置PhyControl0.ctrl_dll_on位域到’ 1 '来激活PHY DLL。
0x00101000就是设置ctrl_start_point和ctrl_inc
/* DMC0 initialization at single Type*/
ldr r0, =APB_DMC_0_BASE
ldr r1, =0x00101000 @PhyControl0 DLL parameter setting, manual 0x00101000
str r1, [r0, #DMC_PHYCONTROL0]
3.DQS清除:设置PhyControl1.ctrl_shiftc 和PhyControl1.ctrl_offsetc位域根据时钟频率和内存tAC参数来校正值。
ldr r1, =0x00000086 @PhyControl1 DLL parameter setting, LPDDR/LPDDR2 Case
str r1, [r0, #DMC_PHYCONTROL1]
4.设置PhyControl0.ctrl_start位域为“1”。
ldr r1, =0x00101002 @PhyControl0 DLL on
str r1, [r0, #DMC_PHYCONTROL0]
ldr r1, =0x00101003 @PhyControl0 DLL start
str r1, [r0, #DMC_PHYCONTROL0]
//获取锁存值
find_lock_val:
ldr r1, [r0, #DMC_PHYSTATUS] @Load Phystatus register value
and r2, r1, #0x7
cmp r2, #0x7 @Loop until DLL is locked
bne find_lock_val
and r1, #0x3fc0
mov r2, r1, LSL #18
orr r2, r2, #0x100000
orr r2 ,r2, #0x1000
orr r1, r2, #0x3 @Force Value locking
str r1, [r0, #DMC_PHYCONTROL0]
ldr r1, =0x0FFF2010 @ConControl auto refresh off
str r1, [r0, #DMC_CONCONTROL]
6.设置MemControl。此时,所有关机模式应关闭。
DMC0_MEMCONTROL=0x00202400
ldr r1, =DMC0_MEMCONTROL @MemControl BL=4, 1 chip, DDR2 type, dynamic self refresh, force precharge, dynamic power down off
str r1, [r0, #DMC_MEMCONTROL]
对照数据手册可知
BL=4
chip数量1
mem类型DDR2
动态自动刷新关闭
其他部分也是一一对应的,这里不再描述。
7.设置MemConfig0寄存器。如果有两个外部内存芯片,也设置MemConfig1寄存器
DMC0_MEMCONFIG_0 = 0x20F01323
ldr r1, =DMC0_MEMCONFIG_0 @MemConfig0 256MB config, 8 banks,Mapping Method[12:15]0:linear, 1:linterleaved, 2:Mixed
str r1, [r0, #DMC_MEMCONFIG0]
chip bank =8
根据2.3.2章节可知COL= 10,ROW=14
8.设置PrechConfig和PwrdnConfig寄存器。
PrechConfig这里使用的是默认值,实际配置PwrdnConfig在后面
ldr r1, =0xFF000000 @PrechConfig,预充电策略配置寄存器
str r1, [r0, #DMC_PRECHCONFIG]
ldr r1, =0xFFFF00FF @PwrdnConfig
str r1, [r0, #DMC_PWRDNCONFIG]
9.根据内存AC参数设置TimingAref, TimingRow, TimingData和TimingPower寄存器。
TimingAref:平均周期刷新间隔控制
TimingRow:AC计时寄存器为存储器的行
TimingData:交流计时寄存器的数据存储
TimingPower:AC计时寄存器的电源模式的记忆
ldr r1, =DMC0_TIMINGA_REF @TimingAref 7.8us*133MHz=1038(0x40E), 100MHz=780(0x30C), 20MHz=156(0x9C), 10MHz=78(0x4E)
str r1, [r0, #DMC_TIMINGAREF]
ldr r1, =DMC0_TIMING_ROW @TimingRow for @200MHz
str r1, [r0, #DMC_TIMINGROW]
ldr r1, =DMC0_TIMING_DATA @TimingData CL=3
str r1, [r0, #DMC_TIMINGDATA]
ldr r1, =DMC0_TIMING_PWR @TimingPower
str r1, [r0, #DMC_TIMINGPOWER]
10.如果需要QoS方案,设置QosControl0 ~ 15和QosConfig0~15寄存器。
QosControl0 ~ 15:服务质量控制寄存器
QosConfig0~15:服务质量配置寄存器
11.等待PhyStatus0.ctrl_locked位域更改为“1”。检查PHY DLL是否被锁定
12.PHY DLL补偿了内存运行过程中进程、电压和温度(PVT)变化引起的时延量变化。因此,为了可靠的运行,PHY DLL不应该关闭。它可以关闭,但运行在低频率。如果使用关闭模式,设置PhyControl0.ctrl_force位字段根据PhyStatus0校正值。位域来固定延迟量。清除
PhyControl0.ctrl_dll_on位域关闭DLL。
13.开机后确认稳定时钟是否发出最小200us
14.使用DirectCmd寄存器发出NOP命令来断言并将CKE保持在逻辑高级
ldr r1, =0x07000000 @DirectCmd chip0 Deselect
str r1, [r0, #DMC_DIRECTCMD]
15. 等待最低400ns
16. 使用DirectCmd寄存器发出一个PALL命令。
ldr r1, =0x01000000 @DirectCmd chip0 PALL
str r1, [r0, #DMC_DIRECTCMD]
17.使用DirectCmd寄存器发出EMRS2命令来编写操作参数。
ldr r1, =0x00020000 @DirectCmd chip0 EMRS2
str r1, [r0, #DMC_DIRECTCMD]
18.使用DirectCmd寄存器发出EMRS3命令来编写操作参数。
ldr r1, =0x00030000 @DirectCmd chip0 EMRS3
str r1, [r0, #DMC_DIRECTCMD]
19.使用DirectCmd寄存器发出EMRS命令来启用内存dll。
ldr r1, =0x00010400 @DirectCmd chip0 EMRS1 (MEM DLL on, DQS# disable)
str r1, [r0, #DMC_DIRECTCMD]
20.使用DirectCmd寄存器发出一个MRS命令来重置内存DLL。
ldr r1, =0x00000542 @DirectCmd chip0 MRS (MEM DLL reset) CL=4, BL=4
str r1, [r0, #DMC_DIRECTCMD]
21.使用DirectCmd寄存器发出一个PALL命令。
ldr r1, =0x01000000 @DirectCmd chip0 PALL
str r1, [r0, #DMC_DIRECTCMD]
22.使用DirectCmd寄存器发出两个自动刷新命令。
ldr r1, =0x05000000 @DirectCmd chip0 REFA
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x05000000 @DirectCmd chip0 REFA
str r1, [r0, #DMC_DIRECTCMD]
23.使用DirectCmd寄存器发出一个MRS命令来编写操作参数,而不需要重新设置
内存DLL。
ldr r1, =0x00000442 @DirectCmd chip0 MRS (MEM DLL unreset)
str r1, [r0, #DMC_DIRECTCMD]
24.等待至少200个时钟周期
25.使用DirectCmd寄存器发出EMRS命令来编写操作参数。如果不使用OCD校准,发出EMRS命令来设置OCD校准的默认值。然后,发出EMRS命令退出OCD校准模式,并对运行参数进行编程
ldr r1, =0x00010780 @DirectCmd chip0 EMRS1 (OCD default)
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x00010400 @DirectCmd chip0 EMRS1 (OCD exit)
str r1, [r0, #DMC_DIRECTCMD]
26.如果有两个外部内存芯片,对chip1内存设备执行步骤14~25。
我们实际是有的,所以根据上面的14~25,进行即可,注意芯片的选择
27.设置ConControl以打开自动刷新计数器。
ldr r1, =0x0FF02030 @ConControl auto refresh on
str r1, [r0, #DMC_CONCONTROL]
28.如果需要关机模式,则设置MemControl寄存器。
ldr r1, =0x00202400 @MemControl BL=4, 2 chip, DDR2 type, dynamic self refresh, force precharge, dynamic power down off
str r1, [r0, #DMC_MEMCONTROL]
DMC1 同样按照上面的28步进行设置即可,最后函数记得返回
// 函数返回
mov pc, lr
我们在start.S中调用sdram_asm_init进行DDR的初始化。
start.S源文件
#define WTCON 0xE2700000
#define SVC_STACK 0xd0037d80
.global _start // 把_start链接属性改为外部,这样其他文件就可以看见_start了
_start:
// 第1步:关看门狗(向WTCON的bit5写入0即可)
ldr r0, =WTCON
ldr r1, =0x0
str r1, [r0]
// 第2步:设置SVC栈
ldr sp, =SVC_STACK
// 第3步:开/关icache
mrc p15,0,r0,c1,c0,0; // 读出cp15的c1到r0中
//bic r0, r0, #(1<<12) // bit12 置0 关icache
orr r0, r0, #(1<<12) // bit12 置1 开icache
mcr p15,0,r0,c1,c0,0;
// 第4步:初始化ddr
bl sdram_asm_init
// 第5步:重定位
// adr指令用于加载_start当前运行地址
adr r0, _start // adr加载时就叫短加载
// ldr指令用于加载_start的链接地址:0xd0024000
ldr r1, =_start // ldr加载时如果目标寄存器是pc就叫长跳转,如果目标寄存器是r1等就叫长加载
// bss段的起始地址
ldr r2, =bss_start // 就是我们重定位代码的结束地址,重定位只需重定位代码段和数据段即可
cmp r0, r1 // 比较_start的运行时地址和链接地址是否相等
beq clean_bss // 如果相等说明不需要重定位,所以跳过copy_loop,直接到clean_bss
// 如果不相等说明需要重定位,那么直接执行下面的copy_loop进行重定位
// 重定位完成后继续执行clean_bss。
// 用汇编来实现的一个while循环
copy_loop:
ldr r3, [r0], #4 // 源
str r3, [r1], #4 // 目的 这两句代码就完成了4个字节内容的拷贝
cmp r1, r2 // r1和r2都是用ldr加载的,都是链接地址,所以r1不断+4总能等于r2
bne copy_loop
// 清bss段,其实就是在链接地址处把bss段全部清零
clean_bss:
ldr r0, =bss_start
ldr r1, =bss_end
cmp r0, r1 // 如果r0等于r1,说明bss段为空,直接下去
beq run_on_dram // 清除bss完之后的地址
mov r2, #0
clear_loop:
str r2, [r0], #4 // 先将r2中的值放入r0所指向的内存地址(r0中的值作为内存地址),
cmp r0, r1 // 然后r0 = r0 + 4
bne clear_loop
run_on_dram:
// 长跳转到led_blink开始第二阶段
ldr pc, =led_blink // ldr指令实现长跳转
// 从这里之后就可以开始调用C程序了
//bl led_blink // bl指令实现短跳转
// 汇编最后的这个死循环不能丢
b .
因为我们已经对DDR进行了初始化,所以我们有大片的内存可以使用了,所以就进行重定位,让代码在DMC0的0x20000000处开始运行。
重定位详情可参考之前笔记------->嵌入式裸机课程之C语言程序调用和重定位学习笔记
这里我们直接修改链接地址即可,我们采用的是链接脚本的方法指定链接地址
link.lds
SECTIONS
{
. = 0x20000000;
.text : {
start.o
sdram_init.o
* (.text)
}
.data : {
* (.data)
}
bss_start = .;
.bss : {
* (.bss)
}
bss_end = .;
}
最后在主Makefile中添加sdram_init.S即可。