对于处理器来说,都不可能内置过大的内存,只保留一小块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] to ‘10b’. & BANKCON3[8]=’1b’.
1.1.8. Issue a PALL(pre-chargeall) command. Program the INIT[1:0] to ‘01b’. 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] to ‘10b’. &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)。
笔者此处暂时只考虑IROM SD/MMC启动,代码从SD/MMC搬移的情况。
对于IROM启动,内部固化代码已进行一定的初始化,启动设备的一些信息已经记录在特定的内部RAM位置(图2-3),并且用户代码可以直接调用固化代码相关函数实现代码从SD/MMC读出到RAM,只要设置相应的参数,并跳转到0x40004008保存的函数地址处(图2-4),即可调用IROM内部固化代码。
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