嵌入式裸机之SDRAM初始化学习笔记

注:以下内容有参考朱老师物联网大讲堂裸机部分课件

1.SDRAM引入

1.1 常见存储器介绍:

嵌入式裸机之SDRAM初始化学习笔记_第1张图片

DRAM介绍
同步动态随机存取内存(synchronous dynamic random-access memory,简称SDRAM),有一个同步接口的动态随机存取内存。
DDR:DDR就是DDR SDRAM,是SDRAM的升级版。(DDR:double rate,双倍速度的SDRAM)
DDR有好多代:DDR1 DDR2 DDR3 DDR4 LPDDR

1.2 DRAM特征:

1、中央处理器(CPU)与主机板上的主存储器(SDRAM)存取资料的的时钟(Clock)相同,所以称为同步。
2、容量大、价格低、掉电易失性、随机读写、总线式访问
3、SDRAM/DDR都属于动态内存(相对于静态内存SRAM),都需要先运行一段初始化代码来初始化才能使用

2.硬件电路

SDRAM在系统中属于SoC外接设备(外部外设。以前说过随着半导体技术发展,很多东西都逐渐集成到SoC内部去了。现在还长期在外部的一般有:Flash、SDRAM/DDR、网卡芯片如DM9000、音频Codec。现在有一些高集成度的芯片也试图把这几个集成进去,做成真正的单芯片解决方案。)
SDRAM通过地址总线和数据总线接口(总线接口)与SoC通信。

2.1 内存部分接口框图

单片内存框图
嵌入式裸机之SDRAM初始化学习笔记_第2张图片
开发板原理图上使用的是K4T1G164QQ,但是实际开发板上贴的不是这个,是另一款。但是这两款是完全兼容的,进行软件编程分析的时候完全可以参考K4T1G164QQ的文档。

全球做SDRAM的厂商不多,二线厂家做的产品参数都是向一线厂家(三星、KingSton)看齐,目的是兼容一线厂家的设计,然后让在意成本的厂商选择它的内存芯片替代一线厂家的内存芯片。SDRAM的这个市场特征就导致这个东西比较标准化,大部分时候细节参数官方(芯片原厂家)都会给你一个参考值。

内存命名规则解读
K4T1G164QE:
K表示三星产品,4表示是DRAM,T表示产品号码,1G表示容量(1Gb,等于128MB,我们开发板X210上一共用了4片相同的内存,所以总容量是128×4=512MB)16表示单芯片是16位宽的,4表示是4bank。

2.2 S5PV210部分的接口框图

嵌入式裸机之SDRAM初始化学习笔记_第3张图片

2.3 数据手册

2.3.1 S5PV210的数据手册

MEMORY ADDRESS MAP
嵌入式裸机之SDRAM初始化学习笔记_第4张图片

2.3.2 内存芯片的数据手册

嵌入式裸机之SDRAM初始化学习笔记_第5张图片

2.4 总结

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)

3.汇编初始化SDRAM实现过程

根据数据手册的步骤对照代码如下(sdram_init.S)

DDR2内存类型的初始化顺序(共28步):
我们以DMC0为例,DMC1同理
1.为了给控制器和内存设备提供稳定的电源,控制器必须断言并保持CKE在逻辑低电平。
然后提供稳定的时钟。注意:XDDR2SEL应该在高位,以保持CKE在低位。
我们的驱动强度设为2X,所以我们就需要将0x0000AAAA写入对应的寄存器
嵌入式裸机之SDRAM初始化学习笔记_第6张图片

嵌入式裸机之SDRAM初始化学习笔记_第7张图片


.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
嵌入式裸机之SDRAM初始化学习笔记_第8张图片

/* 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]

嵌入式裸机之SDRAM初始化学习笔记_第9张图片

嵌入式裸机之SDRAM初始化学习笔记_第10张图片
4.设置PhyControl0.ctrl_start位域为“1”。
嵌入式裸机之SDRAM初始化学习笔记_第11张图片

	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]
	

5.设置ConControl,现在自动刷新应该是关闭的
在这里插入图片描述

	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]

嵌入式裸机之SDRAM初始化学习笔记_第12张图片

对照数据手册可知
BL=4
嵌入式裸机之SDRAM初始化学习笔记_第13张图片
chip数量1
在这里插入图片描述
mem类型DDR2
嵌入式裸机之SDRAM初始化学习笔记_第14张图片
动态自动刷新关闭
在这里插入图片描述
其他部分也是一一对应的,这里不再描述。

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]

嵌入式裸机之SDRAM初始化学习笔记_第15张图片
chip bank =8
嵌入式裸机之SDRAM初始化学习笔记_第16张图片
根据2.3.2章节可知COL= 10,ROW=14
嵌入式裸机之SDRAM初始化学习笔记_第17张图片

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]

嵌入式裸机之SDRAM初始化学习笔记_第18张图片
嵌入式裸机之SDRAM初始化学习笔记_第19张图片
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

4.添加DDR初始化到start.S中

我们在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 .
	

5.重定位

因为我们已经对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即可。

你可能感兴趣的:(嵌入式裸机笔记)