跳至主要内容

【转】vivi开发笔记(九):SDRAM实验

文章说明:calmarrow(lqm)参考thisway.diy的《S3C2410完全开发》

文章引自:http://piaoxiang.cublog.cn

 
    现在基本熟悉了GNU as、ld、objdump、objcopy。可以进行后续的基本实验了。今天把s3c2410的memory controller看了,结合《s3c2410完全开发》实验五,实现了一个sdram的简单实验。源代码如下,下面重点分析s3c2410复位后的内存映射,及其应用。
 
一、基本配置
    EDUKIT-III采用核心子板加扩展板的设计方式,我学习ARM9,所以采用s3c2410的核心子板。核心子板资源如下:
 
    MCU      : S3C2410A
    SDRAM    : 两片HY57V561620CT-H
    NOR FLASH: AM29LV160DB-90EC
 
    扩展板资源:
 
    NAND FLASH: K9F5608UOC
 
二、nand flash boot分析
 
    S3C2410支持从nor/nand flash、eeprom等rom类型的介质启动。现在我想做的是从外部nand flash启动,首先看datasheet第一部分:
 

NAND Flash Boot Loader
・ Supports booting from NAND flash memory
・ 4KB internal buffer for booting
・ Supports storage memory for NAND flash memory after booting

 
    可以明确,首先,s3c2410支持从nand flash存储介质启动,其次,在硬件上,s3c2410除了提供相应的逻辑外,还提供了一个4K的sram作为buffer用于nand启动。最后,s3c2410支持从nand flash启动之后的内存分配形式(也就是说,从nand flash启动之后和从nor flash启动之后的内存分配形式是不同的。s3c2410支持这两种形式,可以说是相对于其他的MCU不同的地方。)
 
    然后看datasheet的第六部分:nand flash controller。overview中首先讲述了用nand flash代替nor flash作为启动介质的原因(成本低)。
 

    S3C2410X boot code can be executed on an external NAND flash memory. In order to support NAND flash boot loader, the S3C2410X is equipped with an internal SRAM buffer called 'Steppingstone'. When booting, the first 4
KBytes of the NAND flash memory will be loaded into Steppingstone and the boot code loaded into Steppingstone will be executed.
    Generally, the boot code will copy NAND flash content to SDRAM. Using hardware ECC, the NAND flash data validity will be checked. Upon the completion of the copy, the main program will be executed on the SDRAM.

 
    可以很明显的看出,s3c2410x启动代码从外部nand flash启动的流程:上电复位后,s3c2410自动读取nand flash的前4KBytes的数据到内部sram buffer中,这个硬件的sram buffer被称为"Steppingstone"。然后执行下载到steppingstone的代码,这部分代码完成将nand flash的内容复制到sdram中,在复制时,利用硬件的ECC验证数据有效性。完成复制后,主程序就开始从sdram执行。
 
    过程应该是很清晰。但是首先怀疑的是,s3c2410如何实现自动读取nand flash的前4KBytes数据到内部sram buffer当中。网上为什么没人就这点产生疑问,并深入分析呢?我分析可能有两种方法:一是像at91rm9200一样,内部集成一个小的rom,固化代码,这部分代码的作用就是完成自动读取功能;二是完全用硬件实现。仔细看了框图,发现s3c2410只有internal sram buffer,并没有rom,所以最大可能就是硬件实现。看一下硬件框图figure6-1,可以发现hardware ECC编解码器,可以看到internal buffer(4KB),另外注意的一个地方是,存在着control state machine和buffer control,而且之间有粗体线链接。也就是说明了用一个控制状态机实现了自动读取4KB数据的过程,完全的硬件实现。如下图所示:
 
    明确了这个问题之后,对从硬件上电到nand flash启动就比较清晰了。而且,也就理解为什么vivi的stage1的head.S必须要小于4KB,因为internal sram buffer只有4KB。如果要完成一个比较复杂的bootloader,那也应该尽量简化stage1,完成基本的初始化之后,把剩余的工作量都放到将nand flash的代码搬移到sdram之后进行。
 
三 memory controller分析
 
    看datasheet第五部分。s3c2410比较特殊,支持1G的内存空间,分为8个bank,每个bank128MBytes,128MB×8=1GB。但是在这8个bank中,又有所不同,并且nand flash不对应任何bank,它是通过一组寄存器来访问的,可看上面框图的register bank。
 

― Total 8 memory banks
   Six memory banks for ROM, SRAM, etc.
   Remaining two memory banks for ROM, SRAM, SDRAM, etc .
― Seven fixed memory bank start address
― One flexible memory bank start address and programmable bank size

 
    可以推断出,sdram应该在bank6,起始地址固定为128M*6=0x30000000,在此之后,就要根据sdram的大小和位宽来决定了,而且有个注意的地方是,bank7必须和bank6一样大小。参考figure5-1和table 5-1就非常清晰了。现在EDUKIT-III上用了两片SDRAM,型号是HY57V561620CT-H,查看datasheet,它是4banks×4M×16bit=256Mbits=32Mbytes,那么两片组合起来就是64MBytes,位宽是32bit,所以bank6的地址范围是[0x30000000-0x33ffffff],bank7的地址范围是[0x34000000-0x37ffffff]。查看s3c2410 table 5-2,可以知道bank选择地址线为A[25:24]--->BA[1:0]。
 
    关于sdram,还应该知道刷新频率和列宽度。HY57V561620CT-H datasheet中,有:
 

・ Auto refresh and self refresh
・ 8192 refresh cycles / 64ms
・ Programmable Burst Length and Burst Type
  - 1, 2, 4, 8 or Full page for Sequential Burst
  - 1, 2, 4 or 8 for Interleave Burst
・ Programmable CAS Latency ; 2, 3 Clocks

 
    所以刷新频率为64ms/8192=7.8125us。
 
    查看PIN DESCRIPTION,可以看出A0-A12为地址,其中ROW Address为RA[0:12],Column Address为CA[0-8],显然CAS的位数为9bits。
 
四、实验内容分析
 
    实验内容很简单,就是完成基本的初始化之后,把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的反汇编来进行分析:
 
    首先看在不去除符号信息的前提下:
 

sdram: $(OBJS)
        $(LD) $(LDFLAGS) $^ -o $@.o
        $(OBJDUMP) -D $@.o >$@_s
        $(OBJCOPY) -O binary -S $@.o $@

 
    这里是反汇编的结果:
 

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>

30000018 <setup_stack>:
30000018:       e3a0d30d        mov     sp, #872415232  ; 0x34000000
3000001c:       eb000023        bl      300000b0 <main>

30000020 <stop>:
30000020:       eafffffe        b       30000020 <stop>

 
    可以很明显的看出,_start为0x30000000,stop为0x30000020。也就是说,经过链接之后,symbol table中的存放位置都是基于运行起始地址0x30000000的。但是需要注意的是,开始运行是PC的值为0x00000000,虽然bl 30000040 <memsetup>是30000040,但是要注意,此处指令为bl,所以只能相对寻址,而不能够绝对寻址,也就是说,它只能跳转到距离0x30000000为0x40的位置,这点查看bl的汇编指令说明就比较清晰了。
 

        ldr pc, =setup_stack
setup_stack:
        ldr sp, =stack_top
        bl main

 
    利用上面的技巧,就可以把PC的值装载到sdram的空间,因为之前代码搬移已经完成了,所以,后续的工作都已经工作在sdram的空间中了。
 
    如果在利用objcopy去除了符号信息之后,反汇编之后的结果只能是以0开始的相对地址,也就看不出上面的东西了,所以,要理解还应该是采用上面的分析方法。这点在《s3c2410完全开发》上是没有详细说明的。写到这里,自己已经比较清晰了。关于其他的分析,《s3c2410完全开发》已经比较详细了,可以参考。
 
    程序源代码如下:
 
文件: sdram.tar.gz
大小: 2KB
下载: 下载

评论

此博客中的热门博文

【转】嵌入式开发中使用DDD进行调试

 在嵌入式程序开发过程中,程序员要进行大量的调试,以此验证程序的正确性,修改潜在的错误。调试器对于程序员来说是不可或缺的必备工具。在Linux环境中,有很多调试工具和调试辅助工具,例如GDB、XXGDB、RHIDE、XWPE、GVD和DDD等。其中,DDD是命令行调试器的图形前端,除了一般的程序调试功能以外,还具有交互式图形数据显示的功能。它在嵌入式应用开发中也十分出色。本文主要讲述DDD(Data Display Debugger)的使用方法。      1.认识DDD     GNU DDD是命令行调试程序,如GDB、DBX、WDB、Ladebug、JDB、XDB、Perl Debugger或Python Debugger的可视化图形前端。它特有的图形数据显示功能(Graphical Data Display)可以把数据结构按照图形的方式显示出来。DDD最初源于1990年Andreas Zeller编写的VSL结构化语言,后来经过一些程序员的努力,演化成今天的模样。DDD的功能非常强大,可以调试用C\C++、Ada、 Fortran、Pascal、Modula-2和Modula-3编写的程序;可以超文本方式浏览源代码;能够进行断点设置、回溯调试和历史纪录编辑;具有程序在终端运行的仿真窗口,并在远程主机上进行调试的能力;图形数据显示功能(Graphical Data Display)是创建该调试器的初衷之一,能够显示各种数据结构之间的关系,并将数据结构以图形化形式显示;具有GDB/DBX/XDB的命令行界面,包括完全的文本编辑、历史纪录、搜寻引擎。     DDD是开源软件,用户可以去 http://www.cs.tubs.de/softech/ddd/ 下载.rpm格式的DDD源码文件。      图1显示的是DDD的主窗口。它主要由选单栏、工具条、数据窗口、源文件窗口、机器码窗口、控制台和命令工具窗口等几部分组成。其中,数据窗口用于观察复杂的数据结构,在删除数据之后,显示仍然有效;源文件窗口显示源代码、断点和当前执行到达的位置,选择该窗口中的"Display"项,可以显示任意表达式的值;机...

【转】cs8900网卡的移植至基于linux2.6内核的s3c2410平台

cs8900网卡的移植至基于linux2.6内核的s3c2410平台(转) 2008-03-11 20:58 硬件环境:SBC-2410X开发板(CPU:S3C2410X) 内核版本:2.6.11.1 运行环境:Debian2.6.8 交叉编译环境:gcc-3.3.4-glibc-2.3.3 第一部分 网卡CS8900A驱动程序的移植 一、从网上将Linux内核源代码下载到本机上,并将其解压: #tar jxf linux-2.6.11.1.tar.bz2 二、打开内核顶层目录中的Makefile文件,这个文件中需要修改的内容包括以下两个方面。 (1)指定目标平台。 移植前:         ARCH?= $(SUBARCH) 移植后: ARCH            :=arm (2)指定交叉编译器。 移植前: CROSS_COMPILE ?= 移植后: CROSS_COMPILE   :=/opt/crosstool/arm-s3c2410-linux-gnu/gcc-3.3.4-glibc-2.3.3/bin/arm-s3c2410-linux-gnu- 注:这里假设编译器就放在本机的那个目录下。 三、添加驱动程序源代码,这涉及到以下几个方面。(1)、从网上下载了cs8900.c和cs8900.h两个针对2.6.7的内核的驱动程序源代码,将其放在drivers/net/arm/目录下面。 #cp cs8900.c ./drivers/net/arm/ #cp cs8900.h ./drivers/net/arm/ 并在cs8900_probe()函数中,memset (&priv,0,sizeof (cs8900_t));函数之后添加如下两条语句: __raw_writel(0x2211d110,S3C2410_BWSCON); __raw_writel(0x1f7c,S3C2410_BANKCON3); 注:其原因在"第二部分"解释。 (2)、修改drivers/net/arm/目录下的Kconfig文件,在最后添加如...

【转】函数指针和指针函数

【函数指针】        在程序运行中,函数代码是程序的算法指令部分,它们和数组一样也占用存储空间,都有相应的地址。可以使用指针变量指向数组的首地址,也可以使用指针变量指向函数代码的首地址,指向函数代码首地址的指针变量称为函数指针。 1.函数指针定义 函数类型 (*指针变量名)(形参列表); "函数类型"说明函数的返回类型,由于"()"的优先级高于"*",所以指针变量名外的括号必不可少,后面的"形参列表"表示指针变量指向的函数所带的参数列表。 例如: int (*f)(int x); double (*ptr)(double x); 在定义函数指针时请注意:     函数指针和它指向的函数的参数个数和类型都应该是―致的; 函数指针的类型和函数的返回值类型也必须是一致的。 2.函数指针的赋值 函数名和数组名一样代表了函数代码的首地址,因此在赋值时,直接将函数指针指向函数名就行了。 例如, int func(int x);   /* 声明一个函数 */ int (*f) (int x);    /* 声明一个函数指针 */ f=func;            /* 将func函数的首地址赋给指针f */ 赋值时函数func不带括号,也不带参数,由于func代表函数的首地址,因此经过赋值以后,指针f就指向函数func(x)的代码的首地址。 3.通过函数指针调用函数 函数指针是通过函数名及有关参数进行调用的。 与其他指针变量相类似,如果指针变量pi是指向某整型变量i的指针,则*p等于它所指的变量i;如果pf是指向某浮点型变量f的指针,则*pf就等价于它所指的变量f。同样地,*f是指向函数func(x)的指针,则*f就代表它所指向的函数func。所以在执行了f=func;之后,(*f)和func代表同一函数。 由于函数指针指向存储区中的某个函数,因此可以通过函数指针调用相应的函数。现在我们就讨论如何用函数指针调用函数,它应执行下面三步: 首先,要说明函数指针变量。 例如:int (*f)...