在6410開發板上點燈的學習過程
最近公司接手了一個大型彙編工程,我也不知道這個年代為什麼還有人會耗費精力去編寫十幾萬行的彙編程式碼,而且這套程式碼即將由我來接手,想想就頭大。
我本人接觸linux的時間並不長,很多東西只是摸過,不能說出個所以然來。
藉著這個機會學習一下,高手勿笑。
我的第一個目標就是利用匯程式設計序在6410開發板上點亮LED。
檢視原理圖,找到連線開發板上的4個LED分別接在了MCU的GPM0,1,2,3,並且是低電平點亮(具體怎麼找到的就不說了),然後無非就是設定暫存器唄。瞭解到6410的IO不需要配置時鐘,那就很簡單了,根據經驗只需要設定IO口為上拉輸出,然後輸出0就OK了。
檢視晶片手冊找到了和GPM相關的暫存器:
配置IO功能的描述如下:
顯然我要把4個引腳全部設定為Output,所以要將0x1111寫入GPMCON暫存器。
下表是GMP的資料和上下拉的配置,我選擇上拉模式,輸出0,也就是GMPDAT為0,GPLPUD為0xAA這樣就可以點亮LED了。
其實我壓根沒有編寫過彙編程式碼,看了一些資料後新建了一個led.s,寫入下面這段程式碼:
1 LDR R0, =0x7F008820 @GPM base address 2 LDR R1, =0x1111 3 STR R1, [R0] @GPM Output4 5 MOV R1, #0xAA 6 LDR R1, [R0, #8] @GPM pull-up enabled 7 8 MOV R1, #0 9 LDR R1, [R0, #4] @GPM OUT 0
作用就是點亮4個LED。那麼問題來了,我該怎麼編譯它。
以下是我的思路,作為小白來說可能是一種比較正常的邏輯思維,不乏寫出來分享一下。
1. 之前寫過linux下的應用程式,以最簡單的“Hello World”為例,建立一個hello.c,編寫一個Hello World,然後用交叉編譯工具編譯:arm-none-linux-gnueabi-gcc hello.c 生成a.out,然後在開發板終端輸入./a.out(此時核心已經起來了),就能在終端中顯示"Hello World",所以我想點燈是不是也是這個原理。於是有了下面的操作:
提示沒有main函式,想想覺得很有道理啊,用C語言程式設計也是要有main函式的啊,於是我把程式碼改成了下面的樣子:
1 main: 2 LDR R0, =0x7F008820 @GPM base address 3 LDR R1, =0x1111 4 STR R1, [R0] @GPM Output 5 6 MOV R1, #0xAA 7 LDR R1, [R0, #8] @GPM pull-up enabled 8 9 MOV R1, #0 10 LDR R1, [R0, #4] @GPM OUT 0 11 12 B .
編譯還是報同樣的錯(後邊的B .是我後來加上去的,我覺得點完燈後不能讓它繼續運行了,否則會出現指令無法識別的錯誤)。
2. 好吧,難道main函式只能寫在C語言裡?那我就用C語言的main函式裡面來呼叫這個彙編函式,於是我將led.s改成了下面的樣子:
1 led_on: 2 LDR R0, =0x7F008820 @GPM base address 3 LDR R1, =0x1111 4 STR R1, [R0] @GPM Output 5 6 MOV R1, #0xAA 7 LDR R1, [R0, #8] @GPM pull-up enabled 8 9 MOV R1, #0 10 LDR R1, [R0, #4] @GPM OUT 0 11 12 B .
然後編寫一個main.c,程式碼如下:
1 extern void led_on (void); 2 3 int main(int argc, const char *argv[]) 4 { 5 led_on(); 6 return 0; 7 }
輸入arm-none-linux-gnueabi-gcc led.s main.c編譯,結果如下:
提示沒有找到led_on。好吧,main.c裡面找不到led.s裡的led_on標號,其實我知道是怎麼回事,就是在led.s裡面沒有宣告led_on這個標號是全域性的,於是將led.s改成了下面的樣子:
1 .global led_on 2 led_on: 3 LDR R0, =0x7F008820 @GPM base address 4 LDR R1, =0x1111 5 STR R1, [R0] @GPM Output 6 7 MOV R1, #0xAA 8 LDR R1, [R0, #8] @GPM pull-up enabled 9 10 MOV R1, #0 11 LDR R1, [R0, #4] @GPM OUT 0 12 13 B .
其實就是在第一行加入了.global led_on,表明led_on這個標號是全域性的,main.c可以訪問到它。然後繼續編譯:
坑就坑在這裡,它竟然編譯過了,並且生成了一個a.out。。。。。。我滿心歡喜將我的a.out拷貝到nfs共享目錄下,然後在開發板終端執行a.out:
提示段錯誤。OK,機智的我很快我就反應過來,核心起來後MMU也起來了,我寫入的地址是一個虛擬地址,並不是實際地址。想到我的u-boot是沒有啟動MMU的(這點是在從u-boot啟動核心的時候發現的,如果跳轉的地址為50008000就表示沒有啟動MMU,如果是C0008000就說明啟動u-boot時就已經啟動MMU了)。所以我重啟板子,在u-boot中通過tftp將a.out下載到50008000這個記憶體中去,然後bootm 跳轉到這個記憶體中去執行,結果發生data abort:
3. 這時我注意到下載的檔案大小是5019個位元組,怎麼可能這麼大?除非是編譯器給我加入了很多其他的東西,為了驗證這個想法,我編了一個main.c,裡面只有一個空的main函式,結果發現編譯出來的a.out有4924個位元組。應該是編譯器加了東西。冷靜思考一下,第一,我把C語言加入進來純粹是為了有一個main函式,之前學習彙編的時候我就知道main函式並不是第一個執行的地方,也就是之前肯定還有別的東西。第二、彙編檔案沒有理由不能單獨存在,之前編譯不過一定是因為我的編譯方式有問題或者檔案內容有問題(事實上兩者都有問題)。
4. 在查詢一些資料後瞭解到彙編檔案的入口是_start(GNU下的)無疑了, 並且我的s檔案格式寫的也有問題,改正之後的led.s變成了這個樣子:
1 .section .text 2 .global _start 3 _start: 4 LDR R0, =0x7F008820 @GPM base address 5 LDR R1, =0x1111 6 STR R1, [R0] @GPM Output 7 8 MOV R1, #0xAA 9 LDR R1, [R0, #8] @GPM pull-up enabled 10 11 MOV R1, #0 12 LDR R1, [R0, #4] @GPM OUT 0 13 loop: 14 B loop 15 16 .end
並且我之前的編譯方式也是不正確的,應該輸入下面幾條命令:
可以看到生成的led.bin只有40個位元組,這還差不多。此處感謝一篇博文:https://blog.csdn.net/liushaowei2008/article/details/7847918
將生成的led.bin通過tftp下載到記憶體中(此時u-boot已經起來了),然後跳轉到指定地址中去,發現LED點亮了。
注意此時led點亮仍是在u-boot啟動之後,我將該程式放在u-boot啟動之前還是啟動不了,因為有些必要的初始化工作我沒做。本次最大的收穫應該是將led.s編譯成led.bin的過程。