TQ2440开发板学习纪实(4)--- 初始化片外RAM,让程序有更大内存空间
到目前為止,我們的程序只能使用S3C2440的片內4KB的RAM。這么小的內存空間,顯然不能應付實際的應用。是時候使用片外的RAM了,本文將詳細介紹片外RAM的初始化過程。
0 ROM,RAM,SRAM,DRAM,SDRAM傻傻分不清
作為軟件出身的軟男,很難從根本上弄清楚ROM,RAM,SDRAM,SRAM等等的原理,這里我們只要了解基本的特性就可以了。
- ROM,NorFlash: 只讀內存,掉電不丟失。只讀指的是不能通過正常的寫入接口寫入數據,但是可以通過特殊的燒寫邏輯寫入。這就意味之,我們的程序可以直接在ROM中執行,但是程序執行時無法在ROM中保存數據。測試表明,寫入操作可以執行,但是沒有任何效果。TQ2440搭配了2MB的NorFlash ROM,我們的程序燒入ROM后能直接執行,但是由于不能寫內存,所以程序功能收到很大限制。為此實驗環境選擇了從Nandflash啟動,應為S3C2440硬件直接會把Nandflash的頭4KB數據拷貝到片內的SRAM執行。
- SRAM:(Static RAM)可讀寫,掉電丟失數據,但是無需定期電路刷新。這種東西非常昂貴,集成度不高,性能也相當好。多用于CPU的cache等關鍵場合。S3C2440A片內集成了4KB的SRAM,已經相當不錯了。到目前為止,我們的實驗程序都是運行在這塊SRAM上的。
- DRAM:(Dynamic RAM)可讀寫,掉電丟失數據,而且必須定期充電刷新。這種RAM相對便宜,集成度高,性能較SRAM差點,但是也相當不錯了。
- SDRAM:(Synchronous DRAM)同步DRAM。首先它是DRAM,只是額外需要同步時鐘才能正常工作。這也是TQ2440板子上搭配的主要內存。核心板子上配備了2塊,一共64MB。本文的目的就是配置使用著64MB的SDRAM。
1 內存地址的轉換全過程
對內存的操作是所有程序的最基本需求,而對內存進行尋址是所有內存操作的前提。高級編程語言里,一般會使用各種符號名來代表內存地址,例如如下C語言代碼:
int main() {int a=10;printf("hello\n"); }- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
main,a,printf都是符號,它在編譯后會被實際的內存地址取代。c語言提供了直接操作內存地址的強大工具—指針,這也是C語言能在底層開發領域統治多年的法寶。
int *pa = &a; int *p = (int*)0x00001010;- 1
- 2
- 1
- 2
C語言不僅允許我們獲得變量的內存地址,而且允許直接使用內存地址。那么這里的0x00001010地址數據到底是如何對應到內存芯片上的實際物理存儲單元的呢?這種對應需要2個步驟:
1.1 從虛擬地址到物理地址
上面代碼中的內存地址數據 0x00001010是核心CPU看到的地址,被稱作虛擬地址。對于低端的單片機,這個虛擬地址直接作為物理地址發送到地址總線。而現代高級CPU內部一般都集成了一個被稱作MMU的內存管理單元,CPU核心發出的虛擬地址首先進入MMU,MMU負責把虛擬地址轉換為地址總線上的物理地址,然后發送到地址總線。
虛擬地址—(MMU)—物理地址
MMU是Windows,Linux等操作系統運行的基礎,也是多進程實現的基礎。S3C2440A的ARM核心也繼承了MMU,只是默認MMU是不啟動的,這就意味著虛擬地址和物理地址完全一樣。我們的實驗程序也沒有啟動MMU,所以在程序中使用的內存地址可以直接理解成物理地址。
1.2 從物理地址到內存存儲單元的行列地址和片選信號
物理地址是一個線性地址,一般不能夠直接用來尋址內存單元。中間需要通過內存控制器來把物理地址轉換成內存單元的行列地址以及片選信號。?
物理地址—(內存控制器)—行列地址及片選信號
總之,只要不同的物理地址最終被映射到不同的內存單元就滿足硬件設計的要求,而不管CPU地址總線,CPU引腳,RAM芯片引腳到底是如何組合的。如何組合是硬件設計師的工作,我們軟件工程師只要使用就可以了。
2 TQ2440的SDRAM硬件配置
2.1 S3C2440內存硬件設計
內存地址相關的引腳共有35個,其中行列地址引腳A26,A25,…A0共27個,片選信號引腳nGCS0,nGCS1,…,nGCS7共8個。可見外部引腳最大尋址空間為8*2^27,正好是1GB。1GB以上的空間僅供CPU內部寄存器使用。
S3C2440把每一個片選信號對應的空間稱作一個BANK,每個BANK大小128M。
2.2 TQ2440開發板SDRAM搭配硬件設計
TQ2440搭配了2片32MB的SDRM,共64MB。這兩片內存并聯成32位數據寬度供CPU使用。
圖中看出,每個內存芯片的行列地址有13位,數據寬度為2個字節,這樣可尋址:2^13*2 = 64KB。啥?不是每個芯片32MB嗎?這里的玄機在于對于SDRAM來說,行列地址共用一組引腳,首先通過A0-A12讀取行地址并暫存起來,然后下一個周期還是通過A0-A12讀取列地址,然后把行列地址拼裝起來成為最后的行列地址。
RAM芯片上還有BA1,BA0兩個引腳,這也是單元地址的一部分,叫做LogicalBank,注意這里的Bank和S3C2440的內存空間BANK沒有什么關系。內存控制器要只有把物理地址轉換為【(BA1,BA0),(A0-A12),片選信號】,并且產生正確的時序才能完成內存單元的尋址。
開發板上SDRAM芯片信號為:LogicalBank大小為4M,共有4個LogicalBank,內存單元大小為16位(2字節),所以單片容量為:4 x 4 x 2 = 32MB。然后兩個內存芯片并聯后接入BANK6。
如何把物理地址轉換為行列地址是內存控制器的工作,這也是初始化SDRAM的重要步驟。
3 S3C2440片外內存初始化方法
從前面的分析,我們知道程序內存地址到SDRAM內存單元地址的整個轉換過程為:
虛擬地址---(MMU)---物理地址(內存控制器)---LogicalBank,行列地址,片選信號 目前我們沒有啟用MMU,也無需配置。現在要做的就是配置內存控制器,使其能夠正確地把物理地址轉換為行列地址和片選信號。?
S3C2440片內集成了完整的內存控制器,我們只需要向相應的控制寄存器寫入合適的數值即可完成配置。
2.1 與SDRAM有關的控制寄存器
- BWSCON
- BANKCON6
- REFRESH
- BANKSIZE
- MRSR6?
其中有些時間周期參數的設置依賴于HCLK的值,故需要記住在設置MPLL時產生的HCLK。上篇博文中我們設置的HCLK=200MHz,這個數值太大無法滿足設置REFRESH中Tsrc的要求,所以本文實驗時同時把HCLK修改成了100MHz,PCLK設置成了50MHz。由于此時FCLK:HCLK不再是1:1而是1:2,所以需要設置CPU的總線模式為異步模式,增加了相關設置代碼。
在我們的試驗中,FCLK=200MHz,HCLK=100MHz,也就是周期是10ns。
上述寄存器需要設置的值都在源碼中了,在此貼出完整的設置代碼:
2.2 配置源碼
.equ WTCON, 0x53000000 .equ INTMSK, 0x4a000008/* Fin=12MHz, FCLK=200MHz */ .equ MPLLCON, 0x4c000004 .equ M_MDIV, 92 .equ M_PDIV, 4 .equ M_SDIV, 1/* Fin=12MHz, UPLLCLK = 48MHz */ .equ UPLLCON, 0x4c000008 .equ U_MDIV, 56 .equ U_PDIV, 2 .equ U_SDIV, 2/* HCLK=FCLK/2=100MHz, PCLK=HCLK/2=50MHz, UCLK=UPLLCLK=48MHz */ .equ CLKDIVN, 0x4c000014 /*if If HDIVN is not 0 and the CPU bus mode is the fast bus mode,the CPU will operate by the HCLK */ .equ HDIVN, 1 .equ DIVN_UPLL, 0 .equ PDIVN, 1/* SDRAM */ .equ BWSCON, 0x48000000 .equ BANKCON6, 0x4800001c .equ REFRESH, 0x48000024 .equ BANKSIZE, 0x48000028 .equ MRSRB6, 0x4800002c /* BWSCON */ .equ BWSCON_WS6, 0 /* wait status: disable */ .equ BWSCON_DW6, 2 /* data width : 32 *//* BANKCON6 */ .equ B6_MT, 3 /* memory type: SDRAM */ .equ B6_Trcd, 0 /* Row to Col delay: 2 clocks,(min: 20ns)*/ .equ B6_SCAN, 1 /* Column address number *//* BANKSIZE */ .equ BANKSIZE_VAL, 0xB1 /* 64MB for BANK7,6 *//* REFRESH */ .equ REFRESH_ENABLE, 1 .equ REFRESH_MODE, 0 .equ REFRESH_Trp, 0 .equ REFRESH_Tsrc, 1 .equ REFRESH_COUNT, 1268/* MRSRB6 */ .equ MRSRB6_VAL, 0x30 /* CL: 3 clock (min: 2.5ns )*/.text .global ResetEntry /* interrupt vector */ ResetEntry:b ResetHandlerb ResetHandlerb ResetHandlerb ResetHandlerb ResetHandlerb ResetHandlerb ResetHandlerb ResetHandler/* The first instruction to run */ ResetHandler:/* ----------- disable watch dog-------- */ldr r0, =WTCONldr r1, =0x0str r1, [r0]/* ------- disable all interrupts------- */ldr r0, =INTMSKldr r1, =0xffffffffstr r1, [r0]/* -----------set clock--------------- */ldr r0, =CLKDIVNldr r1, =(DIVN_UPLL<<3) + (HDIVN<<1) + PDIVNstr r1, [r0]/*switch to asynchronous bus mode*/ .if HDIVN>1mrc p15, 0, r0, c1, c0, 0orr r0, r0, #0xc0000000mcr p15, 0, r0, c1, c0, 0.endif/* set up upll */ldr r0, =UPLLCONldr r1, =(U_MDIV<<12) + (U_PDIV<<4) + U_SDIVstr r1, [r0]nopnopnopnopnopnopnop/* set up mpll */ldr r0, =MPLLCONldr r1, =(M_MDIV<<12) + (M_PDIV<<4) + M_SDIVstr r1, [r0]/* ----------------- set sdram -------------*/ldr r0, =BWSCONldr r1, [r0]ldr r2, =(1<<26 | 3<<24)bic r1, r1, r2ldr r2, =((BWSCON_WS6<<26) | (BWSCON_DW6<<24))orr r1, r1, r2str r1, [r0]ldr r0, =BANKCON6ldr r1, [r0]ldr r2, =((1<<16) | (1<<15) | 0xF)bic r1, r1, r2ldr r2, =(B6_MT<<15 | B6_Trcd<<2 | B6_SCAN)orr r1, r1, r2str r1, [r0]ldr r0, =REFRESHldr r1, [r0]ldr r2, =(1<<23 | 1<<22 | 3<<20 | 3<<18 | 0x7FF)bic r1, r1, r2ldr r2, =(REFRESH_ENABLE<<23 | REFRESH_MODE<<22 | REFRESH_Trp<<20 | REFRESH_COUNT)orr r1, r1, r2str r1, [r0]ldr r0, =BANKSIZEldr r1, =BANKSIZE_VALstr r1, [r0]ldr r0, =MRSRB6ldr r1, =MRSRB6_VALstr r1, [r0]/* --------------set statck----------------- */ldr sp, =0x34000000 /* must be aligned by 4 bytes *//* call C function */b Main.end- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
4 開始使用片外內存
現在已經可以使用64MB的SDRAM了,這相對于片內的4KB SRAM來說已經相當大了,而且足以可以運行大型軟件了。雖然目前我們的程序本身被加載到片內SRAM上運行,但是我們在代碼中是可以使用[0x30000000,0x34000000)這個范圍的SDRAM內存了。?
首先我們可以把堆棧指針SP指向SDRAM中,這樣C語言的函數參數和局部變量就自動被放到SDRAM里了。?
其次我們可以直接通過C指針直接操作SDRAM的。
3.1 把堆棧設置到片外內存上
ldr sp, =0x34000000- 1
- 1
就是這么簡單的一條命令,就可以讓C程序的堆棧搬遷到SDRAM中。
3.2 在片外內存上讀寫數據
void Main(void) {led_init();int a = 10;/* 測試棧是否在SDRAM上 */if(((unsigned)&a) > 0x30000000) {led_on(2);}else{led_off(2);}/* 在SDRAM上直接讀寫內存*/int *pInt = (int*)0x30000000;*pInt = 99;if(*pInt == 99){led_on(1);}else{led_off(1);}while(1){;} }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
5 下一步:把程序加載到SDRAM中
盡管目前我們可以直接操作SDRAM,但是程序代碼本身并沒有在SDRAM中運行。為此我們需要寫一個loader,把程序加載到SDRAM中。loader可以和程序混合成一個程序,也可以兩者分開。如果混合在一起那就是一個完整的能獨立啟動的程序,例如U-Boot。如果將兩者分開,那么必須首先安裝loader到NandFlash中,然后啟動,通過命令加指定的程序到SDRAM執行。
下一步,將實驗NANDFLASH讀寫操作,從而為代碼搬遷做好準備。
總結
以上是生活随笔為你收集整理的TQ2440开发板学习纪实(4)--- 初始化片外RAM,让程序有更大内存空间的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 共享网约车Uber的前世今生
- 下一篇: realsense D435 D435i