第26章 FMC—擴展外部SDRAM
全套200集視頻教程和1000頁PDF教程請到秉火論壇下載:www.firebbs.cn
野火視頻教程優酷觀看網址:http://i.youku.com/firege
本章參考資料:《STM32F4xx 中文參考手冊2》、《STM32F4xx規格書》、庫幫助文檔《stm32f4xx_dsp_stdperiph_lib_um.chm》。
關於SDRAM存儲器,請參考前面的"常用存儲器介紹"章節,實驗中SDRAM芯片的具體參數,請參考其規格書《IS42-45S16400J》來了解。
26.1 SDRAM控制原理
STM32控制器芯片內部有一定大小的SRAM及FLASH作為內存和程序存儲空間,但當程序較大,內存和程序空間不足時,就需要在STM32芯片的外部擴展存儲器了。
STM32F429系列芯片擴展內存時可以選擇SRAM和SDRAM,由於SDRAM的"容量/價格"比較高,即使用SDRAM要比SRAM要划算得多。我們以SDRAM為例講解如何為STM32擴展內存。
給STM32芯片擴展內存與給PC擴展內存的原理是一樣的,只是PC上一般以內存條的形式擴展,內存條實質是由多個內存顆粒(即SDRAM芯片)組成的通用標准模塊,而STM32直接與SDRAM芯片連接。見圖 262,這是一種型號為IS42-45S16400J的SDRAM芯片內部結構框圖,以它為模型進行學習。
圖 261 SDRAM芯片外觀
圖 262 一種SDRAM芯片的內部結構框圖
26.1.1 SDRAM信號線
圖 262虛線框外引出的是SDRAM芯片的控制引腳,其說明見表 261。
表 261 SDRAM控制引腳說明
信號線 |
類型 |
說明 |
CLK |
I |
同步時鍾信號,所有輸入信號都在CLK為上升沿的時候被采集 |
CKE |
I |
時鍾使能信號,禁止時鍾信號時SDRAM會啟動自刷新操作 |
CS# |
I |
片選信號,低電平有效 |
CAS# |
I |
列地址選通,為低電平時地址線表示的是列地址 |
RAS# |
I |
行地址選通,為低電平時地址線表示的是行地址 |
WE# |
I |
寫入使能,低電平有效 |
DQM[0:1] |
I |
數據輸入/輸出掩碼信號,表示DQ信號線的有效部分 |
BA[0:1] |
I |
Bank地址輸入,選擇要控制的Bank |
A[0:11] |
I |
地址輸入 |
DQ[0:15] |
I/O |
數據輸入輸出信號 |
除了時鍾、地址和數據線,控制SDRAM還需要很多信號配合,它們具體作用在描述時序圖時進行講解。
26.1.2 控制邏輯
SDRAM內部的"控制邏輯"指揮着整個系統的運行,外部可通過CS、WE、CAS、RAS以及地址線來向控制邏輯輸入命令,命令經過"命令器譯碼器"譯碼,並將控制參數保存到"模式寄存器中",控制邏輯依此運行。
26.1.3 地址控制
SDRAM包含有"A"以及"BA"兩類地址線,A類地址線是行(Row)與列(Column)共用的地址總線,BA地址線是獨立的用於指定SDRAM內部存儲陣列號(Bank)。在命令模式下,A類地址線還用於某些命令輸入參數。
26.1.4 SDRAM的存儲陣列
要了解SDRAM的儲存單元尋址以及"A"、"BA"線的具體運用,需要先熟悉它內部存儲陣列的結構,見圖 263。
圖 263 SDRAM存儲陣列模型
SDRAM內部包含的存儲陣列,可以把它理解成一張表格,數據就填在這張表格上。和表格查找一樣,指定一個行地址和列地址,就可以精確地找到目標單元格,這是SDRAM芯片尋址的基本原理。這樣的每個單元格被稱為存儲單元,而這樣的表則被稱為存儲陣列(Bank),目前設計的SDRAM芯片基本上內部都包含有4個這樣的Bank,尋址時指定Bank號以及行地址,然后再指定列地址即可尋找到目標存儲單元。SDRAM內部具有多個Bank時的結構見圖 264。
圖 264 SDRAM內有多個Bank時的結構圖
SDRAM芯片向外部提供有獨立的BA類地址線用於Bank尋址,而行與列則共用A類地址線。
圖 262標號中表示的就是它內部的存儲陣列結構,通訊時當RAS線為低電平,則"行地址選通器"被選通,地址線A[11:0]表示的地址會被輸入到"行地址譯碼及鎖存器"中,作為存儲陣列中選定的行地址,同時地址線BA[1:0]表示的Bank也被鎖存,選中了要操作的Bank號;接着控制CAS線為低電平,"列地址選通器"被選通,地址線A[11:0]表示的地址會被鎖存到"列地址譯碼器"中作為列地址,完成尋址過程。
26.1.5 數據輸入輸出
若是寫SDRAM內容,尋址完成后,DQ[15:0]線表示的數據經過圖 262標號 中的輸入數據寄存器,然后傳輸到存儲器陣列中,數據被保存;數據輸出過程相反。
本型號的SDRAM存儲陣列的"數據寬度"是16位(即數據線的數量),在與SDRAM進行數據通訊時,16位的數據是同步傳輸的,但實際應用中我們可能會以8位、16位的寬度存取數據,也就是說16位的數據線並不是所有時候都同時使用的,而且在傳輸低寬度數據的時候,我們不希望其它數據線表示的數據被錄入。如傳輸8位數據的時候,我們只需要DQ[7:0]表示的數據,而DQ[15:8]數據線表示的數據必須忽略,否則會修改非目標存儲空間的內容。所以數據輸入輸出時,還會使用DQM[1:0]線來配合,每根DQM線對應8位數據,如"DQM0(LDQM)"為低電平,"DQM1(HDQM)"為高電平時,數據線DQ[7:0]表示的數據有效,而DQ[15:8]表示的數據無效。
26.1.6 SDRAM的命令
控制SDRAM需要用到一系列的命令,見表 262。各種信號線狀態組合產生不同的控制命令。
表 262 SDRAM命令表
命令名 |
CS# |
RAS# |
CAS# |
WE# |
DQM |
ADDR |
DQ |
COMMAND INHIBIT |
H |
X |
X |
X |
X |
X |
X |
NO OPERATION |
L |
H |
H |
H |
X |
X |
X |
ACTIVE |
L |
L |
H |
H |
X |
Bank/row |
X |
READ |
L |
H |
L |
H |
L/H |
Bank/col |
X |
WRITE |
L |
H |
L |
L |
L/H |
Bank/col |
Valid |
PRECHARGE |
L |
L |
H |
L |
X |
Code |
X |
AUTO REFRESH or SELF REFRESH |
L |
L |
L |
H |
X |
X |
X |
LOAD MODE REGISTER |
L |
L |
L |
L |
X |
Op-code |
X |
BURST TERMINATE |
L |
H |
H |
L |
X |
X |
active |
表中的H表示高電平,L表示低電平,X表示任意電平,High-Z表示高阻態。
1. 命令禁止
只要CS引腳為高電平,即表示"命令禁止"(COMMAND INHBIT),它用於禁止SDRAM執行新的命令,但它不能停止當前正在執行的命令。
2. 空操作
"空操作"(NO OPERATION),"命令禁止"的反操作,用於選中SDRAM,以便接下來發送命令。
3. 行有效
進行存儲單元尋址時,需要先選中要訪問的Bank和行,使它處於激活狀態。該操作通過"行有效"(ACTIVE)命令實現,見圖 265,發送行有效命令時,RAS線為低電平,同時通過BA線以及A線發送Bank地址和行地址。
圖 265 行有效命令時序圖
4. 列讀寫
行地址通過"行有效"命令確定后,就要對列地址進行尋址了。"讀命令"(READ)和"寫命令"(WRITE)的時序很相似,見圖 266,通過共用的地址線A發送列地址,同時使用WE引腳表示讀/寫方向,WE為低電平時表示寫,高電平時表示讀。數據讀寫時,使用DQM線表示有效的DQ數據線。
圖 266 讀取命令時序
本型號的SDRAM芯片表示列地址時僅使用A[7:0]線,而A10線用於控制是否"自動預充電",該線為高電平時使能,低電平時關閉。
5. 預充電
SDRAM 的尋址具有獨占性,所以在進行完讀寫操作后,如果要對同一個Bank 的另一行進行尋址,就要將原來有效(ACTIVE)的行關閉,重新發送行/列地址。Bank 關閉當前工作行,准備打開新行的操作就是預充電(Precharge)。
預充電可以通過獨立的命令控制,也可以在每次發送讀寫命令的同時使用"A10"線控制自動進行預充電。實際上,預充電是一種對工作行中所有存儲陣列進行數據重寫,並對行地址進行復位,以准備新行的工作。
獨立的預充電命令時序見圖 267。該命令配合使用A10線控制,若A10為高電平時,所有Bank都預充電;A10為低電平時,使用BA線選擇要預充電的Bank。
圖 267 PRECHARGE命令時序
6. 刷新
SDRAM要不斷進行刷新(Refresh)才能保留住數據,因此它是 DRAM 最重要的操作。刷新操作與預充電中重寫的操作本質是一樣的。
但因為預充電是對一個或所有Bank 中的工作行操作,並且不定期,而刷新則是有固定的周期,依次對所有行進行操作,以保證那些久久沒被訪問的存儲單元數據正確。
刷新操作分為兩種:"自動刷新"(Auto Refresh)與"自我刷新"(Self Refresh),發送命令后CKE時鍾為有效時(低電平),使用自動刷新操作,否則使用自我刷新操作。不論是何種刷新方式,都不需要外部提供行地址信息,因為這是一個內部的自動操作。
對於"自動刷新", SDRAM 內部有一個行地址生成器(也稱刷新計數器)用來自動地依次生成行地址,每收到一次命令刷新一行。在刷新過程中,所有Bank都停止工作,而每次刷新所占用的時間為N個時鍾周期(視SDRAM型號而定,通常為N=9),刷新結束之后才可進入正常的工作狀態,也就是說在這N個時鍾期間內,所有工作指令只能等待而無法執行。一次次地按行刷新,刷新完所有行后,將再次對第一行重新進行刷新操作,這個對同一行刷新操作的時間間隔,稱為SDRAM的刷新周期,通常為64ms。顯然刷新會對SDRAM的性能造成影響,但這是它的DRAM的特性決定的,也是DRAM相對於SRAM取得成本優勢的同時所付出的代價。
"自我刷新"則主要用於休眠模式低功耗狀態下的數據保存,也就是說即使外部控制器不工作了,SDRAM都能自己確保數據正常。在發出"自我刷新"命令后,將 CKE 置於無效狀態(低電平),就進入自我刷新模式,此時不再依靠外部時鍾工作,而是根據SDRAM內部的時鍾進行刷新操作。在自我刷新期間除了 CKE 之外的所有外部信號都是無效的,只有重新使 CKE 有效才能退出自我刷新模式並進入正常操作狀態。
7. 加載模式寄存器
前面提到SDRAM的控制邏輯是根據它的模式寄存器來管理整個系統的,而這個寄存器的參數就是通過"加載模式寄存器"命令(LOAD MODE REGISTER)來配置的。發送該命令時,使用地址線表示要存入模式寄存器的參數"OP-Code",各個地址線表示的參數見圖 268。
圖 268 模式寄存器解析圖
模式寄存器的各個參數介紹如下:
Burst Length
Burst Length譯為突發長度,下面簡稱BL。突發是指在同一行中相鄰的存儲單元連續進行數據傳輸的方式,連續傳輸所涉及到存儲單元(列)的數量就是突發長度。
上文講到的讀/寫操作,都是一次對一個存儲單元進行尋址,如果要連續讀/寫就還要對當前存儲單元的下一個單元進行尋址,也就是要不斷的發送列地址與讀/寫命令(行地址不變,所以不用再對行尋址)。雖然由於讀/寫延遲相同可以讓數據的傳輸在 I/O 端是連續的,但它占用了大量的內存控制資源,在數據進行連續傳輸時無法輸入新的命令,效率很低。
為此,人們開發了突發傳輸技術,只要指定起始列地址與突發長度,內存就會依次地自動對后面相應數量的存儲單元進行讀/寫操作而不再需要控制器連續地提供列地址。這樣,除了第一筆數據的傳輸需要若干個周期外,其后每個數據只需一個周期的即可獲得。其實我們在EERPOM及FLASH讀寫章節講解的按頁寫入就是突發寫入,而它們的讀取過程都是突發性質的。
非突發連續讀取模式:不采用突發傳輸而是依次單獨尋址,此時可等效於 BL=1。雖然也可以讓數據連續地傳輸,但每次都要發送列地址與命令信息,控制資源占用極大。突發連續讀取模式:只要指定起始列地址與突發長度,尋址與數據的讀取自動進行,而只要控制好兩段突發讀取命令的間隔周期(與 BL 相同)即可做到連續的突發傳輸。而BL 的數值,也是不能隨便設或在數據進行傳輸前臨時決定。在初始化SDRAM調用LOAD MODE REGISTER命令時就被固定。BL可用的選項是 1、2、4、8,常見的設定是 4 和8。若傳輸時實際需要數據長度小於設定的BL值,則調用"突發停止"(BURST TERMINATE)命令結束傳輸。
BT
模式寄存器中的BT位用於設置突發模式,突發模式分為順序(Sequential)與間隔(Interleaved)兩種。在順序方式中,操作按地址的順序連續執行,如果是間隔模式,則操作地址是跳躍的。跳躍訪問的方式比較亂,不太符合思維習慣,我們一般用順序模式。順序訪問模式時按照"0-1-2-3-4-5-6-7"的地址序列訪問。
CASLatency
模式寄存器中的CASLatency是指列地址選通延遲,簡稱CL。在發出讀命令(命令同時包含列地址)后,需要等待幾個時鍾周期數據線DQ才會輸出有效數據,這之間的時鍾周期就是指CL,CL一般可以設置為2或3個時鍾周期,見圖 269。
圖 269 CL=2和CL=3的說明圖
CL只是針對讀命令時的數據延時,在寫命令是不需要這個延時的,發出寫命令時可同時發送要寫入的數據。
Op Mode
OP Mode指Operating Mode,SDRAM的工作模式。當它被配置為"00"的時候表示工作在正常模式,其它值是測試模式或被保留的設定。實際使用時必須配置成正常模式。
WB
WB用於配置寫操作的突發特性,可選擇使用BL設置的突發長度或非突發模式。
Reserved
模式寄存器的最后三位的被保留,沒有設置參數。
26.1.7 SDRAM的初始化流程
最后我們來了解SDRAM的初始化流程。SDRAM並不是上電后立即就可以開始讀寫數據的,它需要按步驟進行初始化,對存儲矩陣進行預充電、刷新並設置模式寄存器,見圖 2610。
圖 2610 SDRAM初始化流程
該流程說明如下:
(1) 給SDRAM上電,並提供穩定的時鍾,至少100us;
(2) 發送"空操作"(NOP)命令;
(3) 發送"預充電"(PRECHARGE)命令,控制所有Bank進行預充電,並等待tRP時間, tRP表示預充電與其它命令之間的延遲;
(4) 發送至少2個"自動刷新"(AUTO REFRESH)命令,每個命令后需等待tRFC時間,tRFC表示自動刷新時間;
(5) 發送"加載模式寄存器"(LOAD MODE REGISTER)命令,配置SDRAM的工作參數,並等待tMRD時間,tMRD表示加載模式寄存器命令與行有行或刷新命令之間的延遲;
(6) 初始化流程完畢,可以開始讀寫數據。
其中tRP、tRFC、tMRD等時間參數跟具體的SDRAM有關,可查閱其數據手冊獲知,STM32 FMC訪問時配置需要這些參數。
26.1.8 SDRAM的讀寫流程
初始化步驟完成,開始讀寫數據,其時序流程見圖 2611及圖 2612。
圖 2611 CL=2時,帶AUTO PRECHARGE的讀時序
圖 2612 帶AUTO PRECHARGE 命令的寫時序
讀時序和寫時序的命令過程很類似,下面我們統一解說:
(1) 發送"行有效"(ACTIVE)命令,發送命令的同時包含行地址和Bank地址,然后等待tRCD時間,tRCD表示行有效命令與讀/寫命令之間的延遲;
(2) 發送"讀/寫"(READ/WRITE)命令,在發送命令的同時發送列地址,完成尋址的地址輸入。對於讀命令,根據模式寄存器的CL定義,延遲CL個時鍾周期后,SDRAM的數據線DQ才輸出有效數據,而寫命令是沒有CL延遲的,主機在發送寫命令的同時就可以把要寫入的數據用DQ輸入到SDRAM中,這是讀命令與寫命令的時序最主要的區別。圖中的讀/寫命令都通過地址線A10控制自動預充電,而SDRAM接收到帶預充電要求的讀/寫命令后,並不會立即預充電,而是等待tWR時間才開始,tWR表示寫命令與預充電之間的延遲;
(3) 執行"預充電"(auto precharge)命令后,需要等待tRP時間,tRP表示預充電與其它命令之間的延遲;
(4) 圖中的標號處的tRAS,表示自刷新周期,即在前一個"行有效"與"預充電"命令之間的時間;
(5) 發送第二次"行有效"(ACTIVE)命令准備讀寫下一個數據,在圖中的標號 處的tRC,表示兩個行有效命令或兩個刷新命令之間的延遲。
其中tRCD、tWR、tRP、tRAS以及tRC等時間參數跟具體的SDRAM有關,可查閱其數據手冊獲知,STM32 FMC訪問時配置需要這些參數。
26.2 FMC簡介
STM32F429使用FMC外設來管理擴展的存儲器,FMC是Flexible Memory Controller的縮寫,譯為可變存儲控制器。它可以用於驅動包括SRAM、SDRAM、NOR FLASH以及NAND FLSAH類型的存儲器。在其它系列的STM32控制器中,只有FSMC控制器(Flexible Static Memory Controller),譯為可變靜態存儲控制器,所以它們不能驅動SDRAM這樣的動態存儲器,因為驅動SDRAM時需要定時刷新,STM32F429的FMC外設才支持該功能,且只支持普通的SDRAM,不支持DDR類型的SDRAM。我們只講述FMC的SDRAM控制功能。
26.3
FMC框圖剖析
STM32的FMC外設內部結構見圖 2613。
圖 2613 FMC控制器框圖
1. 通訊引腳
在框圖的右側是FMC外設相關的控制引腳,由於控制不同類型存儲器的時候會有一些不同的引腳,看起來有非常多,其中地址線FMC_A和數據線FMC_D是所有控制器都共用的。這些FMC引腳具體對應的GPIO端口及引腳號可在《STM32F4xx規格書》中搜索查找到,不在此列出。針對SDRAM控制器,我們是整理出以下的FMC與SDRAM引腳對照表 263。
表 263 FMC中的SDRAM控制信號線
FMC引腳名稱 |
對應SDRAM引腳名 |
說明 |
FMC_NBL[3:0] |
DQM[3:0] |
數據掩碼信號 |
FMC_A[12:0] |
A[12:0] |
行/列地址線 |
FMC_A[15:14] |
BA[1:0] |
Bank地址線 |
FMC_D[31:0] |
DQ[31:0] |
數據線 |
FMC_SDCLK |
CLK |
同步時鍾信號 |
FMC_SDNWE |
WE# |
寫入使能 |
FMC_SDCKE[1:0] |
CKE |
SDCKE0:SDRAM 存儲區域 1 時鍾使能 SDCKE1:SDRAM 存儲區域 2 時鍾使能 |
FMC_SDNE[1:0] |
-- |
SDNE0:SDRAM 存儲區域 1 芯片使能 SDNE1:SDRAM 存儲區域 2 芯片使能 |
FMC_NRAS |
RAS# |
行地址選通信號 |
FMC_NCAS |
CAS# |
列地址選通信號 |
其中比較特殊的是FMC_A[15:14]引腳用作Bank的尋址線;而FMC_SDCKE線和FMC_SDNE都各有2條,FMC_SDCKE用於控制SDRAM的時鍾使能,FMC_SDNE用於控制SDRAM芯片的片選使能。它們用於控制STM32使用不同的存儲區域驅動SDRAM,使用編號為0的信號線組會使用STM32的存儲器區域1,使用編號為1的信號線組會使用存儲器區域2。使用不同存儲區域時,STM32訪問SDRAM的地址不一樣,具體將在"FMC的地址映射"小節講解。
2. 存儲器控制器
上面不同類型的引腳是連接到FMC內部對應的存儲控制器中的。NOR/PSRAM/SRAM設備使用相同的控制器,NAND/PC卡設備使用相同的控制器,而SDRAM存儲器使用獨立的控制器。不同的控制器有專用的寄存器用於配置其工作模式。
控制SDRAM的有FMC_SDCR1/FMC_SDCR2控制寄存器、FMC_SDTR1/FMC_SDTR2時序寄存器、FMC_SDCMR命令模式寄存器以及FMC_SDRTR刷新定時器寄存器。其中控制寄存器及時序寄存器各有2個,分別對應於SDRAM存儲區域1和存儲區域2的配置。
FMC_SDCR控制寄存器可配置SDCLK的同步時鍾頻率、突發讀使能、寫保護、CAS延遲、行列地址位數以及數據總線寬度等。
FMC_SDTR時序寄存器用於配置SDRAM訪問時的各種時間延遲,如TRP行預充電延遲、TMRD加載模式寄存器激活延遲等。
FMC_SDCMR命令模式寄存器用於存儲要發送到SDRAM模式寄存器的配置,以及要向SDRAM芯片發送的命令。
FMC_SDRTR用於配置SDRAM的自動刷新周期。
3. 時鍾控制邏輯
FMC外設掛載在AHB3總線上,時鍾信號來自於HCLK(默認180MHz),控制器的時鍾輸出就是由它分頻得到。如SDRAM控制器的FMC_SDCLK引腳輸出的時鍾,是用於與SDRAM芯片進行同步通訊,它的時鍾頻率可通過FMC_SDCR1寄存器的SDCLK位配置,可以配置為HCLK的1/2或1/3,也就是說,與SDRAM通訊的同步時鍾最高頻率為90MHz。
26.4 FMC的地址映射
FMC連接好外部的存儲器並初始化后,就可以直接通過訪問地址來讀寫數據,這種地址訪問與I2C EEPROM、SPI FLASH的不一樣,后兩種方式都需要控制I2C或SPI總線給存儲器發送地址,然后獲取數據;在程序里,這個地址和數據都需要分開使用不同的變量存儲,並且訪問時還需要使用代碼控制發送讀寫命令。而使用FMC外接存儲器時,其存儲單元是映射到STM32的內部尋址空間的;在程序里,定義一個指向這些地址的指針,然后就可以通過指針直接修改該存儲單元的內容,FMC外設會自動完成數據訪問過程,讀寫命令之類的操作不需要程序控制。FMC的地址映射見圖 2614。
圖 2614 FMC的地址映射
圖中左側的是Cortex-M4內核的存儲空間分配,右側是STM32 FMC外設的地址映射。可以看到FMC的NOR/PSRAM/SRAM/NAND FLASH以及PC卡的地址都在External RAM地址空間內,而SDRAM的地址是分配到External device區域的。正是因為存在這樣的地址映射,使得訪問FMC控制的存儲器時,就跟訪問STM32的片上外設寄存器一樣(片上外設的地址映射即圖中左側的"Peripheral"區域)。
1. SDRAM的存儲區域
FMC把SDRAM的存儲區域分成了Bank1和Bank2兩塊,這里的Bank與SDRAM芯片內部的Bank是不一樣的概念,只是FMC的地址區域划分而已。每個Bank有不一樣的起始地址,且有獨立的FMC_SDCR控制寄存器和FMC_SDTR時序寄存器,還有獨立的FMC_SDCKE時鍾使能信號線和FMC_SDCLK信號線。FMC_SDCKE0和FMC_SDCLK0對應的存儲區域1的地址范圍是0xC000 0000-0xCFFF FFFF,而FMC_SDCKE1和FMC_SDCLK1對應的存儲區域2的地址范圍是0xD000 0000- 0xDFFF FFFF。當程序里控制內核訪問這些地址的存儲空間時,FMC外設會即會產生對應的時序,對它外接的SDRAM芯片進行讀寫。
2. External RAM 與External device的區別
比較遺憾的是FMC給SDRAM分配的區域不在External RAM區,這個區域可以直接執行代碼,而SDRAM所在的External device區卻不支持這個功能。這里說的可直接執行代碼的特性就是在"常用存儲器"章節介紹的XIP(eXecute In Place)特性,即存儲器上若存儲了代碼,CPU可直接訪問代碼執行,無需緩存到其它設備上再運行;而且XIP特性還對存儲器的種類有要求,SRAM/SDRAM及NOR Flash都支持這種特性,而NAND FLASH及PC卡是不支持XIP的。結合存儲器的特性和STM32 FMC存儲器種類的地址分配,就發現它的地址規划不合理了,NAND FLASH和PC卡這些不支持XIP的存儲器卻占據了External RAM的空間,而支持XIP的SDRAM存儲器的空間卻被分配到了Extern device區。為了解決這個問題,通過配置"SYSCFG_MEMRMP"寄存器的"SWP_FMC"寄存器位可用於交換SDRAM與NAND/PC卡的地址映射,使得存儲在SDRAM中的代碼能被執行,只是由於SDRAM的最高同步時鍾是90MHz,代碼的執行速度會受影響。
本章主要講解當STM32的片內SRAM不夠用時使用SDRAM擴展內存,但假如程序太大,它的程序空間FLASH不夠用怎么辦呢?首先是裁剪代碼,目前STM32F429系列芯片內部FLASH空間最高可達2MB,實際應用中只要我們把代碼中的圖片、字模等占據大空間的內容放到外部存儲器中,純粹的代碼很難達到2MB。如果還不夠用,非要擴展程序空間的話,一種方法是使用FMC擴展NOR FLASH,把程序存儲到NOR上,程序代碼能夠直接在NOR FLASH上執行。另一種方法是把程序存儲在其它外部存儲器,如SD卡,需要時把存儲在SD卡上的代碼加載到SRAM或SDRAM上,再在RAM上執行代碼。
如果SDRAM不是用於存儲可執行代碼,只是用來保存數據的話,在External RAM或Exteranl device區域都沒有區別,不需要與NAND的映射地址交換。
26.5 SDRAM時序結構體
控制FMC使用SDRAM存儲器時主要是配置時序寄存器以及控制寄存器,利用ST標准庫的SDRAM時序結構體以及初始化結構體可以很方便地寫入參數。
SDRAM時序結構體的成員見代碼清單 241。
代碼清單 261 SDRAM時序結構體FMC_SDRAMTimingInitTypeDef
1 /* @brief 控制SDRAM的時序參數,這些參數的單位都是"周期"
2 * 各個參數的值可設置為1-16個周期。 */
3 typedef struct
4 {
5 uint32_t FMC_LoadToActiveDelay; /*TMRD:加載模式寄存器命令后的延遲*/
6 uint32_t FMC_ExitSelfRefreshDelay; /*TXSR:自刷新命令后的延遲 */
7 uint32_t FMC_SelfRefreshTime; /*TRAS:自刷新時間*/
8 uint32_t FMC_RowCycleDelay; /*TRC:行循環延遲*/
9 uint32_t FMC_WriteRecoveryTime; /*TWR:恢復延遲 */
10 uint32_t FMC_RPDelay; /*TRP:行預充電延遲*/
11 uint32_t FMC_RCDDelay; /*TRCD:行到列延遲*/
12 } FMC_SDRAMTimingInitTypeDef;
這個結構體成員定義的都是SDRAM發送各種命令后必須的延遲,它的配置對應到FMC_SDTR中的寄存器位。所有成員參數值的單位是周期,參數值大小都可設置成"1-16"。關於這些延時時間的定義可以看"SDRAM初始化流程"和"SDRAM讀寫流程"小節的時序圖了解。具體參數值根據SDRAM芯片的手冊說明來配置。各成員介紹如下:
(1) FMC_LoadToActiveDelay
本成員設置TMRD延遲(Load Mode Register to Active),即發送加載模式寄存器命令后要等待的時間,過了這段時間才可以發送行有效或刷新命令。
(2) FMC_ExitSelfRefreshDelay
本成員設置退出TXSR延遲(Exit Self-refresh delay),即退出自我刷新命令后要等待的時間,過了這段時間才可以發送行有效命令。
(3) FMC_SelfRefreshTime
本成員設置自我刷新時間TRAS,即發送行有效命令后要等待的時間,過了這段時間才執行預充電命令。
(4) FMC_RowCycleDelay
本成員設置TRC延遲(Row cycle delay),即兩個行有效命令之間的延遲,以及兩個相鄰刷新命令之間的延遲
(5) FMC_WriteRecoveryTime
本成員設置TWR延遲(Recovery delay),即寫命令和預充電命令之間的延遲,等待這段時間后才開始執行預充電命令。
(6) FMC_RPDelay
本成員設置TRP延遲(Row precharge delay),即預充電命令與其它命令之間的延遲。
(7) FMC_RCDDelay
本成員設置TRCD延遲(Row to column delay),即行有效命令到列讀寫命令之間的延遲。
這個SDRAMTimingInitTypeDef時序結構體配置的延時參數,將作為下一節的FMC SDRAM初始化結構體的一個成員。
26.6 SDRAM初始化結構體
FMC的SDRAM初始化結構體見代碼清單 262。
代碼清單 262 SDRAM初始化結構體FMC_SDRAMInitTypeDef
1 /* @brief FMC SDRAM 初始化結構體類型定義 */
2 typedef struct
3 {
4 uint32_t FMC_Bank; /*選擇FMC的SDRAM存儲區域*/
5 uint32_t FMC_ColumnBitsNumber; /*定義SDRAM的列地址寬度 */
6 uint32_t FMC_RowBitsNumber; /*定義SDRAM的行地址寬度 */
7 uint32_t FMC_SDMemoryDataWidth; /*定義SDRAM的數據寬度 */
8 uint32_t FMC_InternalBankNumber; /*定義SDRAM內部的Bank數目 */
9 uint32_t FMC_CASLatency; /*定義CASLatency的時鍾個數*/
10 uint32_t FMC_WriteProtection; /*定義是否使能寫保護模式 */
11 uint32_t FMC_SDClockPeriod; /*配置同步時鍾SDCLK的參數*/
12 uint32_t FMC_ReadBurst; /*是否使能突發讀模式*/
13 uint32_t FMC_ReadPipeDelay; /*定義在CAS個延遲后再等待多
14 少個HCLK時鍾才讀取數據 */
15 FMC_SDRAMTimingInitTypeDef* FMC_SDRAMTimingStruct; /*定義SDRAM的時序參數*/
16 } FMC_SDRAMInitTypeDef;
這個結構體,除最后一個成員是上一小節講解的時序配置外,其它結構體成員的配置都對應到FMC_SDCR中的寄存器位。各個成員意義在前面的小節已有具體講解,其可選參數介紹如下,括號中的是STM32標准庫定義的宏:
(1) FMC_Bank
本成員用於選擇FMC映射的SDRAM存儲區域,可選擇存儲區域1或2 (FMC_Bank1/2_SDRAM)。
(2) FMC_ColumnBitsNumber
本成員用於設置要控制的SDRAM的列地址寬度,可選擇8-11位(FMC_ColumnBits_Number_8/9/10/11b)。
(3) FMC_RowBitsNumber
本成員用於設置要控制的SDRAM的行地址寬度,可選擇設置成11-13位(FMC_RowBits_Number_11/12/13b)。
(4) FMC_SDMemoryDataWidth
本成員用於設置要控制的SDRAM的數據寬度,可選擇設置成8、16或32位(FMC_SDMemory_Width_8/16/32b)。
(5) FMC_InternalBankNumber
本成員用於設置要控制的SDRAM的內部Bank數目,可選擇設置成2或4個Bank數目(FMC_InternalBank_Number_2/4),請注意區分這個結構體成員與FMC_Bank的區別。
(6) FMC_CASLatency
本成員用於設置CASLatency即CL的時鍾數目,可選擇設置為1、2或3個時鍾周期(FMC_CAS_Latency_1/2/3)。
(7) FMC_WriteProtection
本成員用於設置是否使能寫保護模式,如果使能了寫保護則不能向SDRAM寫入數據,正常使用都是禁止寫保護的。
(8) FMC_SDClockPeriod
本成員用於設置FMC與外部SDRAM通訊時的同步時鍾參數,可以設置成STM32的HCLK時鍾頻率的1/2、1/3或禁止輸出時鍾(FMC_SDClock_Period_2/3或FMC_SDClock_Disable)。
(9) FMC_ReadBurst
本成員用於設置是否使能突發讀取模式,禁止時等效於BL=1,使能時BL的值等於模式寄存器中的配置。
(10) FMC_ReadPipeDelay
本成員用於配置在CASLatency個時鍾周期后,再等待多少個HCLK時鍾周期才進行數據采樣,在確保正確的前提下,這個值設置為越短越好,可選擇設置的參數值為0、1或2個HCLK時鍾周期(FMC_ReadPipe_Delay_0/1/2)。
(11) FMC_SDRAMTimingStruct
這個成員就是我們上一小節講解的SDRAM時序結構體了,設置完時序結構體后再把賦值到這里即可。
配置完SDRAM初始化結構體后,調用FMC_SDRAMInit函數把這些配置寫入到FMC的SDRAM控制寄存器及時序寄存器,實現FMC的初始化。
26.7 SDRAM命令結構體
控制SDRAM時需要各種命令,通過向FMC的命令模式寄存器FMC_SDCMR寫入控制參數,即可控制FMC對外發送命令,為了方便使用,STM32標准庫也把它封裝成了結構體,見代碼清單 263。
代碼清單 263 SDRAM命令結構體
1 typedef struct
2 {
3 uint32_t FMC_CommandMode; /*要發送的命令 */
4 uint32_t FMC_CommandTarget; /*目標存儲器區域 */
5 uint32_t FMC_AutoRefreshNumber; /*若發送的是自動刷新命令,
6 此處為發送的刷新次數,其它命令時無效 */
7 uint32_t FMC_ModeRegisterDefinition; /*若發送的是加載模式寄存器命令,
8 此處為要寫入SDRAM模式寄存器的參數 */
9 } FMC_SDRAMCommandTypeDef;
命令結構體中的各個成員介紹如下:
(1) FMC_CommandMode
本成員用於配置將要發送的命令,它可以被賦值為表 264中的宏,這些宏代表了不同命令;
表 264 FMC可輸出的SDRAM控制命令
宏 |
命令說明 |
FMC_Command_Mode_normal |
正常模式命令 |
FMC_Command_Mode_CLK_Enabled |
使能CLK命令 |
FMC_Command_Mode_PALL |
對所有Bank預充電命令 |
FMC_Command_Mode_AutoRefresh |
自動刷新命令 |
FMC_Command_Mode_LoadMode |
加載模式寄存器命令 |
FMC_Command_Mode_Selfrefresh |
自我刷新命令 |
FMC_Command_Mode_PowerDown |
掉電命令 |
(2) FMC_CommandTarget
本成員用於選擇要控制的FMC存儲區域,可選擇存儲區域1或2(FMC_Command_Target_bank1/2);
(3) FMC_AutoRefreshNumber
有時需要連續發送多個"自動刷新"(Auto Refresh)命令時,配置本成員即可控制它發送多少次,可輸入參數值為1-16,若發送的是其它命令,本參數值無效。如FMC_CommandMode成員被配置為宏FMC_Command_Mode_AutoRefresh,而FMC_AutoRefreshNumber被設置為2時,FMC就會控制發送2次自動刷新命令。
(4) FMC_ModeRegisterDefinition
當向SDRAM發送加載模式寄存器命令時,這個結構體成員的值將通過地址線發送到SDRAM的模式寄存器中,這個成員值長度為13位,各個位一一對應SDRAM的模式寄存器。
配置完這些結構體成員,調用庫函數FMC_SDRAMCmdConfig即可把這些參數寫入到FMC_SDCMR寄存器中,然后FMC外設就會發送相應的命令了。
26.8 FMC—擴展外部SDRAM實驗
本小節以型號為"IS42S16400J"的SDRAM芯片為STM32擴展內存。它的行地址寬度為12位,列地址寬度為8位,內部含有4個Bank,數據線寬度為16位,容量大小為8MB。
學習本小節內容時,請打開配套的"FMC—讀寫SDRAM"工程配合閱讀。本實驗僅講解基本的SDRAM驅動,不涉及內存管理的內容,在本書的《MDK編譯過程及文件類型全解》章節將會講解使用更簡單的方法從SDRAM中分配變量,以及使用C語言標准庫的malloc函數來分配SDRAM的空間。
26.8.1 硬件設計
圖 2615 SDRAM硬件連接圖
SDRAM與STM32相連的引腳非常多,主要是地址線和數據線,這些具有特定FMC功能的GPIO引腳可查詢《STM32F4xx規格書》中的說明來了解。
關於該SDRAM芯片的更多信息,請參考其規格書《IS42-45S16400J》了解。若您使用的實驗板FLASH的型號或控制引腳不一樣,可在我們工程的基礎上修改,程序的控制原理相同。
26.8.2 軟件設計
為了使工程更加有條理,我們把SDRAM初始化相關的代碼獨立分開存儲,方便以后移植。在"工程模板"之上新建"bsp_sdram.c"及"bsp_sdram.h"文件,這些文件也可根據您的喜好命名,它們不屬於STM32標准庫的內容,是由我們自己根據應用需要編寫的。
1. 編程要點
(13) 初始化通訊使用的目標引腳及端口時鍾;
(14) 使能FMC外設的時鍾;
(15) 配置FMC SDRAM的時序、工作模式;
(16) 根據SDRAM的初始化流程編寫初始化函數;
(17) 建立機制訪問外部SDRAM存儲器;
(18) 編寫測試程序,對讀寫數據進行校驗。
2. 代碼分析
FMC硬件相關宏定義
我們把FMC SDRAM硬件相關的配置都以宏的形式定義到"bsp_sdram.h"文件中,見代碼清單 242。
代碼清單 264 SDRAM硬件配置相關的宏(省略了大部分數據線)
1 /*A行列地址信號線*/
2 #define FMC_A0_GPIO_PORT GPIOF
3 #define FMC_A0_GPIO_CLK RCC_AHB1Periph_GPIOF
4 #define FMC_A0_GPIO_PIN GPIO_Pin_0
5 #define FMC_A0_PINSOURCE GPIO_PinSource0
6 #define FMC_A0_AF GPIO_AF_FMC
7 /*......*/
8 /*此處省略A1-A11信號線的宏,具體可參考工程中的代碼*/
9 /*BA地址線*/
10 #define FMC_BA0_GPIO_PORT GPIOG
11 #define FMC_BA0_GPIO_CLK RCC_AHB1Periph_GPIOG
12 #define FMC_BA0_GPIO_PIN GPIO_Pin_4
13 #define FMC_BA0_PINSOURCE GPIO_PinSource4
14 #define FMC_BA0_AF GPIO_AF_FMC
15 /*......*/
16 /*此處省略BA1信號線的宏,具體可參考工程中的代碼*/
17
18 /*DQ數據信號線*/
19 #define FMC_D0_GPIO_PORT GPIOD
20 #define FMC_D0_GPIO_CLK RCC_AHB1Periph_GPIOD
21 #define FMC_D0_GPIO_PIN GPIO_Pin_14
22 #define FMC_D0_PINSOURCE GPIO_PinSource14
23 #define FMC_D0_AF GPIO_AF_FMC
24 /*......*/
25 /*此處省略D1-A15信號線的宏,具體可參考工程中的代碼*/
26
27 /*控制信號線*/
28 /*CS片選*/
29 #define FMC_CS_GPIO_PORT GPIOH
30 #define FMC_CS_GPIO_CLK RCC_AHB1Periph_GPIOH
31 #define FMC_CS_GPIO_PIN GPIO_Pin_6
32 #define FMC_CS_PINSOURCE GPIO_PinSource6
33 #define FMC_CS_AF GPIO_AF_FMC
34 /*WE寫使能*/
35 #define FMC_WE_GPIO_PORT GPIOC
36 #define FMC_WE_GPIO_CLK RCC_AHB1Periph_GPIOC
37 #define FMC_WE_GPIO_PIN GPIO_Pin_0
38 #define FMC_WE_PINSOURCE GPIO_PinSource0
39 #define FMC_WE_AF GPIO_AF_FMC
40 /*RAS行選通*/
41 #define FMC_RAS_GPIO_PORT GPIOF
42 #define FMC_RAS_GPIO_CLK RCC_AHB1Periph_GPIOF
43 #define FMC_RAS_GPIO_PIN GPIO_Pin_11
44 #define FMC_RAS_PINSOURCE GPIO_PinSource11
45 #define FMC_RAS_AF GPIO_AF_FMC
46 /*CAS列選通*/
47 #define FMC_CAS_GPIO_PORT GPIOG
48 #define FMC_CAS_GPIO_CLK RCC_AHB1Periph_GPIOG
49 #define FMC_CAS_GPIO_PIN GPIO_Pin_15
50 #define FMC_CAS_PINSOURCE GPIO_PinSource15
51 #define FMC_CAS_AF GPIO_AF_FMC
52 /*CLK同步時鍾,存儲區域2*/
53 #define FMC_CLK_GPIO_PORT GPIOG
54 #define FMC_CLK_GPIO_CLK RCC_AHB1Periph_GPIOG
55 #define FMC_CLK_GPIO_PIN GPIO_Pin_8
56 #define FMC_CLK_PINSOURCE GPIO_PinSource8
57 #define FMC_CLK_AF GPIO_AF_FMC
58 /*CKE時鍾使能,存儲區域2*/
59 #define FMC_CKE_GPIO_PORT GPIOH
60 #define FMC_CKE_GPIO_CLK RCC_AHB1Periph_GPIOH
61 #define FMC_CKE_GPIO_PIN GPIO_Pin_7
62 #define FMC_CKE_PINSOURCE GPIO_PinSource7
63 #define FMC_CKE_AF GPIO_AF_FMC
64
65 /*DQM1數據掩碼*/
66 #define FMC_UDQM_GPIO_PORT GPIOE
67 #define FMC_UDQM_GPIO_CLK RCC_AHB1Periph_GPIOE
68 #define FMC_UDQM_GPIO_PIN GPIO_Pin_1
69 #define FMC_UDQM_PINSOURCE GPIO_PinSource1
70 #define FMC_UDQM_AF GPIO_AF_FMC
71 /*DQM0數據掩碼*/
72 #define FMC_LDQM_GPIO_PORT GPIOE
73 #define FMC_LDQM_GPIO_CLK RCC_AHB1Periph_GPIOE
74 #define FMC_LDQM_GPIO_PIN GPIO_Pin_0
75 #define FMC_LDQM_PINSOURCE GPIO_PinSource0
76 #define FMC_LDQM_AF GPIO_AF_FMC
以上代碼根據硬件的連接,把與SDRAM通訊使用的引腳號、引腳源以及復用功能映射都以宏封裝起來。其中FMC_CKE和FMC_CLK引腳對應的是FMC的存儲區域2,所以后面我們對SDRAM的尋址空間也是要指向存儲區域2的。
初始化FMC的 GPIO
利用上面的宏,編寫FMC的GPIO引腳初始化函數,見代碼清單 243。
代碼清單 265 FMC的GPIO初始化函數(省略了大部分數據線)
1 /**
2 * @brief 初始化控制SDRAM的IO
3 * @param 無
4 * @retval 無
5 */
6 static void SDRAM_GPIO_Config(void)
7 {
8 GPIO_InitTypeDef GPIO_InitStructure;
9
10 /*此處省略大量地址線、數據線以及控制信號線,
11 它們的時鍾配置都相同,具體請查看工程中的代碼*/
12 /* 使能SDRAM相關的GPIO時鍾 */
13 /*地址信號線*/
14 RCC_AHB1PeriphClockCmd(FMC_A0_GPIO_CLK | /*...*/
15 /*數據信號線*/ /*控制信號線*/
16 FMC_D0_GPIO_CLK |FMC_CS_GPIO_CLK | , ENABLE);
17
18 /*--所有GPIO的配置都相同,此處省略大量引腳初始化,具體請查看工程中的代碼*/
19 /* 通用 GPIO 配置 */
20 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //配置為復用功能
21 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
22 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽輸出
23 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
24
25 /*A行列地址信號線針對引腳配置*/
26 GPIO_InitStructure.GPIO_Pin = FMC_A0_GPIO_PIN;
27 GPIO_Init(FMC_A0_GPIO_PORT, &GPIO_InitStructure);
28 GPIO_PinAFConfig(FMC_A0_GPIO_PORT, FMC_A0_PINSOURCE , FMC_A0_AF);
29 /*...*/
30 /*DQ數據信號線針對引腳配置*/
31 GPIO_InitStructure.GPIO_Pin = FMC_D0_GPIO_PIN;
32 GPIO_Init(FMC_D0_GPIO_PORT, &GPIO_InitStructure);
33 GPIO_PinAFConfig(FMC_D0_GPIO_PORT, FMC_D0_PINSOURCE , FMC_D0_AF);
34 /*...*/
35 /*控制信號線*/
36 GPIO_InitStructure.GPIO_Pin = FMC_CS_GPIO_PIN;
37 GPIO_Init(FMC_CS_GPIO_PORT, &GPIO_InitStructure);
38 GPIO_PinAFConfig(FMC_CS_GPIO_PORT, FMC_CS_PINSOURCE , FMC_CS_AF);
39 /*...*/
40 }
與所有使用到GPIO的外設一樣,都要先把使用到的GPIO引腳模式初始化,以上代碼把FMC SDRAM的所有信號線全都初始化為FMC復用功能,所有引腳配置都是一樣的。
配置FMC的模式
接下來需要配置FMC SDRAM的工作模式,這個函數的主體是根據硬件連接的SDRAM特性,對時序結構體以及初始化結構體進行賦值。見代碼清單 244。
代碼清單 266 配置FMC的模式
1 /**
2 * @brief 初始化配置使用SDRAM的FMC及GPIO接口,
3 * 本函數在SDRAM讀寫操作前需要被調用
4 * @param None
5 * @retval None
6 */
7 void SDRAM_Init(void)
8 {
9 FMC_SDRAMInitTypeDef FMC_SDRAMInitStructure;
10 FMC_SDRAMTimingInitTypeDef FMC_SDRAMTimingInitStructure;
11
12 /* 配置FMC接口相關的 GPIO*/
13 SDRAM_GPIO_Config();
14
15 /* 使能 FMC 時鍾 */
16 RCC_AHB3PeriphClockCmd(RCC_AHB3Periph_FMC, ENABLE);
17
18 /* SDRAM時序結構體,根據SDRAM參數表配置----------------*/
19 /* SDCLK: 90 Mhz (HCLK/2 :180Mhz/2) 1個時鍾周期Tsdclk =1/90MHz=11.11ns*/
20 /* TMRD: 2 Clock cycles */
21 FMC_SDRAMTimingInitStructure.FMC_LoadToActiveDelay = 2;
22 /* TXSR: min=70ns (7x11.11ns) */
23 FMC_SDRAMTimingInitStructure.FMC_ExitSelfRefreshDelay = 7;
24 /* TRAS: min=42ns (4x11.11ns) max=120k (ns) */
25 FMC_SDRAMTimingInitStructure.FMC_SelfRefreshTime = 4;
26 /* TRC: min=70 (7x11.11ns) */
27 FMC_SDRAMTimingInitStructure.FMC_RowCycleDelay = 7;
28 /* TWR: min=1+ 7ns (1+1x11.11ns) */
29 FMC_SDRAMTimingInitStructure.FMC_WriteRecoveryTime = 2;
30 /* TRP: 15ns => 2x11.11ns */
31 FMC_SDRAMTimingInitStructure.FMC_RPDelay = 2;
32 /* TRCD: 15ns => 2x11.11ns */
33 FMC_SDRAMTimingInitStructure.FMC_RCDDelay = 2;
34
35 /* FMC SDRAM 控制配置 */
36 /*選擇存儲區域*/
37 FMC_SDRAMInitStructure.FMC_Bank = FMC_Bank2_SDRAM;
38 /* 行地址線寬度: [7:0] */
39 FMC_SDRAMInitStructure.FMC_ColumnBitsNumber = FMC_ColumnBits_Number_8b;
41 /* 列地址線寬度: [11:0] */
42 FMC_SDRAMInitStructure.FMC_RowBitsNumber = FMC_RowBits_Number_12b;
43 /* 數據線寬度 */
44 FMC_SDRAMInitStructure.FMC_SDMemoryDataWidth = SDRAM_MEMORY_WIDTH;
45 /* SDRAM內部bank數量*/
46 FMC_SDRAMInitStructure.FMC_InternalBankNumber =FMC_InternalBank_Number_4;
48 /* CAS潛伏期 */
49 FMC_SDRAMInitStructure.FMC_CASLatency = FMC_CAS_Latency_2;
50 /* 禁止寫保護*/
51 FMC_SDRAMInitStructure.FMC_WriteProtection =
52 FMC_Write_Protection_Disable;
53 /* SDCLK時鍾分頻因子,SDCLK = HCLK/SDCLOCK_PERIOD*/
54 FMC_SDRAMInitStructure.FMC_SDClockPeriod = FMC_SDClock_Period_2;
55 /* 突發讀模式設置*/
56 FMC_SDRAMInitStructure.FMC_ReadBurst = FMC_Read_Burst_Enable;
57 /* 讀延遲配置 */
58 FMC_SDRAMInitStructure.FMC_ReadPipeDelay = FMC_ReadPipe_Delay_0;
59 /* SDRAM時序參數 */
60 FMC_SDRAMInitStructure.FMC_SDRAMTimingStruct =&FMC_SDRAMTimingInitStructure;
62
63 /* 調用初始化函數,向寄存器寫入配置 */
64 FMC_SDRAMInit(&FMC_SDRAMInitStructure);
65
66 /* 執行FMC SDRAM的初始化流程*/
67 SDRAM_InitSequence();
68 }
這個函數的執行流程如下:
(1) 初始化GPIO引腳以及FMC時鍾
函數開頭調用了前面定義的SDRAM_GPIO_Config函數對FMC用到的GPIO進行初始化,並且使用庫函數RCC_AHB3PeriphClockCmd使能FMC外設的時鍾。
(2) 時序結構體賦值
接下來對時序結構體FMC_SDRAMTimingInitStructure賦值。在前面我們了解到時序結構體各個成員值的單位是同步時鍾SDCLK的周期數,而根據我們使用的SDRAM芯片,可查詢得它對這些時序要求,見表 265。
表 265 SDRAM的延時參數(摘自《IS42-45S16400J》規格書)
時間參數 |
說明 |
最小值 |
單位 |
trc |
兩個刷新命令或兩個行有效命令之間的延遲 |
63 |
ns |
tras |
行有效與預充電命令之間的延遲 |
42 |
ns |
trp |
預充電與行有效命令之間的延遲 |
15 |
ns |
trcd |
行有效與列讀寫命令之間的延遲 |
15 |
ns |
twr |
寫入命令到預充電命令之間的延遲 |
2 |
cycle |
txsr |
退出自我刷新到行有效命令之間的延遲 |
70 |
ns |
tmrd |
加載模式寄存器命令與行有效或刷新命令之間的延遲 |
2 |
cycle |
部分時間參數以ns為單位,因此我們需要進行單位轉換,而以SDCLK時鍾周期數(cycle)為單位的時間參數,直接賦值到時序結構體成員里即可。
由於我們配置FMC輸出的SDCLK時鍾頻率為HCLK的1/2(在后面的程序里配置的),即FSDCLK=90MHz,可得1個SDCLK時鍾周期長度為TSDCLK=1/FSDCLK =11.11ns,然后設置各個成員的時候,只要保證時間大於以上SDRAM延時參數表的要求即可。如trc要求大於63ns,而11.11ns x 7=77.77ns,所以FMC_RowCycleDelay(TRC)成員值被設置為7個時鍾周期,依葫蘆畫瓢完成時序參數的設置。
(3) 配置FMC初始化結構體
函數接下來對FMC SDRAM的初始化結構體賦值。包括行列地址線寬度、數據線寬度、SDRAM內部Bank數量以及CL長度,這些都是根據外接的SDRAM的特性設置的,其中CL長度要與后面初始化流程中給SDRAM模式寄存器中的賦值一致。
設置存儲區域
FMC_Bank成員設置FMC的SDRAM存儲區域映射選擇為FMC_Bank2_SDRAM,這是由於我們的SDRAM硬件連接到FMC_CKE1和FMC_CLK1,所以對應到存儲區域2;
行地址、列地址、數據線寬度及內部Bank數量
這些結構體成員都是根據SDRAM芯片的特性配置的,行地址寬度為8位,列地址寬度為12位,數據線寬度為16位,SDRAM內部有4個Bank;
CL長度
CL的長度這里被設置為2個同步時鍾周期,它需要與后面SDRAM模式寄存器中的配置一樣;
寫保護
FMC_WriteProtection用於設置寫保護,如果使能了這個功能是無法向SDRAM寫入數據的,所以我們關閉這個功能;
同步時鍾參數
FMC_SDClockPeriod成員被設置為FMC_SDClock_Period_2 ,所以同步時鍾的頻率就被設置為HCLK的1/2了;
突發讀模式及讀延遲
為了加快讀取速度,我們使能突發讀功能,且讀延遲周期為0;
時序參數
最后向FMC_SDRAMTimingStruct賦值為前面的時序結構體,包含了我們設定的SDRAM時間參數。
賦值完成后調用庫函數FMC_SDRAMInit把初始化結構體配置的各種參數寫入到FMC_SDCR控制寄存器及FMC_SDTR時序寄存器中。函數的最后調用SDRAM_InitSequence函數實現執行SDRAM的上電初始化時序。
實現SDRAM的初始化時序
在上面配置完成STM32的FMC外設參數后,在讀寫SDRAM前還需要執行前面介紹的SDRAM上電初始化時序,它就是由SDRAM_InitSequence函數實現的,見代碼清單 458。
代碼清單 267 SDRAM上電初始化時序
1 /**
2 * @brief 對SDRAM芯片進行初始化配置
3 * @param None.
4 * @retval None.
5 */
6 static void SDRAM_InitSequence(void)
7 {
8 FMC_SDRAMCommandTypeDef FMC_SDRAMCommandStructure;
9 uint32_t tmpr = 0;
10
11 /* Step 3 -----------------------------------------------*/
12 /* 配置命令:開啟提供給SDRAM的時鍾 */
13 FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_CLK_Enabled;
14 FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_COMMAND_TARGET_BANK;
15 FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 1;
16 FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = 0;
17 /* 檢查SDRAM標志,等待至SDRAM空閑 */
18 while (FMC_GetFlagStatus(FMC_BANK_SDRAM, FMC_FLAG_Busy) != RESET);
19 /* 發送上述命令*/
20 FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure);
21
22 /* Step 4 ---------------------------------------------*/
23 /*延時 */
24 SDRAM_delay(10);
25
26 /* Step 5 -------------------------------------------*/
27 /* 配置命令:對所有的bank預充電 */
28 FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_PALL;
29 FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_COMMAND_TARGET_BANK;
30 FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 1;
31 FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = 0;
32 /* 檢查SDRAM標志,等待至SDRAM空閑 */
33 while (FMC_GetFlagStatus(FMC_BANK_SDRAM, FMC_FLAG_Busy) != RESET);
34 /* 發送上述命令*/
35 FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure);
36
37 /* Step 6 --------------------------------------------*/
38 /* 配置命令:自動刷新 */
39 FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_AutoRefresh;
40 FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_COMMAND_TARGET_BANK;
41 FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 2;
42 FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = 0;
43 /* 檢查SDRAM標志,等待至SDRAM空閑 */
44 while (FMC_GetFlagStatus(FMC_BANK_SDRAM, FMC_FLAG_Busy) != RESET);
45 /* 發送自動刷新命令*/
46 FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure);
47
48 /* Step 7 ----------------------------------------------*/
49 /* 設置sdram寄存器配置 */
50 tmpr = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_8 |
51 SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL |
52 SDRAM_MODEREG_CAS_LATENCY_2 |
53 SDRAM_MODEREG_OPERATING_MODE_STANDARD |
54 SDRAM_MODEREG_WRITEBURST_MODE_SINGLE;
55
56 /* 配置命令:設置SDRAM寄存器 */
57 FMC_SDRAMCommandStructure.FMC_CommandMode = FMC_Command_Mode_LoadMode;
58 FMC_SDRAMCommandStructure.FMC_CommandTarget = FMC_COMMAND_TARGET_BANK;
59 FMC_SDRAMCommandStructure.FMC_AutoRefreshNumber = 1;
60 FMC_SDRAMCommandStructure.FMC_ModeRegisterDefinition = tmpr;
61 /* 檢查SDRAM標志,等待至SDRAM空閑 */
62 while (FMC_GetFlagStatus(FMC_BANK_SDRAM, FMC_FLAG_Busy) != RESET);
63
64 /* 發送上述命令*/
65 FMC_SDRAMCmdConfig(&FMC_SDRAMCommandStructure);
66
67 /* Step 8 --------------------------------------------*/
68
69 /* 設置刷新計數器 */
70 /*刷新速率 = (COUNT + 1) x SDRAM 頻率時鍾
71 COUNT =( SDRAM 刷新周期/行數) - 20*/
72 /* 64ms/4096=15.62us (15.62 us x FSDCLK) - 20 =1386 */
73 FMC_SetRefreshCount(1386);
74 /* 發送上述命令*/
75 while (FMC_GetFlagStatus(FMC_BANK_SDRAM, FMC_FLAG_Busy) != RESET);
76 }
SDRAM的初始化流程實際上是發送一系列控制命令,利用命令結構體FMC_SDRAMCommandTypeDef及庫函數FMC_SDRAMCmdConfig配合即可發送各種命令。函數中按次序發送了使能CLK命令、預充電命令、2個自動刷新命令以及加載模式寄存器命令,每次發調用FMC_SDRAMCmdConfig發送命令后需要調用庫函數FMC_GetFlagStatus檢查BUSY標志位,等待上一個命令操作完畢。
其中發送加載模式寄存器命令時使用了一些自定義的宏,使用這些宏組合起來然后賦值到命令結構體的FMC_ModeRegisterDefinition成員中,這些宏定義見代碼清單 268。
代碼清單 268 加載模式寄存器命令相關的宏
1 /**
2 * @brief FMC SDRAM 模式配置的寄存器相關定義
3 */
4 #define SDRAM_MODEREG_BURST_LENGTH_1 ((uint16_t)0x0000)
5 #define SDRAM_MODEREG_BURST_LENGTH_2 ((uint16_t)0x0001)
6 #define SDRAM_MODEREG_BURST_LENGTH_4 ((uint16_t)0x0002)
7 #define SDRAM_MODEREG_BURST_LENGTH_8 ((uint16_t)0x0004)
8 #define SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL ((uint16_t)0x0000)
9 #define SDRAM_MODEREG_BURST_TYPE_INTERLEAVED ((uint16_t)0x0008)
10 #define SDRAM_MODEREG_CAS_LATENCY_2 ((uint16_t)0x0020)
11 #define SDRAM_MODEREG_CAS_LATENCY_3 ((uint16_t)0x0030)
12 #define SDRAM_MODEREG_OPERATING_MODE_STANDARD ((uint16_t)0x0000)
13 #define SDRAM_MODEREG_WRITEBURST_MODE_PROGRAMMED ((uint16_t)0x0000)
14 #define SDRAM_MODEREG_WRITEBURST_MODE_SINGLE ((uint16_t)0x0200)
這些宏是根據"SDRAM的模式寄存器"的位定義的,例如突發長度、突發模式、CL長度、SDRAM工作模式以及突發寫模式,其中的CL長度注意要與前面FMC SDRAN初始化結構體中定義的一致。
設置自動刷新周期
在上面SDRAM_InitSequence函數的最后,我們還調用了庫函數FMC_SetRefreshCount設置FMC自動刷新周期,這個函數會向刷新定時寄存器FMC_SDRTR寫入計數值,這個計數值每個SDCLK周期自動減1,減至0時FMC會自動向SDRAM發出自動刷新命令,控制SDRAM刷新,SDRAM每次收到刷新命令后,刷新一行,對同一行進行刷新操作的時間間隔稱為SDRAM的刷新周期。
根據STM32F4xx 中文參考手冊的說明,COUNT值的計算公式如下:
刷新速率 = (COUNT + 1) x SDRAM 頻率時鍾
COUNT =( SDRAM 刷新周期/行數) – 20
而查詢我們的SDRAM芯片規格書,可知它的SDRAM刷新周期為64ms,行數為4096,可算出它的SDRAM刷新要求:
TRefresh = 64ms/4096=15.62us
即每隔15.62us需要收到一次自動刷新命令。
所以:
COUNTA = TRefresh/TSDCLK=15.62x90=1406
但是根據要求,如果SDRAM在接受讀請求后出現內部刷新請求,則必須將刷新速率增加 20 個 SDRAM 時鍾周期以獲得重充足的裕量。
最后計算出:COUNT=COUNTA-20=1386。
以上就是函數FMC_SetRefreshCount參數值的計算過程。
使用指針的方式訪問SDRAM存儲器
完成初始化SDRAM后,我們就可以利用它存儲數據了,由於SDRAM的存儲空間是被映射到內核的尋址區域的,我們可以通過映射的地址直接訪問SDRAM,訪問這些地址時,FMC外設自動讀寫SDRAM,程序上無需額外操作。
通過地址訪問內存,最直接的方式就是使用C語言的指針方式了,見代碼清單 269。
代碼清單 269 使用指針的方式訪問SDRAM
1 /*SDRAM起始地址存儲空間2的起始地址*/
2 #define SDRAM_BANK_ADDR ((uint32_t)0xD0000000)
3 /*SDRAM大小,8M字節*/
4 #define IS42S16400J_SIZE 0x800000
5
6 uint32_t temp;
7
8 /*向SDRAM寫入8位數據*/
9 *( uint8_t*) (SDRAM_BANK_ADDR ) = (uint8_t)0xAA;
10 /*從SDRAM讀取數據*/
11 temp = *( uint8_t*) (SDRAM_BANK_ADDR );
12
13 /*寫/讀 16位數據*/
14 *( uint16_t*) (SDRAM_BANK_ADDR+10 ) = (uint16_t)0xBBBB;
15 temp = *( uint16_t*) (SDRAM_BANK_ADDR+10 );
16
17 /*寫/讀 32位數據*/
18 *( uint32_t*) (SDRAM_BANK_ADDR+20 ) = (uint32_t)0xCCCCCCCC;
19 temp = *( uint32_t*) (SDRAM_BANK_ADDR+20 );
為方便使用,代碼中首先定義了宏SDRAM_BANK_ADDR表示SDRAM的起始地址,該地址即FMC映射的存儲區域2的首地址;宏IS42S16400J_SIZE表示SDRAM的大小,所以從地址(SDRAM_BANK_ADDR)到(SDRAM_BANK_ADDR+IS42S16400J_SIZE)都表示在SDRAM的存儲空間,訪問這些地址,直接就能訪問SDRAM。
配合這些宏,使用指針的強制轉換以及取指針操作即可讀寫SDRAM的數據,使用上跟普通的變量無異。
直接指定變量存儲到SDRAM空間
每次存取數據都使用指針來訪問太麻煩了,為了簡化操作,可以直接指定變量存儲到SDRAM空間,見代碼清單 2610。
代碼清單 2610 直接指定變量地址的方式訪問SDRAM
1 /*SDRAM起始地址存儲空間2的起始地址*/
2 #define SDRAM_BANK_ADDR ((uint32_t)0xD0000000)
3 /*絕對定位方式訪問SDRAM,這種方式必須定義成全局變量*/
4 uint8_t testValue __attribute__((at(SDRAM_BANK_ADDR)));
5 testValue = 0xDD;
這種方式使用關鍵字"__attribute__((at()))"來指定變量的地址,代碼中指定testValue存儲到SDRAM的起始地址,從而實現把變量存儲到SDRAM上。要注意使用這種方法定義變量時,必須在函數外把它定義成全局變量,才可以存儲到指定地址上。
更常見的是利用這種方法定義一個很大的數組,整個數組都指定到SDRAM地址上,然后就像使用malloc函數一樣,用戶自定義一些內存管理函數,動態地使用SDRAM的內存,我們在使用emWin寫GUI應用的時候就是這樣做的。
在本書的《MDK編譯過程及文件類型全解》章節將會講解使用更簡單的方法從SDRAM中分配變量,以及使用C語言標准庫的malloc函數來分配SDRAM的空間,更有效地進行內存管理。
3. main函數
最后我們來編寫main函數,進行SDRAM芯片讀寫校驗,見代碼清單 2414。
代碼清單 2611 main函數
1 /**
2 * @brief 主函數
3 * @param 無
4 * @retval 無
5 */
6 int main(void)
7 {
8 /* LED 端口初始化 */
9 LED_GPIO_Config();
10 /* 初始化串口 */
11 Debug_USART_Config();
12 printf("\r\n秉火STM32F429 SDRAM 讀寫測試例程\r\n");
13 /*初始化SDRAM模塊*/
14 SDRAM_Init();
15
16 /*藍燈亮,表示正在讀寫SDRAM測試*/
17 LED_BLUE;
18 /*對SDRAM進行讀寫測試,檢測SDRAM是否正常*/
19 if (SDRAM_Test()==1)
20 {
21 //測試正常綠燈亮
22 LED_GREEN;
23 }
24 else
25 {
26 //測試失敗紅燈亮
27 LED_RED;
28 }
29 while (1);
30 }
函數中初始化了LED、串口,接着調用前面定義好的SDRAM_Init函數初始化FMC及SDRAM,然后調用自定義的測試函數SDRAM_Test嘗試使用SDRAM存取8、16及32位數據,並進行讀寫校驗,它就是使用指針的方式存取數據並校驗而已,此處不展開。
注意對SDRAM存儲空間的數據操作都要在SDRAM_Init初始化FMC之后,否則數據是無法正常存儲的。
下載驗證
用USB線連接開發板"USB TO UART"接口跟電腦,在電腦端打開串口調試助手,把編譯好的程序下載到開發板。在串口調試助手可看到SDRAM測試的調試信息。
26.9 每課一問
5. 如果要把SDRAM映射到FMC SDRAM的存儲區域1,需要如何修改STM32與SDRAM的硬件連接?程序上需要修改哪些內容?