ARM匯編必知必會
ADC 帶進位的32位數(shù)加法
ADD 32位數(shù)相加
AND 32位數(shù)的邏輯與
B 在32M空間內(nèi)的相對跳轉(zhuǎn)指令
BIC 32位數(shù)的邏輯位清零
BKPT 斷點指令
BL 帶鏈接的相對跳轉(zhuǎn)指令
BLX 帶鏈接的切換跳轉(zhuǎn)
BX 切換跳轉(zhuǎn)
CDPCDP2 協(xié)處理器數(shù)據(jù)處理操作
CLZ 零計數(shù)
CMN 比較兩個數(shù)的相反數(shù)
CMP 32位數(shù)比較
EOR 32位邏輯異或
LDCLDC2 從協(xié)處理器取一個或多個32位值
LDM 從內(nèi)存送多個32位字到ARM寄存器
LDR 從虛擬地址取一個單個的32位值
MCRMCR2MCRR 從寄存器送數(shù)據(jù)到協(xié)處理器
MLA 32位乘累加
MOV 傳送一個32位數(shù)到寄存器
MRCMRC2MRRC 從協(xié)處理器傳送數(shù)據(jù)到寄存器
MRS 把狀態(tài)寄存器的值送到通用寄存器
MSR 把通用寄存器的值傳送到狀態(tài)寄存器
MUL 32位乘
MVN 把一個32位數(shù)的邏輯“非”送到寄存器
ORR 32位邏輯或
PLD 預裝載提示指令
QADD 有符號32位飽和加
QDADD 有符號雙32位飽和加
QSUB 有符號32位飽和減
QDSUB 有符號雙32位飽和減
RSB 逆向32位減法
RSC 帶進位的逆向32法減法
SBC 帶進位的32位減法
SMLAxy 有符號乘累加(16位*16位)+32位=32位
SMLAL 64位有符號乘累加((32位*32位)+64位=64位)
SMALxy 64位有符號乘累加((32位*32位)+64位=64位)
SMLAWy 32位有號乘累加((32位*16位)>>16位)+32位=32位
SMULL 64位有符號乘累加(32位*32位)=64位
SMULxy 32位有符號乘(16位*16位=32位)
SMULWy 32位有符號乘(32位*16位>>16位=32位)
STCSTC2 從協(xié)處理器中把一個或多個32位值存到內(nèi)存
STM 把多個32位的寄存器值存放到內(nèi)存
STR 把寄存器的值存到一個內(nèi)存的虛地址內(nèi)間
SUB 32位減法
SWI 軟中斷
SWP 把一個字或者一個字節(jié)和一個寄存器值交換
TEQ 等值測試
TST 位測試
UMLAL 64位無符號乘累加((32位*32位)+64位=64位)
UMULL 64位無符號乘累加(32位*32位)=64位
基于RISC 的ARM CPU
ARM是一種RISC體系結(jié)構(gòu)的處理器芯片。和傳統(tǒng)的CISC體系結(jié)構(gòu)不同,RISC 有以下的幾個特點:
◆ 簡潔的指令集——為了保證CPU可以在高時鐘頻率下單周期執(zhí)行指令,RISC指令集只提供很有限的操作(例如add,sub,mul等),而復雜的操作都需要由這些簡單的指令來組合進行模擬。并且,每一條指令不僅執(zhí)行時間固定,其指令長度也是固定的,這樣,在譯碼階段就可以對下一條指令進行預取。
◆ Load-Store 結(jié)構(gòu)——這個應(yīng)該是RISC 設(shè)計中比較有特點的一部分。在RISC 中,CPU并不會對內(nèi)存中的數(shù)據(jù)進行操作,所有的計算都要求在寄存器中完成。而寄存器和內(nèi)存的通信則由單獨的指令來完成。而在CSIC中,CPU是可以直接對內(nèi)存進行操作的,這也是一個比較特別的地方。
◆ 更多的寄存器——和CISC 相比,基于RISC的處理器有更多的通用寄存器可以使用,且每個寄存器都可以進行數(shù)據(jù)存儲或者尋址。
圖:user模式下ARM處理器體系結(jié)構(gòu)
◆ r13 - 指向當前棧頂,相當于x86的esp,這個東西在匯編指令中要用sp 表示
◆ r14 - 稱作鏈接寄存器,指向函數(shù)的返回地址。用lr表示,這和x86將返回地址保存在棧中是不同的
◆ r15 - 類似于x86的eip,其值等于當前正在執(zhí)行的指令的地址+8(因為在取址和執(zhí)行之間多了一個譯碼的階段),這個用pc表示
ARM 指令集
ARM處理器可以支持3種指令集——ARM,Thumb和Jazelle。
采用那種指令集,由cspr中的標志位來決定。大體說來:
◆ ARM——這是ARM自身的32 位指令集
◆ Thumb ——這是一個全16 位的指令集,在16 位外部數(shù)據(jù)總線寬度下,這個指令集的效率要比32 位的ARM指令高一些。
◆ Jazelle ——這是一個8位指令集,用來加速Java字節(jié)碼的執(zhí)行
{S} [Rd], [Rn], [Rm]
其中:
* {S} —— 加上這個后綴的指令會更新cpsr 寄存器
* [Rd] —— 目的寄存器
* [Rn]/[Rm] —— 源寄存器
一般來說,arm 指令有3個操作數(shù),其中Rm寄存器在執(zhí)行指令前可以進入桶形移位器進行移位操作,而Rn則會直接進入ALU 單元。如果一條arm 指令只有2 個操作數(shù),那么源寄存器按照Rm 來處理。例如,一條加法指令:
add r0, r1, #1
就會把r1+1的結(jié)果存放到r0中。
Load-Store 指令體系
◆ 單寄存器傳輸(這是與x86 最為相像的)
◆ 多寄存器傳輸
◆ 交換指令
單寄存器傳輸
先看第一個,很簡單:把單一的數(shù)據(jù)傳入(LDR) 或傳出(STR)寄存器,對內(nèi)存的訪問可以是DWORD(32-bit), WORD(16-bit)和BYTE(8-bit)。指令的格式如下:
DWORD:
Rd, addressing1
WORD:
H Rd, addressing2 無符號版
SH Rd, addressing2 有符號版
BYTE:
B Rd, addressing1 無符號版
SB Rd, addressing2 有符號版
addressing1 和addressing2 的分類下面再說,現(xiàn)在理解成某種尋址方式就可以了。
在單寄存器傳輸方面,還有以下三種變址模式,他們是:
◆ preindex
這種變址方式和x86的尋址機制是很類似的,先對寄存器進行運算,然后尋址,但是在尋之后,基址寄存器的內(nèi)容并不發(fā)生改變,例如:
ldr r0, [r1, #4]
的含義就是把r1+4 這個地址處的DOWRD 加載到r0,而尋址后,r1 的內(nèi)容并不改變。
◆ preindex with writeback
這種變址方式有點類似于++i的含義,尋址前先對基地址寄存器進行運算,然后尋址. 其基本的語法是在尋址符[]后面加上一個"!" 來表示.例如:
ldr r0, [r1, #4]!
就可以分解成:
add r1, r1, #4
ldr r0, [r1, #0]
◆ postindex
自然這種變址方式和i++的方式就很類似了,先利用基址寄存器進行尋址,然后對基址寄存器進行運算,其基本語法是把offset 部分放到[]外面,例如:
ldr r0, [r1], #4
就可以分解成:
ldr r0, [r1, #0]
add r1, r1, #4
如果你還記得x86 的SIB 操作的話,那么你一定想ARM是否也有,答案是有也沒有。在ss上面提到的addressing1 和addressing2的區(qū)別就是比例寄存器的使用,addressing1可以使用[base, scale, 桶形移位器]來實現(xiàn)SB 的效果,或者通過[base,offset](這里的offset 可以是立即數(shù)或者寄存器)來實現(xiàn)SI 的效果,而addressing2則只能用后者了。于是每一種變址方式最多可以有3 種尋址方式,這樣一來,最多可以有9種用來尋址的指令形式。例如:
ldr r0, [r1, r2, LSR #0x04]!
ldr r0, [r1, -#0x04]
ldr r0, [r1], LSR #0x04
每樣找了一種,大概就是這個意思。到此,單寄存器傳輸就結(jié)束了,掌握這些足夠應(yīng)付差事了。下面來看看多寄存器傳輸吧。
多寄存器傳輸
<尋址模式> Rn{!}, {r^}
我們先來搞明白尋址模式,多寄存器傳輸模式有4 種:
也就是說以A開頭的都是在Rn的原地開始操作,而B開頭的都是以Rn的下一個位置開始操作。如果你仍然感到困惑,我們不妨看個例子。
所有的示例指令執(zhí)行前:
mem32[0x1000C] = 0x04
mem32[0x10008] = 0x03
mem32[0x10004] = 0x02
mem32[0x10000] = 0x01
r0 = 0x00010010
r1 = 0x00000000
r3 = 0x00000000
r4 = 0x00000000
1) ldmia r0!, {r1-r3} 2) ldmib r0!, {r1-r3}
執(zhí)行后:
r0 = 0x0010001C
r1 = 0x01
r2 = 0x02
r3 = 0x03
至于DA 和DB 的模式,和IA / IB 是類似的,不多說了。
最后要說的是,使用ldm 和stm指令對進行寄存器組的保護是很常見和有效的功能。配對方案:
stmia / ldmdb
stmib / ldmda
stmda / ldmib
stmdb / ldmia
繼續(xù)來看兩個例子:
執(zhí)行前:
r0 = 0x00001000
r1 = 0x00000003
r2 = 0x00000002
r3 = 0x00000001
執(zhí)行的指令:
stmib r0!, {r1-r3}
mov r1, #1 ; These regs have been modified
mov r2, #2
mov r3, #3
當前寄存器狀態(tài):
r0 = 0x0000100C
r1 = 0x00000001
r2 = 0x00000002
r3 = 0x00000003
ldmia r0!, {r1-r3}
最后的結(jié)果:
r0 = 0x00001000
r1 = 0x00000003
r2 = 0x00000002
r3 = 0x00000001
另外,我們還可以利用這個指令對完成內(nèi)存塊的高效copy:
loop
ldmia r9!, {r0-r7}
stmia r10!, {r0-r7}
cmp r9, r11
bne loop
說到這里,讀者應(yīng)該對RISC的Load-Store體系結(jié)構(gòu)有一個大概的了解了,能夠正確配對使用指令,是很重要的。
ARM 異常處理
如果您閱讀ARM手冊,您會發(fā)現(xiàn),在ARM中,經(jīng)常強調(diào)Exception(異常)這個概念,在ARM里,Interrupt(中斷)也是一種形式的異常。ARM的Exception同其所定義的5種異常模式是密切相關(guān)的,CPU在捕獲到任何一個Exception后,必定會進入某個異常模式,異常類型及捕獲到該異常后CPU所進入的異常模式之間的對應(yīng)關(guān)系是ARM所預先定義好的。如果您對X86比較熟悉,您會發(fā)現(xiàn),不象X86,系統(tǒng)定義了不同的中斷,比如鍵盤中斷,鼠標中斷等等,并且系統(tǒng)也定義了這些中斷所對應(yīng)的中斷向量。ARM沒有定義這些,ARM只會告訴你,有外部中斷產(chǎn)生,并切換到IRQ或FIQ模式,然后執(zhí)行IRQ或FIQ所對應(yīng)的中斷向量。至于到底是鍵盤中斷,還是鼠標中斷,這得由操作系統(tǒng)提供的中斷函數(shù)自己去判斷,比如通過查詢中斷控制器的某個或某些寄存器。ARM這樣做的原因是:ARM只是一個CORE,它并不定義也不去假想其外部環(huán)境,這樣可以使得ARM CORE更加緊湊和簡潔,同時也給SOC設(shè)計者提供了更多的靈活性和發(fā)揮空間。您一定要相信,ARM被如此廣泛使用不是“蓋”的,從系統(tǒng)開發(fā)者角度看,ARM是一種最簡單、最靈活的CPU,它的優(yōu)雅和簡潔性就像C語言一樣。呵呵,C語言是我最喜歡的語言。
好了,“臭屁”了這么多,我們言歸正傳。對ARM異常處理的研究務(wù)必要弄清楚以下幾個方面:
(1) 異常類型
(2) 異常類型及處理該異常時CPU的執(zhí)行模式
(3) 異常向量地址
(4) 異常處理過程
異常類型
ARM定義了如下類型的異常(江南七怪,這樣好記):
(1)
(2)
(3)
(4)
(5)
(6)
(7)
執(zhí)行模式
當產(chǎn)生異常后,CPU會進入相應(yīng)的異常模式并處理該異常:
向量地址
ARM的異常向量地址可以處于4G物理空間的低端(0x00000000起),也可以處于高端(0xffff0000起),具體是哪種情況,根據(jù)具體的CPU及其配置而定。下面是7種異常的向量地址(挎弧內(nèi)為高端情形):
(1)
(2)
(3)
(4)
(5)
(6)
(7)
每個中斷向量為4字節(jié),一般的操作系統(tǒng)在該地址處放置一條跳轉(zhuǎn)指令“LDR PC,終端處理函數(shù)地址”。另外要注意的是,在IRQ異常和Data Abort異常之間空了4個字節(jié),這4個字節(jié)是保留的。
處理過程
Linux BOOTLOADER全程詳解
網(wǎng)上關(guān)于Linux的BOOTLOADER文章不少了,但是大都是vivi,blob等比較龐大的程序,讀起來不太方便,編譯出的文件也比較大,而且更多的是面向開發(fā)用的引導代碼,做成產(chǎn)品時還要裁減,這一定程度影響了開發(fā)速度,對初學者學習開銷也比較大,在此分析一種簡單的BOOTLOADER,是在三星公司提供的2410BOOTLOADER上稍微修改后的結(jié)果,編譯出來的文件大小不超過4k,希望對大家有所幫助.
1.幾個重要的概念
COMPRESSED KERNEL and DECOMPRESSED KERNEL
壓縮后的KERNEL,按照文檔資料,現(xiàn)在不提倡使用DECOMPRESSED KERNEL,而要使用COMPRESSED KERNEL,它包括了解壓器.因此要在ram分配時給壓縮和解壓的KERNEL提供足夠空間,這樣它們不會相互覆蓋.當執(zhí)行指令跳轉(zhuǎn)到 COMPRESSED KERNEL后,解壓器就開始工作,如果解壓器探測到解壓的代碼會覆蓋掉COMPRESSED KERNEL,那它會直接跳到COMPRESSED KERNEL后存放數(shù)據(jù),并且重新定位KERNEL,所以如果沒有足夠空間,就會出錯.
Jffs2 File System
可以使armlinux應(yīng)用中產(chǎn)生的數(shù)據(jù)保存在FLASH上,我的板子還沒用到這個.
RAMDISK
使用RAMDISK可以使ROOT FILE SYSTEM在沒有其他設(shè)備的情況下啟動.一般有兩種加載方式,我就介紹最常用的吧,把COMPRESSED RAMDISK IMAGE放到指定地址,然后由BOOTLOADER把這個地址通過啟動參數(shù)的方式ATAG_INITRD2傳遞給KERNEL.具體看代碼分析.
啟動參數(shù)(摘自IBM developer)
在調(diào)用內(nèi)核之前,應(yīng)該作一步準備工作,即:設(shè)置 Linux 內(nèi)核的啟動參數(shù)。Linux 2.4.x 以后的內(nèi)核都期望以標記列表(tagged list)的形式來傳遞啟動參數(shù)。啟動參數(shù)標記列表以標記 ATAG_CORE 開始,以標記 ATAG_NONE 結(jié)束。每個標記由標識被傳遞參數(shù)的 tag_header 結(jié)構(gòu)以及隨后的參數(shù)值數(shù)據(jù)結(jié)構(gòu)來組成。數(shù)據(jù)結(jié)構(gòu) tag 和 tag_header 定義在Linux 內(nèi)核源碼的include/asm/setup.h 頭文件中.
在嵌入式 Linux 系統(tǒng)中,通常需要由 BOOTLOADER 設(shè)置的常見啟動參數(shù)有:ATAG_CORE、ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_INITRD等。
(注)參數(shù)也可以用COMMANDLINE來設(shè)定,在我的BOOTLOADER里,我兩種都用了.
2.開發(fā)環(huán)境和開發(fā)板配置:
CPU:S3C2410,BANK6上有64M的SDRAM(兩塊),BANK0上有32M NOR FLASH,串口當然是逃不掉的.這樣,按照數(shù)據(jù)手冊,地址分配如下:
0x4000_0000開始是4k的片內(nèi)DRAM.
0x0000_0000開始是32M FLASH 16bit寬度
0x3000_0000開始是64M SDRAM 32bit寬度
注意:控制寄存器中的BANK6和BANK7部分必須相同.
0x4000_0000(片內(nèi)DRAM)存放4k以內(nèi)的BOOTLOADER IMAGE
0x3000_0100開始存放啟動參數(shù)
0x3120_0000 存放COMPRESSED KERNEL IMAGE
0x3200_0000 存放COMPRESSED RAMDISK
0x3000_8000 指定為DECOMPRESSED KERNEL IMAGE ADDRESS
0x3040_0000 指定為DECOMPRESSED RAMDISK IMAGE ADDRESS
開發(fā)環(huán)境:Redhat Linux,armgcc toolchain, armlinux KERNEL
如何建立armgcc的編譯環(huán)境:建議使用toolchain,而不要自己去編譯armgcc,偶試過好多次,都以失敗告終.
先下載arm-gcc 3.3.2 toolchain
將arm-linux-gcc-3.3.2.tar.bz2 解壓到 /toolchain
# tar jxvf arm-linux-gcc-3.3.2.tar.bz2
# mv /usr/local/arm/3.3.2 /toolchain
在makefile 中在把arch=arm CROSS_COMPILE設(shè)置成toolchain的路徑還有就是INCLUDE = -I ../include -I /root/my/usr/local/arm/3.3.2/include.,否則庫函數(shù)就不能用了
3.啟動方式:
可以放在FLASH里啟動,或者用Jtag仿真器.由于使用NOR FLASH,根據(jù)2410的手冊,片內(nèi)的4K DRAM在不需要設(shè)置便可以直接使用,而其他存儲器必須先初始化,比如告訴memory controller,BANK6里有兩塊SDRAM,數(shù)據(jù)寬度是32bit,= =.否則memory control會按照復位后的默認值來處理存儲器.這樣讀寫就會產(chǎn)生錯誤.
所以第一步,通過仿真器把執(zhí)行代碼放到0x4000_0000,(在編譯的時候,設(shè)定TEXT_BASE=0x40000000)
第二步,通過 AxD把linux KERNEL IMAGE放到目標地址(SDRAM)中,等待調(diào)用
第三步,執(zhí)行BOOTLOADER代碼,從串口得到調(diào)試數(shù)據(jù),引導armlinux
4.代碼分析
講了那么多執(zhí)行的步驟,是想讓大家對啟動有個大概印象,接著就是BOOTLOADER內(nèi)部的代碼分析了,BOOTLOADER文章內(nèi)容網(wǎng)上很多,我這里精簡了下,刪除了不必要的功能.
BOOTLOADER一般分為2部分,匯編部分和c語言部分,匯編部分執(zhí)行簡單的硬件初始化,C部分負責復制數(shù)據(jù),設(shè)置啟動參數(shù),串口通信等功能.
BOOTLOADER的生命周期:
1. 初始化硬件,比如設(shè)置UART(至少設(shè)置一個),檢測存儲器= =.
2. 設(shè)置啟動參數(shù),這是為了告訴內(nèi)核硬件的信息,比如用哪個啟動界面,波特率 = =.
3. 跳轉(zhuǎn)到Linux KERNEL的首地址.
4. 消亡
當然,在引導階段,象vivi等,都用虛地址,如果你嫌煩的話,就用實地址,都一樣.
我們來看代碼:
2410init.s
.global _start//開始執(zhí)行處
_start:
//下面是中斷向量
b reset @ Supervisor Mode//重新啟動后的跳轉(zhuǎn)
??
??
reset:
ldr r0,=WTCON /WTCON地址為53000000,watchdog的控制寄存器 */
ldr r1,=0x0
str r1,[r0]
ldr r0,=INTMSK
ldr r1,=0xffffffff
str r1,[r0]
ldr r0,=INTSUBMSK
ldr r1,=0x3ff
str r1,[r0]
ldr r0, =GPFCON
ldr r1, =0x55aa
str r1, [r0]
ldr r0, =GPFUP
ldr r1, =0xff
str r1, [r0]
ldr r0,=GPFDAT
ldr r1,=POWEROFFLED1
str r1,[r0]
ldr r0,=CLKDIVN
ldr r1,=0x3
str r1,[r0]
ldr r0,=LOCKTIME
ldr r1,=0xffffff
str r1,[r0]
ldr r0,=MPLLCON
ldr r1,=((M_MDIV<<12)+(M_PDIV<<4)+M_SDIV) //Fin=12MHz,Fout=203MHz
str r1,[r0]
ldr r1,=GSTATUS2
ldr r10,[r1]
tst r10,#OFFRST
bne 1000f
//以上這段,我沒動,就用三星寫的了,下面是主要要改的地方
add r0,pc,#MCDATA - (.+8)// r0指向MCDATA地址,那里存放著MC初始化要用到的數(shù)據(jù)
ldr r1,=BWSCON // r1指向MC控制器寄存器的首地址
add r2,r0,#52 // 復制次數(shù),偏移52字
1: //按照偏移量進行循環(huán)復制
ldr r3,[r0],#4
str r3,[r1],#4
cmp r2,r0
bne 1b
.align 2
MCDATA:
.word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
上面這行就是BWSCON的數(shù)據(jù),具體參數(shù)意義如下:
需要更改設(shè)置DW6 和DW7都設(shè)置成10,即32bit,DW0 設(shè)置成01,即16bit
下面都是每個BANK的控制器數(shù)據(jù),大都是時鐘相關(guān),可以用默認值,設(shè)置完MC后,就跳到調(diào)用main函數(shù)的部分
.word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC)) .word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC)) .word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC)) .word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC)) .word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC)) .word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC)) .word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN)) .word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN)) .word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT) .word 0xB2 .word 0x30 .word 0x30 .align 2 .global call_main //調(diào)用main函數(shù),函數(shù)參數(shù)都為0 call_main: ldr sp,STACK_START mov fp,#0 mov a1, #0 mov a2, #0 bl main STACK_START: .word STACK_BASE undefined_instruction: software_interrupt: prefetch_abort: data_abort: not_used: irq: fiq:
2410init.c file int main(int argc,char **argv) { u32 test = 0; void (*theKERNEL)(int zero, int arch, unsigned long params_addr) = (void (*)(int, int, unsigned long))RAM_COMPRESSED_KERNEL _BASE; //壓縮后的IMAGE地址 int i,k=0; // downPt=(RAM_COMPRESSED_KERNEL_BASE); chkBs=(_RAM_STARTADDRESS);//SDRAM開始的地方 // fromPt=(FLASH_LINUXKERNEL); MMU_EnableICache(); ChangeClockDivider(1,1); // 1:2:4 ChangeMPllValue(M_MDIV,M_PDIV,M_SDIV); //Fin=12MHz FCLK=200MHz Port_Init();//設(shè)置I/O端口,在使用com口前,必須調(diào)用這個函數(shù),否則通信芯片根本得不到數(shù)據(jù) Uart_Init(PCLK, 115200);//PCLK使用默認的200000,撥特率115200 Uart_SendString("ntLinux S3C2410 Nor BOOTLOADERn"); Uart_SendString("ntChecking SDRAM 2410loader.c...n"); for(;chkBs<0x33FA0140;chkBs=chkBs+0x4,test++)//
//根據(jù)我的經(jīng)驗,最好以一個字節(jié)為遞增,我們的板子,在256byte遞增檢測的時候是沒問題的,但是
//以1byte遞增就出錯了,第13跟數(shù)據(jù)線隨幾的會冒”1”,檢測出來是硬件問題,現(xiàn)象如下
//用仿真器下代碼測試SDRAM,開始沒貼28F128A3J FLASH片子,測試結(jié)果很好,但在上了FLASH片子//之后,測試數(shù)據(jù)(data)為0x00000400
連續(xù)成批寫入讀出時,操作大約1k左右內(nèi)存空間就會出錯,//而且隨機。那個出錯數(shù)據(jù)總是變?yōu)?x00002400,數(shù)據(jù)總線10位和13位又沒短路
發(fā)生。用其他數(shù)據(jù)//測試比如0x00000200;0x00000800沒這問題。dx幫忙。
//至今沒有解決,所以我用不了Flash.
{
chkPt1 = chkBs;
*(u32 *)chkPt1 = test;//寫數(shù)據(jù)
if(*(u32 *)chkPt1==1024))//讀數(shù)據(jù)和寫入的是否一樣?
{
chkPt1 += 4;
Led_Display(1);
Led_Display(2);
Led_Display(3);
Led_Display(4);
}
else
goto error;
}
Uart_SendString("ntSDRAM Check Successful!ntMemory Maping...");
get_memory_map();
//獲得可用memory 信息,做成列表,后面會作為啟動參數(shù)傳給KERNEL
//所謂內(nèi)存映射就是指在4GB 物理地址空間中有哪些地址范圍被分配用來尋址系統(tǒng)的 RAM 單元。
Uart_SendString("ntMemory Map Successful!n");
//我用仿真器把KERNEL,RAMDISK直接放在SDRAM上,所以下面這段是不需要的,但是如果KERNEL,RAMDISK在FLASH里,那就需要.
Uart_SendString("tLoading KERNEL IMAGE from FLASH... n "); Uart_SendString("tand copy KERNEL IMAGE to SDRAM at 0x31000000n"); Uart_SendString("ttby LEIJUN DONG dongleijun4000@hotmail.com n"); for(k = 0;k < 196608;k++,downPt += 1,fromPt += 1)//3*1024*1024/32linux KERNEL des,src,length=3M * (u32 *)downPt = * (u32 *)fromPt; Uart_SendString("ttloading COMPRESSED RAMDISK...n"); downPt=(RAM_COMPRESSED_RAMDISK_BASE); fromPt=(FLASH_RAMDISK_BASE); for(k = 0;k < 196608;k++,downPt += 1,fromPt += 1)//3*1024*1024/32linux KERNEL des,src,length=3M * (u32 *)downPt = * (u32 *)fromPt; Uart_SendString("ttloading jffs2...n"); downPt=(RAM_JFFS2); fromPt=(FLASH_JFFS2); for(k = 0;k < (1024*1024/32);k++,downPt += 1,fromPt += 1) * (u32 *)downPt = * (u32 *)fromPt; Uart_SendString( "Load Success...Run...n "); setup_start_tag();//開始設(shè)置啟動參數(shù) setup_memory_tags();//內(nèi)存印象 setup_commandline_tag("console=ttyS0,115200n8");//啟動命令行 setup_initrd2_tag();//root device setup_RAMDISK_tag();//ramdisk image setup_end_tag(); asm ("mrc p15, 0, %0, c1, c0, 0": "=r" (i)); i &= ~0x1000; asm ("mcr p15, 0, %0, c1, c0, 0": : "r" (i)); asm ("mcr p15, 0, %0, c7, c5, 0": : "r" (i));
//下面這行就跳到了COMPRESSED KERNEL的首地址
theKERNEL(0, ARCH_NUMBER, (unsigned long *)(RAM_BOOT_PARAMS));
//啟動kernel時候,I-cache可以開也可以關(guān),r0必須是0,r1必須是CPU型號
(可以從linux/arch/arm/tools/mach-types中找到),r2必須是參數(shù)的物理開始地址
error: Uart_SendString("nnPanic SDRAM check error!n"); return 0; } static void setup_start_tag(void) { params = (struct tag *)RAM_BOOT_PARAMS;//啟動參數(shù)開始的地址 params->hdr.tag = ATAG_CORE; params->hdr.size = tag_size(tag_core); params->u.core.flags = 0; params->u.core.pagesize = 0; params->u.core.rootdev = 0; params = tag_next(params); } static void setup_memory_tags(void) { int i; for(i = 0; i < NUM_MEM_AREAS; i++) { if(memory_map[i].used) { params->hdr.tag = ATAG_MEM; params->hdr.size = tag_size(tag_mem32); params->u.mem.start = memory_map[i].start; params->u.mem.size = memory_map[i].len; params = tag_next(params); } } } static void setup_commandline_tag(char *commandline) { int i = 0; params->hdr.tag = ATAG_CMDLINE; params->hdr.size = 8; //console=ttyS0,115200n8 strcpy(params->u.cmdline.cmdline, p); params = tag_next(params); } static void setup_initrd2_tag(void) { params->hdr.tag = ATAG_INITRD2; params->hdr.size = tag_size(tag_initrd); params->u.initrd.start = RAM_COMPRESSED_RAMDISK_BASE; params->u.initrd.size = 2047;//k byte params = tag_next(params); } static void setup_ramdisk_tag(void) { params->hdr.tag = ATAG_RAMDISK; params->hdr.size = tag_size(tag_ramdisk); params->u.ramdisk.start = RAM_DECOMPRESSED_RAMDISK_BASE; params->u.ramdisk.size = 7.8*1024; //k byte params->u.ramdisk.flags = 1; // automatically load ramdisk params = tag_next(params); } static void setup_end_tag(void) { params->hdr.tag = ATAG_NONE; params->hdr.size = 0; } void Uart_Init(int pclk,int baud)//串口是很重要的 { int i; if(pclk == 0) pclk = PCLK; rUFCON0 = 0x0; //UART channel 0 FIFO control register, FIFO dISAble rUMCON0 = 0x0; //UART chaneel 0 MODEM control register, AFC dISAble //UART0 rULCON0 = 0x3; //Line control register : Normal,No parity,1 stop,8 bits 下面這段samsung好象寫的不太對,但是我按照Normal,No parity,1 stop,8 bits算出來的確是0x245 // [10] [9] [8] [7] [6] [5] [4] [3:2] [1:0] // Clock Sel, Tx Int, Rx Int, Rx Time Out, Rx err, Loop-back, Send break, Transmit Mode, Receive Mode // 0 1 0 , 0 1 0 0 , 01 01 // PCLK Level Pulse DISAble Generate Normal Normal Interrupt or Polling rUCON0 = 0x245; // Control register rUBRDIV0=( (int)(PCLK/16./ baud) -1 ); //Baud rate divisior register 0 delay(10); }
經(jīng)過以上的折騰,接下來就是kernel的活了.能不能啟動kernel,得看你編譯kernel的水平了.
ARM嵌入式入門的建議
由于很多人總問這個問題,所以這里做一個總結(jié)文檔供大家參考。這里必須先說明,以下的步驟都是針對Linux系統(tǒng)的,并不面向WinCE。也許你會注意到,現(xiàn)在做嵌入式的人中,做linux研究的人遠比做WinCE的人多,很多產(chǎn)家提供的資料也是以linux為主。我一直很難理解,其實WinCE的界面比linux的界面好看多了,使用起來也很方便,更為重要的是,WinCE的開發(fā)和Windows下的開發(fā)基本一樣,學起來簡單得多,但是學linux或者使用linux做嵌入式的人就是遠比WinCE多。在和很多工作的人交流時我了解到,他們公司從沒考慮使用WinCE,因為成本高,都是使用linux進行開發(fā)。我讀研究生的的實驗室中也沒有使用WinCE的,大都研究linux,也有少部分項目使用vxwork,但是就沒有聽說過使用WinCE的,原因就是開源!當然現(xiàn)在WinCE6.0聽說也開源,不過在成本和資源上linux已經(jīng)有了無人能擋的優(yōu)勢。與此相對應(yīng)的是,越來越多的電子廠商已經(jīng)開始使用linux開發(fā)產(chǎn)品。舉個例子,Google近期開發(fā)的智能手機操作系統(tǒng)Android其實就是使用linux-2.6.23內(nèi)核進行改進得到的。第一,學習基本的裸機編程。
對于學硬件的人而言,必須先對硬件的基本使用方法有感性的認識,更必須深刻認識該硬件的控制方式,如果一開始就學linux系統(tǒng)、學移植那么只會馬上就陷入一個很深的漩渦。我在剛剛開始學ARM的時候是選擇ARM7(主意是當時ARM9還很貴),學ARM7的時候還是保持著學51單片機的思維,使用ADS去編程,第一個實驗就是控制led。學過一段時間ARM的人都會笑這樣很笨,實際上也不是,我倒是覺得有這個過程會好很多,因為無論做多復雜的系統(tǒng)最終都會落實到這些最底層的硬件控制,因此對這些硬件的控制有了感性的認識就好很多了
學習裸機的編程的同時要好好理解這個硬件的構(gòu)架、控制原理,這些我稱他為理解硬件。所謂的理解硬件就是說,理解這個硬件是怎么組織這么多資源的,這些資源又是怎么由cpu、由編程進行控制的。比如說,s3c2410中有AD轉(zhuǎn)換器,有GPIO(通用IO口),還有nandflash控制器,這些東西都有一些寄存器來控制,這些寄存器都有一個地址,那么這些地址是什么意思?又怎么通過寄存器來控制這些外圍設(shè)備的運轉(zhuǎn)?還有,norflash內(nèi)部的每一個單元在這個芯片的內(nèi)存中都有一個相應(yīng)的地址單元,那么這些地址與剛剛說的寄存器地址又有什么關(guān)系?他們是一樣的嗎?而與norflash相對應(yīng)的nandflash內(nèi)部的儲存單元并不是線性排放的,那么s3c2410怎么將nandflash的地址映射在內(nèi)存空間上進行使用?或者簡單地說應(yīng)該怎么用nandflash?再有,使用ADS進對ARM9行編程時都需要使用到一個初始化的匯編文件,這個文件究竟有什么用?他里面的代碼是什么意思?不要這個可以嗎?
諸如此類都是對硬件的理解,理解了這些東西就對硬件有很深的理解了,這對以后更深一步的學習將有很大的幫助,如果跳過這一步,我相信越往后學越會覺得迷茫,越覺得這寫東西深不可測。因為,你的根基沒打好。
不過先聲明一下,本人并沒有使用ADS對ARM9進行編程,我是學完ARM7后直接就使用ARM9學linux系統(tǒng)的,因此涉及使用ADS對ARM9進行編程的問題我很難回答^_^,自己去研究研究吧。
對于這部分不久將提供一份教程,這個教程中的例程并不是我為我們所代理的板子寫的,是我在我們學院實驗室拿的,英培特為他們自己的實驗箱寫的,不過很有借鑒意義,可以作為一份有價值的參考。
第二,使用linux系統(tǒng)進行一些基本的實驗。
第三,研究完整的linux系統(tǒng)的的運行過程。
所謂完整的linux系統(tǒng)包括哪些部分呢?
三部分:bootloader、linux kernel(linux內(nèi)核)、rootfile(根文件系統(tǒng))。
那么這3部分是怎么相互協(xié)作來構(gòu)成這個系統(tǒng)的呢?各自有什么用呢?三者有什么聯(lián)系?怎么聯(lián)系?系統(tǒng)的執(zhí)行流程又是怎么樣的呢?搞清楚這個問題你對整個系統(tǒng)的運行就很清楚了,對于下一步制作這個linux系統(tǒng)就打下了另一個重要的根基。介紹這方面的資料網(wǎng)上可以挖掘到幾噸,自己好好研究吧。
第四,開始做系統(tǒng)移植。
上面說到完整的linux有3部分,而且你也知道了他們之間的關(guān)系和作用,那么現(xiàn)在你要做的便是自己動手學會制作這些東西。
當然我不可能叫你編寫這些代碼,這不實現(xiàn)。事實上這個3者都能在網(wǎng)下載到相應(yīng)的源代碼,但是這個源代碼不可能下載編譯后就能在你的系統(tǒng)上運行,需要很多的修改,直到他能運行在你的板子上,這個修改的過程就叫移植。在進行移植的過程中你要學的東西很多,要懂的相關(guān)知識也很多,等你完成了這個過程你會發(fā)現(xiàn)你已經(jīng)算是一個初出茅廬的高手了。
在這個過程中如果你很有研究精神的話你必然會想到看源代碼。很多書介紹你怎么閱讀linux源代碼,我不提倡無目的地去看linux源代碼,用許三多的話說,這沒有意義。等你在做移植的時候你覺得你必須去看源代碼時再去找基本好書看看,這里我推薦一本好書倪繼利的《linux內(nèi)核的分析與編程》,這是一本針對linux-2.6.11內(nèi)核的書,說得很深,建議先提高自己的C語言編程水平再去看。
至于每個部分的移植網(wǎng)上也可以找到好多噸的資料,自己研究研究吧,不過要提醒的是,很多介紹自己經(jīng)驗的東西都或多或少有所保留,你按照他說的去做總有一些問題,但是他不會告訴你怎么解決,這時就要靠自己,如果自己都靠不住就找我一起研究研究吧,我也不能保證能解決你的問題,因為我未必遇到過你的問題,不過我相信能給你一點建議,也許有助你解決問題。
這一步的最終目的是,從源代碼的官方主頁上(都是外國的,悲哀)下載標準的源代碼包,然后進行修改,最終運行在板子上。
盜用阿基米德的一句話:“給我一根網(wǎng)線,我能將linux搞定”。
第五,研究linux驅(qū)動程序的編寫。
移植系統(tǒng)并不是最終的目的,最終的目的是開發(fā)產(chǎn)品,做項目,這些都要進行驅(qū)動程序的開發(fā)。
Linux的驅(qū)動程序可以說是五花八門,linux2.4和linux2.6的編寫有相當大的區(qū)別,就是同為linux2.6但是不同版本間的驅(qū)動程序也有區(qū)別,因此編寫linux的驅(qū)動程序變都不是那么容易的事情,對于最新版本的驅(qū)動程序的編寫甚至還沒有足夠的參考資料。那么我的建議就是使用、移植一個不算很新的版本內(nèi)核,這樣到時學驅(qū)動的編程就有足夠的資料了。
這部分的推薦書籍可以參考另一篇文章《推薦幾本學習嵌入式linux的書籍》。
第六,研究應(yīng)用程序的編寫。
做作品做項目除了編寫驅(qū)動程序,最后還要編寫應(yīng)用程序。現(xiàn)在的趨勢是圖形應(yīng)用程序的開發(fā),而圖形應(yīng)用程序中用得最多的還是qt/e函數(shù)庫。我一直就使用這個函數(shù)庫來開發(fā)自己的應(yīng)用程序,不過我希望你能使用國產(chǎn)的MiniGUI函數(shù)庫。盜用周杰倫的廣告詞就是“支持國產(chǎn),支持MiniGUI”。MiniGUI的編程比較相似Windows下的VC編程,比較容易上手,效果應(yīng)該說是相當不錯的,我曾使用過來開發(fā)ARM7的程序。不過MiniGUI最大的不好就是沒有像qtopia這樣的圖形操作平臺,這大大限制了他的推廣,我曾經(jīng)幻想過與北京飛漫公司(就是MiniGUI的版權(quán)擁有者)合作使用MiniGUI函數(shù)庫開發(fā)像qtopia這樣的圖形操作平臺,不過由于水平有限這只能是幻想了,呵呵。
完成這一步你基本就學完了嵌入式linux的全部內(nèi)容了。
還有一個小小的經(jīng)驗想和大家分享。我在學習嵌入式linux的過程中很少問人,客觀原因是身邊的老師、同學師兄都沒有這方面的高手,主觀原因是我不喜歡問人,喜歡自己研究解決問題。這樣做有個好處,就是可以提高自己解決問題的能力,因為做這些東西總有很多問題你難以理解,別人也沒有這方面的經(jīng)驗,也不是所有問題都有人給你答案,這時必須要自己解決問題,這樣,個人的解決問題能力就顯得非常關(guān)鍵了。因此我的建議就是一般的問題到網(wǎng)上搜索一下,確實找不到答案了就問問高手,還是不行了就自己去研究,不要一味去等別人幫你解決問題。
記住,問題是學習的最好機會。
評論