8.2第1個(gè)ARM裸板程序及引申(下)
第006節(jié)_編程知識_字節(jié)序_位操作
字節(jié)序:
假設(shè)int a = 0x12345678;
前面說了16進(jìn)制每位是4個(gè)bit,在內(nèi)存中,是以8個(gè)bit作為1byte進(jìn)行存儲的,因此0x12345678中每兩位作為1byte,其中0x78是低位,0x12是高位。
在內(nèi)存中的存儲方式有兩種:

0x12345678的低位(0x78)存在低地址,即方式1,叫做小字節(jié)序(Little endian);
0x12345678的高位(0x12)存在低地址,即方式2,叫做大字節(jié)序(Big endian);
一般的arm芯片都是小字節(jié)序,對于2440可以設(shè)置某個(gè)寄存器,讓整個(gè)系統(tǒng)使用大字節(jié)序或小字節(jié)序,它默認(rèn)使用小字節(jié)序。
位操作:
1. 移位
左移:
int a = 0x123; ?int b = a<<2;--> b=0x48C
右移:
int a = 0x123; ?int b = a>>2;--> b=0x48
左移是乘4,右移是除4;
2. 取反 原來問0的位變1,原來為1的位變0;
int a = 0x123; int b = ~a;a=2
3. 位與
1 & 1 = 1
1 & 0 = 0?
0 & 1 = 0?
0 & 0 = 0
int a = 0x123; int b = 0x456; int c = a&b;--> c=0x2
4. 位或
1 | 1 = 1
1 | 0 = 1
0 | 1 = 1
0 | 0 = 0
int a = 0x123; int b = 0x456; int c = a|b;--> c=0x577
5. 置位 把a(bǔ)的bit7、8置位(變?yōu)?)
int a = 0x123; int b = a|(1<<7)|(1<<8);--> c=0x1a3
6. 清位 把a(bǔ)的bit7、8清位(變?yōu)?)
int a = 0x123; int b = (a& ~(1<<7))&(~(1<<8));--> c=0x23
置位和清位在后面寄存器的操作中,會經(jīng)常使用。
第007節(jié)_編寫C程序控制LED
C語言的指針操作:
①所有的變量在內(nèi)存中都有一塊區(qū)域;
②可以通過變量/指針操作內(nèi)存;

TYPE *p = val1;
*p = val2;
把val2寫入地址val1的內(nèi)存中,寫入sizeof(TYPE)
字節(jié);
TYPE *p = addr;?
*p = val;
把val寫入地址addrd的內(nèi)存,,寫入sizeof(TYPE)
字節(jié);
a. 我們寫出了main函數(shù), 誰來調(diào)用它? b. main函數(shù)中變量保存在內(nèi)存中, 這個(gè)內(nèi)存地址是多少? 答: 我們還需要寫一個(gè)匯編代碼, 給main函數(shù)設(shè)置內(nèi)存, 調(diào)用main函數(shù)
led.c源碼:

start.S源碼:

Makefile源碼:

最后將上面三個(gè)文件放入U(xiǎn)buntu主機(jī)編譯,然后燒寫到開發(fā)板即可。
第008節(jié)_幾條匯編指令_bl_add_sub_ldm_stm
⑥ADD/SUB 加法/減法
舉例1:
add r0,r1,#4
效果為
r0=r1+4;
舉例2:
sub r0,r1,#4
效果為
r0=r1-4;
舉例3:
sub r0,r1,r2
效果為
r0=r1-r2;
⑦BL (Brarch and Link)帶返回值的跳轉(zhuǎn) 跳轉(zhuǎn)到指定指令,并將返回地址(下一條指令)保存在lr寄存器;
⑧LDM/STM 讀內(nèi)存,寫入多個(gè)寄存器/把多個(gè)寄存器的值寫入內(nèi)存
可搭配的后綴有 過后增加(Increment After)、預(yù)先增加(Increment Before)、過后減少(Decrement After)、預(yù)先減少(Decrement Before);
舉例1:
stmdb sp!, (fp,ip,lr,pc)
假設(shè)Sp=4096。 db意思是先減后存,按 高編號寄存器存在高地址 存。?

舉例2:
ldmia sp, (fp,ip,pc)

009節(jié)_解析C程序的內(nèi)部機(jī)制
003_led.c內(nèi)部機(jī)制分析:
start.S:
①設(shè)置棧;
②調(diào)用main,并把返回值地址保存到lr中;
led.c的main()內(nèi)容:
①定義2個(gè)局部變量;
②設(shè)置變量;
③return 0;
問題:
①為什么要設(shè)置棧?
因?yàn)閏函數(shù)要用。
②怎么使用棧?
a.保存局部變量;
b.保存lr等寄存器;
③調(diào)用者如何傳參數(shù)給被調(diào)用者?
④被調(diào)用者如何傳返回值給調(diào)用者?
⑤怎么從棧中恢復(fù)那些寄存器?
在arm中有個(gè)ATPCS規(guī)則,約定r0-r15寄存器的用途。
r0-r3:調(diào)用者和被調(diào)用者之間傳參數(shù);
r4-r11:函數(shù)可能被使用,所以在函數(shù)的入口保存它們,在函數(shù)的出口恢復(fù)它們;
下面分析個(gè)實(shí)例 start.S:

led.c:

將前面的程序反匯編得到led.dis如下:


分析上面的匯編代碼:
開發(fā)板上電后,將從0地址開始執(zhí)行,即開始執(zhí)行
mov sp, #4096:設(shè)置棧地址在4k RAM的最高處,sp=4096;
bl? ? c <main>:調(diào)到c地址處的main函數(shù),并保存下一行代碼地址到lr,即lr=8;
mov ip, sp:給ip賦值sp的值,ip=sp=4096
stmdb sp!, {fp, ip, lr, pc}:按高編號寄存器存在高地址,依次將pc、lr、ip、fp存入sp-4中;
sub fp, ip, #4:fp的值為ip-4=4096-4=4092;
sub sp, sp, #8:sp的值為sp-8=(4096-4x4)-8=4072;
mov r3, #1442840576:r3賦值0x5600 0000;?
add r3, r3, #80:r3的值加0x50,即r3=0x5600 0050;
str r3, [fp, #-16]:r3存入[fp-16]所在的地址,即地址4076處存放0x5600 0050;
mov r3, #1442840576:r3賦值0x5600 0000;?
add r3, r3, #84:r3的值加0x54,即r3=0x5600 0054;
str r3, [fp, #-20]:r3存入[fp-20]所在的地址,即地址4072處存放0x5600 0054;
ldr r2, [fp, #-16]:r2取[fp-16]地址處的值,即[4076]地址的值,r2=0x5600 0050;
mov r3, #256:r3賦值為0x100;
str r3, [r2]:將r3寫到r2內(nèi)容所對應(yīng)的地址,即0x5600 0050地址處的值為0x100;;對應(yīng)c語言*pGPFCON = 0x100;;
ldr r2, [fp, #-20]:r2取[fp-20]地址處的值,即[4072]地址的值,r2=0x5600 0054;
mov r3, #0:r3賦值為0x00;
str r3, [r2]:將r3寫到r2內(nèi)容所對應(yīng)的地址,即0x5600 0054地址處的值為0x00;對應(yīng)c語言*pGPFDAT = 0;
mov r3, #0:r3賦值為0x00;
mov r0, r3:r0=r3=0x00;
sub sp, fp, #12:sp=fp-12=4092-12=4080;
ldmia sp, {fp, sp, pc}:從棧中恢復(fù)寄存器,fp=4080地址處的值=原來的fp,sp=4084地址處的值=4096,pc=4088地址處的值=8,隨后調(diào)到0x08地址處繼續(xù)執(zhí)行。
過程中的內(nèi)存數(shù)據(jù)情況:

前面那個(gè)例子,匯編調(diào)用main.c并沒有傳遞參數(shù),這里修改下c程序,讓其傳遞參數(shù)。
start.S:

led.c:

led.elf:



簡單分析下反匯編:
?mov sp, #4096:設(shè)置棧地址在4k RAM的最高處,sp=4096;
?mov r0, #4:r0=4,作為參數(shù);
?bl 58 <led_on>:調(diào)到58地址處的led_on函數(shù),并保存下一行代碼地址到lr,即lr=8;在led_on中會使用到r0;
?ldr r0, [pc, #12]:r0=[pc+12]處的值=[c+12=20]的值=0x186a0=1000000,作為參數(shù);
?bl 24 <delay>:調(diào)用24地址處的delay函數(shù),并保存下一行代碼地址到lr,即lr=24;在delay中會使用到r0;
?mov r0, #5:r0=5,作為參數(shù);
?bl 58 <led_on>:調(diào)到58地址處的led_on函數(shù),并保存下一行代碼地址到lr,即lr=58;在led_on中會使用到r0;
010節(jié)_完善LED程序_編寫按鍵程序
在上一節(jié)視頻里,我們編寫的程序代碼是先點(diǎn)亮led1,然后延時(shí)一會,再點(diǎn)亮led2,進(jìn)入死循環(huán)。
但在開發(fā)板上的實(shí)際效果是led1先亮,延時(shí)一會,led2再亮,然后一會之后,led1再次亮了。
這和我們的設(shè)計(jì)的代碼流程不吻合,這是因?yàn)?440里面有個(gè)看門狗定時(shí)器,開發(fā)板上電后,需要在一定時(shí)間內(nèi)“喂狗”(設(shè)置相應(yīng)的寄存器),否則就會重啟開發(fā)板。
之所以這樣設(shè)計(jì),是為了讓芯片出現(xiàn)死機(jī)時(shí),能夠自己復(fù)位,重新運(yùn)行。
這里我們寫個(gè)led燈循環(huán)的程序,步驟如下:
這里暫時(shí)用不到看門狗,先關(guān)閉看門狗,從參考手冊可知,向0x53000000寄存器寫0即可關(guān)閉看門狗;
設(shè)置內(nèi)存的棧,通過寫讀操作來判斷是Nand Flash還是Nor Flash;
設(shè)置GPFCON讓GPF4/5/6配置為輸出引腳;
循環(huán)點(diǎn)燈,依次設(shè)置GPFDAT寄存器;
完整代碼如下:


led.c


2440里面有很多寄存器,如果每次對不同的寄存器進(jìn)行查詢和操作會很麻煩,因此可以先提前定義成宏,做成一個(gè)頭文件,每次調(diào)用就行。
再舉一個(gè)按鍵控制LED的程序,,步驟如下:
這里暫時(shí)用不到看門狗,先關(guān)閉看門狗,從參考手冊可知,向0x53000000寄存器寫0即可關(guān)閉看門狗;
設(shè)置內(nèi)存的棧,通過寫讀操作來判斷是Nand Flash還是Nor Flash;
設(shè)置GPFCON讓GPF4/5/6配置為輸出引腳;
設(shè)置3個(gè)按鍵引腳為輸入引腳;
循環(huán)執(zhí)行,讀取按鍵引腳值,點(diǎn)亮對應(yīng)的led燈;
完整代碼如下:



視頻教程??
