S3C2410提供了外接ROM、 SRAM、 SDRAM、 NOR Flash、 NAND Flash的接口。 S3C2410外接存储器的空间被分为8 个BANKS,每BANK容量为128M:当访问BANKx(x从0到7,对应的地址范围(x*128M到(x+1)*128M-1,BANK6、7有稍微差别)时,片选信号nGCSx有效。本文所用的开发板,使了64M的NAND Flash和64M的SDRAM,NAND Flash不对应任何BANK,它是通过几组
寄存器来访问的,在上电后,NAND Flash开始的4k数据被自动地复制到芯片内部一个被称为“Steppingstone”的RAM上。Steppingstone被映射为地址0,上面的4k程序完成必要的初始化;SDRAM使用BANK6,它的物理起始地址6*128M=0x30000000。
四、实验内容分析
实验内容很简单,就是完成基本的初始化之后,把steppingstone的4K数据搬移到sdram中。然后在sdram中执行灯循环点亮程序。结合这个实验,也可以很清晰的明白,前面几个基本实验,从nand flash启动后,所有代码搬移到了steppingstone中,实际执行时也是在steppingstone中,也就是boot internal sram(4KB)中执行的,所以运行时域和加载时域都是0x00000000,设置的堆栈可以是1024,也可以是4096,但是注意一是最大为4096,二是保证不与可执行代码发生冲突。在这个程序中,运行时域和加载时域是不相同的。加载时域是0x00000000,但是运行时域是0x30000000。《s3c2410完全开发》对这个地方讲解不是太详细。经过实验,和王老师的帮助,弄清楚了到底怎么回事。现在关于运行时域和加载时域的具体分析如下:
根据nand flash的特点,初始代码的加载时域为0x00000000,也就是当前PC的值为0x00000000,两种跳转指令b(l)等只能相对寻址,最大范围是+/-32MBytes,所以如果不改变PC的话,不可能能利用b或者bl跳转到sdram的空间中。跳转指令ldr则不受此寻址空间的限制,可以进行绝对寻址。需要了解的一个细节就是,链接后所有的标号都是基于运行地址的,比如运行地址为0x30000000,那么第一个标号_start地址就是0x30000000,所以可以利用ldr的绝对寻址来完成到sdram的跳转。下面根据编写的sdram的反汇编来进行分析:
先来分析这个工程中主Makefile的语法和含义:
CFLAGS := -I./include //gcc的编译选项,表示要在当前目录的include目录下查找相对应的头文件
LDFLAGS := -Ttext 0x30000000 //ld的链接选项,表示在链接时将整个工程的代码段也就是Text段加载到0x30000000
OBJS := $(patsubst %.s, %.o, $(wildcard arch/*.s))//两个Makefile中函数的运用,在规则中,通配符会被自动展开。但在变量的定义和函数引用时,通配符将失效。这种情况下如果需要通配符有效,就需要使用函数“wildcard”,它的用法是:$(wildcard PATTERN...)。在Makefile中,它被展开为已经存在的、使用空格分开的、匹配此模式的所有文件列表,而patsubst函数则是替换函数,上面的意思也就是将arch文件夹下面所有以.s结尾的文件全部展开,然后将这些以.s结尾的文件全部替换为以.o结尾
OBJS += $(patsubst %.c, %.o, $(wildcard init/*.c))
all: sdram
sdram: $(OBJS)
$(LD) $(LDFLAGS) $^ -o $@.o //将所有的.o文件也就是目标文件全部链接到sdram.o文件中
$(OBJDUMP) -D $@.o >$@_s //将链接成的目标文件sdram.o中的符号地址全部输出到sdram_s中
$(OBJCOPY) -O binary -S $@.o $@ //利用objcopy将目标文件sdram.o转化为二进制的格式sdram
clean:
find . -name "*.o" | xargs rm -f
$(RM) sdram*
debug:
@echo "OBJS: $(OBJS)"
# compile rules
%.o: %.s
$(CC) $(CFLAGS) -c $< -o $@
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
在来分析在使用SDRAM之前需要初始化多个寄存器,这些寄存器很多都是类似的,并且由于我们只是使用BANK6所以大部分寄存器不用理会。一般我们将初始化的一些代码,主要是对寄存器操作的代码利用汇编代码来编写:
@ WDT Register
.equ WDTCON, 0x53000000
@ Memory Control Register Base Address
.equ MEM_CTL_BASE, 0x48000000
@ Sdram Base Address
.equ SDRAM_BASE, 0x30000000
@ Stack top address
.equ stack_top, 0x34000000
@
@ start
@
.text
.global _start
_start:
@ disable watch dog timer
mov r0, #WDTCON
mov r1, #0x0
str r1, [r0]
@ memory setup 初始化SDRAM寄存器
bl memsetup
@ copy the 4K code from steppingstone 将steppingstone的4KB的数据复制到sdram的起始处
bl copy_steppingston_to_sdram
@ jump to sdram space
ldr pc, =setup_stack //为什么将setup_stack标号的地址赋给pc,就能跳转到SDRAM中执行,因为在前面利用copy_steppingston_to_sdram已经将程序从steppingstone复制到了SDRAM中来,所以当前语句的下一句也就是在SDRAM中要执行的下一句
setup_stack:
ldr sp, =stack_top //设置堆栈,一般将堆栈设置在BANK7中最大只能为4K
bl main //跳转到主函数去执行,此时main函数也已经复制到SDRAM中来
stop:
b stop
@
@ sub routines
@
@ r1: src base address
@ r2: dst base address
@ r3: data block length
copy_steppingston_to_sdram: //将steppingstone中4KB的内容复制到sdram中
mov r1, #0x00000000 //基地址也就是steppingstone的地址
ldr r2, =SDRAM_BASE //目的地址也就是sdram的地址
mov r3, #4096 //复制的字节数也就是4KB
1:
@ auto-indexing
@ first transfer, and then write back to the base register r1
ldr r4, [r1], #4 //每次复制4个字节
str r4, [r2], #4
@ r1 is equal to counter
cmp r1, r3
bne 1b
mov pc, lr
@ r1: memory control register base address
@ r2: memory control register table address
@ r3: r1+13 words(because there is 13 registers)
memsetup: //SDRAM寄存器设置初始化
mov r1, #MEM_CTL_BASE //与SDRAM相关的寄存器是从0x48000000地址开始的,每个寄存器占4个字节
adrl r2, mem_cfg_val //adrl中等范围的地址读取,将mem_cfg_val标号表示的地址值读取到r2寄存器中来
add r3, r1, #13*4 //因为总共有13个寄存器,每个寄存器占4个字节,r3作为循环的结束条件
1:
@ write initial values to registers
ldr r4, [r2], #4 //取出r2寄存器中的地址值对应的值赋给r4,然后r2加上4,指向下一个初始值
str r4, [r1], #4 //将r4的值赋值给r1对应的寄存器,然后将r1加上4,指向下一个寄存器
cmp r1, r3
bne 1b
mov pc, lr //返回到调用处
.align 4
mem_cfg_val://相当于定义一个寄存器初值表,将每个寄存器的初始值通过一个标号全部定义在一起,因为每个寄存器的地址是规律的也就是相差4个字节所以可以很方便的通过查表来赋值
.long 0x22111110 @ BWSCON
.long 0x00000700 @ BANKCON0
.long 0x00000700 @ BANKCON1
.long 0x00000700 @ BANKCON2
.long 0x00000700 @ BANKCON3
.long 0x00000700 @ BANKCON4
.long 0x00000700 @ BANKCON5
.long 0x00018005 @ BANKCON6
.long 0x00018005 @ BANKCON7
.long 0x008e07a3 @ REFRESH
.long 0x000000b2 @ BANKSIZE
.long 0x00000030 @ MRSRB6
.long 0x00000030 @ MRSRB7
.end
现在来分析这13个寄存器的初始值,寄存器每位分别表示什么含义:
1.BWSCON:对应BANK0-BANK7,每BANK使用4位。这4位分别表示:
a.STx:启动/禁止SDRAM的数据掩码引脚,对于SDRAM,此位为0;对于
SRAM,此位为1。
b.WSx:是否使用存储器的WAIT信号,通常设为0
c.DWx:使用两位来设置存储器的位宽:00-8位,01-16位,10-32位,
11-保留。
d.比较特殊的是BANK0对应的4位,它们由硬件跳线决定,只读。 对于本开发板,使用两片容量为32Mbyte、位宽为16的SDRAM组成容量为64Mbyte、位宽为32的存储器,所以其BWSCON相应位为:0010。对于本开发板,BWSCON可设为0x22111110:其实我们只需要将BANK6对应的4位设为0010即可,其它的是什么值没什么影响,这个值是参考手册上给出的。
2.BANKCON0-BANKCON5:我们没用到,使用默认值0x00000700即可
3.BANKCON6-BANKCON7:设为0x00018005 ,在8个BANK中,只有BANK6和BANK7可以使用SRAM或SDRAM,所以BANKCON6-7与BANKCON0-5有点不同:
a.MT([16:15]):用于设置本BANK外接的是SRAM还是SDRAM:SRAM-00,SDRAM-11
b.当MT=11时,还需要设置两个参数:
Trcd([3:2]):RAS to CAS delay,设为推荐值01,因为在这里我们知道RAS是0:12也就是13位,而CAS是0:8也就是9位,所以delay就是3
SCAN([1:0]):SDRAM的列地址位数,对于本开发板使用的SDRAM HY57V561620CT-H,列地址位数为9,所以SCAN=01。如果使用其他型号的SDRAM,您需要查看它的数据手册来决定SCAN的取值:00-8位,01-9位,10-10位。
4.REFRESH(SDRAM refresh control register):设为0x008e0000+ R_CNT ,其中R_CNT用于控制SDRAM的刷新周期,占用REFRESH寄存器的[10:0]位,23位为SDRAM Refresh Enable一般为1允许自动和自我刷新,22位为SDRAM刷新的模式,是auto还是self,一般我们选择auto.
它的取值可如下计算(SDRAM时钟频率就是HCLK):
R_CNT = 2^11 + 1 – SDRAM时钟频率(MHz) * SDRAM刷新周期(uS)
在未使用PLL时,SDRAM时钟频率等于晶振频率12MHz;SDRAM的刷新周期在SDRAM的数据手册上有标明,在本开发板使用的SDRAM HY57V561620CT-H的数据手册上,可看见这么一行“8192 refresh cycles / 64ms”:所以,刷新周期=64ms/8192 = 7.8125 uS。 对于本实验,R_CNT = 2^11 + 1 – 12 * 7.8125 = 1955, REFRESH=0x008e0000 + 1955 = 0x008e07a3.
5.BANKSIZE:0x000000b2
位[7]=1:Enable burst operation
位[5]=1:SDRAM power down mode enable
位[4]=1:SCLK is active only during the access (recommended)
位[2:1]=010:BANK6、BANK7对应的地址空间与BANK0-5不同。BANK0-5的地址空间都是固定的128M,地址范围是(x*128M)到(x+1)*128M-1,x表示0到5。但是BANK7的起始地址是可变的,您可以从S3C2410数据手册第5章“Table 5-1. Bank 6/7 Addresses”中了解到BANK6、7的地址范围与地址空间的关系。本开发板仅使用BANK6的64M空间,我们可以令位
[2:1]=010(128M/128M)或001(64M/64M):这没关系,多出来的空间程序会检测出来,不会发生使用不存在的内存的情况——后面介绍到的bootloader和linux内核都会作内存检测,位[6]、位[3]没有使用。
6.MRSRB6、MRSRB7:0x00000030
能让我们修改的只有位[6:4](CL),SDRAM HY57V561620CT-H不支持CL=1的情况,所以位[6:4]取值为010(CL=2)或011(CL=3)。
为了让程序结构简单一点,我都使用函数调用的方式。第一条指令是禁止WATCH DOG,您如果细心的话,一定会发现程序LEDS运行得有些不正常,那是因为WATCH DOG在不断地重启系统。以前为了程序简单,我没有把这段程序加上去。往WTCON寄存器(地址0x53000000)写入0即可禁止WATCH DOG。第二条指令设置本节开头所描述的13个寄存器,以便使用SDRAM。往下程序做的事情就是:将Steppingstone中的代码复制到SDRAM中(起始地址为 0x30000000),然后向pc寄存器直接赋值跳到SDRAM中执行下一条指令“ldr sp, =0x34000000”。
在目录SDRAM下执行make指令生成可执行文件sdram后,下载到板子上运行,可以发现与LEDS程序相比,LED闪烁得更慢:这就对了,外部SDRAM的性能比起内部SRAM来说性能是差些。 把程序从性能更好的内部SRAM移到外部SDRAM中去,是否多此一举呢?内部
SRAM只有4k大小,如果我们的程序大于4k,那么就不能指望利用内部SRAM来运行了。所以得想办法把存储在NAND Flash中的代码,复制到SDRAM中去。对于NAND Flash中的前4k,芯片自动把它复制到内部SRAM中,我们可以很轻松地再把它复制到SDRAM中(实验五中函数copy_steppingstone_to_sdram就做这事)。但是对于4k之后的代码,复制它就不那么轻松了,这就是nand flash之后的问题啦。
分析完初始化的汇编代码后,我们来看看生成的sdram二进制可执行文件反汇编的结果:
这里是反汇编的结果(因为链接地址是0x30000000,并且head.s问可执行文件的起始文件):Disassembly of section .text:
30000000 <_start>: 30000000: e3a00453 mov r0, #1392508928 ; 0x53000000 30000004: e3a01000 mov r1, #0 ; 0x0 30000008: e5801000 str r1, [r0] 3000000c: eb00000c bl 30000044 <memsetup> 30000010: eb000003 bl 30000024 <copy_steppingston_to_sdram> 30000014: e59ff088 ldr pc, [pc, #88] ; 300000a4 <mem_cfg_val+0x34> //这里面的#88不是很理解
30000018 <setup_stack>: 30000018: e3a0d30d mov sp, #872415232 ; 0x34000000 3000001c: eb000023 bl 300000b0 <main> //注意这里c代码中的函数也会被加载进来
30000020 <stop>: 30000020: eafffffe b 30000020 <stop>
|
可以很明显的看出,_start为0x30000000,stop为0x30000020。也就是说,经过链接之后,symbol table中的存放位置都是基于运行起始地址0x30000000的。但是需要注意的是,开始运行是PC的值为0x00000000,虽然bl 30000044 <memsetup>是30000044,但是要注意,此处指令为bl,所以只能相对寻址,而不能够绝对寻址,也就是说,它只能跳转到距离0x30000000为0x44的位置,这点查看bl的汇编指令说明就比较清晰了。
ldr pc,=setup_stack setup_stack: ldr sp, =stack_top bl main
|
利用上面的技巧,就可以把PC的值装载到sdram的空间,因为之前代码搬移已经完成了,所以,后续的工作都已经工作在sdram的空间中了。
如果在利用objcopy去除了符号信息之后,反汇编之后的结果只能是以0开始的相对地址,也就看不出上面的东西了,所以,要理解还应该是采用上面的分析方法。这点在《s3c2410完全开发》上是没有详细说明的。写到这里,自己已经比较清晰了。关于其他的分析,《s3c2410完全开发》已经比较详细了,可以参考。