Tiny4412使用C語言實現流水燈的裸機程序

本文轉自:http://www.techbulo.com/1347.htmlhtml

在前邊咱們使用匯編完成了一個流水燈實驗: https://my.oschina.net/cht2000/blog/1622224linux

可是,彙編語言可讀性太差,在這一節咱們用 C語言來實現了一樣的功能,而之後的試驗也儘可能用 C語言實現。編程

咱們在編寫上位機程序時,C語言程序執行的第一條指令,並不在main函數中。生成一個 C程序的可執行文件時,編譯器一般會在咱們的代碼中加上幾個被稱爲啓動文件的代碼—— crtl.o 、crti.o、crtend.o 、crtn.o 等,它們是標準庫文件。這些代碼設置C程序的堆棧等,而後調用 main 函數。它們依賴於操做系統,在裸板上這些代碼沒法執行,因此須要本身寫一個。服務器

這段代碼很簡單, 關鍵指令只有2條。本身編寫的 start .S啓動文件內容以下:函數

.text
.globl _start
_start:

	ldr sp, =0x02060000 
 
	// 調用C函數以前必須設置棧,棧用於保存運行環境,給局部變量分配空間
	// 參考ROM手冊P14, 咱們把棧指向BL2上方1K處(1K已經夠用),
	// 即:0x02020000 (iRAM基地址) + 5K(iROM代碼用) + 8K(BL1用) + 16K(BL2用) + 1K(用做棧))
 
	bl main  // 調用main函數(main這個名稱不是固定的,能夠隨意改)
 
halt_loop:
	b halt_loop

它在第 4行設置好棧指針後,就能夠經過第8行調用C函數 main了-----------C函數執行前,必須設置棧。oop

問:CPU不是有看門狗嘛?爲何沒有看到關看門狗的代碼?這樣程序能正常運行嗎?操作系統

答:在文章《Exynos 4412的啓動過程分析》中,咱們已經介紹過了,在執行咱們的程序前,CPU會首先執行iROM中的代碼和BL1的代碼,在這兩部分程序中會關閉看門狗。.net

其實咱們本身關閉看門狗也很簡單,只需往寄存器WTCON寫入0便可;翻譯

問:爲何調用C函數要設置棧?指針

答:1.  棧的總體做用

  1. 保存現場;
  2. 傳遞參數:彙編代碼調用C函數時,需傳遞參數;
  3. 保存臨時變量:包括函數的非靜態局部變量以及編譯器自動生成的其餘臨時變量;

2.  詳細解釋

1)保存現場:

現場,意思就至關於案發現場,總有一些現場的狀況,要記錄下來的,不然被別人破壞掉以後,你就沒法恢復現場了。而此處說的現場,就是指CPU運行的時候,用到了一些寄存器,好比r0,r1等等,對於這些寄存器的值,若是你不保存而直接跳轉到子函數中去執行,那麼極可能就被其破壞了,由於其函數執行也要用到這些寄存器。所以,在函數調用以前,應該將這些寄存器等現場,暫時保持起來(入棧push),等調用函數執行完畢返回後(出棧pop),再恢復現場。這樣CPU就能夠正確的繼續執行了。保存寄存器的值,通常用的是push指令,將對應的某些寄存器的值,一個個放到棧中,把對應的值壓入到棧裏面,即所謂的壓棧。而後待被調用的子函數執行完畢的時候,再調用pop,把棧中的一個個的值,賦值給對應的那些你剛開始壓棧時用到的寄存器,把對應的值從棧中彈出去,即所謂的出棧。其中保存的寄存器中,也包括lr的值(由於用bl指令進行跳轉的話,那麼以前的PC的值是存在lr中的),而後在子程序執行完畢的時候,再把棧中的lr的值pop出來,賦值給PC,這樣就實現了子函數的正確的返回。

2)傳遞參數:

C語言進行函數調用的時候,經常會傳遞給被調用的函數一些參數,對於這些C語言級別的參數,被編譯器翻譯成彙編語言的時候,就要找個地方存放一下,而且讓被調用的函數可以訪問,不然就沒發實現傳遞參數了。對於找個地方放一下,分兩種狀況。一種狀況是,自己傳遞的參數很少於4個,就能夠經過寄存器r0~r3傳送參數。由於在前面的保存現場的動做中,已經保存好了對應的寄存器的值,那麼此時,這些寄存器就是空閒的,能夠供咱們使用的了,那就能夠放參數。另外一種狀況是,參數多於4個時,寄存器不夠用,就得用棧了。

3)臨時變量保存在棧中:

包括函數的非靜態局部變量以及編譯器自動生成的其餘臨時變量。

如今,咱們能夠很容易寫出控制 LED 的程序了。畢竟是用C語言嘛,至關的靈活。 main函數在main.c文件中,代碼以下:

#define GPM4CON (*(volatile unsigned int *)0x110002E0)
#define GPM4DAT (*(volatile unsigned int *)0x110002E4)
 
void delay(volatile int time)
{
	for(; time > 0; time-- );
}
 
int main(void)
{
	unsigned long tmp = 0;
	int i = 0;
 
	/*
	* GPM4_0-GPM4_3 設置爲輸出功能
	*/
 
	tmp = GPM4CON;
	tmp &= ~0xffff;
	tmp |= 0x1111;
	GPM4CON = tmp;
 
	/*
	* 實現流水燈
	*/

	while(1)
	{
		GPM4DAT = i;
		if (++i == 16)
			i = 0;
		delay(9999999);
	}
	 
	return 0;
}

來看看Makefile:

objs := start.o main.o
led.bin : $(objs)
	arm-linux-ld -Tled.lds -N -o led.elf $^
	arm-linux-objcopy -O binary -S led.elf $@
	arm-linux-objdump -D -m arm led.elf > led.dis
 
%.o:%.c
	arm-linux-gcc -Wall -marm -c -O2 -o $@ $<
 
%.o:%.S
	arm-linux-gcc -Wall -marm -c -O2 -o $@ $<
 
clean:
	rm -f *.dis *.bin *.elf *.o

執行 make 命令時,它的目是去生成第1個目標,即 led.bin ;

led.bin 依賴於start.o 和 main.o,因此要先生成這 2個.o 文件;

start.o 依賴於start.S ,符合第 11 行的規則,會使用第 12 行的命令生成start.o ;

相似的, main.o 依賴於main.c ,符合第 8行的規則,會使用第 9行的命令生成 main.o ;

當這 2個.o 文件都生成以後,就會執行第 4~6行的命令生成 led.bin文件: 第 4行將編譯獲得的 .o 文 件鏈接爲led.elf

可執行程序,第 5行是生成二進制格式的可執行程序,第 6行是獲得反彙編程序以供查看。

連接腳本還和彙編流水燈同樣。這裏再也不介紹。

好了,下面開始驗證咱們的程序了。

1.將程序源碼上傳到服務器,並執行make,生成led.bin文件。

2.借鑑上一個實驗的步驟,將程序燒寫到SD卡。

相關文章
相關標籤/搜索