ARM匯編必知必會(huì)
ADC 帶進(jìn)位的32位數(shù)加法
ADD 32位數(shù)相加
AND 32位數(shù)的邏輯與
B 在32M空間內(nèi)的相對(duì)跳轉(zhuǎn)指令
BIC 32位數(shù)的邏輯位清零
BKPT 斷點(diǎn)指令
BL 帶鏈接的相對(duì)跳轉(zhuǎn)指令
BLX 帶鏈接的切換跳轉(zhuǎn)
BX 切換跳轉(zhuǎn)
CDPCDP2 協(xié)處理器數(shù)據(jù)處理操作
CLZ 零計(jì)數(shù)
CMN 比較兩個(gè)數(shù)的相反數(shù)
CMP 32位數(shù)比較
EOR 32位邏輯異或
LDCLDC2 從協(xié)處理器取一個(gè)或多個(gè)32位值
LDM 從內(nèi)存送多個(gè)32位字到ARM寄存器
LDR 從虛擬地址取一個(gè)單個(gè)的32位值
MCRMCR2MCRR 從寄存器送數(shù)據(jù)到協(xié)處理器
MLA 32位乘累加
MOV 傳送一個(gè)32位數(shù)到寄存器
MRCMRC2MRRC 從協(xié)處理器傳送數(shù)據(jù)到寄存器
MRS 把狀態(tài)寄存器的值送到通用寄存器
MSR 把通用寄存器的值傳送到狀態(tài)寄存器
MUL 32位乘
MVN 把一個(gè)32位數(shù)的邏輯“非”送到寄存器
ORR 32位邏輯或
PLD 預(yù)裝載提示指令
QADD 有符號(hào)32位飽和加
QDADD 有符號(hào)雙32位飽和加
QSUB 有符號(hào)32位飽和減
QDSUB 有符號(hào)雙32位飽和減
RSB 逆向32位減法
RSC 帶進(jìn)位的逆向32法減法
SBC 帶進(jìn)位的32位減法
SMLAxy 有符號(hào)乘累加(16位*16位)+32位=32位
SMLAL 64位有符號(hào)乘累加((32位*32位)+64位=64位)
SMALxy 64位有符號(hào)乘累加((32位*32位)+64位=64位)
SMLAWy 32位有號(hào)乘累加((32位*16位)>>16位)+32位=32位
SMULL 64位有符號(hào)乘累加(32位*32位)=64位
SMULxy 32位有符號(hào)乘(16位*16位=32位)
SMULWy 32位有符號(hào)乘(32位*16位>>16位=32位)
STCSTC2 從協(xié)處理器中把一個(gè)或多個(gè)32位值存到內(nèi)存
STM 把多個(gè)32位的寄存器值存放到內(nèi)存
STR 把寄存器的值存到一個(gè)內(nèi)存的虛地址內(nèi)間
SUB 32位減法
SWI 軟中斷
SWP 把一個(gè)字或者一個(gè)字節(jié)和一個(gè)寄存器值交換
TEQ 等值測(cè)試
TST 位測(cè)試
UMLAL 64位無(wú)符號(hào)乘累加((32位*32位)+64位=64位)
UMULL 64位無(wú)符號(hào)乘累加(32位*32位)=64位
基于RISC 的ARM CPU
ARM是一種RISC體系結(jié)構(gòu)的處理器芯片。和傳統(tǒng)的CISC體系結(jié)構(gòu)不同,RISC 有以下的幾個(gè)特點(diǎn):
◆ 簡(jiǎn)潔的指令集——為了保證CPU可以在高時(shí)鐘頻率下單周期執(zhí)行指令,RISC指令集只提供很有限的操作(例如add,sub,mul等),而復(fù)雜的操作都需要由這些簡(jiǎn)單的指令來(lái)組合進(jìn)行模擬。并且,每一條指令不僅執(zhí)行時(shí)間固定,其指令長(zhǎng)度也是固定的,這樣,在譯碼階段就可以對(duì)下一條指令進(jìn)行預(yù)取。
◆ Load-Store 結(jié)構(gòu)——這個(gè)應(yīng)該是RISC 設(shè)計(jì)中比較有特點(diǎn)的一部分。在RISC 中,CPU并不會(huì)對(duì)內(nèi)存中的數(shù)據(jù)進(jìn)行操作,所有的計(jì)算都要求在寄存器中完成。而寄存器和內(nèi)存的通信則由單獨(dú)的指令來(lái)完成。而在CSIC中,CPU是可以直接對(duì)內(nèi)存進(jìn)行操作的,這也是一個(gè)比較特別的地方。
◆ 更多的寄存器——和CISC 相比,基于RISC的處理器有更多的通用寄存器可以使用,且每個(gè)寄存器都可以進(jìn)行數(shù)據(jù)存儲(chǔ)或者尋址。
圖:user模式下ARM處理器體系結(jié)構(gòu)
◆ r13 - 指向當(dāng)前棧頂,相當(dāng)于x86的esp,這個(gè)東西在匯編指令中要用sp 表示
◆ r14 - 稱作鏈接寄存器,指向函數(shù)的返回地址。用lr表示,這和x86將返回地址保存在棧中是不同的
◆ r15 - 類似于x86的eip,其值等于當(dāng)前正在執(zhí)行的指令的地址+8(因?yàn)樵谌≈泛蛨?zhí)行之間多了一個(gè)譯碼的階段),這個(gè)用pc表示
ARM 指令集
ARM處理器可以支持3種指令集——ARM,Thumb和Jazelle。
采用那種指令集,由cspr中的標(biāo)志位來(lái)決定。大體說(shuō)來(lái):
◆ ARM——這是ARM自身的32 位指令集
◆ Thumb ——這是一個(gè)全16 位的指令集,在16 位外部數(shù)據(jù)總線寬度下,這個(gè)指令集的效率要比32 位的ARM指令高一些。
◆ Jazelle ——這是一個(gè)8位指令集,用來(lái)加速Java字節(jié)碼的執(zhí)行
{S} [Rd], [Rn], [Rm]
其中:
* {S} —— 加上這個(gè)后綴的指令會(huì)更新cpsr 寄存器
* [Rd] —— 目的寄存器
* [Rn]/[Rm] —— 源寄存器
一般來(lái)說(shuō),arm 指令有3個(gè)操作數(shù),其中Rm寄存器在執(zhí)行指令前可以進(jìn)入桶形移位器進(jìn)行移位操作,而Rn則會(huì)直接進(jìn)入ALU 單元。如果一條arm 指令只有2 個(gè)操作數(shù),那么源寄存器按照Rm 來(lái)處理。例如,一條加法指令:
add r0, r1, #1
就會(huì)把r1+1的結(jié)果存放到r0中。
Load-Store 指令體系
◆ 單寄存器傳輸(這是與x86 最為相像的)
◆ 多寄存器傳輸
◆ 交換指令
單寄存器傳輸
先看第一個(gè),很簡(jiǎn)單:把單一的數(shù)據(jù)傳入(LDR) 或傳出(STR)寄存器,對(duì)內(nèi)存的訪問(wèn)可以是DWORD(32-bit), WORD(16-bit)和BYTE(8-bit)。指令的格式如下:
DWORD:
Rd, addressing1
WORD:
H Rd, addressing2 無(wú)符號(hào)版
SH Rd, addressing2 有符號(hào)版
BYTE:
B Rd, addressing1 無(wú)符號(hào)版
SB Rd, addressing2 有符號(hào)版
addressing1 和addressing2 的分類下面再說(shuō),現(xiàn)在理解成某種尋址方式就可以了。
在單寄存器傳輸方面,還有以下三種變址模式,他們是:
◆ preindex
這種變址方式和x86的尋址機(jī)制是很類似的,先對(duì)寄存器進(jìn)行運(yùn)算,然后尋址,但是在尋之后,基址寄存器的內(nèi)容并不發(fā)生改變,例如:
ldr r0, [r1, #4]
的含義就是把r1+4 這個(gè)地址處的DOWRD 加載到r0,而尋址后,r1 的內(nèi)容并不改變。
◆ preindex with writeback
這種變址方式有點(diǎn)類似于++i的含義,尋址前先對(duì)基地址寄存器進(jìn)行運(yùn)算,然后尋址. 其基本的語(yǔ)法是在尋址符[]后面加上一個(gè)"!" 來(lái)表示.例如:
ldr r0, [r1, #4]!
就可以分解成:
add r1, r1, #4
ldr r0, [r1, #0]
◆ postindex
自然這種變址方式和i++的方式就很類似了,先利用基址寄存器進(jìn)行尋址,然后對(duì)基址寄存器進(jìn)行運(yùn)算,其基本語(yǔ)法是把offset 部分放到[]外面,例如:
ldr r0, [r1], #4
就可以分解成:
ldr r0, [r1, #0]
add r1, r1, #4
如果你還記得x86 的SIB 操作的話,那么你一定想ARM是否也有,答案是有也沒有。在ss上面提到的addressing1 和addressing2的區(qū)別就是比例寄存器的使用,addressing1可以使用[base, scale, 桶形移位器]來(lái)實(shí)現(xiàn)SB 的效果,或者通過(guò)[base,offset](這里的offset 可以是立即數(shù)或者寄存器)來(lái)實(shí)現(xiàn)SI 的效果,而addressing2則只能用后者了。于是每一種變址方式最多可以有3 種尋址方式,這樣一來(lái),最多可以有9種用來(lái)尋址的指令形式。例如:
ldr r0, [r1, r2, LSR #0x04]!
ldr r0, [r1, -#0x04]
ldr r0, [r1], LSR #0x04
每樣找了一種,大概就是這個(gè)意思。到此,單寄存器傳輸就結(jié)束了,掌握這些足夠應(yīng)付差事了。下面來(lái)看看多寄存器傳輸吧。
多寄存器傳輸
<尋址模式> Rn{!}, {r^}
我們先來(lái)搞明白尋址模式,多寄存器傳輸模式有4 種:
也就是說(shuō)以A開頭的都是在Rn的原地開始操作,而B開頭的都是以Rn的下一個(gè)位置開始操作。如果你仍然感到困惑,我們不妨看個(gè)例子。
所有的示例指令執(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 是類似的,不多說(shuō)了。
最后要說(shuō)的是,使用ldm 和stm指令對(duì)進(jìn)行寄存器組的保護(hù)是很常見和有效的功能。配對(duì)方案:
stmia / ldmdb
stmib / ldmda
stmda / ldmib
stmdb / ldmia
繼續(xù)來(lái)看兩個(gè)例子:
執(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
當(dāng)前寄存器狀態(tài):
r0 = 0x0000100C
r1 = 0x00000001
r2 = 0x00000002
r3 = 0x00000003
ldmia r0!, {r1-r3}
最后的結(jié)果:
r0 = 0x00001000
r1 = 0x00000003
r2 = 0x00000002
r3 = 0x00000001
另外,我們還可以利用這個(gè)指令對(duì)完成內(nèi)存塊的高效copy:
loop
ldmia r9!, {r0-r7}
stmia r10!, {r0-r7}
cmp r9, r11
bne loop
說(shuō)到這里,讀者應(yīng)該對(duì)RISC的Load-Store體系結(jié)構(gòu)有一個(gè)大概的了解了,能夠正確配對(duì)使用指令,是很重要的。
ARM 異常處理
如果您閱讀ARM手冊(cè),您會(huì)發(fā)現(xiàn),在ARM中,經(jīng)常強(qiáng)調(diào)Exception(異常)這個(gè)概念,在ARM里,Interrupt(中斷)也是一種形式的異常。ARM的Exception同其所定義的5種異常模式是密切相關(guān)的,CPU在捕獲到任何一個(gè)Exception后,必定會(huì)進(jìn)入某個(gè)異常模式,異常類型及捕獲到該異常后CPU所進(jìn)入的異常模式之間的對(duì)應(yīng)關(guān)系是ARM所預(yù)先定義好的。如果您對(duì)X86比較熟悉,您會(huì)發(fā)現(xiàn),不象X86,系統(tǒng)定義了不同的中斷,比如鍵盤中斷,鼠標(biāo)中斷等等,并且系統(tǒng)也定義了這些中斷所對(duì)應(yīng)的中斷向量。ARM沒有定義這些,ARM只會(huì)告訴你,有外部中斷產(chǎn)生,并切換到IRQ或FIQ模式,然后執(zhí)行IRQ或FIQ所對(duì)應(yīng)的中斷向量。至于到底是鍵盤中斷,還是鼠標(biāo)中斷,這得由操作系統(tǒng)提供的中斷函數(shù)自己去判斷,比如通過(guò)查詢中斷控制器的某個(gè)或某些寄存器。ARM這樣做的原因是:ARM只是一個(gè)CORE,它并不定義也不去假想其外部環(huán)境,這樣可以使得ARM CORE更加緊湊和簡(jiǎn)潔,同時(shí)也給SOC設(shè)計(jì)者提供了更多的靈活性和發(fā)揮空間。您一定要相信,ARM被如此廣泛使用不是“蓋”的,從系統(tǒng)開發(fā)者角度看,ARM是一種最簡(jiǎn)單、最靈活的CPU,它的優(yōu)雅和簡(jiǎn)潔性就像C語(yǔ)言一樣。呵呵,C語(yǔ)言是我最喜歡的語(yǔ)言。
好了,“臭屁”了這么多,我們言歸正傳。對(duì)ARM異常處理的研究務(wù)必要弄清楚以下幾個(gè)方面:
(1) 異常類型
(2) 異常類型及處理該異常時(shí)CPU的執(zhí)行模式
(3) 異常向量地址
(4) 異常處理過(guò)程
異常類型
ARM定義了如下類型的異常(江南七怪,這樣好記):
(1)
(2)
(3)
(4)
(5)
(6)
(7)
執(zhí)行模式
當(dāng)產(chǎn)生異常后,CPU會(huì)進(jìn)入相應(yīng)的異常模式并處理該異常:
向量地址
ARM的異常向量地址可以處于4G物理空間的低端(0x00000000起),也可以處于高端(0xffff0000起),具體是哪種情況,根據(jù)具體的CPU及其配置而定。下面是7種異常的向量地址(挎弧內(nèi)為高端情形):
(1)
(2)
(3)
(4)
(5)
(6)
(7)
每個(gè)中斷向量為4字節(jié),一般的操作系統(tǒng)在該地址處放置一條跳轉(zhuǎn)指令“LDR PC,終端處理函數(shù)地址”。另外要注意的是,在IRQ異常和Data Abort異常之間空了4個(gè)字節(jié),這4個(gè)字節(jié)是保留的。
處理過(guò)程
Linux BOOTLOADER全程詳解
網(wǎng)上關(guān)于Linux的BOOTLOADER文章不少了,但是大都是vivi,blob等比較龐大的程序,讀起來(lái)不太方便,編譯出的文件也比較大,而且更多的是面向開發(fā)用的引導(dǎo)代碼,做成產(chǎn)品時(shí)還要裁減,這一定程度影響了開發(fā)速度,對(duì)初學(xué)者學(xué)習(xí)開銷也比較大,在此分析一種簡(jiǎn)單的BOOTLOADER,是在三星公司提供的2410BOOTLOADER上稍微修改后的結(jié)果,編譯出來(lái)的文件大小不超過(guò)4k,希望對(duì)大家有所幫助.
1.幾個(gè)重要的概念
COMPRESSED KERNEL and DECOMPRESSED KERNEL
壓縮后的KERNEL,按照文檔資料,現(xiàn)在不提倡使用DECOMPRESSED KERNEL,而要使用COMPRESSED KERNEL,它包括了解壓器.因此要在ram分配時(shí)給壓縮和解壓的KERNEL提供足夠空間,這樣它們不會(huì)相互覆蓋.當(dāng)執(zhí)行指令跳轉(zhuǎn)到 COMPRESSED KERNEL后,解壓器就開始工作,如果解壓器探測(cè)到解壓的代碼會(huì)覆蓋掉COMPRESSED KERNEL,那它會(huì)直接跳到COMPRESSED KERNEL后存放數(shù)據(jù),并且重新定位KERNEL,所以如果沒有足夠空間,就會(huì)出錯(cuò).
Jffs2 File System
可以使armlinux應(yīng)用中產(chǎn)生的數(shù)據(jù)保存在FLASH上,我的板子還沒用到這個(gè).
RAMDISK
使用RAMDISK可以使ROOT FILE SYSTEM在沒有其他設(shè)備的情況下啟動(dòng).一般有兩種加載方式,我就介紹最常用的吧,把COMPRESSED RAMDISK IMAGE放到指定地址,然后由BOOTLOADER把這個(gè)地址通過(guò)啟動(dòng)參數(shù)的方式ATAG_INITRD2傳遞給KERNEL.具體看代碼分析.
啟動(dòng)參數(shù)(摘自IBM developer)
在調(diào)用內(nèi)核之前,應(yīng)該作一步準(zhǔn)備工作,即:設(shè)置 Linux 內(nèi)核的啟動(dòng)參數(shù)。Linux 2.4.x 以后的內(nèi)核都期望以標(biāo)記列表(tagged list)的形式來(lái)傳遞啟動(dòng)參數(shù)。啟動(dòng)參數(shù)標(biāo)記列表以標(biāo)記 ATAG_CORE 開始,以標(biāo)記 ATAG_NONE 結(jié)束。每個(gè)標(biāo)記由標(biāo)識(shí)被傳遞參數(shù)的 tag_header 結(jié)構(gòu)以及隨后的參數(shù)值數(shù)據(jù)結(jié)構(gòu)來(lái)組成。數(shù)據(jù)結(jié)構(gòu) tag 和 tag_header 定義在Linux 內(nèi)核源碼的include/asm/setup.h 頭文件中.
在嵌入式 Linux 系統(tǒng)中,通常需要由 BOOTLOADER 設(shè)置的常見啟動(dòng)參數(shù)有:ATAG_CORE、ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_INITRD等。
(注)參數(shù)也可以用COMMANDLINE來(lái)設(shè)定,在我的BOOTLOADER里,我兩種都用了.
2.開發(fā)環(huán)境和開發(fā)板配置:
CPU:S3C2410,BANK6上有64M的SDRAM(兩塊),BANK0上有32M NOR FLASH,串口當(dāng)然是逃不掉的.這樣,按照數(shù)據(jù)手冊(cè),地址分配如下:
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開始存放啟動(dòng)參數(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,偶試過(guò)好多次,都以失敗告終.
先下載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 中在把a(bǔ)rch=arm CROSS_COMPILE設(shè)置成toolchain的路徑還有就是INCLUDE = -I ../include -I /root/my/usr/local/arm/3.3.2/include.,否則庫(kù)函數(shù)就不能用了
3.啟動(dòng)方式:
可以放在FLASH里啟動(dòng),或者用Jtag仿真器.由于使用NOR FLASH,根據(jù)2410的手冊(cè),片內(nèi)的4K DRAM在不需要設(shè)置便可以直接使用,而其他存儲(chǔ)器必須先初始化,比如告訴memory controller,BANK6里有兩塊SDRAM,數(shù)據(jù)寬度是32bit,= =.否則memory control會(huì)按照復(fù)位后的默認(rèn)值來(lái)處理存儲(chǔ)器.這樣讀寫就會(huì)產(chǎn)生錯(cuò)誤.
所以第一步,通過(guò)仿真器把執(zhí)行代碼放到0x4000_0000,(在編譯的時(shí)候,設(shè)定TEXT_BASE=0x40000000)
第二步,通過(guò) AxD把linux KERNEL IMAGE放到目標(biāo)地址(SDRAM)中,等待調(diào)用
第三步,執(zhí)行BOOTLOADER代碼,從串口得到調(diào)試數(shù)據(jù),引導(dǎo)armlinux
4.代碼分析
講了那么多執(zhí)行的步驟,是想讓大家對(duì)啟動(dòng)有個(gè)大概印象,接著就是BOOTLOADER內(nèi)部的代碼分析了,BOOTLOADER文章內(nèi)容網(wǎng)上很多,我這里精簡(jiǎn)了下,刪除了不必要的功能.
BOOTLOADER一般分為2部分,匯編部分和c語(yǔ)言部分,匯編部分執(zhí)行簡(jiǎn)單的硬件初始化,C部分負(fù)責(zé)復(fù)制數(shù)據(jù),設(shè)置啟動(dòng)參數(shù),串口通信等功能.
BOOTLOADER的生命周期:
1. 初始化硬件,比如設(shè)置UART(至少設(shè)置一個(gè)),檢測(cè)存儲(chǔ)器= =.
2. 設(shè)置啟動(dòng)參數(shù),這是為了告訴內(nèi)核硬件的信息,比如用哪個(gè)啟動(dòng)界面,波特率 = =.
3. 跳轉(zhuǎn)到Linux KERNEL的首地址.
4. 消亡
當(dāng)然,在引導(dǎo)階段,象vivi等,都用虛地址,如果你嫌煩的話,就用實(shí)地址,都一樣.
我們來(lái)看代碼:
2410init.s
.global _start//開始執(zhí)行處
_start:
//下面是中斷向量
b reset @ Supervisor Mode//重新啟動(dòng)后的跳轉(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
//以上這段,我沒動(dòng),就用三星寫的了,下面是主要要改的地方
add r0,pc,#MCDATA - (.+8)// r0指向MCDATA地址,那里存放著MC初始化要用到的數(shù)據(jù)
ldr r1,=BWSCON // r1指向MC控制器寄存器的首地址
add r2,r0,#52 // 復(fù)制次數(shù),偏移52字
1: //按照偏移量進(jìn)行循環(huán)復(fù)制
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
下面都是每個(gè)BANK的控制器數(shù)據(jù),大都是時(shí)鐘相關(guān),可以用默認(rè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)用這個(gè)函數(shù),否則通信芯片根本得不到數(shù)據(jù) Uart_Init(PCLK, 115200);//PCLK使用默認(rèn)的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)驗(yàn),最好以一個(gè)字節(jié)為遞增,我們的板子,在256byte遞增檢測(cè)的時(shí)候是沒問(wèn)題的,但是
//以1byte遞增就出錯(cuò)了,第13跟數(shù)據(jù)線隨幾的會(huì)冒”1”,檢測(cè)出來(lái)是硬件問(wèn)題,現(xiàn)象如下
//用仿真器下代碼測(cè)試SDRAM,開始沒貼28F128A3J FLASH片子,測(cè)試結(jié)果很好,但在上了FLASH片子//之后,測(cè)試數(shù)據(jù)(data)為0x00000400
連續(xù)成批寫入讀出時(shí),操作大約1k左右內(nèi)存空間就會(huì)出錯(cuò),//而且隨機(jī)。那個(gè)出錯(cuò)數(shù)據(jù)總是變?yōu)?x00002400,數(shù)據(jù)總線10位和13位又沒短路
發(fā)生。用其他數(shù)據(jù)//測(cè)試比如0x00000200;0x00000800沒這問(wèn)題。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 信息,做成列表,后面會(huì)作為啟動(dòng)參數(shù)傳給KERNEL
//所謂內(nèi)存映射就是指在4GB 物理地址空間中有哪些地址范圍被分配用來(lái)尋址系統(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 [email protected] 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è)置啟動(dòng)參數(shù) setup_memory_tags();//內(nèi)存印象 setup_commandline_tag("console=ttyS0,115200n8");//啟動(dòng)命令行 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));
//啟動(dòng)kernel時(shí)候,I-cache可以開也可以關(guān),r0必須是0,r1必須是CPU型號(hào)
(可以從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;//啟動(dòng)參數(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好象寫的不太對(duì),但是我按照Normal,No parity,1 stop,8 bits算出來(lái)的確是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)過(guò)以上的折騰,接下來(lái)就是kernel的活了.能不能啟動(dòng)kernel,得看你編譯kernel的水平了.
ARM嵌入式入門的建議
由于很多人總問(wèn)這個(gè)問(wèn)題,所以這里做一個(gè)總結(jié)文檔供大家參考。這里必須先說(shuō)明,以下的步驟都是針對(duì)Linux系統(tǒng)的,并不面向WinCE。也許你會(huì)注意到,現(xiàn)在做嵌入式的人中,做linux研究的人遠(yuǎn)比做WinCE的人多,很多產(chǎn)家提供的資料也是以linux為主。我一直很難理解,其實(shí)WinCE的界面比linux的界面好看多了,使用起來(lái)也很方便,更為重要的是,WinCE的開發(fā)和Windows下的開發(fā)基本一樣,學(xué)起來(lái)簡(jiǎn)單得多,但是學(xué)linux或者使用linux做嵌入式的人就是遠(yuǎn)比WinCE多。在和很多工作的人交流時(shí)我了解到,他們公司從沒考慮使用WinCE,因?yàn)槌杀靖撸际鞘褂胠inux進(jìn)行開發(fā)。我讀研究生的的實(shí)驗(yàn)室中也沒有使用WinCE的,大都研究linux,也有少部分項(xiàng)目使用vxwork,但是就沒有聽說(shuō)過(guò)使用WinCE的,原因就是開源!當(dāng)然現(xiàn)在WinCE6.0聽說(shuō)也開源,不過(guò)在成本和資源上linux已經(jīng)有了無(wú)人能擋的優(yōu)勢(shì)。與此相對(duì)應(yīng)的是,越來(lái)越多的電子廠商已經(jīng)開始使用linux開發(fā)產(chǎn)品。舉個(gè)例子,Google近期開發(fā)的智能手機(jī)操作系統(tǒng)Android其實(shí)就是使用linux-2.6.23內(nèi)核進(jìn)行改進(jìn)得到的。第一,學(xué)習(xí)基本的裸機(jī)編程。
對(duì)于學(xué)硬件的人而言,必須先對(duì)硬件的基本使用方法有感性的認(rèn)識(shí),更必須深刻認(rèn)識(shí)該硬件的控制方式,如果一開始就學(xué)linux系統(tǒng)、學(xué)移植那么只會(huì)馬上就陷入一個(gè)很深的漩渦。我在剛剛開始學(xué)ARM的時(shí)候是選擇ARM7(主意是當(dāng)時(shí)ARM9還很貴),學(xué)ARM7的時(shí)候還是保持著學(xué)51單片機(jī)的思維,使用ADS去編程,第一個(gè)實(shí)驗(yàn)就是控制led。學(xué)過(guò)一段時(shí)間ARM的人都會(huì)笑這樣很笨,實(shí)際上也不是,我倒是覺得有這個(gè)過(guò)程會(huì)好很多,因?yàn)闊o(wú)論做多復(fù)雜的系統(tǒng)最終都會(huì)落實(shí)到這些最底層的硬件控制,因此對(duì)這些硬件的控制有了感性的認(rèn)識(shí)就好很多了
學(xué)習(xí)裸機(jī)的編程的同時(shí)要好好理解這個(gè)硬件的構(gòu)架、控制原理,這些我稱他為理解硬件。所謂的理解硬件就是說(shuō),理解這個(gè)硬件是怎么組織這么多資源的,這些資源又是怎么由cpu、由編程進(jìn)行控制的。比如說(shuō),s3c2410中有AD轉(zhuǎn)換器,有GPIO(通用IO口),還有nandflash控制器,這些東西都有一些寄存器來(lái)控制,這些寄存器都有一個(gè)地址,那么這些地址是什么意思?又怎么通過(guò)寄存器來(lái)控制這些外圍設(shè)備的運(yùn)轉(zhuǎn)?還有,norflash內(nèi)部的每一個(gè)單元在這個(gè)芯片的內(nèi)存中都有一個(gè)相應(yīng)的地址單元,那么這些地址與剛剛說(shuō)的寄存器地址又有什么關(guān)系?他們是一樣的嗎?而與norflash相對(duì)應(yīng)的nandflash內(nèi)部的儲(chǔ)存單元并不是線性排放的,那么s3c2410怎么將nandflash的地址映射在內(nèi)存空間上進(jìn)行使用?或者簡(jiǎn)單地說(shuō)應(yīng)該怎么用nandflash?再有,使用ADS進(jìn)對(duì)ARM9行編程時(shí)都需要使用到一個(gè)初始化的匯編文件,這個(gè)文件究竟有什么用?他里面的代碼是什么意思?不要這個(gè)可以嗎?
諸如此類都是對(duì)硬件的理解,理解了這些東西就對(duì)硬件有很深的理解了,這對(duì)以后更深一步的學(xué)習(xí)將有很大的幫助,如果跳過(guò)這一步,我相信越往后學(xué)越會(huì)覺得迷茫,越覺得這寫東西深不可測(cè)。因?yàn)椋愕母鶝]打好。
不過(guò)先聲明一下,本人并沒有使用ADS對(duì)ARM9進(jìn)行編程,我是學(xué)完ARM7后直接就使用ARM9學(xué)linux系統(tǒng)的,因此涉及使用ADS對(duì)ARM9進(jìn)行編程的問(wèn)題我很難回答^_^,自己去研究研究吧。
對(duì)于這部分不久將提供一份教程,這個(gè)教程中的例程并不是我為我們所代理的板子寫的,是我在我們學(xué)院實(shí)驗(yàn)室拿的,英培特為他們自己的實(shí)驗(yàn)箱寫的,不過(guò)很有借鑒意義,可以作為一份有價(jià)值的參考。
第二,使用linux系統(tǒng)進(jìn)行一些基本的實(shí)驗(yàn)。
第三,研究完整的linux系統(tǒng)的的運(yùn)行過(guò)程。
所謂完整的linux系統(tǒng)包括哪些部分呢?
三部分:bootloader、linux kernel(linux內(nèi)核)、rootfile(根文件系統(tǒng))。
那么這3部分是怎么相互協(xié)作來(lái)構(gòu)成這個(gè)系統(tǒng)的呢?各自有什么用呢?三者有什么聯(lián)系?怎么聯(lián)系?系統(tǒng)的執(zhí)行流程又是怎么樣的呢?搞清楚這個(gè)問(wèn)題你對(duì)整個(gè)系統(tǒng)的運(yùn)行就很清楚了,對(duì)于下一步制作這個(gè)linux系統(tǒng)就打下了另一個(gè)重要的根基。介紹這方面的資料網(wǎng)上可以挖掘到幾噸,自己好好研究吧。
第四,開始做系統(tǒng)移植。
上面說(shuō)到完整的linux有3部分,而且你也知道了他們之間的關(guān)系和作用,那么現(xiàn)在你要做的便是自己動(dòng)手學(xué)會(huì)制作這些東西。
當(dāng)然我不可能叫你編寫這些代碼,這不實(shí)現(xiàn)。事實(shí)上這個(gè)3者都能在網(wǎng)下載到相應(yīng)的源代碼,但是這個(gè)源代碼不可能下載編譯后就能在你的系統(tǒng)上運(yùn)行,需要很多的修改,直到他能運(yùn)行在你的板子上,這個(gè)修改的過(guò)程就叫移植。在進(jìn)行移植的過(guò)程中你要學(xué)的東西很多,要懂的相關(guān)知識(shí)也很多,等你完成了這個(gè)過(guò)程你會(huì)發(fā)現(xiàn)你已經(jīng)算是一個(gè)初出茅廬的高手了。
在這個(gè)過(guò)程中如果你很有研究精神的話你必然會(huì)想到看源代碼。很多書介紹你怎么閱讀linux源代碼,我不提倡無(wú)目的地去看linux源代碼,用許三多的話說(shuō),這沒有意義。等你在做移植的時(shí)候你覺得你必須去看源代碼時(shí)再去找基本好書看看,這里我推薦一本好書倪繼利的《linux內(nèi)核的分析與編程》,這是一本針對(duì)linux-2.6.11內(nèi)核的書,說(shuō)得很深,建議先提高自己的C語(yǔ)言編程水平再去看。
至于每個(gè)部分的移植網(wǎng)上也可以找到好多噸的資料,自己研究研究吧,不過(guò)要提醒的是,很多介紹自己經(jīng)驗(yàn)的東西都或多或少有所保留,你按照他說(shuō)的去做總有一些問(wèn)題,但是他不會(huì)告訴你怎么解決,這時(shí)就要靠自己,如果自己都靠不住就找我一起研究研究吧,我也不能保證能解決你的問(wèn)題,因?yàn)槲椅幢赜龅竭^(guò)你的問(wèn)題,不過(guò)我相信能給你一點(diǎn)建議,也許有助你解決問(wèn)題。
這一步的最終目的是,從源代碼的官方主頁(yè)上(都是外國(guó)的,悲哀)下載標(biāo)準(zhǔn)的源代碼包,然后進(jìn)行修改,最終運(yùn)行在板子上。
盜用阿基米德的一句話:“給我一根網(wǎng)線,我能將linux搞定”。
第五,研究linux驅(qū)動(dòng)程序的編寫。
移植系統(tǒng)并不是最終的目的,最終的目的是開發(fā)產(chǎn)品,做項(xiàng)目,這些都要進(jìn)行驅(qū)動(dòng)程序的開發(fā)。
Linux的驅(qū)動(dòng)程序可以說(shuō)是五花八門,linux2.4和linux2.6的編寫有相當(dāng)大的區(qū)別,就是同為linux2.6但是不同版本間的驅(qū)動(dòng)程序也有區(qū)別,因此編寫linux的驅(qū)動(dòng)程序變都不是那么容易的事情,對(duì)于最新版本的驅(qū)動(dòng)程序的編寫甚至還沒有足夠的參考資料。那么我的建議就是使用、移植一個(gè)不算很新的版本內(nèi)核,這樣到時(shí)學(xué)驅(qū)動(dòng)的編程就有足夠的資料了。
這部分的推薦書籍可以參考另一篇文章《推薦幾本學(xué)習(xí)嵌入式linux的書籍》。
第六,研究應(yīng)用程序的編寫。
做作品做項(xiàng)目除了編寫驅(qū)動(dòng)程序,最后還要編寫應(yīng)用程序?,F(xiàn)在的趨勢(shì)是圖形應(yīng)用程序的開發(fā),而圖形應(yīng)用程序中用得最多的還是qt/e函數(shù)庫(kù)。我一直就使用這個(gè)函數(shù)庫(kù)來(lái)開發(fā)自己的應(yīng)用程序,不過(guò)我希望你能使用國(guó)產(chǎn)的MiniGUI函數(shù)庫(kù)。盜用周杰倫的廣告詞就是“支持國(guó)產(chǎn),支持MiniGUI”。MiniGUI的編程比較相似Windows下的VC編程,比較容易上手,效果應(yīng)該說(shuō)是相當(dāng)不錯(cuò)的,我曾使用過(guò)來(lái)開發(fā)ARM7的程序。不過(guò)MiniGUI最大的不好就是沒有像qtopia這樣的圖形操作平臺(tái),這大大限制了他的推廣,我曾經(jīng)幻想過(guò)與北京飛漫公司(就是MiniGUI的版權(quán)擁有者)合作使用MiniGUI函數(shù)庫(kù)開發(fā)像qtopia這樣的圖形操作平臺(tái),不過(guò)由于水平有限這只能是幻想了,呵呵。
完成這一步你基本就學(xué)完了嵌入式linux的全部?jī)?nèi)容了。
還有一個(gè)小小的經(jīng)驗(yàn)想和大家分享。我在學(xué)習(xí)嵌入式linux的過(guò)程中很少問(wèn)人,客觀原因是身邊的老師、同學(xué)師兄都沒有這方面的高手,主觀原因是我不喜歡問(wèn)人,喜歡自己研究解決問(wèn)題。這樣做有個(gè)好處,就是可以提高自己解決問(wèn)題的能力,因?yàn)樽鲞@些東西總有很多問(wèn)題你難以理解,別人也沒有這方面的經(jīng)驗(yàn),也不是所有問(wèn)題都有人給你答案,這時(shí)必須要自己解決問(wèn)題,這樣,個(gè)人的解決問(wèn)題能力就顯得非常關(guān)鍵了。因此我的建議就是一般的問(wèn)題到網(wǎng)上搜索一下,確實(shí)找不到答案了就問(wèn)問(wèn)高手,還是不行了就自己去研究,不要一味去等別人幫你解決問(wèn)題。
記住,問(wèn)題是學(xué)習(xí)的最好機(jī)會(huì)。
評(píng)論