歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

ARM 2440——Nand flash啟動模式詳解(LED程序為例)

研究arm也有2個月了,現在才感覺理解了arm在Nand flash模式下的啟動過程,現在來這裡記錄下來以表達我無比喜悅的心情。閒話少說,趁著還沒有忘記學習過程中的感受,直接進入正題。

大家都知道,arm在Nand flash啟動模式下啟動時系統會將Nand flash中的前4KB代碼拷貝到SRAM(也就是Steppingstone中),由SRAM配置中斷向量表和完成Nand flash訪問的必要初始化,然後將Nand flash中的全部程序代碼拷貝到SDRAM中,最後由SRAM跳轉到SDRAM,然後程序就正常執行了,這一過程看上去很簡單,但是真正理解這一過程還是不簡單的,盡管這樣,還是想告訴大家仔細理解還是比較容易理解這個過程的。如果您是ADS用戶,你省去了很多麻煩,但我不確定你省去的這些麻煩是否值得,這裡介紹的是一種麻煩的方式,linux下的led程序。

代碼Head.s

  1. .extern main  
  2. .text  
  3. .global _start  
  4. _start:  
  5.     b reset  
  6.   
  7. reset:  
  8.     ldr sp,=4096  
  9.     bl disable_watch_dog  
  10.     bl clock_init  
  11.     bl memsetup  
  12.     bl copy_steppingstone_to_sdram  
  13.     ldr pc,=on_sdram  
  14.   
  15. on_sdram:  
  16.     msr cpsr_c,#0xdf  
  17.     ldr sp,=0x34000000  
  18.     ldr lr,=halt_loop  
  19.     ldr pc,=Main  
  20.   
  21. halt_loop:  
  22.     b halt_loop  

我認為,最需要理解的就是這段代碼了。先簡單的解釋下這段代碼。

(1)由於arm執行reset之後pc會被清零,也就是reset中斷的中斷入口地址,因此,第一條指令就是b reset,跳轉到reset中斷處理函數。

(2)由於這裡硬件配置都是C語言來完成的,而且我們的初始代碼比較小,完全不會超出4KB,因此可以在SRAM使用堆棧,故將SP設置為4096,以提供C運行環境

(3)接下來的3個bl分別完成了關閉看門夠定時器,配置時鐘信號和存儲器配置的工作,第四個bl是將SRAM的4KB空間內的代碼拷貝到了SDRAM中。

(4)接下來的ldr句將pc賦值為on_sdram的地址,實現了從SRAM到SDRAM的跳轉(下面會講為什麼)

(5)on_sdram中切換到了了系統模式然後分配了系統模式堆棧,將鏈接寄存器設置為halt_loop然後就跳轉到了SDRAM中的Main

上面的解釋只是大體上說明了代碼的意思,但是初學者總會有個疑問就是為什麼ldr pc,=on_sdram就實現了從SRAM到SDRAM的跳轉呢?我被這個問題困擾了很長時間,到今天才想明白了這個問題,問題的關鍵就是相對跳轉和絕對跳轉的問題。為了說明這個問題我先解釋一下bl指令跟ldr指令在執行過程中的區別。

B指令是相對跳轉指令,B 指令是最簡單的跳轉指令。一旦遇到一個 B 指令,ARM 處理器將立即跳轉到給定的目標地址,從那裡繼續執行。注意存儲在跳轉指令中的實際值是相對當前PC 值的一個偏移量,而不是一個絕對地址,它的值由匯編器來計算(參考尋址方式中的相對尋址)。它是 24 位有符號數,左移兩位後有符號擴展為 32 位,表示的有效偏移為 26 位(前後32MB 的地址空間),同樣的,BL、BX都是相對跳轉。

LDR偽指令是將第二操作直接賦值給第一操作數,當執行ldr pc,=Main時是將Main的絕對地址賦值給了PC。

好了,知道這兩個指令的區別之後我們來看代碼是如何實現的從SRAM到SDRAM的跳轉,首先需要指出,2440的開發板有SRAM和SDRAM,SRAM是從地址0x00000000開始的4KB內存空間,SDRAM是從0x30000000開始的64M空間。

無論用ADS編譯還是用arm-linux-gcc編譯都會將代碼鏈接到0x30000000以後(也就是SDRAM中),ADS用戶可以通過查看ADS的工程配置,其中有項配置是RO起始地址是0x30000000,linux用戶在鏈接時需要用-T指定代碼的其實地址為0x30000000。

根據編譯原理,在鏈接階段程序中函數的地址就已經確定了,也就是說函數的實際地址都在0x30000000之後,而程序的入口函數也就是這裡的_start的地址就是0x300000000,其他函數都會大於這個數。 【Linux公社 http://www.linuxidc.com 】

但是由於arm上電後系統會將Nand flash的前4KB代碼拷貝到SRAM中,也就是_start函數開始的4KB指令將被拷貝到SRAM中執行,根據上例,在0x00000000處執行的指令就是“b reset”,由於b是相對跳轉,是在當前pc值的基礎上加減某個數而跳轉到將要執行的代碼處,因此,pc加減該數之後將到達reset函數的位置,故reset函數不能寫到4KB之外的空間中,否則arm的啟動將會失敗,同樣的,接下來的幾個bl都是執行的相對跳轉,所以都相對當前pc進行的跳轉,由於Nand flash總共只有64M的空間,所以相對跳轉是不可能會跳轉到SDRAM的,因為跳轉到SDRAM至少要發生0x30000000的跳轉,而這個相對位移遠遠大於64M。

而ldr pc,=Main是將Main函數的實際地址賦值給pc,而Main的實際地址是在0x30000000之後,這樣,就從SRAM跳轉到了SDRAM。

由於這個過程設計到了硬件格局和編譯原理,所以對一般人來講,理解起來確實比較困難,而且受本人水平限制,很多地方只能說是只可意會不可言傳,如果誤導了大家請大家諒解。當然如果看到這裡還不能理解arm的啟動過程可以聯系QQ630905224來討論這個問題。下面是相關的其他代碼,我附在這裡,2440addr.h沒有貼出,由於我也是使用arm自帶示例程序中的代碼,而且代碼有四千多行,多數地址是沒有用到的,如果有人需要就聯系我的QQ吧。其他的代碼如下

代碼Init.s
  1. #include "2440addr.h"  
  2.   
  3. void disable_watch_dog(void);  
  4. void clock_init(void);  
  5. void memsetup(void);  
  6. void copy_steppingstone_to_sdram(void);  
  7. void inituart(void);  
  8.   
  9. void disable_watch_dog(void)  
  10. {  
  11.     rWTCON = 0;  
  12. }  
  13.   
  14. void clock_init(void)  
  15. {  
  16.     rCLKDIVN  = 0x03;  
  17.   
  18.     /*  
  19.      *如果HDIVN非0,CPU的總線模式應該從  
  20.      *“fast bus mode”變為“asynchronous   
  21.      *bus mode”  
  22.      */  
  23.     __asm__(  
  24.             "mrc    p15, 0, r1, c1, c0, 0\n"  
  25.             "orr    r1, r1, #0xc0000000\n"  
  26.             "mcr    p15, 0, r1, c1, c0, 0\n"  
  27.            );  
  28.   
  29.     rMPLLCON = (92<<12)|(1<<4)|(2);  
  30.     //rMPLLCON =  ((0x5c<<12)|(0x01<<4)|(0x02));  
  31. }  
  32.   
  33. void memsetup(void)  
  34. {  
  35.     volatile unsigned long *p = (volatile unsigned long *)0x48000000;  
  36.   
  37.     /* 這個函數之所以這樣賦值,而不是像前面的實驗(比如mmu實驗)那樣將配置值  
  38.      * 寫在數組中,是因為要生成”位置無關的代碼”,使得這個函數可以在被復制到  
  39.      * SDRAM之前就可以在steppingstone中運行  
  40.      */  
  41.     /* 存儲控制器13個寄存器的值 */  
  42.     p[0] = 0x22011110;     //BWSCON  
  43.     p[1] = 0x00000700;     //BANKCON0  
  44.     p[2] = 0x00000700;     //BANKCON1  
  45.     p[3] = 0x00000700;     //BANKCON2  
  46.     p[4] = 0x00000700;     //BANKCON3    
  47.     p[5] = 0x00000700;     //BANKCON4  
  48.     p[6] = 0x00000700;     //BANKCON5  
  49.     p[7] = 0x00018005;     //BANKCON6  
  50.     p[8] = 0x00018005;     //BANKCON7  
  51.   
  52.     /* REFRESH,  
  53.      * HCLK=12MHz:  0x008C07A3,  
  54.      * HCLK=100MHz: 0x008C04F4  
  55.      */   
  56.     p[9]  = 0x008C04F4;  
  57.     p[10] = 0x000000B1;     //BANKSIZE  
  58.     p[11] = 0x00000030;     //MRSRB6  
  59.     p[12] = 0x00000030;     //MRSRB7  
  60. }  
  61.   
  62. void copy_steppingstone_to_sdram(void)  
  63. {  
  64.     unsigned int *pdwSrc  = (unsigned int *)0;  
  65.     unsigned int *pdwDest = (unsigned int *)0x30000000;  
  66.   
  67.     while (pdwSrc < (unsigned int *)4096)  
  68.     {  
  69.         *pdwDest = *pdwSrc;  
  70.         pdwDest++;  
  71.         pdwSrc++;  
  72.     }  
  73. }  
Copyright © Linux教程網 All Rights Reserved