本帖最后由 微末凡尘 于 2014-7-29 23:37 编辑
Helper2416跳到内存运行
HELPER2416开发板助学计划
在谈这个问题之前,我们先回顾一下helper2416的启动方式,它分为两种启动方式,其一为IROM模式下的SD卡启动,这个我们暂不研究。另一个就是传统的nandflash启动。在使用nandflash启动的时候,地址的0x00000000起始处映射的是8KB的stepping stone,也就是图1最右边那个映射图中绿色的SRAM块。
为什么我们要提这个呢?我 上次发了一个帖子,点亮了LED灯。那个程序就是利用helper2416官方提供的烧写工具,从SD卡启动,利用SD卡将nandflash里面的裸机代码放到stepping stone中。然后当我们切换回nandflash启动时,代码就在0x00000000开始的这个8K的stepping stone运行。由于我们代码量比较少,这样完全没有问题。但是一般的启动过程,都会进行一个搬移工作,也就是将代码搬移到内存中运行,所以最近就想着琢磨琢磨内存。Helper2416的板子有两种内存,一种是128M的,一种是64M的。我手上的是64M的DDR2版本的,就以这个为例来谈这个问题啦。
板子上面这块DDR2的芯片型号是HSPS5162GFR-S5C,这个手册在官方压缩包里面并没有给出,我找到一份,贡献给大家啦。
H5PS5162GFRseries(Rev1.2)_PDF_规格书.pdf
(2.39 MB, 下载次数: 11)
在这个里面,有这样的描述 “512Mb DDR2 SDRAM ” 说明这个芯片是512Mb即 64MB(这里小写的b代表bit-位,大写的B代表Byte-字节)。
接着我们当然是看我们的芯片手册里面的相关部分了。芯片手册中,内存相关章节是第6章,MOBILE DRAM CONTROLLER 。由于大家手上都有英文手册,我这里就不再粘贴出来了。OVERVIEW里面描述,S3C2416支持3种内存,1SDRAM(C传统的2440多采用),2DDR(一般市面上的6410开发板采用这种内存),3DDR2(Helper2416采用的就是这种,市面上一些210开发板也是使用的这种内存。)。2416这个DRAM控制器有2个chip select signal(分别对应两个memory bank)这个如下图1,他们用来使用2个SDRAM,或者2个DDR,或者2个DDR2。但是需要特别注意的是,不能混合使用,仅能使用两种相同的内存。
图1 内存分布
图1中,我们可以看到SDRAM有nsCS0以及nsCS1,这就是我们提到的两个
chip select signal。到底使用的是哪一个呢?我们可以从开发板的电路图2中看到,helper2416使用的是nsCS0.并且使用13根地址线,使用16根数据线。
图2 DDR2部分电路图
如何初始化内存呢,在我们芯片手册中给出了详细的说明。我们就根据这个流程来进行我们的初始化工作。
图3,内存初始化流程
我先一次性将代码粘贴出来,然后一段一段解析。
.text
.global init_mem
#define ELFIN_MEMCTL_BASE 0x48000000
#define BANKCFG_OFFSET 0x00
#define BANKCON1_OFFSET 0x04
#define BANKCON2_OFFSET 0x08
#define BANKCON3_OFFSET 0x0c
#define REFRESH_OFFSET 0x10
#define TIMEOUT_OFFSET 0x14
#define INIT_NORMAL 0x0
#define INIT_PALL 0x1
#define INIT_MRS 0x2
#define INIT_EMRS 0x3
#define INIT_MASK 0x3
#define PHYS_SDRAM_1 0x30000000 /* SDRAM Bank #1 */
#define PHYS_SDRAM_1_SIZE 0x04000000 /* 64 MB */
#define CFG_BANK_CFG_VAL_DDR2 0x00049253
#define CFG_BANK_CON2_VAL_DDR2 0x006E0035
init_mem:
ldr r4, =ELFIN_MEMCTL_BASE
/* Step 1: BANKCFG Setting */
ldr r2, =CFG_BANK_CFG_VAL_DDR2
str r2, [r4, #BANKCFG_OFFSET]
ldr r1, =0x44000040
str r1, [r4, #BANKCON1_OFFSET]
/* Step 2: BANKCON2 Setting */
ldr r2, =CFG_BANK_CON2_VAL_DDR2
str r2, [r4, #BANKCON2_OFFSET]
/* Step 3: issue PALL */
orr r2, r1, #INIT_PALL
str r2, [r4, #BANKCON1_OFFSET]
/* Step 4: Issue a EMRS2 command */
ldr r2, =0x80000000
str r2, [r4, #BANKCON3_OFFSET]
orr r2, r1, #INIT_EMRS
str r2, [r4, #BANKCON1_OFFSET]
/* Step 5: Issue a EMRS3 command */
ldr r2, =0xc0000000
str r2, [r4, #BANKCON3_OFFSET]
orr r2, r1, #INIT_EMRS
str r2, [r4, #BANKCON1_OFFSET]
/* Step 6: Issue a EMRS1 command */
ldr r2, =0x44000000
str r2, [r4, #BANKCON3_OFFSET]
orr r2, r1, #INIT_EMRS
str r2, [r4, #BANKCON1_OFFSET]
/* Step 7: issue MRS */
ldr r2, =0x44000130
str r2, [r4, #BANKCON3_OFFSET]
orr r2, r1, #INIT_MRS
str r2, [r4, #BANKCON1_OFFSET]
/* Step 8: issue PALL */
orr r2, r1, #INIT_PALL
str r2, [r4, #BANKCON1_OFFSET]
/* Step 9: write 0xff into the refresh timer */
mov r3, #0xff
str r3, [r4, #REFRESH_OFFSET]
/* Step 10: wait more than 120 clk */
mov r3, #0x200
10: subs r3, r3, #1
bne 10b
/* Step 11: issue MRS */
ldr r2, =0x44000030
str r2, [r4, #BANKCON3_OFFSET]
orr r2, r1, #INIT_MRS
str r2, [r4, #BANKCON1_OFFSET]
/* Step 12: Issue a EMRS1 command */
ldr r2, =0x47800030
str r2, [r4, #BANKCON3_OFFSET]
orr r2, r1, #INIT_EMRS
str r2, [r4, #BANKCON1_OFFSET]
ldr r2, =0x44000030
str r2, [r4, #BANKCON3_OFFSET]
orr r2, r1, #INIT_EMRS
str r2, [r4, #BANKCON1_OFFSET]
/* Step 13: write 0x412 into the refresh timer */
mov r3, #0x412
str r3, [r4, #REFRESH_OFFSET]
/* Step 14: Normal Mode */
orr r2, r1, #INIT_NORMAL
str r2, [r4, #BANKCON1_OFFSET]
mov pc, lr 复制代码 芯片手册初始化流程(上面有一张图)中
1. Setting the BANKCFG & BANKCON1, 2, 3
ldr r4, =ELFIN_MEMCTL_BASE ,我们首先将内存这块的基地址0x48000000赋值给r4
/* Step 1: BANKCFG Setting */ ldr r2, =0x00049253
str r2, [r4, #BANKCFG_OFFSET]
然后将0x00049253(二进制 0000_0000_0000_0100_1001_0010_0101_0011)写入BANKCON1
ldr r1, =0x44000040
str r1, [r4, #BANKCON1_OFFSET]
我们找到寄存器,得到如图4,信息:
图4-BANKCFG
至于为什么这么配置,我们可以看我们的DDR2内存芯片手册(上面已经给出):
这里面指出,Row Address为13bit,Column Address为10bit。这里我们其实不用配置RASBW1、CASBW1、ADDRCFG1,但是它既然在这里,我们顺便给它一个值也没什么影响。
ldr r1, =0x44000040 str r1, [r4, #BANKCON1_OFFSET]
这一句就是将BANKCON1配置为如下:
图5 BANKCON1
/* Step 2: BANKCON2 Setting */
ldr r2, =0x006E0035
str r2, [r4, #BANKCON2_OFFSET]
将BANKCON2配置如下:
图6 BANKCONF2
至于上面这些参数为什么这么选择,我一时也没有找到有效的参考信息,不过差查看我们的内存的芯片,在后面几项都写的5,应该是表示可以支持的为5,而S3C2416显然没有支持到5,所以我就直接参考uboot的参数选择了。
做完如上这写,我们的第一步已经完成。为什么没有配置BANKCONF3呢?答:这个寄存器里面没有什么需要配置的,对它的描述如下:Mobile DRAM (E)MRS Register,这个寄存器后面会用到。
再回到我们的初始化流程,它如是写道:
2. Wait 200us to allow DRAM power and clock stabilize.
它的意思是等待DRAM供电和时钟稳定,在这之前,时钟已经稳定,这里我们就直接忽略这一步。
3. Wait minimum of 400 ns then issue a PALL(pre-charge all) command. Program the INIT[1:0] to ‘01b’. This automatically issues a PALL(pre-charge all) command to the DRAM.
等待什么的,我们继续忽略 。就将INIT[1:0]改为01b。由于INIT是在BANKCON1寄存器中的,寄存器的图我们上面已经粘贴出来,这里就不重复粘贴,的操作如下:
/* Step 3: issue PALL */ orr r2, r1, #1
str r2, [r4, #BANKCON1_OFFSET]
这一步执行完毕,我们再继续。
4. Issue an EMRS command to EMR(2), provide LOW to BA0, High to BA1.
Program the INIT[1:0] of Control Register1 to ‘11b’ & BANKCON3[31]=’1b’
这句要将EMRS改为EMR2,使BA0为低,BA1为高。那么BA[1:0]值刚好为2.BA就是Bank Address,块地址。我们的DDR2芯片一共有4块。而我们的BA[1:0]G刚好有4个值。然后它说将INIT[1:0]置位‘11b’,这两位我们刚才操作过,设置为11表示执行EMRS命令。然后将BANKCON3[31]置为1,其实就使BA1为1,BA0为0.寄存器如图7, 个人觉得这个寄存器有点小问题,这个寄存器有三种模式EMR(1)/EMR(2)/EMR(3).寄存器的模式是根据最高两位来确定的。这里为EMRS(1)是最高两位是01,为EMRS(2)最高两位为10,为EMRS(3)是最高两位为11,而不应该像手册中那样全部都可以是10 。从这里,我们还可以看出,在流程中分为两行的,上面一行写的是目的,下面一行写的是具体操作。
图7 EMRS
图7 ,EMRS(2) EMRS(3)
其实,看懂这个之后,再实现代码也不那么纠结:
/* Step 4: Issue a EMRS2 command */
ldr r2, =0x80000000
str r2, [r4, #BANKCON3_OFFSET] BANKCON3[31]=’1b’
orr r2, r1, #0X3
str r2, [r4, #BANKCON1_OFFSET] INIT[1:0] 写 ‘11b’
我们接着瞧 ,
5. Issue an EMRS command to EMR(3), provide High to BA0 and BA1.
Program the INIT[1:0] of Control Register1 to ‘11b’ & BANKCON3[31:30]=’11b’
这一段和上一段写的地址都差不多,就写进去的值不一样。就不多描述。
/* Step 5: Issue a EMRS3 command */
ldr r2, =0xc0000000
str r2, [r4, #BANKCON3_OFFSET]
orr r2, r1, # 0X3
str r2, [r4, #BANKCON1_OFFSET]
6. Issue an EMRS to enable DLL and RDQS, nDQS, ODT disable.
这个操作其实更改的也是EMRS(1)部分。
/* Step 6: Issue a EMRS1 command */
ldr r2, =0x44000000
str r2, [r4, #BANKCON3_OFFSET]
orr r2, r1, #INIT_EMRS
str r2, [r4, #BANKCON1_OFFSET]
写入0x44000000后,寄存器如下:
图8 寄存器内容
7. Issue a MRS( Mode Register Set ) command for DLL reset.(Toissue DLL Reset command, provide HIGH to A8 and LOW to BA0-BA1, and A13-A15.) Program the INIT[1:0] to ‘10b’. & BANKCON3[8]=’1b’
到这里,出现了MRS,大意是 模式设置寄存器,那么EMRS就是 扩展模式设置寄存器。
/* Step 7: issue MRS */
ldr r2, =0x44000130
str r2, [r4, #BANKCON3_OFFSET]
orr r2, r1, #INIT_MRS
str r2, [r4, #BANKCON1_OFFSET]
其实就是操作的这么一位:
图9 MSR
8. Issue a PALL(pre-charge all) command.
Program the INIT[1:0] to ‘01b’. This automatically issues a PALL(pre-charge all) command to the DRAM.
/* Step 8: issue PALL */
orr r2, r1, #INIT_PALL
str r2, [r4, #BANKCON1_OFFSET]
这之后一直都是这样类似的操作,就不做过多的解释。
在内存初始化完成之后,我们就将我们垫脚石里面的代码搬运到内存里面去执行。( 本来这个搬运操作应该是搬运nandflash里面的代码到内存的。我们这里还没有初始化nandflash,就搬运垫脚石里面的代码练练手咯,以后会搬运nandflash里面的代码过去。让它的功能更加强大! ),搬运代码如下: copy_to_ram:
ldr r0, =0x00000000 @垫脚石——的开始地址
ldr r1, =0x30000000 @搬运的目的地址
add r3, r0, #1024*8 @垫脚石——的结束地址(我们的2416stepping stone是8K的,就用起始地址加上8K得到结束地址)
copy_loop:
ldr r2, [r0], #4 @将垫脚石里面的内容放入r2,然后地址+4
str r2, [r1], #4 @将r2的内容写入内存,内存地址+4 复制代码
搬运完成后,我们做一些初始化,来运行C语言程序。
init_stack:
ldr sp, =0x34000000 @初始化堆栈,将sp指向64M内存的最后一个地址处
mov pc, lr
clean_bss:
ldr r0, =bss_start @bss段,就是未初始化的变量存放的地方,这里将这片区域全部清0
ldr r1, =bss_end @bss_start和bss_end在连接器脚本文件中定义。
cmp r0,r1 @比较bss段起始和结尾地址
moveq pc,lr @如果为空,则返回。不为空,继续往下执行
clean_loop:
mov r2, #0 @向r2中写0
str r2, [r0], #4 @把r2中的值写入r0指向的地址,r0+4
cmp r0,r1 @比较r0和r1
bne clean_loop @如果不等,跳回clean_loop继续执行清0工作。 复制代码 这些都做完了,我们就要跳到C里面执行啦:
reset:
bl set_svc @带链接相对跳转,执行完成后mov pc,lr返回这里
bl disable_watchdog @带链接相对跳转,执行完成后mov pc,lr返回这里
bl disable_interrupt @带链接相对跳转,执行完成后mov pc,lr返回这里
bl disable_mmu @带链接相对跳转,执行完成后mov pc,lr返回这里
bl init_clock @带链接相对跳转,执行完成后mov pc,lr返回这里
bl init_mem @带链接相对跳转,执行完成后mov pc,lr返回这里
bl copy_to_ram @带链接相对跳转,执行完成后mov pc,lr返回这里
bl init_stack @带链接相对跳转,执行完成后mov pc,lr返回这里
bl clean_bss @带链接相对跳转,执行完成后mov pc,lr返回这里
ldr pc, =main @绝对跳转,跳到main处执行。 复制代码 主函数代码及其简单,如下: 就是让CPB1输出低电平,点亮LED灯。
#define GPBCON (volatile unsigned long*)0x56000010
#define GPBDAT (volatile unsigned long*)0x56000014
int main()
{
*GPBCON = 0XFFFFFFF7;
*GPBDAT = 0XFFFFFFFD;
return 0;
} 复制代码
这样整个工程就完毕了。最下方有整个代码,可以下载下来瞧瞧。
下面补充2个东西:
1,相对跳转和绝对跳转:
b指令就是一个相对跳转指令。比如有两端代码a,b。a链接地址在2,b链接地址在7.采用相对跳转,要从a跳到b,它会先计算b-a的值,然后在a上加上这个值,跳过去。
而直接给pc指针赋值,属于绝对跳转。我给pc指针赋值7,它就跳到地址7处执行。
这样说大家可能绝对这两个指令其实也没啥区别。但是,看看我们上面那个程序。在脚本文件中,我定义链接起始地址为0x30000000,这个地址在内存。可是,我上一个帖子中,程序一直不在内存中运行,就是因为我一直使用的b这类指令,都是相对跳转,跳来跳去,一直都在stepping stone里面。
SECTIONS
{
<font color="#ff0000"><b> . = 0x30000000; //链接起始地址</b></font>
. = ALIGN(4);
.text :
{
start.o(.text)
*(.text)
}
……
} 复制代码 我们现在要让程序从stepping stone跑到内存中去运行,就使用绝对跳转。跳到main函数的链接地址去。这样就跑到内存了。
第2个问题就是,这个工程和第一个工程相比,添加了一个mem.o文件和一个main.c文件。这样Makefile文件就要做一点修改,不然编译会报错。修改的也不多,就是加上两个依赖。如下图,增加的地方为红色。
all : start.o <b><font color="#ff0000">mem.o main.o</font></b>
arm-linux-ld -Tled.lds -o led.elf $^
arm-linux-objcopy -O binary led.elf led.bin
%.o : %.S
arm-linux-gcc -g -c $^
%.o : %.c
arm-linux-gcc -g -c $^
.PHONY:clean
clean :
rm *.o *.elf *.bin 复制代码 至于最后这个奇葩,如下。是由于我用samba共享目录之后用UltraEdit在Windows环境下编辑。每次编辑后,它都会自动加一个.bak备份文件,我很不喜欢。但如果直接在clean后面加上 rm *.bak,我如果一直在linux下编辑,就不会有.bak文件,使用make clean老是有个警告也很不爽,于是乎,我就又加了一条规则咯。希望这个东西不要给大家带来困惑。
.YHONY:clean_bak
clean_bak :
rm *.bak 复制代码
代码如下:
led2.zip
(3.46 KB, 下载次数: 15)
里面有我编译好的bin文件。使用方法可参考开发板手册,或者我上一个帖子。
其实这个启动代码功能还不完备,我们的MMU还没有使用,缓存也是关闭的,nandflash也还没有用起来。但是我们迈出了最伟大的一步,进入到了C的环境。后面的东西,我们就在C的环境下进行初始化。然后欢快的跑起来。
大家如果发现帖子里面的问题,或是我哪里写的不明白,有歧义,希望大家能够指出。
论坛ID:微末凡尘
提交时间:2014.07.29