对于处理器来说,都不可能内置过大的内存,只保留一小块SRAM作为芯片启动用
例如:S3C2416内部SRAM只有64KB,其中的8KB是作为SteppingStone,用来做一些基本的初始化,并进一步引导用户的代码启动。用户的代码往往是在外部的内存空间执行,因为通过处理器的存储器接口,可以外扩较大的内存空间。对于较大型的设计,用户代码以及变量往往都是在外部内存空间执行或存放。
因此笔者就S3C2416初始化外部DDR2以及代码从sd卡搬移到外部内存并执行作简单的介绍。

1、内存控制器初始化

S3C2416支持多种存储器接口(如:SRAM、DDR、DDR2等),一般设计都是采用DDR2存储器接口来外扩内存的。在使用外部内存之前,是必须先初始化存储器接口的,以确定访问时序等参数。

1.1. DDR2初始化流程

对于DDR2的初始化,S3C2416数据手册都是给出了详细的流程的:

1.1.1. Setting the BANKCFG &BANKCON1, 2, 3.
1.1.2. Wait 200us to allow DRAMpower and clock stabilize.
1.1.3. Wait minimum of 400 nsthen issue a PALL(pre-charge all) command. Program the INIT[1:0] to ‘01b’. Thisautomatically issues a PALL(pre-charge all) command to the DRAM.
1.1.4. Issue an EMRS command toEMR(2), provide LOW to BA0, High to BA1. Program the INIT[1:0] of ControlRegister1 to ‘11b’ & BANKCON3[31]=’1b’.
1.1.5. Issue an EMRS command toEMR(3), provide High to BA0 and BA1. Program the INIT[1:0] of Control Register1to ‘11b’ & BANKCON3[31:30]=’11b’.
1.1.6. Issue an EMRS to enableDLL and RDQS, nDQS, ODT disable.
1.1.7. Issue a Mode Register Setcommand for DLL reset.(To issue DLL Reset command, provide HIGH to A8 and LOWto BA0-BA1, and A13-A15.)  Program theINIT[1:0] to10b’. & BANKCON3[8]=’1b’.
1.1.8. Issue a PALL(pre-chargeall) command. Program the INIT[1:0] to01b’. This automatically issues aPALL(pre-charge all) command to the DRAM.
1.1.9. Issue 2 or moreauto-refresh commands.
1.1.10. Issue a MRS command withLOW to A8 to initialize device operation. Program the INIT[1:0] to10b’. &BANKCON3[8]=’0b’.
1.1.11. Wait 200 clock afterstep 7, execute OCD Calibration.
1.1.12. The external DRAM is nowready for normal operation.

1.2. DDR2初始化代码实现

对于不同的DDR2,主要是配置第一步BANKCFG &BANKCON1, 2,其它初始化流程可以通用:

  • BANKCFG 主要是用来配置外扩DDR2的行地址线,列地址线以及接口位宽等。
  • BANKCON1 用来配置DDR2控制器一些控制属性,如自动预充,功耗控制,写缓存等。
  • BANKCON2 用来配置DDR2的时序参数,如果时序参数设置得过快,将无法初始化相应的DDR2,参数设置得过慢,将造成DDR2读写性能低。因此,需要对照相应的DDR2芯片数据手册时序参数来作设置,通常要比手册参数预留一定的裕度(如多1~2个clock)。

笔者所用的DDR2型号为K4T51163QJ-BCE79(DDR2@400M 5-5-5),64MB,行地址线为13,列地址线为10,16位总线。K4T51163QJ-BCE79给出的Active命令到发出Read/Write命令时间间隔tRCD=5tCK=12.5ns,Precharge命令到发送Active命令时间间隔tRP=5tCK=12.5ns,Read/Write命令发出后经过5tCK=12.5ns数据才有效(CAS锁存时间),Active命令到Precharge命令时间间隔tRAS=45ns,平均刷新周期间隔为tREFI=780us,指令刷新时间tRFC=105ns。

S3C2416时序参数配置寄存器BANKCON2都是以HCLK为基准来计时序的clock的。例如在HCLK=133M情况下,设置BANKCON2中[1:0]为1(2个HCLK)即可满足DDR2芯片tRP=12.5ns的时序要求,为了保险,设置3个HCLK为宜。
笔者在LowlevelInit.s汇编文件中实现DDR2初始化函数,不同的DDR2只需根据相应芯片参数修改BANKCFG & BANKCON1, 2,即可使用。

;DRAM Controller base address
;DRAM寄存器的基地址:
DRAM_BASE       EQU 0x48000000
;偏移量:
BANKCFG_OFS     EQU 0x00
BANKCON1_OFS    EQU 0x04
BANKCON2_OFS    EQU 0x08
BANKCON3_OFS    EQU 0x0C
REFRESH_OFS     EQU 0x10
TIMEOUT_OFS     EQU 0x14

        PRESERVE8
        AREA   LOWLEVELINIT, CODE, READONLY
        ARM
        EXPORT ERAM_Init
;K4T51163QJ-BCE79(DDR2@400M 5-5-5),64MB,Raw Addr A0~A12(RAS),Column Addr A0~A9(CAS)
; 设置DDR0 13位行地址,10位列地址,DDR2接口,16位总线
; DDR命令根据nRAS,nCAS,nWE,nCS控制总线分辨
; Active命令:打开行及bank地址
; Read命令:在Active后,打开列地址读
; Write命令:在Active后,打开列地址写
; Precharge命令:关闭bank,根据A[10]确定关闭指定或所有bank(只能同时访问一个bank)
; AUTOREFRESH or SELF REFRESH命令:刷新命令
; LOAD MODEREGISTER命令:写模式寄存器
ERAM_Init
;配置BANKCFG P152
        LDR     R0,=DRAM_BASE
        LDR     R1,=(2<<17)+(2<<11)+(0<<6)+(1<<1)+(1<<0)
        STR     R1,[R0, #BANKCFG_OFS]

; DQS delay3,Write buffer,Auto pre-charge,bank address 在高位
        LDR     R1,=(3<<28)+(1<<26)+(1<<8)+(0<<7)+ \
                             (1<<6)+(0<<5)+(1<<4)
        STR     R1,[R0, #BANKCON1_OFS]    

; s3c2416 DDR2寄存器的clk设置值是相对HCLK的
; RAS [23:20]Row active time 45ns HCLK=133M DDR2=266M 6clock
; Active命令到Precharge命令的最小时间45ns
; ARFC[19:16] Row cycle time tRFC=105ns 14clock
; 指令刷新时间105ns
; CAS Latency[5:4] CAS latency control 12.5ns 2clock
; Read/Write命令发出后经过5tCK=12.5ns数据才有效
; tRCD [3:2]RAS to CAS delay 12.5ns 2clock
; Active命令需经5tCK=12.5ns后才发出Read/Write命令
; tRP [1:0]Row pre-charge time 12.5ns 2clock
; Precharge命令到发送Active命令5tCK=12.5ns
; 故两个Active命令所需的最小时间tRC=tRAS+tRP=57.5ns
        LDR     R1,=(6<<20)+(13<<16)+(3<<4)+(2<<2)+(2<<0)
        STR     R1,[R0, #BANKCON2_OFS]

;发送PALL命令(即让S3C2416向DDR2内存发送命令) 
; issue aPALL(pre-charge all) command,即Precharge命令
        LDR     R1,[R0, #BANKCON1_OFS]
        ;将bit[1,0]=00,然后再设置位01
        BIC     R1,R1, #0x03
        ORR     R1,R1, #0x01
        STR     R1,[R0, #BANKCON1_OFS]
;发送MRS命令(即让S3C2416向DDR2内存发送命令)  
; issue anEMRS(extern mode register) command to EMR(2)
        ;向BANKCON3写入需要发送的命令内容:
        LDR     R1,=(0x2<<30)+(0<<23)+(0<<19)+(0<<16)
        STR     R1,[R0, #BANKCON3_OFS]
        ;将BANKCON1的bit[1,0]=11
        LDR     R1,[R0, #BANKCON1_OFS]
        ORR     R1,R1, #0x03              
        STR     R1,[R0, #BANKCON1_OFS]
;再发送: 
; issue anEMRS(extern mode register) command to EMR(3)
        ;向BANKCON3写入需要发送的命令内容:
        LDR     R1,=(0x3<<30)
        STR     R1,[R0, #BANKCON3_OFS]
        ;将BANKCON1的bit[1,0]=11
        LDR     R1,[R0, #BANKCON1_OFS]
        ORR     R1,R1, #0x03              
        STR     R1,[R0, #BANKCON1_OFS]
;再发送:  目的使能2416芯片为了使用DDR2的DLL and RDQS, nDQS,禁止ODT             
; issue anEMRS to enable DLL and RDQS, nDQS, ODT disable
        ;获取BANKCON3当前寄存器的内容值给R2:
        LDR     R1,=0xFFFF0000
        LDR     R2,[R0, #BANKCON3_OFS]
        ;将其高16位清零:
        BIC     R2,R2, R1
        ;然后再赋值:
        LDR     R1,=(0x1<<30)+(0<<28)+(0<<27)+(1<<26)+ \
                             (7<<23)+(0<<19)+(0<<22)+(0<<18)+\
                             (0x0<<17)+(0<<16)             
        ORR     R1,R1, R2
        STR     R1,[R0, #BANKCON3_OFS]
        ;将BANKCON1的bit[1,0]=11, 目的是为了让上面一句起作用.
        LDR     R1,[R0, #BANKCON1_OFS]
        ORR     R1,R1, #0x03              
        STR     R1,[R0, #BANKCON1_OFS]
;对上面的补充: 
; issue amode register set command for DLL reset
        LDR     R1,=0x0000FFFF
        LDR     R2,[R0, #BANKCON3_OFS]
        ;清低16位:
        BIC     R2,R2, R1
        ;然后再设置低16位想要的值:
        LDR     R1,=(0x1<<9)+(1<<8)+(0<<7)+(3<<4)                     
        ORR     R1,R1, R2
        STR     R1,[R0, #BANKCON3_OFS]
        ;将BANKCON1的bit[1,0]=10, 目的是为了让上面一句起作用.
        LDR     R1,[R0, #BANKCON1_OFS]
        BIC     R1,R1, #0x03
        ORR     R1,R1, #0x02              
        STR     R1,[R0, #BANKCON1_OFS]

; Issue aPALL(pre-charge all) command 
        ;将BANKCON1的bit[1,0]=01
        LDR     R1,[R0, #BANKCON1_OFS]
        BIC     R1,R1, #0x03
        ORR     R1,R1, #0x01
        STR     R1,[R0, #BANKCON1_OFS]

;设置DDR2的自动刷新所需要的计数值:               
; Issue 2 ormore auto-refresh commands
        LDR     R1,=0x20
        STR     R1,[R0, #REFRESH_OFS]

; 
; Issue a MRScommand with LOW to A8 to initialize device operation
        LDR     R1,=0x0000FFFF
        LDR     R2,[R0, #BANKCON3_OFS]
        BIC     R2,R2, R1
        LDR     R1,=(0x1<<9)+(0<<8)+(0<<7)+(3<<4)                 
        ORR     R1,R1, R2
        STR     R1,[R0, #BANKCON3_OFS]
        ;
        LDR     R1,[R0, #BANKCON1_OFS]
        BIC     R1,R1, #0x03
        ORR     R1,R1, #0x02              
        STR     R1,[R0, #BANKCON1_OFS]
;等待200个CLOCK,然后执行OCD:               
; Wait 200clock, execute OCD Calibration
        LDR     R1,=200
0       SUBS    R1,R1, #1
        BNE     %B0

; Issue aEMRS1 command to over OCD Mode Calibration
        LDR     R1,=0xFFFF0000
        LDR     R2,[R0, #BANKCON3_OFS]
        BIC     R2,R2, R1
        LDR     R1,=(0x1<<30)+(0<<28)+(0<<27)+(1<<26)+ \
                     (0<<23)+(0<<19)+(0<<22)+(0<<18)+\
                     (0x0<<17)+(0<<16)             
        ORR     R1,R1, R2
        STR     R1,[R0, #BANKCON3_OFS]
        ;
        LDR     R1,[R0, #BANKCON1_OFS]
        ORR     R1,R1, #0x03              
        STR     R1,[R0, #BANKCON1_OFS]

; Refreshperiod is 7.8us, HCLK=100M, REFCYC=780
        LDR     R1,=780
        STR     R1,[R0, #REFRESH_OFS]

; issue aNormal mode
        LDR     R1,[R0, #BANKCON1_OFS]
        BIC     R1,R1, #0x03
        STR     R1,[R0, #BANKCON1_OFS]

        BX      LR

2. 代码搬移

S3C2416上电复位后会自动从启动设备拷贝用户最前面8k代码到Steppingstone中,这8k的代码称为BL1(BootLoader1),用来进一步引导用户的其它代码到相应的运行域。外部内存初始化成功后,即可把代码从存储设备读出到外部内存的运行域,堆栈等变量也可放到外部内存空间(由链接文件指定)。
代码搬移首先要识别出启动设备,从而调用相应的设备拷贝函数。拷贝函数应确定代码在存储设备中的位置,代码的大小,以及这段代码的运行地址,这样才能正确地搬移代码到RAM位置并执行。代码在存储设备中的位置对于nand,是在0x0地址偏移处。SD/MMC分普通卡与SDHC(高容量卡,大于2G),对于普通卡sd/mmc,代码位置是从SD/MMC最末块地址倒数18块之前的位置(图2-1)。对于高容量卡,代码位置是从SD/MMC最末块地址倒数1026块之前的位置(图2-2)。

04_外部内存初始化以及代码搬移 - 图1

图2-1 普通卡启动块分配

04_外部内存初始化以及代码搬移 - 图2

图2-2 SDHC启动块分配

笔者此处暂时只考虑IROM SD/MMC启动,代码从SD/MMC搬移的情况。
对于IROM启动,内部固化代码已进行一定的初始化,启动设备的一些信息已经记录在特定的内部RAM位置(图2-3),并且用户代码可以直接调用固化代码相关函数实现代码从SD/MMC读出到RAM,只要设置相应的参数,并跳转到0x40004008保存的函数地址处(图2-4),即可调用IROM内部固化代码。

04_外部内存初始化以及代码搬移 - 图3

图2-3 IROM SD/MMC启动时的全局变量

04_外部内存初始化以及代码搬移 - 图4

图2-4 SD/MMC块拷贝函数

SD/MMC的代码搬移函数实现如下:

; SD/MMCDevice Boot Block Assignment
eFuseBlockSize          EQU     1
SdReservedBlockSize     EQU     1
BL1BlockSize            EQU     16
SdBL1BlockStart         EQU     SdReservedBlockSize+ \
                        eFuseBlockSize +BL1BlockSize                 
globalBlockSizeHide     EQU     0x40003FFC
CopyMovitoMem           EQU     0x40004008

                EXPORT  __CodeSize__
                EXPORT  __CodeAddr__
; 引出代码搬移函数,以供启动代码调用
                EXPORT  CopyCodeToRAM

; 引入链接器产生符号,以确定代码运行位置,编译生成的大小
                IMPORT  ||ImageERROM0Base||
                IMPORT  ||LoadERROM0Length||  
                IMPORT  ||LoadERROM1Length||  
                IMPORT  ||LoadRWRAMRW$$Length||

; 链接器产生代码链接运行位置
__CodeAddr__    DCD     ||ImageERROM0Base||
; 链接器各个段需保存的代码以及需初始代的变量大小
__CodeSize__    DCD     ||LoadERROM0Length||+ \
                        ||LoadERROM1Length|| + \
                        ||LoadRWRAMRW$$Length||

;拷贝程序到RAM中:                      
CopyCodeToRAM
        ;压栈
        STMFD   SP!,{LR} ; 保存返回地址
        MMC_SD_Boot ; 暂不对nand启动处理
; 不需要卡初始化
        LDR     R3, =0
; 拷贝sd卡代码到链接执行域内存代码处
        LDR     R2, __CodeAddr__

; 计算代码的大小,以block计,不足512字节的算1个block
; 代码的大小包括Code RO-data RW-data(代码需保存需初始化的RW的初始值)
; 代码保存在ROM中,应从加载域得到ROM的大小,而不是执行域,编译器可能压缩
; 代码段保存在加载域的ROM中
        LDR     R0, __CodeSize__
        LDR     R1,=0x1ff
        TST     R0,R1 ; 是否不足一个block(512Bytes)
        BEQ     %F0   ; 代码恰好block对齐,不用加多一个block
        ADD     R0,R0, #512               
0       LSR     R1,R0, #9 ; 得到代码的block大小

; 计算代码在SD/MMC卡中的block起启地址
        LDR     R4,=SdBL1BlockStart   
        LDR     R0,=globalBlockSizeHide
        LDR     R0,[R0] ; SD/MMC的总block块
        SUB     R0,R4   ; 减去保留块及BL1大小
        CMP     R1,#16  ; 代码不足8k,直接BL1处拷贝
        BLS     ParameterOK; 代码少于16个block跳转
        SUB     R0,R1      ; 再减去代码的大小,代码的block位置

; 调用IROM Movi拷贝函数,仅适用于IROM启动,卡访问时钟25M   
ParameterOK
        LDR     R4,=CopyMovitoMem
        LDR     R4,[R4]
        BLX     R4
        MOVS    R0,R0 ; 返回值确定函数成功还是失败
MMC_SD_Boot_Loop           
        BEQ     MMC_SD_Boot_Loop; 返回0说明拷贝失败

AfterCopy  
        LDMFD   SP!,{PC} ; 函数返回   

3. 链接文件

初始化外部RAM后,用链接文件指定对RAM的分配,笔者DDR2在Bank0,0x30000000处共64MB大小,分配前32M为代码区域,后32M为变量区域。

;*************************************************************
; ***Scatter-Loading Description File generated by uVision ***
;*************************************************************
; load region size_region
LR_IROM10x30000000
{
;启动代码8k在Steppingstone中执行,启动代码相关的代码必须链接在8k范围内,并且是与地址无关的,可链接到任意地址。
  ER_ROM0 0x30000000 0x2000 
  {
    ;load address = execution address
    s3c2416.o (RESET, +First)
    LowLevelInit.o
  }
  ;前32M为代码区域
  ER_ROM1 +0 0x2000000-0x2000
  { 
    *(InRoot$$Sections)
    .ANY (+RO)
  }
  ;后32M为变量区域
  ; RWdata
  RW_RAM 0x32000000 0x2000000
  {
   .ANY (+RW +ZI)
  }
}

4. 附录

启动代码中加入对外部内存初始化函数以及代码搬移函数的调用,即可编写实现任意大小的c功能函数了(c代码及变量使用总大小不超过外部RAM大小),编译后的可执行代码通过SdBoot工具进行格式转换,烧写进sd卡,设置从SD卡启动即可。
LowLevelInit.s,板级初始化代码实现,包括DDR2初始化函数实现,代码拷贝函数实现
s3c2416.sct,MDK链接文件,对初始化的外部内存进行链接分配。
源码下载: http://pan.baidu.com/s/1o6ufy4a