天天看點

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

目錄

  • 第一節 硬體知識_LED原理圖
    • 1、 點亮LED需要做的事情:
    • 2、 LED原理圖
  • 第二節:S3C2440啟動流程與GPIO
    • 1、原理圖中的net
    • 2、看晶片手冊
    • 3、補充幾個概念、補充S3C2440架構知識、啟動過程知識
  • 第三節:編寫第一個程式點亮LED
    • 1、幾條彙編代碼:
    • 2、彙程式設計式為:
    • 3、 編譯、燒寫程式
  • 第四節:彙編與機器碼
    • 1、幾個CPU寄存器名稱(别稱)的概念
    • 2、檢視機器碼的方法:
  • 4、 一個作業:
  • 第5節:程式設計知識_進制
  • 第6節:位元組序和位操作
    • 1、 位元組序
    • 2、 位操作
      • (1) 移位:移位運算符可以在二進制的基礎上對數字進行平移。
      • (2) 取反:
      • (3) 位與:
      • (4) 位或:
      • (5) 置位:這裡舉的例子是想要把bit7、8置為1
      • (6) 清位:這裡舉的例子是想要把bit7、8置為0
    • 第7講:編寫C程式控制LED
      • 1、 C指針操作
      • 2、 控制LED
    • 第7節:幾個彙編指令
      • 1、 add和sub
      • 2、BL:(branch and linlc)(跳轉并傳回)
      • 3、ldm和stm(操作多個寄存器)
  • 第9講:解剖c程式的内部機制
  • 第10節:完善LED程式_編寫案件程式
    • 1、 看門狗概念:
    • 2、 led循環點亮三盞燈程式
    • 3、編寫按鍵程式

第一節 硬體知識_LED原理圖

1、 點亮LED需要做的事情:

(1) 看原理圖确定控制LED的引腳;

(2) 看主晶片手冊,确定如何設定/控制引腳;

(3) 寫程式。

2、 LED原理圖

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式
01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

再改進:如果引腳能力不足,可以用三極管,使引腳僅起開關作用。

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式
01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

注:主晶片引腳輸出高/低電平即可改變LED狀态,是以我們不用去關心GPIO引腳輸出的到底是3.3V還是1.2V。

第二節:S3C2440啟動流程與GPIO

1、原理圖中的net

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式
01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

注:韋東山的LED1的引腳為GPF4,我的友善之臂闆子是GPB5;另外,所說的“輸出I/O”指電平。

2、看晶片手冊

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式
01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

GPBCON是端口配置寄存器,用來配置為輸出引腳。設定GPBCON[11:10] = 0b01,GPB5配置為輸出;注:[11:10]表示第11和第10位(位就是比特,就是bits,一個二進制就是一比特); 0b01中,0b表示二進制,01表示11和10這兩位分别是01。就是說把0x400寫入到GPBCON寄存器,即寫到位址0x56000010上(0x400是用“寄存器位檢視小工具”把第11位和第10位置為01得到的十六進制數)。

GPBDAT是端口資料寄存器,用來設定端口電平狀态,方法為把0或1寫到對應的位。設定GPBDAT[5] = 1,則輸出高電平;設定GPBDAT=0,則輸出低電平。就是說若輸出高電平的話則把0x20寫到位址0x56000014上,若輸出低電平則把寫到位址0x56000014上(0x20是第五位置為1得到的十六進制數)。

3、補充幾個概念、補充S3C2440架構知識、啟動過程知識

概念補充:

Nor flash 一般是小程式用,nand flash用于大程式;

GPIO就是通用I/O口;

SOC是system on chip片内系統;

SRAM(static read access memory靜态随機存儲器,RAM存儲在斷電後資料消失);

基位址即起始位址。

架構為:

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

啟動過程:

(1) NOR啟動:NOR Flash基位址為0,片内RAM位址為0x40000000;

過程為:CPU讀出NOR上第1個指令(前4位元組),然後執行,然後再讀第2個指令(也是4位元組),然後執行。以此一直執行。

(2) NAND 啟動:片内4kRAM基位址為0

過程為:2440硬體把nand前4k内容複制到片内RAN,然後CPU從0位址取出第一條指令執行。

注意flash與SRAM的差別:

(1)FLASH是用來存儲程式的,SRAM是用來存儲程式運作中的中間變量(SRAM一般作為CPU的二級緩存)。

(2)flash寫入的内容不會因電源關閉而失去,讀取速度慢,成本較低,一般用作程式存儲器或者低速資料讀取的情況;

SRAM有最快的讀寫速度,但電源掉落後其内容也會失去,價格昂貴,一般用作cpu的二級緩存,記憶體條也不用這個,适合高速資料讀取的場合。

兩者的關系為flash為ROM,sram為RAM。

第三節:編寫第一個程式點亮LED

1、幾條彙編代碼:

(可以了解[R1]表示的是記憶體的位址,(1)、(2)所說的“R1的值x”實際上是位址)

(1)LDR:讀記憶體。格式為:LDR R0, [R1] (假設R1的值是x,讀取位址x上的資料(4位元組)儲存到R0中)。

(2)STR:寫記憶體指令。格式為:STR R0,[R1] (假設R1的值是x,把R0的值寫到位址x中去(也是4位元組))。

(3)B:跳轉

(4)MOV:格式為:MOV R0,R1 (把R1的值給指派給R0,R0 = R1)

MOV R0,#0x100 (就是R0 = 0x100)

(5)LDR R0,= 0x12345678 (這是僞指令,它會被拆分為幾條真正的RAM指令,最終結果為:R0 = 0x12345678)

注意第(4)個指令MOV和第(5)個指令的差別:MOV的R1隻能是簡單數(立即數),比如#0x100。具體原因如下圖:

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

2、彙程式設計式為:

(這是一個.S檔案)

/*
*點亮LED:GPB5
*/

.text
.global _start
_start:

/*配置GPB5為輸出引腳
*把0x400寫到位址0x56000010
*我了解,0x56000010這個端口配置寄存器(包含所有的GPB)的位址給CPU的r1寄存器,再把0x400這個十六進制數給CPU的r0寄存器,再把r0的值給r1寄存器,則5這個口被激活
*/
	ldr r1, =0x56000010/*把0x56000010這個位址作為值給r1*/
	ldr r0, =0x400/*把0x400這個位址作為值給r0。或者用mov r0, #0x400*/
	str r0, [r1]/*把r0的值寫到r1的位址中去*/

/*設定GPB5輸出高電平
*把0寫到位址0x56000014
*/
	ldr r1, =0x56000014/*把0x56000014這個位址給r1*/
	ldr r0, =0/*把0這個位址給r0*/
	str r0, [r1]/*把r0的值寫到r1的位址中去*/

/*死循環*/
halt:
	b halt
           

3、 編譯、燒寫程式

韋東山是用的線上遠端Ubuntu系統,我用的是我電腦上的Ubuntu系統:

寫好的彙程式設計式,要在Ubuntu中輸入以下指令分别進行編譯、連結、轉bin檔案

arm-linux-gcc -c -o led_on.o led_on.S
arm-linux-ld -Ttext 0 led_on.o -o led_on.elf
arm-linux-objcopy -O binary -S led_on.elf led_on.bin
           

注:為了友善,可以用makefile

第四節:彙編與機器碼

1、幾個CPU寄存器名稱(别稱)的概念

pc(program cunter程式計數器):把一個位址寫到R15寄存器(也就是pc寄存器)中去的時候,程式就跳到那個位址去。

當執行一條指令時,首先需要根據PC中存放的指令位址,将指令由記憶體取到指令寄存器中,此過程稱為“取指令”。與此同時,PC中的位址或自動加1或由轉移指針給出下一條指令的位址。此後經過分析指令,執行指令。完成第一條指令的執行,而後根據PC取出第二條指令的位址,如此循環,執行每一條指令。

lr(link register傳回位址):當一個函數執行結束後,要傳回剛開始的位址,這個寄存器就是存放這個位址的。

sp(stack pointer):棧指針

2、檢視機器碼的方法:

在Ubuntu系統執行指令:

arm-linux-objdump -D led_on.elf > led_on.dis
           

把led_on.dis檔案(反彙編檔案)用notepad++打開

注:編譯器把彙編碼轉換為機器碼,機器碼就是bin檔案裡的内容

led_on.dis如下:

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

bin檔案要用UltraEdit這個軟體打開如下:

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式
01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式
01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

對于上面這一副圖檔中藍色框選的部分,[0x1c]是位址,位址中的内容是下面藍色橫線中的0x000050。

ldr r1,[pc,#20]的意思就是把0x5000050存到r1中去。

4、 一個作業:

直接在上面的led_on.bin檔案(機器碼)做修改,使得led2點亮。注:led2的引腳為GPB6。若寫彙程式設計式則應該将前面彙編編碼的“ldr r0, = 0x400”換成ldr r0, = 0x1000

作業答案如下:

我們需要直接修改機器碼,就相當于是把彙編碼中的mov r0,#0x400 轉換為mov r0,#0x1000,即确定mov r0,#0x1000的機器碼。

先打開ARM架構手冊(ARM Architecture Reference Manual.pdf)檢視MOV指令機器碼的結構:

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式
01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

Shifter_operand是立即數,由rotate(4位)和immed(8位)兩部分組成。如果确定了rotate和immed的值那麼機器碼就确定了。确定rotate和immed方法如下:

比如說,我們要确定mov r0,#0x1000的機器碼,方法如下:

0x1000的二進制為:

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

第12位為1,也就是說需要由00000001(注:immed是始終等于1的)到第12位為1需要循環右移20位,20/2=10,也就是說rotate為10(即1010),即其立即數為1010 0000 0001;然後把e3a00b01(這是GPB5的)的二進制的後12為位的立即數改成1010 0000 0001就得到了mov r0,#0x1000的機器碼0xe3a00a01,然後把led1的bin檔案的e3a00b01替換為e3a00a01,led2就亮了。

第5節:程式設計知識_進制

1、 在windows cmd指令中輸入calc就會彈出電腦。Windows電腦中的程式員電腦可以進行進制的轉換

2、 N進制逢n進1

3、 為什麼引入二進制?

因為半導體隻有2個狀态(on:1;off : 0),資料使用多個半導體表示,使用二進制可以與硬體的半導體對應,如:8bit資料如下圖表示:

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

為什麼用八進制?答案如下圖:

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

如何快速地轉換2/8/16進制:

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

二進制到8/16進制,用8421法,8進制是3位一組(421),然後相加;16進制是4位一組(8421),然後相加。

8/16進制到二進制,是把8進制的每位展開為3位2進制數,把16進制數的每位展開為4位2進制數。

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

C語言各種進制表示方法:

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

第6節:位元組序和位操作

1、 位元組序

一般的arm晶片預設是小位元組序,對于2440可以設定某個寄存器來設定使用小位元組序還是大位元組序。注:下面圖檔中的A就是低位址,A+3是高位址。

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

2、 位操作

(1) 移位:移位運算符可以在二進制的基礎上對數字進行平移。

左移運算是将一個二進制位的操作數按指定移動的位數向左移動,移出位被丢棄,右邊移出的空位一律補0。右移運算是将一個二進制位的操作數按指定移動的位數向右移動,移出位被丢棄,左邊移出的空位一律補0,或者補符号位,這由不同的機器而定。在使用補碼作為機器數的機器中,正數的符号位為0,負數的符号位為1。

左移n位就相當于乘以2的n次方;右移n位相當于除以2的n次方。具體這樣算的原因是,左移了以後,剩下的所有值的權重增加了;右移了以後,剩下的所有值的權重減少加了。

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

(2) 取反:

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

(3) 位與:

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

(4) 位或:

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

(5) 置位:這裡舉的例子是想要把bit7、8置為1

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

(6) 清位:這裡舉的例子是想要把bit7、8置為0

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

第7講:編寫C程式控制LED

1、 C指針操作

(1) 變量和指針的原理

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

(2) 指針的使用方法

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

2、 控制LED

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

是以由上得到main函數:

int main() {
	unsigned *pGPBCON = 0x56000010;
	unsigned *pGPBDAT = 0x56000014;
	*pGPBCON = 0x400;
	*pGPBDAT = 0;
	
	return 0;
}
           

那麼問題來了:

(1)寫出了main函數之後,誰來調用它;

(2)main函數中的變量(pGPBCON、pGPBDAT)儲存在記憶體中,這個記憶體位址是多少?

怎麼來回答呢,我們寫一個彙程式設計式,建立檔案start.S,用于給main函數設定記憶體和調用main函數,寫程式如下:

注:普及一下sp指針 在随機存儲器區劃出一塊區域作為堆棧區,資料可以一個個順序地存入(壓入)到這個區域之中,這個過程稱為‘壓棧’(push)。通常用一個指針(堆棧指針SP—StackPointer)實作做一次調整,SP總指向最後一個壓入堆棧的資料所在的資料單元(棧頂)。從堆棧中讀取資料時,按照堆棧指針指向的堆棧單元讀取堆棧資料,這個過程叫做 ‘彈出’(pop ),每彈出一個資料,SP 即向相反方向做一次調整,如此就實作了後進先出的原則。
01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

在Ubuntu中執行:(注:我編寫的led.c和start.S)

arm-linux-gcc -c -o led.o led.c
 arm-linux-gcc -c -o start.o start.S
	arm-linux-ld -Ttext 0 start.o led.o -o led.elf
	arm-linux-objcopy -O binary -S led.elf led.bin
	arm-linux-objdump -D led.elf > led.dis
           

至于為什麼這樣操作(.c檔案+.S檔案兩個檔案),原因見下一節。

第7節:幾個彙編指令

執行arm-linux-objdump -D led.elf > led.dis得到反彙編檔案led.dis并用notepad打開,如下:

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式
01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

1、 add和sub

sub r0 , r1 , #4 意為r0 = r1 – 4

sub r0, r1 , r2 意為r0 = r1 – r2

add r0, r1 , #4 意為 r0 = r1 +4

2、BL:(branch and linlc)(跳轉并傳回)

注:傳回位址就是下一條指令的位址。

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

3、ldm和stm(操作多個寄存器)

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

下圖中ia、db的意思:

過後增加(Increment After)、預先增加(Increment Before)、過後減少(Decrement After)、預先減少(Decrement Before)。

對下面這兩幅圖注解:

db表示先減後存,假設sp=4096,則需要先減為4092再存。存的時候規則是高編号寄存器存高位址,即pc(r15)先存,存入記憶體中的4092~4095。然後依次是lr、ip、fp的存儲

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式
01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

下面這兩幅圖檔講的是ldmid sp,{fp,sp,pc}

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式
01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

第9講:解剖c程式的内部機制

分析start.S和led.c,剖析其内部機制,把程式的反彙編搞明白就了解了C程式的内部機制:

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

提出以下五個問題:

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

先補充一下理論知識:

(1)r0-r3寄存器是傳參和傳遞函數傳回值。用于調用者和被調用者之間的參數傳遞。

(2)r4-r11寄存器,在你的函數中有可能其中的一些會被用到。入口處儲存,出口處恢複。

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

看上面的反彙編,mov sp, #4096,說明這是nand啟動,程式(即機器碼)拷貝到片内4k記憶體,從片内記憶體的0位址開始存放(e3a0da01存放到0位址,依次儲存)。

注:反彙編中的00000000<.comment>不存放。comment是注釋。

一上電,執行0位址的e3a0da01,即sp指到4096.然後執行eb0000000,即跳轉到c位址(去執行main()),并把傳回位址8賦予lr寄存器。

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

現在來分析主函數反彙編語言,看下圖。(注意,下圖是對主函數反彙編的解釋,你需要同時結合着下下一幅圖一塊看,看主函數在記憶體上的機制。如stmdb sp!,{fp,ip,lr,pc}在記憶體上的機制是在下下圖從左往右數的第三個記憶體圖)

對于10位址語句stmdb sp!,{fp,ip,lr,pc}進行注解:

(1)pc=目前指令位址+8,是以pc=10+8=0x18;

(2)上圖已經說了lr=8;

(3)fp是未定義值。

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

下圖是片内4k記憶體的分析:

結合上圖和下面這幅圖,你會看到:機器碼存到了記憶體中的位置,寄存器如何存到記憶體中,局部變量是如何存到棧中。最重要的是,結合上下兩幅圖,你會明白你控制led時對端口配置寄存器和端口狀态寄存器所進行的操作,在記憶體中是如何進行的。

01-第一個裸機程式led及其引申第一節 硬體知識_LED原理圖第二節:S3C2440啟動流程與GPIO第三節:編寫第一個程式點亮LED第四節:彙編與機器碼4、 一個作業:第5節:程式設計知識_進制第6節:位元組序和位操作第9講:解剖c程式的内部機制第10節:完善LED程式_編寫案件程式

從以上可知:所謂棧就是sp寄存器指向的記憶體。

注:關于第五個問題,怎麼從棧中恢複寄存器。反彙編中記憶體位址54-57、58-61兩個語句就是恢複記憶體的。

還有一個問題是,怎麼傳參數到被調用函數(main())的?下面寫一個程式進行驗證。

start.S檔案

.text
.global _start

_start:

	/* 設定記憶體: sp 棧 */
	ldr sp, =4096  /* nand啟動 */
//	ldr sp, =0x40000000+4096  /* nor啟動 */

	mov r0, #4/*把4賦給r0寄存器,也就是把4傳入led_on(int which)中的形參*/
	bl led_on

	ldr r0, =100000/*傳參*/
	bl delay

	mov r0, #5/*傳參*/
	bl led_on

halt:
	b halt
	
           

led.c檔案

void delay(volatile int d)/*volatile的作用是作為指令關鍵字,確定本條指令不會因編譯器的優化而省略*/
{
	while (d--);
}

int led_on(int which)
{
	unsigned int *pGPFCON = (unsigned int *)0x56000050;
	unsigned int *pGPFDAT = (unsigned int *)0x56000054;

	if (which == 4)
	{
		/* 配置GPF4為輸出引腳 */
		*pGPFCON = 0x100;
	}
	else if (which == 5)
	{
		/* 配置GPF5為輸出引腳 */
		*pGPFCON = 0x400;
	}
	
	/* 設定GPF4/5輸出0 */
	*pGPFDAT = 0;

	return 0;
}
           

現在将上面這個start.S和led_on編譯完成後,生成一個反彙編檔案并檢視,如下:

led.elf:     file format elf32-littlearm

Disassembly of section .text:

00000000 <_start>:
   0:	e3a0da01 	mov	sp, #4096	; 0x1000
   4:	e3a00004 	mov	r0, #4	; 0x4
   8:	eb000012 	bl	58 <led_on>
   c:	e59f000c 	ldr	r0, [pc, #12]	; 20 <.text+0x20>/*注:位址20的000186a0轉成十進制是10000*/
  10:	eb000003 	bl	24 <delay>
  14:	e3a00005 	mov	r0, #5	; 0x5
  18:	eb00000e 	bl	58 <led_on>

0000001c <halt>:
  1c:	eafffffe 	b	1c <halt>
  20:	000186a0 	andeq	r8, r1, r0, lsr #13

00000024 <delay>:
  24:	e1a0c00d 	mov	ip, sp
  28:	e92dd800 	stmdb	sp!, {fp, ip, lr, pc}
  2c:	e24cb004 	sub	fp, ip, #4	; 0x4
  30:	e24dd004 	sub	sp, sp, #4	; 0x4
  34:	e50b0010 	str	r0, [fp, #-16]
  38:	e51b3010 	ldr	r3, [fp, #-16]
  3c:	e2433001 	sub	r3, r3, #1	; 0x1
  40:	e50b3010 	str	r3, [fp, #-16]
  44:	e51b3010 	ldr	r3, [fp, #-16]
  48:	e3730001 	cmn	r3, #1	; 0x1
  4c:	0a000000 	beq	54 <delay+0x30>
  50:	eafffff8 	b	38 <delay+0x14>
  54:	e89da808 	ldmia	sp, {r3, fp, sp, pc}

00000058 <led_on>:
  58:	e1a0c00d 	mov	ip, sp
  5c:	e92dd800 	stmdb	sp!, {fp, ip, lr, pc}
  60:	e24cb004 	sub	fp, ip, #4	; 0x4
  64:	e24dd00c 	sub	sp, sp, #12	; 0xc
  68:	e50b0010 	str	r0, [fp, #-16]
  6c:	e3a03456 	mov	r3, #1442840576	; 0x56000000
  70:	e2833050 	add	r3, r3, #80	; 0x50
  74:	e50b3014 	str	r3, [fp, #-20]
  78:	e3a03456 	mov	r3, #1442840576	; 0x56000000
  7c:	e2833054 	add	r3, r3, #84	; 0x54
  80:	e50b3018 	str	r3, [fp, #-24]
  84:	e51b3010 	ldr	r3, [fp, #-16]
  88:	e3530004 	cmp	r3, #4	; 0x4
  8c:	1a000003 	bne	a0 <led_on+0x48>
  90:	e51b2014 	ldr	r2, [fp, #-20]
  94:	e3a03c01 	mov	r3, #256	; 0x100
  98:	e5823000 	str	r3, [r2]
  9c:	ea000005 	b	b8 <led_on+0x60>
  a0:	e51b3010 	ldr	r3, [fp, #-16]
  a4:	e3530005 	cmp	r3, #5	; 0x5
  a8:	1a000002 	bne	b8 <led_on+0x60>
  ac:	e51b2014 	ldr	r2, [fp, #-20]
  b0:	e3a03b01 	mov	r3, #1024	; 0x400
  b4:	e5823000 	str	r3, [r2]
  b8:	e51b3018 	ldr	r3, [fp, #-24]
  bc:	e3a02000 	mov	r2, #0	; 0x0
  c0:	e5832000 	str	r2, [r3]
  c4:	e3a03000 	mov	r3, #0	; 0x0
  c8:	e1a00003 	mov	r0, r3
  cc:	e24bd00c 	sub	sp, fp, #12	; 0xc
  d0:	e89da800 	ldmia	sp, {fp, sp, pc}
Disassembly of section .comment:

00000000 <.comment>:
   0:	43434700 	cmpmi	r3, #0	; 0x0
   4:	4728203a 	undefined
   8:	2029554e 	eorcs	r5, r9, lr, asr #10
   c:	2e342e33 	mrccs	14, 1, r2, cr4, cr3, {1}
  10:	Address 0x10 is out of bounds.
           

第10節:完善LED程式_編寫案件程式

1、 看門狗概念:

看門狗定時器(WDT,Watch Dog Timer)是單片機的一個組成部分,它實際上是一個計數器,一般給看門狗一個數字,程式開始運作後看門狗開始從這個數字開始倒計時計數。倒計時到0的時候系統便會複位。

2、 led循環點亮三盞燈程式

start.S檔案

.text
.global _start

_start:

	/* 關閉看門狗 */
	ldr r0, =0x53000000
	ldr r1, =0
	str r1, [r0]

	/* 設定記憶體: sp 棧 */
	/* 分辨是nor/nand啟動
	 * 寫0到0位址, 再讀出來
	 * 如果得到0, 表示0位址上的内容被修改了, 它對應ram, 這就是nand啟動
	 * 否則就是nor啟動
	 */
	mov r1, #0
	ldr r0, [r1] /* 讀出原來的值備份 */
	str r1, [r1] /* 0->[0] */ 
	ldr r2, [r1] /* r2=[0] */
	cmp r1, r2   /* r1==r2? 如果相等表示是NAND啟動 */
	ldr sp, =0x40000000+4096 /* 先假設是nor啟動 */
	moveq sp, #4096  /* nand啟動 */
	streq r0, [r1]   /* 恢複原來的值 */
	

	bl main

halt:
	b halt
           
main.c
           
void delay(volatile int d)
{
	while (d--);
}

int main(void)
{
	volatile unsigned int *pGPFCON = (volatile unsigned int *)0x56000050;
	volatile unsigned int *pGPFDAT = (volatile unsigned int *)0x56000054;
	int val = 0;  /* val: 0b000, 0b111 */
	int tmp;

	/* 設定GPFCON讓GPF4/5/6配置為輸出引腳 */
	*pGPFCON &= ~((3<<8) | (3<<10) | (3<<12));//清零
	*pGPFCON |=  ((1<<8) | (1<<10) | (1<<12));//置一
 
	/* 循環點亮 */
	while (1)
	{
		tmp = ~val;
		tmp &= 7;
		*pGPFDAT &= ~(7<<4);//清零
		*pGPFDAT |= (tmp<<4);
		delay(100000);
		val++;
		if (val == 8)
			val = 0;
		
	}

	return 0;
}
           

對上面的led.c進行改進:把寄存器定義為一個宏,并且把宏直接放到一個頭檔案中。這樣更友善了。

改變如下:

#include "s3c2440_soc.h"

/*
s3c2440_soc.h中包含:
#define GPFCON (*((volatile unsigned int *)0x56000050))
#define GPFDAT (*((volatile unsigned int *)0x56000054))
*/

void delay(volatile int d)
{
	while (d--);
}

int main(void)
{
	int val = 0;  /* val: 0b000, 0b111 */
	int tmp;

	/* 設定GPFCON讓GPF4/5/6配置為輸出引腳 */
	GPFCON &= ~((3<<8) | (3<<10) | (3<<12));
	GPFCON |=  ((1<<8) | (1<<10) | (1<<12));

	/* 循環點亮 */
	while (1)
	{
		tmp = ~val;
		tmp &= 7;
		GPFDAT &= ~(7<<4);
		GPFDAT |= (tmp<<4);
		delay(100000);
		val++;
		if (val == 8)
			val =0;
		
	}

	return 0;
}
           

3、編寫按鍵程式

首先看原理圖,找按鍵對應的端口寄存器,再對該寄存器進行操作。

key_led.c

#include "s3c2440_soc.h"

void delay(volatile int d)
{
	while (d--);
}

int main(void)
{
	int val1, val2;
	
	/* 設定GPFCON讓GPF4/5/6配置為輸出引腳 */
	GPFCON &= ~((3<<8) | (3<<10) | (3<<12));
	GPFCON |=  ((1<<8) | (1<<10) | (1<<12));

	/* 配置3個按鍵引腳為輸入引腳:
	 * GPF0(S2),GPF2(S3),GPG3(S4)
	 * 注:00為input
	 */
	GPFCON &= ~((3<<0) | (3<<4));  /* gpf0,2 */
	GPGCON &= ~((3<<6));  /* gpg3 */

	/* 循環點亮 */
	while (1)
	{
		val1 = GPFDAT;
		val2 = GPGDAT;

		if (val1 & (1<<0)) /* s2 --> gpf6 */
		{
			/* 松開 */
			GPFDAT |= (1<<6);
		}
		else
		{
			/* 按下 */
			GPFDAT &= ~(1<<6);
		}

		if (val1 & (1<<2)) /* s3 --> gpf5 */
		{
			/* 松開 */
			GPFDAT |= (1<<5);
		}
		else
		{
			/* 按下 */
			GPFDAT &= ~(1<<5);
		}

		if (val2 & (1<<3)) /* s4 --> gpf4 */
		{
			/* 松開 */
			GPFDAT |= (1<<4);
		}
		else
		{
			/* 按下 */
			GPFDAT &= ~(1<<4);
		}

		
	}

	return 0;
}