板子上電后就會從這里開始執行,主要完成基本初始化,還有判斷是從nor還是nand啟動,再實現把程序搬到SDRAM當中,在搬運成功后再跳到main函數里面執行。
我們現在開始來看看它的具體代碼吧!
GET和INCLUDE的功能是相同的,功能都是引進一些編譯過的文件。
GET option.inc
GET memcfg.inc
GET 2440addr.inc
定義SDRAM工作在Reflesh模式下,SDRAM有兩種刷新模式:selfreflesh,autoreflesh。后者是在其使用過程當中設置的。
BIT_SELFREFRESH EQU (1<<22)
下面是對arm處理器模式寄存器對應的常數進行賦值,arm處理器有一個CPSR寄存器,它的后五位決定了處理器處于哪個模式下。可以看出常數的定義都不會超過后5位的。
USERMODE EQU 0x10
FIQMODE EQU 0x11
IRQMODE EQU 0x12
SVCMODE EQU 0x13
ABORTMODE EQU 0x17
UNDEFMODE EQU 0x1b
MODEMASK EQU 0x1f
NOINT EQU 0xc0
各個異常模式的堆棧
UserStack EQU (_STACK_BASEADDRESS-0x3800) ;0x33ff4800 ~
SVCStack EQU (_STACK_BASEADDRESS-0x2800) ;0x33ff5800 ~
UndefStack EQU (_STACK_BASEADDRESS-0x2400) ;0x33ff5c00 ~
AbortStack EQU (_STACK_BASEADDRESS-0x2000) ;0x33ff6000 ~
IRQStack EQU (_STACK_BASEADDRESS-0x1000) ;0x33ff7000 ~
FIQStack EQU (_STACK_BASEADDRESS-0x0) ;0x33ff8000 ~
這一段是統一arm的工作狀態和對應的軟件編譯方式(16位編譯環境使用tasm.exe編譯)。arm處理器的工作狀態分為兩種:32位,arm執行字對齊的arm指令集;16位,arm執行半字對齊的Thumb指令集。不同的工作狀態,編譯方式也不一樣。所以下面的程序就是判斷arm的工作方式來確定它的編譯方式。
GBLL THUMBCODE//定義THUMBCODE 這個變量GBLL 聲明一個全局邏輯變量并初始化為{FALSE}
[ {CONFIG} = 16//"["表示"if","|"表示"else","]"表示"endif",對于CONFIG是在ADS編譯中定義的內部變量。
THUMBCODE SETL {TRUE}
CODE32
|
THUMBCODE SETL {FALSE}
]//如果ARM是在16位的工作狀態的話,就使全局變量THUMBCODE設置為ture。
MACRO//這個是宏定義的關鍵字
MOV_PC_LR//作用是子程序返回
[ THUMBCODE
bx lr//當目標程序是Thumb時,就要使用BX跳轉返回,并轉換模式。
|
mov pc,lr//目標程序是ARM指令集,直接把lr賦給pc就可以了。
]
MEND//宏定義的結束標志。
MACRO
MOVEQ_PC_LR//這個是帶“相等”條件的子程序返回。和上面說的類似。
[ THUMBCODE
bxeq lr
|
moveq pc,lr
]
MEND
在宏定義下面的handlexxx HANDLER handlexxx都會展成以下的程序段,這段程序主要把中斷服務程序的入口地址傳送給pc,在程序的用34字空間來存放中斷服務程序的入口地址,每個字空間都會有一個標號,以handlerxxx開頭的。
MACRO
$HandlerLabel HANDLER $HandleLabel
$HandlerLabel
sub sp,sp,#4 //先預留空間,為了存儲跳轉地址。
stmfd sp!,{r0} //把工作寄存器按入堆棧。
ldr r0,=$HandleLabel
ldr r0,[r0] //這兩句的功能是把中斷程序的入口地址先放在中間變量r0處。
str r0,[sp,#4]//把中斷服務程序的入口地址按入堆棧。
ldmfd sp!,{r0,pc}//最后把堆棧中的中斷程序入口地址彈給pc寄存器,這樣就可以執行相應的中斷服務程序了。
MEND
S3C2440有兩種中斷模式:一種有中斷向量表的,一種則沒有。有表的話實時性比較好。當一個外部中斷0發生后,程序自動跳轉到地址0x20處,0x20地址單元的指令為“ldr pc, = HandlerEINT0”,因此程序跳轉到HandlerEINT0處執行這個宏操作,就是把外部中斷地址賦給PC。
一個arm程序是由R0,RW,ZI三個段組成。其中R0為代碼段,RW是已經初始化的全局變量,ZI是未初始化的全局變量,BOOTLOADER要將RW段復制到RAM中并將ZI段清零。
編譯器使用下列段來記錄各段的起始地址和結束地址
|Image$$RO$$Base| ; RO 段起始地址|Image$$RO$$Limit| ; RO 段結束地址加1|Image$$RW$$Base| ; RW 段起始地址
|Image$$RW$$Limit| ; RW 段結束地址加1|Image$$ZI$$Base| ; ZI 段起始地址|Image$$ZI$$Limit| ; ZI 段結束地址加1
這些標號的值是通過編譯器的設定來確定的如編譯軟件中對ro-base 和rw-base 的設定,例如ro-base=0xc000000 rw-base=0xc5f0000,在這里用IMPORT 偽指令( 和c 語言的extren 一樣) 引入|Image$$RO$$Base|,|Image$$RO$$Limit|...等比較古怪的變量是編譯器生成的。RO, RW, ZI 這三個段都保存在Flash 中,但RW,ZI 在Flash 中的地址肯定不是程序運行時變量所存儲的位置,因此我們的程序在初始化時應該把Flash 中的RW,ZI 拷貝到RAM 的對應位置。這些變量是通過ADS 的工程設置里面設定的RO Base 和RW Base 設定的,最終由編譯腳本和連接程序導入程序.
IMPORT |Image$$RO$$Base|
IMPORT |Image$$RO$$Limit|
IMPORT |Image$$RW$$Base|
IMPORT |Image$$ZI$$Base|
IMPORT |Image$$ZI$$Limit|
引入外部變量mmu的快速總線模式和同步總線模式兩個變量
IMPORT MMU_SetAsyncBusMode
IMPORT MMU_SetFastBusMode
我們所熟知的main函數
IMPORT Main
把鏡像從Nandflash拷貝到SDRAM的函數
IMPORT RdNF2SDRAM
定義arm匯編程序段,段名叫init段,為只讀段
AREA Init,CODE,READONLY
ENTRY
EXPORT __ENTRY//導出__ENTRY標號
__ENTRY
ResetEntry
ASSERT :DEF:ENDIAN_CHANGE//判斷模式改變是否定義過(ASSERT是偽指令,:DEF:lable判斷lable是否定義過了)
[ ENDIAN_CHANGE
ASSERT :DEF:ENTRY_BUS_WIDTH//判斷是否定義了總線寬度
[ ENTRY_BUS_WIDTH=32//如果存儲器是32位的總線寬度
b ChangeBigEndian ;DCD 0xea000007
]
[ ENTRY_BUS_WIDTH=16//如果存儲器是16位的總線寬度
andeq r14,r7,r0,lsl #20 ;DCD 0x0007ea00
]
[ ENTRY_BUS_WIDTH=8//如果是存儲器是8位總線寬度
streq r0,[r0,-r10,ror #1] ;DCD 0x070000ea
]
|//如果總線寬度沒有定義的話,就直接跳轉到復位中斷
b ResetHandler//程序執行的地跳跳轉指令
]
b HandlerUndef ;handler for Undefined mode
b HandlerSWI ;handler for SWI interrupt
b HandlerPabort ;handler for PAbort
b HandlerDabort ;handler for DAbort
b . ;reserved
b HandlerIRQ ;handler for IRQ interrupt
b HandlerFIQ ;handler for FIQ interrupt
;@0x20
b EnterPWDN ; Must be @0x20.//進入powerdown模式
以上8條跳轉指令,是8個異常中斷處理向量,一定要按照順序排好,據我了解,每次出現異常的話,是由硬件自行查表的。
HandlerFIQ HANDLER HandleFIQ
HandlerIRQ HANDLER HandleIRQ
HandlerUndef HANDLER HandleUndef
HandlerSWI HANDLER HandleSWI
HandlerDabort HANDLER HandleDabort
HandlerPabort HANDLER HandlePabort
下面這段程序很重要,他是實現第二次查表的程序。arm把所有中斷都歸為一個IRQ和一個FIRQ中斷異常,我們為了要知道具體的中斷,從而才可以跳到中斷對應的中斷服務程序。
IsrIRQ
sub sp,sp,#4 //保留pc寄存器的值
stmfd sp!,{r8-r9}//把r8 r9按入堆棧
ldr r9,=INTOFFSET//把中斷偏移INTOFFSET的地址裝入r9里面
ldr r9,[r9]//取出INTOFFSET單元里面的值給r9
ldr r8,=HandleEINT0//向量表的入口地址賦給r8
add r8,r8,r9,lsl #2//求出具體中斷向量的地址
ldr r8,[r8]//中斷向量里面存儲的中斷服務程序的入口地址賦給r8
str r8,[sp,#8]//按入堆棧
ldmfd sp!,{r8-r9,pc}//堆棧彈出,跳轉到相應的中斷服務程序
LTORG//聲明文字池
板子上電后就,程序就執行0x00處的b ResetHandler
ResetHandler
ldr r0,=WTCON //關閉看門狗
ldr r1,=0x0
str r1,[r0]
ldr r0,=INTMSK
ldr r1,=0xffffffff //關閉所有中斷
str r1,[r0]
ldr r0,=INTSUBMSK
ldr r1,=0x7fff //關閉所有子中斷
str r1,[r0]
[ {FALSE}
;rGPFDAT = (rGPFDAT & ~(0xf<<4)) | ((~data & 0xf)<<4);
; Led_Display
ldr r0,=GPBCON
ldr r1,=0x155500
str r1,[r0]//使GPB10~GPB4為輸出口,GPB3~GPB0為輸入口
ldr r0,=GPBDAT
ldr r1,=0x0
str r1,[r0]//使GPB10~GPB4輸出為低電平,GPB3~GPB0輸入為低電平
]
通過數據手冊可以發現,當輸出為1時,LED滅,反之亦然。
LOCKTIME是pll的lock time計數器。為了減少pll的lock time,調整LOCKTIME寄存器。
ldr r0,=LOCKTIME
ldr r1,=0xffffff//賦給這個值后,UPLL和MPLL的locktime的值都會設定好了。具體為什么是設定這個值,你就去問問三星公司吧,我也不太懂。
str r1,[r0]
說到這里,大家可能不太懂。我就在這里細說一下吧。這個涉及到arm9的時鐘模塊的知識。arm9有個時鐘控制邏輯,它可以產生cpu的FCLK時鐘、AHB總線外圍接口器件的HCLK時鐘以及APB總線外圍接口器件的PCLK時鐘。arm9有兩個鎖相環PLL,一個用于FCLK、HCLK、HCLK。一個用于USB模塊。這兩個PLL我們分別稱之為MPLL和UPLL。在系統復位之后,PLL按照默認的配置進行操作,由于認為它這時是一個不穩定的狀態,所以這時用外部時鐘作為FCLK時鐘的輸出。只有當向PLLCON寄存器設置相應的值后,PLL就會按照軟件設置的頻率運行了。這時就換成使用PLL的輸出作為FCLK了。對于FCLK先后不是有兩次不同時鐘作為輸入,這樣就余姚一個適應的時間,這個時間的設定就是我們這里在LOCKTIME寄存器里面設置的常數啦。
[ PLL_ON_START//設置CLKDIVN的值在PLL鎖存時間之后有效。
ldr r0,=CLKDIVN
ldr r1,=CLKDIV_VAL ; 0=1:1:1, 1=1:1:2, 2=1:2:2, 3=1:2:4, 4=1:4:4, 5=1:4:8, 6=1:3:3, 7=1:3:6.
str r1,[r0]
可以看出是對FCLK、PCLK以及HCLK三者的比率設置。只要通過對CLKDIVN執行操作就可以得到相應需要的比率了。
[ CLKDIV_VAL>1 //如果 Fclk:Hclk不是1:1的話執行下面
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000;R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
|
mrc p15,0,r0,c1,c0,0
bic r0,r0,#0xc0000000;R1_iA:OR:R1_nF
mcr p15,0,r0,c1,c0,0
]
這里可以看出,如果FCLK:HCLK不是1:1的關系的話,就要轉成異步總線模式。反之,如果是這個比例關系的話,就轉成快速總線模式。
ldr r0,=UPLLCON//對UPLL進行配置
ldr r1,=((U_MDIV<<12)+(U_PDIV<<4)+U_SDIV)//這里就是非常熟悉的PMS啦,Fin = 12.0MHz, UCLK = 48MHz
str r1,[r0]
nop ; Caution: After UPLL setting, at least 7-clocks delay must be inserted for setting hardware be completed.
nop
nop
nop
nop
nop
nop
ldr r0,=MPLLCON//對MPLL進行配置
ldr r1,=((M_MDIV<<12)+(M_PDIV<<4)+M_SDIV) ;Fin = 12.0MHz, FCLK = 400MHz
str r1,[r0]
]
ldr r1,=GSTATUS2
ldr r0,[r1]
tst r0,#0x2
判斷是否是從休眠模式喚醒的,對GSTATUS2[2]的檢測就可以判斷出是否從休眠模式喚醒的。
bne WAKEUP_SLEEP//如果是的話就跳轉。
EXPORT StartPointAfterSleepWakeUp//定義一個外部的StartPointAfterSleepWakeUp
StartPointAfterSleepWakeUp
adrl r0, SMRDATA
ldr r1,=BWSCON
add r2, r0, #52
0
ldr r3, [r0], #4
str r3, [r1], #4
cmp r2, r0
bne %B0
這段代碼的作用就是設置存儲控制器。在代碼的后面有一個SMRDATA的數據區,用r0來定義它的起始地址,用r2來定義它的結束地址。r3是代表那13個存儲控制器.代碼很明顯,就是把內存的數據賦給這13個存儲控制器里面的。
ldr r0,=GPFCON
ldr r1,=0x0
str r1,[r0]//對GPF設置為輸入的功能
ldr r0,=GPFUP
ldr r1,=0xff
str r1,[r0]//禁止上拉電阻
ldr r1,=GPFDAT
ldr r0,[r1]
bic r0,r0,#(0x1e<<1)//bic是r0與#(0x1e<<1)的反碼按位相與。
tst r0,#0x1//這里就是測試最后一位是否為0,為0時說明是有按鍵按下了。
bne %F1//當按鍵0沒有被按下的時候,就跳轉啦。
這段代碼是檢測EINT0是否被按下了。
ldr r0,=GPFCON
ldr r1,=0x55aa
str r1,[r0]//GPF7~GPF4設置為輸出,GPF3~GPF0設置為EINT0~EINT3
ldr r0,=GPFDAT
ldr r1,=0x0
str r1,[r0] //很明顯,GPF7~GPF4設置為LED燈的控制,低電平全部亮了。起到指示的用途。
mov r1,#0
mov r2,#0
mov r3,#0
mov r4,#0
mov r5,#0
mov r6,#0
mov r7,#0
mov r8,#0
ldr r9,=0x4000000 ;64MB
ldr r0,=0x30000000
0
stmia r0!,{r1-r8}
subs r9,r9,#32
bne %B0
很明顯可以看出,程序利用r1~r8這幾個寄存器把0x30000000到0x34000000的內存全部清零了。
1
bl InitStacks//初始化堆棧
ldr r0, =BWSCON
ldr r0, [r0]
ands r0, r0, #6//OM[1:0] != 0, 從NOR FLash或者內存啟動,不用讀取NAND FLASH
bne copy_proc_beg//不需要從NAND FLASH啟動就在這里跳轉啦
adr r0, ResetEntry//OM[1:0] == 0,就從NAND FLash啟動
cmp r0, #0//在進行比較,是否入口地址是在0處,如果不是則是用仿真器
bne copy_proc_beg//仿真器也不需要在NAND FLASH啟動
nand_boot_beg
[ {TRUE}
bl RdNF2SDRAM
]
ldr pc, =copy_proc_beg
我們來看下RdNF2SDRAM具體是怎么工作的,這段代碼的作用就是把NAND的程序讀到RAM里面。
void RdNF2SDRAM( )
{
U32 i;
U32 start_addr = 0x0;
unsigned char * to = (unsigned char *)0x30000000;
U32 size = 0x100000;//可以算出是8M的大小。
rNF_Init();//我們來仔細看看這個函數吧。
如下:
static void rNF_Init(void)
{
rNFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4)|(0<<0);//TACLS=1,TWRPH0=4,TWRPH1=0初始化ECC,CLE&ALE持續時間的設置,TWRPH0和TWRPH1持續時間的設置。
rNFCONT = (0<<13)|(0<<12)|(0<<10)|(0<<9)|(0<<8)|(1<<6)|(1<<5)|(1<<4)|(1<<1)|(1<<0);//在讀寫NANDFLASH之前,對6,5,4位的設置是確保可以使用ECC;對13位清零,使得可以寫,擦除還有讀0x4E000038~0x4E00003C區域的內容;由于對于這范圍區域的讀寫我們不加任何限制,所以我們就不用設置中斷來通知系統這個范圍的區域被讀寫了,也就是10位清零了;RnB是表示存儲器現在是否處于忙碌狀態,9位的設置為1時,表示可以用中斷來通知CPU現在存儲器的狀態,而8位的設置是用來說明是上升沿觸發還是下降沿觸發。
rNFSTAT = 0;
rNF_Reset();
}
我們來看下rNF_Reset()它的具體代碼吧,代碼如下:
static void rNF_Reset()
{
NF_CE_L();
NF_CLEAR_RB();
NF_CMD(CMD_RESET);
NF_DETECT_RB();
NF_CE_H();
}
代碼看上去很煩人,其實不是的,就是一堆宏定義,我直接翻譯一下吧,翻譯如下:
rNFCONT &= ~(1<<1); //位1清零,表示片選使能,這樣片子就可以工作了。
rNFSTAT |= (1<<2);//清零2位,這里不需要判斷片子是否忙碌。
rNFCMD = (CMD_RESET);//其中CMD_RESET=0xff。
while(!(rNFSTAT&(1<<2)));//當RnB從低電平變換到高電平的時候,就會跳出這個循環。就是在等待NANDFLASH操作完畢。
rNFCONT |= (1<<1);//使片子停止工作。
這樣NANDFLASH的初始化工作終于完成了。我們現在回到RdNF2SDRAM里面來,接著往下分析。
switch(rNF_ReadID())我們來分析一下里面這個函數吧,代碼如下:
static char rNF_ReadID()
{
char pMID;
char pDID;
char nBuff;
char n4thcycle;
int i;
NF_nFCE_L();//又是使能片子工作
NF_CLEAR_RB();//清除NFSTAT的2位,為以后判斷片子是否工作完畢。
NF_CMD(CMD_READID); //往NFCMD送讀ID指令。
NF_ADDR(0x0);//往NFADDR送地址
for ( i = 0; i < 100; i++ );
pMID = NF_RDDATA8();
pDID = NF_RDDATA8();
nBuff = NF_RDDATA8();
n4thcycle = NF_RDDATA8();
NF_nFCE_H();
return (pDID);
}//最后返回pDID為什么會有其它值,我就不太理解了。我們再返回到主程序里面看看。
switch(rNF_ReadID())
{
case 0x76:
for(i = (start_addr >> 9); size > 0; )//在這種情況下,認為一頁的大小為512字節
{
rSB_ReadPage(i, to);
size -= 512;
to += 512;
i ++;
}
break;
case 0xf1:
case 0xda:
case 0xdc:
case 0xd3:
for(i = (start_addr >> 11); size > 0; )//在這種情況下,認為是2048字節為一頁
{
rLB_ReadPage(i, to);
size -= 2048;
to += 2048;
i ++;
}
break;
}
}
其實都是把NANDFLASH的開始第二頁的內容存放在一個指針數組里面,這個指針數組的起始地址在0x30000000。就是我們等下在下面看到的to[i]數組了。下面兩個函數完成的功能是一樣的,只是區別在于一頁是多大,512或者是2048。
static void rSB_ReadPage(U32 addr, unsigned char * to)
{
U32 i;
rNF_Reset();
// Enable the chip
NF_nFCE_L();
NF_CLEAR_RB();
// Issue Read command
NF_CMD(CMD_READ);
// Set up address
NF_ADDR(0x00);
NF_ADDR((addr) & 0xff);
NF_ADDR((addr >> 8) & 0xff);
NF_ADDR((addr >> 16) & 0xff);
NF_DETECT_RB(); // wait tR(max 12us)
for (i = 0; i < 512; i++)
{
to[i] = NF_RDDATA8();
}
NF_nFCE_H();
}
static void rLB_ReadPage(U32 addr, unsigned char * to)
{
U32 i;
rNF_Reset();
// Enable the chip
NF_nFCE_L();
NF_CLEAR_RB();
// Issue Read command
NF_CMD(CMD_READ);
// Set up address
NF_ADDR(0x00);
NF_ADDR(0x00);
NF_ADDR((addr) & 0xff);
NF_ADDR((addr >> 8) & 0xff);
NF_ADDR((addr >> 16) & 0xff);
NF_CMD(CMD_READ3);
NF_DETECT_RB(); // wait tR(max 12us)
for (i = 0; i < 2048; i++)
{
to[i] = NF_RDDATA8();
}
NF_nFCE_H();
}
可以看出剛開始的時候都是先復位一下的,不同的地方在于每次是怎樣把傳進來的地址經過轉換再付給NFADDR寄存器的,具體怎么樣要看NAND的數據手冊。
我們接著回到2440init.s的程序來,接著就有以下一句:
ldr pc, =copy_proc_beg
在前面也看到copy_proc_beg這個標號出現很多次,這個標號下面的代碼完成的功能就是把nand flash的內容拷貝到ram當中。
copy_proc_beg
adr r0, ResetEntry
ldr r2, BaseOfROM
cmp r0, r2//兩個進行比較
ldreq r0, TopOfROM//如果相同的話,為r0賦上R0的結束位置,也是RW的起始位置。
beq InitRam //如果相同的話,就跳到這個標號的位置。
ldr r3, TopOfROM//以下代碼是針對代碼在NOR FLASH時的拷貝方法。
0
ldmia r0!, {r4-r7}
stmia r2!, {r4-r7}
cmp r2, r3
bcc %B0//這幾段代碼的功能就是把ResetEntry的內容搬到BaseOfROM(R0的起始位置,后面有聲明的)。
sub r2, r2, r3
sub r0, r0, r2 //這里使 ResetEntry的位置往下移,為了后面的數據拷貝做準備。
InitRam
ldr r2, BaseOfBSS
ldr r3, BaseOfZero
0
cmp r2, r3
ldrcc r1, [r0], #4
strcc r1, [r2], #4
bcc %B0 //可以看出這一段是對ResetEntry里面定義好的數據拷貝到RW段。
mov r0, #0
ldr r3, EndOfBSS
1
cmp r2, r3
strcc r0, [r2], #4
bcc %B1//如果拷貝完數據后還剩下多余的空間的話,就往里面填充0
ldr pc, =%F2 ;goto compiler address
2
ldr r0,=HandleIRQ
ldr r1,=IsrIRQ
str r1,[r0]//這三條語句很明顯就是說明了,HandleIRQ這個中斷向量的存儲單元被賦上了IsrIRQ標號的地址,這樣發生IRQ中斷后就會直接去到二級表,去確認具體發生哪個中斷。
[ :LNOT:THUMBCODE
bl Main //到這里,我們就看到了進入MAIN函數了。
b .
]
[ THUMBCODE ;for start-up code for Thumb mode
orr lr,pc,#1
bx lr
CODE16
bl Main //可以看到以上代碼表示如果arm是在THUMBCODE指令模式下的話,就進行模式轉換。
b .
CODE32
]
到這里,我們已經把2440init.s的啟動代碼分析了一遍了。如有任何錯誤的話,請大家指出!謝謝!