這一部分將對前文沒有提到的幾段關鍵代碼進行簡單說明,介紹一下原始碼組織圖和Makefile系統,展示一下實驗運行結果,並提供全部原始碼下載。
1. 定時器初始化和延時程式
因為在 CS8900A的驅動程式中需要用到延時,因此有必要對S3C2440的計時器進行使能和初始化,並編寫延時程式。
S3C2440A共有5個定時器,編號為Timer0 ~ Timer4。其中Timer0 ~ Timer3都有輸出引腳,可以通過定時器來控制引腳電平周期性的變化,這稱為脈衝寬度調製(PWM:Pulse Width Modulation)功能。而Timer4沒有輸出引腳,也就沒有PWM功能,所以Timer4常被程式裡的延時函數使用。
定時器組件的時鐘源為PCLK,但是需要經過兩級預分頻之後才真正供定時器使用。第一級預分頻由TCFG0寄存器控制,其位[7:0]設定預分頻器0的值,供Timer0和Timer1使用,位[15:8]設定預分頻器1的值,供Timer2 ~ Timer4使用。第二級預分頻由TCFG1寄存器控制,其每四位控制一個定時器,可以從2分頻、4分頻、8分頻、16分頻、外接TCLK0/TCLK1 這五種頻率中選擇。
我們的延時函數使用Timer4,其它定時器全部關閉。初始化程式中設定:TCFG0 = 0x0f00; 表示Timer4的第一級預分頻值為 15+1 = 16。寄存器TCFG1使用預設值全0,表示第二級預分頻為2分頻。前面已經設定PCLK為50MHz,這樣Timer4實際的工作頻率為:
50MHz/16/2 = 50000000/32 = 1562500Hz
注意計算時鐘頻率時的MHz是指10^6,而不是2^20;同理KHz是指1000Hz,而不是1024Hz。
我們在TCON中把Timer4設為”自動載入“。當Timer4啟動時,TCNTB4的值將被自動裝入內部寄存器TCNT4,然後在工作頻率下,TCNT4開始減1計數,當到達0時,TCNTB4的值又被自動裝入TCNT4,下一個計數流程開始。我們把TCNTB4設為15625,則一個計數流程的的長度為10毫秒。
假設要延時的時間為msec毫秒,則共需要的計數值為 tmo = msec*15625/10,設一個變數timestamp儲存已經過去的時間戳記,每次讀取TCNT4的值後更新timestamp,直到它大於 tmo 。程式如下:
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
while( timestamp
< tmo )
{
thisdec = TCNTO4
& 0xffff;
if( lastdec
>= thisdec )
/* normal mode
*/
{
timestamp += lastdec
- thisdec;
}
else
/* we have an overflow ...
*/
{
timestamp += lastdec
+ TIMER_LOAD_VAL
- thisdec;
}
lastdec = thisdec;
}
TCNT4的值可由寄存器TCNTO4讀出。程式中儲存了最近兩次讀出的TCNTO4值, 如果本次值比上次小,說明在同一個計數流程內;如果本次值比上次大,說明已經進入了下一個計數流程。
內容導航
2. 串口標準輸入輸出
要想在Bootloader中使用scanf()和print()並不容易,因為不能直接使用C庫函數。scanf()要從串口獲得輸入, print()要向串口進行輸出。必須自己實現常用的C庫函數, 不僅包括輸入輸出函數,還包括字串操作函數如strcmp(), strcpy()等。幸好在《嵌入式Linux應用開發完全手冊》這本書的原始碼中提供了這樣簡化的C庫,所以就直接拿來用了。
代碼中定義了兩個全域數組作為輸入輸出緩衝區:
static unsigned char g_pcOutBuf[ 1024 ];
static unsigned char g_pcInBuf[ 1024 ];
其實我們可以把這兩個緩衝區定位在CPU的 SteppingStone 裡面,這樣可以節省2K的空間。
scanf()的實現裡面調用 getc() 函數, printf() 的實現裡面調用 putc() 函數。我們自己寫getc()函數為從串口讀取字元, putc()函數實現為向串口發送字元, 這樣標準輸入輸出就跟串口聯絡在一起了。
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
/* 發送一個字元
*/
void putc(unsigned char c)
{
/* 等待,直到發送緩衝區中的資料已經全部發送出去
*/
while (!(UTRSTAT0
& TXD0READY));
/* 向UTXH0寄存器中寫入資料,UART即自動將它發送出去
*/
UTXH0 = c;
}
/* 接收字元
*/
unsigned char getc(void)
{
unsigned char ret;
/* 等待,直到接收緩衝區中的有資料
*/
while(!(UTRSTAT0
& RXD0READY));
/* 直接讀取URXH0寄存器,即可獲得接收到的資料
*/
ret = URXH0;
if (ret
== 0x0d || ret
== 0x0a)
{
putc(0x0d);
putc(0x0a);
}
else
{
putc(ret);
}
return ret;
} 內容導航
3. 原始碼組織圖
原始碼跟目錄下只有兩個檔案, 主Makefile和連結指令碼sboot.lds。
檔案夾start內有start.S和nand.c,前者是上電後最初啟動並執行彙編代碼,後者含有Nand Flash的讀函數,負責把S-Boot代碼從Nand拷貝到RAM中。
檔案夾main內有main.c,是一個死迴圈,提供若干菜單供使用者選擇,然後調用相應功能的程式。
檔案夾lib內是簡化和移植過的C標準庫,包括輸入輸出和字串操作函數。
檔案夾include內是一些標頭檔。
檔案夾app內有boot_linux.c和tftp.c,從名字就能看出它們的功能。
檔案夾device內含有裝置驅動程式,如串口初始化、定時器初始化和延時函數、網卡驅動、網路通訊協定實現等。
每個檔案夾內都有自己的Makefile,根目錄下的主Makefile會進入各個子目錄並調用各自的Makefile。每個子目錄下的Makefile把自己編譯的代碼連結成一個build-in.o檔案, 主Makefile把各個子目錄下的build-in.o連結成一個可執行檔。
編譯器使用自己製作的 arm-hwlee-linux-gnueabi-gcc. 可以從這裡下載。 給gcc增加 -nostdinc 選項, 表示不使用標準C庫函數,不到/usr/include目錄下尋找包含檔案, 只在-I$(INCLUDEDIR)指定的目錄尋找包含檔案。
內容導航
4. 提供全部原始碼下載:
檔案:S-Boot.tar.gz
大小:41KB
下載:下載
5. 運行結果
圖中,首先選擇3從TFTP伺服器下載核心到RAM中, 然後選擇4從RAM成功啟動核心。
選擇2還有通過串口Kermit協議下載核心的功能,前文沒有對這部分代碼作分析,有時間再補上。下面附一張: