本章參考資料:《STM32F76xxx參考手冊2》、《STM32F7xx規格書》、庫幫助文檔《STM32F779xx_User_Manual.chm》。
關於SDRAM存儲器,請參考前面的“常用存儲器介紹”章節,實驗中SDRAM芯片的具體參數,請參考其規格書《W9825G6KH》來了解。
26.1 SDRAM控制原理
STM32控制器芯片內部有一定大小的SRAM及FLASH作為內存和程序存儲空間,但當程序較大,內存和程序空間不足時,就需要在STM32芯片的外部擴展存儲器了。
STM32F767系列芯片擴展內存時可以選擇SRAM和SDRAM,由於SDRAM的“容量/價格”比較高,即使用SDRAM要比SRAM要划算得多。我們以SDRAM為例講解如何為STM32擴展內存。
給STM32芯片擴展內存與給PC擴展內存的原理是一樣的,只是PC上一般以內存條的形式擴展,內存條實質是由多個內存顆粒(即SDRAM芯片)組成的通用標准模塊,而STM32直接與SDRAM芯片連接。見圖 26-2,這是一種型號為W9825G6KH的SDRAM芯片內部結構框圖,以它為模型進行學習。
圖 26-1 SDRAM芯片外觀
圖 26-2 一種SDRAM芯片的內部結構框圖
26.1.1 SDRAM信號線
圖 26-2虛線框外引出的是SDRAM芯片的控制引腳,其說明見表 26-1。
表 26-1 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:12] |
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”線的具體運用,需要先熟悉它內部存儲陣列的結構,見圖 26-3。
圖 26-3 SDRAM存儲陣列模型
SDRAM內部包含的存儲陣列,可以把它理解成一張表格,數據就填在這張表格上。和表格查找一樣,指定一個行地址和列地址,就可以精確地找到目標單元格,這是SDRAM芯片尋址的基本原理。這樣的每個單元格被稱為存儲單元,而這樣的表則被稱為存儲陣列(Bank),目前設計的SDRAM芯片基本上內部都包含有4個這樣的Bank,尋址時指定Bank號以及行地址,然后再指定列地址即可尋找到目標存儲單元。SDRAM內部具有多個Bank時的結構見圖 26-4。
圖 26-4 SDRAM內有多個Bank時的結構圖
SDRAM芯片向外部提供有獨立的BA類地址線用於Bank尋址,而行與列則共用A類地址線。
圖 262標號„中表示的就是它內部的存儲陣列結構,通訊時當RAS線為低電平,則“行地址選通器”被選通,地址線A[12: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]表示的數據無效。
1.1.6 SDRAM的命令
控制SDRAM需要用到一系列的命令,見表 26-2。各種信號線狀態組合產生不同的控制命令。
表 26-2 SDRAM命令表
表中的H表示高電平,L表示低電平,X表示任意電平,High-Z表示高阻態。
1. 命令禁止
只要CS引腳為高電平,即表示“命令禁止”(COMMAND INHBIT),它用於禁止SDRAM執行新的命令,但它不能停止當前正在執行的命令。
2. 空操作
“空操作”(NO OPERATION),“命令禁止”的反操作,用於選中SDRAM,以便接下來發送命令。
3. 行有效
進行存儲單元尋址時,需要先選中要訪問的Bank和行,使它處於激活狀態。該操作通過“行有效”(ACTIVE)命令實現,見圖 26-5,發送行有效命令時,RAS線為低電平,同時通過BA線以及A線發送Bank地址和行地址。
圖 26-5 行有效命令時序圖
4. 列讀寫
行地址通過“行有效”命令確定后,就要對列地址進行尋址了。“讀命令”(READ)和“寫命令”(WRITE)的時序很相似,見圖 26-6,通過共用的地址線A發送列地址,同時使用WE引腳表示讀/寫方向,WE為低電平時表示寫,高電平時表示讀。數據讀寫時,使用DQM線表示有效的DQ數據線。
圖 26-6 讀取命令時序
本型號的SDRAM芯片表示列地址時僅使用A[8:0]線,而A10線用於控制是否“自動預充電”,該線為高電平時使能,低電平時關閉。
5. 預充電
SDRAM 的尋址具有獨占性,所以在進行完讀寫操作后,如果要對同一個Bank 的另一行進行尋址,就要將原來有效(ACTIVE)的行關閉,重新發送行/列地址。Bank 關閉當前工作行,准備打開新行的操作就是預充電(Precharge)。
預充電可以通過獨立的命令控制,也可以在每次發送讀寫命令的同時使用“A10”線控制自動進行預充電。實際上,預充電是一種對工作行中所有存儲陣列進行數據重寫,並對行地址進行復位,以准備新行的工作。
獨立的預充電命令時序見圖 26-7。該命令配合使用A10線控制,若A10為高電平時,所有Bank都預充電;A10為低電平時,使用BA線選擇要預充電的Bank。
圖 26-7 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”,各個地址線表示的參數見圖 26-8。
圖 26-8 模式寄存器解析圖
模式寄存器的各個參數介紹如下:
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個時鍾周期,見圖 26-9。
圖 26-9 CL=2和CL=3的說明圖
CL只是針對讀命令時的數據延時,在寫命令是不需要這個延時的,發出寫命令時可同時發送要寫入的數據。
Op Mode
OP Mode指Operating Mode,SDRAM的工作模式。當它被配置為“00”的時候表示工作在正常模式,其它值是測試模式或被保留的設定。實際使用時必須配置成正常模式。
WB
WB用於配置寫操作的突發特性,可選擇使用BL設置的突發長度或非突發模式。
Reserved
模式寄存器的最后三位的被保留,沒有設置參數。
26.1.7 SDRAM的初始化流程
最后我們來了解SDRAM的初始化流程。SDRAM並不是上電后立即就可以開始讀寫數據的,它需要按步驟進行初始化,對存儲矩陣進行預充電、刷新並設置模式寄存器,見圖 26-10。
圖 26-10 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的讀寫流程
初始化步驟完成,開始讀寫數據,其時序流程見圖 26-11及圖 26-12。
圖 26-11 CL=2時,帶AUTO PRECHARGE的讀時序
圖 26-12 帶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簡介
STM32F767使用FMC外設來管理擴展的存儲器,FMC是Flexible Memory Controller的縮寫,譯為可變存儲控制器。它可以用於驅動包括SRAM、SDRAM、NOR FLASH以及NAND FLSAH類型的存儲器。在其它系列的STM32控制器中,只有FSMC控制器(Flexible Static Memory Controller),譯為可變靜態存儲控制器,所以它們不能驅動SDRAM這樣的動態存儲器,因為驅動SDRAM時需要定時刷新,STM32F767的FMC外設才支持該功能,且只支持普通的SDRAM,不支持DDR類型的SDRAM。我們只講述FMC的SDRAM控制功能。
26.3 FMC框圖剖析
STM32的FMC外設內部結構見圖 26-13。
圖 26-13 FMC控制器框圖
1. 通訊引腳
在框圖的右側是FMC外設相關的控制引腳,由於控制不同類型存儲器的時候會有一些不同的引腳,看起來有非常多,其中地址線FMC_A和數據線FMC_D是所有控制器都共用的。這些FMC引腳具體對應的GPIO端口及引腳號可在《STM32F7xx規格書》中搜索查找到,不在此列出。針對SDRAM控制器,我們是整理出以下的FMC與SDRAM引腳對照表 26-3。
表 26-3 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(默認216MHz),控制器的時鍾輸出就是由它分頻得到。如SDRAM控制器的FMC_SDCLK引腳輸出的時鍾,是用於與SDRAM芯片進行同步通訊,它的時鍾頻率可通過FMC_SDCR1寄存器的SDCLK位配置,可以配置為HCLK的1/2或1/3,也就是說,與SDRAM通訊的同步時鍾最高頻率為108MHz。
26.4 FMC的地址映射
FMC連接好外部的存儲器並初始化后,就可以直接通過訪問地址來讀寫數據,這種地址訪問與I2C EEPROM、SPI FLASH的不一樣,后兩種方式都需要控制I2C或SPI總線給存儲器發送地址,然后獲取數據;在程序里,這個地址和數據都需要分開使用不同的變量存儲,並且訪問時還需要使用代碼控制發送讀寫命令。而使用FMC外接存儲器時,其存儲單元是映射到STM32的內部尋址空間的;在程序里,定義一個指向這些地址的指針,然后就可以通過指針直接修改該存儲單元的內容,FMC外設會自動完成數據訪問過程,讀寫命令之類的操作不需要程序控制。FMC的地址映射見圖 26-14。
圖 26-14 FMC的地址映射
圖中左側的是Cortex-M7內核的存儲空間分配,右側是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的最高同步時鍾是108MHz,代碼的執行速度會受影響。
本章主要講解當STM32的片內SRAM不夠用時使用SDRAM擴展內存,但假如程序太大,它的程序空間FLASH不夠用怎么辦呢?首先是裁剪代碼,目前STM32F767系列芯片內部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存儲器時主要是配置時序寄存器以及控制寄存器,利用ST32中的HAL庫的SDRAM時序結構體以及初始化結構體可以很方便地寫入參數。
SDRAM時序結構體的成員見代碼清單 24-1。
代碼清單 26-1 SDRAM時序結構體FMC_SDRAM_TimingTypeDef
1 /* @brief 控制SDRAM的時序參數,這些參數的單位都是“周期”
2 * 各個參數的值可設置為1-16個周期。 */
3 typedef struct
4 {
5 uint32_t LoadToActiveDelay; /*TMRD:加載模式寄存器命令后的延遲*/
6 uint32_t ExitSelfRefreshDelay; /*TXSR:自刷新命令后的延遲 */
7 uint32_t SelfRefreshTime; /*TRAS:自刷新時間*/
8 uint32_t RowCycleDelay; /*TRC:行循環延遲*/
9 uint32_t WriteRecoveryTime; /*TWR:恢復延遲 */
10 uint32_t RPDelay; /*TRP:行預充電延遲*/
11 uint32_t RCDDelay; /*TRCD:行到列延遲*/
12 } FMC_SDRAM_TimingTypeDef;
這個結構體成員定義的都是SDRAM發送各種命令后必須的延遲,它的配置對應到FMC_SDTR中的寄存器位。所有成員參數值的單位是周期,參數值大小都可設置成“1-16”。關於這些延時時間的定義可以看“SDRAM初始化流程”和“SDRAM讀寫流程”小節的時序圖了解。具體參數值根據SDRAM芯片的手冊說明來配置。各成員介紹如下:
(1) LoadToActiveDelay
本成員設置TMRD延遲(Load Mode Register to Active),即發送加載模式寄存器命令后要等待的時間,過了這段時間才可以發送行有效或刷新命令。
(2) ExitSelfRefreshDelay
本成員設置退出TXSR延遲(Exit Self-refresh delay),即退出自我刷新命令后要等待的時間,過了這段時間才可以發送行有效命令。
(3) SelfRefreshTime
本成員設置自我刷新時間TRAS,即發送行有效命令后要等待的時間,過了這段時間才執行預充電命令。
(4) RowCycleDelay
本成員設置TRC延遲(Row cycle delay),即兩個行有效命令之間的延遲,以及兩個相鄰刷新命令之間的延遲
(5) WriteRecoveryTime
本成員設置TWR延遲(Recovery delay),即寫命令和預充電命令之間的延遲,等待這段時間后才開始執行預充電命令。
(6) RPDelay
本成員設置TRP延遲(Row precharge delay),即預充電命令與其它命令之間的延遲。
(7) FMC_RCDDelay
本成員設置TRCD延遲(Row to column delay),即行有效命令到列讀寫命令之間的延遲。
1.6 SDRAM初始化結構體
FMC的SDRAM初始化結構體見代碼清單 262。
代碼清單 262 SDRAM初始化結構體FMC_SDRAMInitTypeDef
1 /* @brief FMC SDRAM 初始化結構體類型定義 */
2 typedef struct
3 {
4 uint32_t Bank; /*選擇FMC的SDRAM存儲區域*/
5 uint32_t ColumnBitsNumber; /*定義SDRAM的列地址寬度 */
6 uint32_t RowBitsNumber; /*定義SDRAM的行地址寬度 */
7 uint32_t MemoryDataWidth; /*定義SDRAM的數據寬度 */
8 uint32_t InternalBankNumber; /*定義SDRAM內部的Bank數目 */
9 uint32_t CASLatency; /*定義CASLatency的時鍾個數*/
10 uint32_t WriteProtection; /*定義是否使能寫保護模式 */
11 uint32_t SDClockPeriod; /*配置同步時鍾SDCLK的參數*/
12 uint32_t ReadBurst; /*是否使能突發讀模式*/
13 uint32_t ReadPipeDelay; /*定義在CAS個延遲后再等待多
14 少個HCLK時鍾才讀取數據 */
15 } FMC_SDRAM_InitTypeDef;
這個結構體成員的配置都對應到FMC_SDCR中的寄存器位。各個成員意義在前面的小節已有具體講解,其可選參數介紹如下,括號中的是STM32 HAL庫定義的宏:
(1) Bank
本成員用於選擇FMC映射的SDRAM存儲區域,可選擇存儲區域1或2 (FMC_SDRAM_BANK1/FMC_SDRAM_BANK2)。
(2) ColumnBitsNumber
本成員用於設置要控制的SDRAM的列地址寬度,可選擇8-11位(FMC_SDRAM_COLUMN_BITS_NUM_8/9/10/11b)。
(3) RowBitsNumber
本成員用於設置要控制的SDRAM的行地址寬度,可選擇設置成11-13位(FMC_SDRAM_ROW_BITS_NUM_11/12/13b)。
(4) MemoryDataWidth
本成員用於設置要控制的SDRAM的數據寬度,可選擇設置成8、16或32位(FMC_SDRAM_MEM_BUS_WIDTH_8/16/32b)。
(5) InternalBankNumber
本成員用於設置要控制的SDRAM的內部Bank數目,可選擇設置成2或4個Bank數目(FMC_SDRAM_INTERN_BANKS_NUM_2/4),請注意區分這個結構體成員與Bank的區別。
(6) CASLatency
本成員用於設置CASLatency即CL的時鍾數目,可選擇設置為1、2或3個時鍾周期(FMC_SDRAM_CAS_LATENCY_1/2/3)。
(7) WriteProtection
本成員用於設置是否使能寫保護模式,如果使能了寫保護則不能向SDRAM寫入數據,正常使用都是禁止寫保護的。
(8) ClockPeriod
本成員用於設置FMC與外部SDRAM通訊時的同步時鍾參數,可以設置成STM32的HCLK時鍾頻率的1/2、1/3或禁止輸出時鍾(FMC_SDRAM_CLOCK_PERIOD_2/3或FMC_SDRAM_CLOCK_DISABLE)。
(9) ReadBurst
本成員用於設置是否使能突發讀取模式,禁止時等效於BL=1,使能時BL的值等於模式寄存器中的配置。
(10) ReadPipeDelay
本成員用於配置在CASLatency個時鍾周期后,再等待多少個HCLK時鍾周期才進行數據采樣,在確保正確的前提下,這個值設置為越短越好,可選擇設置的參數值為0、1或2個HCLK時鍾周期(FMC_SDRAM_RPIPE_DELAY_0/1/2)。
配置完SDRAM初始化結構體后,調用FMC_SDRAMInit函數把這些配置寫入到FMC的SDRAM控制寄存器及時序寄存器,實現FMC的初始化。
26.7 SDRAM命令結構體
控制SDRAM時需要各種命令,通過向FMC的命令模式寄存器FMC_SDCMR寫入控制參數,即可控制FMC對外發送命令,為了方便使用,STM32 HAL庫也把它封裝成了結構體,見代碼清單 26-3。
代碼清單 263 SDRAM命令結構體
1 typedef struct
2 {
3 uint32_t CommandMode; /*要發送的命令 */
4 uint32_t CommandTarget; /*目標存儲器區域 */
5 uint32_t AutoRefreshNumber; /*若發送的是自動刷新命令,
6 此處為發送的刷新次數,其它命令時無效 */
7 uint32_t ModeRegisterDefinition; /*若發送的是加載模式寄存器命令,
8 此處為要寫入SDRAM模式寄存器的參數 */
9 } FMC_SDRAM_CommandTypeDef;
命令結構體中的各個成員介紹如下:
(1) CommandMode
本成員用於配置將要發送的命令,它可以被賦值為表 26-4中的宏,這些宏代表了不同命令;
表 26-4 FMC可輸出的SDRAM控制命令
宏 |
命令說明 |
FMC_SDRAM_CMD_NORMAL_MODE |
正常模式命令 |
FMC_SDRAM_CMD_CLK_ENABLE |
使能CLK命令 |
FMC_SDRAM_CMD_PALL |
對所有Bank預充電命令 |
FMC_SDRAM_CMD_AUTOREFRESH_MODE |
自動刷新命令 |
FMC_SDRAM_CMD_LOAD_MODE |
加載模式寄存器命令 |
FMC_SDRAM_CMD_SELFREFRESH_MODE |
自我刷新命令 |
FMC_SDRAM_CMD_POWERDOWN_MODE |
掉電命令 |
(2) CommandTarget
本成員用於選擇要控制的FMC存儲區域,可選擇存儲區域1或2或1和2(FMC_SDRAM_CMD_TARGET_BANK1/2,FMC_SDRAM_CMD_TARGET_BANK1_2);
(3) AutoRefreshNumber
有時需要連續發送多個 “自動刷新”(Auto Refresh)命令時,配置本成員即可控制它發送多少次,可輸入參數值為1-16,若發送的是其它命令,本參數值無效。如CommandMode成員被配置為宏FMC_SDRAM_CMD_AUTOREFRESH_MODE,而AutoRefreshNumber被設置為2時,FMC就會控制發送2次自動刷新命令。
(4) ModeRegisterDefinition
當向SDRAM發送加載模式寄存器命令時,這個結構體成員的值將通過地址線發送到SDRAM的模式寄存器中,這個成員值長度為13位,各個位一一對應SDRAM的模式寄存器。
配置完這些結構體成員,調用庫函數HAL_SDRAM_SendCommand即可把這些參數寫入到FMC_SDCMR寄存器中,然后FMC外設就會發送相應的命令了。
26.8 FMC—擴展外部SDRAM實驗
本小節以型號為“W9825G6KH”的SDRAM芯片為STM32擴展內存。它的行地址寬度為13位,列地址寬度為9位,內部含有4個Bank,數據線寬度為16位,容量大小為32MB。
學習本小節內容時,請打開配套的“FMC—讀寫SDRAM”工程配合閱讀。本實驗僅講解基本的SDRAM驅動,不涉及內存管理的內容,在本書的《MDK編譯過程及文件類型全解》章節將會講解使用更簡單的方法從SDRAM中分配變量,以及使用C語言標准庫的malloc函數來分配SDRAM的空間。
26.8.1 硬件設計
圖 26-15 SDRAM硬件連接圖
SDRAM與STM32相連的引腳非常多,主要是地址線和數據線,這些具有特定FMC功能的GPIO引腳可查詢《STM32F7xx規格書》中的說明來了解。
關於該SDRAM芯片的更多信息,請參考其規格書《W9825G6KH》了解。若您使用的實驗板FLASH的型號或控制引腳不一樣,可在我們工程的基礎上修改,程序的控制原理相同。
1.8.2 軟件設計
為了使工程更加有條理,我們把SDRAM初始化相關的代碼獨立分開存儲,方便以后移植。在“工程模板”之上新建“bsp_sdram.c”及“bsp_sdram.h”文件,這些文件也可根據您的喜好命名,它們不屬於STM32 HAL庫的內容,是由我們自己根據應用需要編寫的。
1. 編程要點
(1) 初始化通訊使用的目標引腳及端口時鍾;
(2) 使能FMC外設的時鍾;
(3) 配置FMC SDRAM的時序、工作模式;
(4) 根據SDRAM的初始化流程編寫初始化函數;
(5) 建立機制訪問外部SDRAM存儲器;
(6) 編寫測試程序,對讀寫數據進行校驗。
2. 代碼分析
FMC硬件相關宏定義
我們把FMC SDRAM硬件相關的配置都以宏的形式定義到 “bsp_sdram.h”文件中,見代碼清單 24-4。
代碼清單 26-4 SDRAM硬件配置相關的宏(省略了大部分數據線)
1 /*地址信號線*/
2 #define FMC_A0_GPIO_PORT GPIOF
3 #define FMC_A0_GPIO_CLK() __GPIOF_CLK_ENABLE()
4 #define FMC_A0_GPIO_PIN GPIO_PIN_0
5 /*省略一些引腳*/
6 #define FMC_A12_GPIO_PORT GPIOG
7 #define FMC_A12_GPIO_CLK() __GPIOG_CLK_ENABLE()
8 #define FMC_A12_GPIO_PIN GPIO_PIN_2
9
10 /*數據信號線*/
11 #define FMC_D0_GPIO_PORT GPIOD
12 #define FMC_D0_GPIO_CLK() __GPIOD_CLK_ENABLE()
13 #define FMC_D0_GPIO_PIN GPIO_PIN_14
14
15 /*省略一些引腳*/
16 #define FMC_D15_GPIO_PORT GPIOD
17 #define FMC_D15_GPIO_CLK() __GPIOD_CLK_ENABLE()
18 #define FMC_D15_GPIO_PIN GPIO_PIN_10
19
20 /*控制信號線*/
21 #define FMC_CS_GPIO_PORT GPIOH
22 #define FMC_CS_GPIO_CLK() __GPIOH_CLK_ENABLE()
23 #define FMC_CS_GPIO_PIN GPIO_PIN_6
24
25 #define FMC_BA0_GPIO_PORT GPIOG
26 #define FMC_BA0_GPIO_CLK() __GPIOG_CLK_ENABLE()
27 #define FMC_BA0_GPIO_PIN GPIO_PIN_4
28
29 #define FMC_BA1_GPIO_PORT GPIOG
30 #define FMC_BA1_GPIO_CLK() __GPIOG_CLK_ENABLE()
31 #define FMC_BA1_GPIO_PIN GPIO_PIN_5
32
33 #define FMC_WE_GPIO_PORT GPIOC
34 #define FMC_WE_GPIO_CLK() __GPIOC_CLK_ENABLE()
35 #define FMC_WE_GPIO_PIN GPIO_PIN_0
36
37 #define FMC_RAS_GPIO_PORT GPIOF
38 #define FMC_RAS_GPIO_CLK() __GPIOF_CLK_ENABLE()
39 #define FMC_RAS_GPIO_PIN GPIO_PIN_11
40
41 #define FMC_CAS_GPIO_PORT GPIOG
42 #define FMC_CAS_GPIO_CLK() __GPIOG_CLK_ENABLE()
43 #define FMC_CAS_GPIO_PIN GPIO_PIN_15
44
45 #define FMC_CLK_GPIO_PORT GPIOG
46 #define FMC_CLK_GPIO_CLK() __GPIOG_CLK_ENABLE()
47 #define FMC_CLK_GPIO_PIN GPIO_PIN_8
48
49 #define FMC_CKE_GPIO_PORT GPIOH
50 #define FMC_CKE_GPIO_CLK() __GPIOH_CLK_ENABLE()
51 #define FMC_CKE_GPIO_PIN GPIO_PIN_7
52
53 /*UDQM LDQM*/
54 #define FMC_UDQM_GPIO_PORT GPIOE
55 #define FMC_UDQM_GPIO_CLK() __GPIOE_CLK_ENABLE()
56 #define FMC_UDQM_GPIO_PIN GPIO_PIN_1
57
58 #define FMC_LDQM_GPIO_PORT GPIOE
59 #define FMC_LDQM_GPIO_CLK() __GPIOE_CLK_ENABLE()
60 #define FMC_LDQM_GPIO_PIN GPIO_PIN_0
以上代碼根據硬件的連接,把與SDRAM通訊使用的引腳號、引腳源以及復用功能映射都以宏封裝起來。其中FMC_CKE和FMC_CLK引腳對應的是FMC的存儲區域2,所以后面我們對SDRAM的尋址空間也是要指向存儲區域2的。
初始化FMC的 GPIO
利用上面的宏,編寫FMC的GPIO引腳初始化函數,見代碼清單 245。
代碼清單 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 FMC_A0_GPIO_CLK();FMC_A1_GPIO_CLK(); FMC_A2_GPIO_CLK();
15 /*數據信號線*/ /*控制信號線*/
16 FMC_UDQM_GPIO_CLK();FMC_LDQM_GPIO_CLK();
17
18 /*--所有GPIO的配置都相同,此處省略大量引腳初始化,具體請查看工程中的代碼*/
19 /* 通用 GPIO 配置 */
20 GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;//配置為復用功能
21 GPIO_InitStructure.Pull = GPIO_PULLUP;
22 GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
23 GPIO_InitStructure.Alternate = GPIO_AF12_FMC;
24
25 /*A行列地址信號線 針對引腳配置*/
26 GPIO_InitStructure.Pin = FMC_A0_GPIO_PIN;
27 HAL_GPIO_Init(FMC_A0_GPIO_PORT, &GPIO_InitStructure);
28
29 /*...*/
30 /*DQ數據信號線 針對引腳配置*/
31 GPIO_InitStructure.Pin = FMC_D0_GPIO_PIN;
32 HAL_GPIO_Init(FMC_D0_GPIO_PORT, &GPIO_InitStructure);
33
34 /*...*/
35 /*控制信號線*/
36 GPIO_InitStructure.Pin = FMC_CS_GPIO_PIN;
37 HAL_GPIO_Init(FMC_CS_GPIO_PORT, &GPIO_InitStructure);
38
39 /*...*/
40 }
與所有使用到GPIO的外設一樣,都要先把使用到的GPIO引腳模式初始化,以上代碼把FMC SDRAM的所有信號線全都初始化為FMC復用功能,所有引腳配置都是一樣的。
配置FMC的模式
接下來需要配置FMC SDRAM的工作模式,這個函數的主體是根據硬件連接的SDRAM特性,對時序結構體以及初始化結構體進行賦值。見錯誤!未找到引用源。。
代碼清單 26-6 配置FMC的模式
1 void SDRAM_Init(void)
2 {
3
4 FMC_SDRAM_TimingTypeDef SdramTiming;
5 /* 配置FMC接口相關的 GPIO*/
6 SDRAM_GPIO_Config();
7
8 /* 使能 FMC 時鍾 */
9 __FMC_CLK_ENABLE();
10
11 /*執行SDRAM1的內存初始化序列 */
12 hsdram1.Instance = FMC_SDRAM_DEVICE;
13 /* hsdram1結構體初始化*/
14 hsdram1.Init.SDBank = FMC_SDRAM_BANK2;
15 hsdram1.Init.ColumnBitsNumber = FMC_SDRAM_COLUMN_BITS_NUM_9;//SDRAM列數
16 hsdram1.Init.RowBitsNumber = FMC_SDRAM_ROW_BITS_NUM_13;//SDRAM行數
17 hsdram1.Init.MemoryDataWidth = FMC_SDRAM_MEM_BUS_WIDTH_16;//總線數據寬度為16位
18 hsdram1.Init.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4;//4個扇區
19 hsdram1.Init.CASLatency = FMC_SDRAM_CAS_LATENCY_3;//列地址選通信延時
20 hsdram1.Init.WriteProtection = FMC_SDRAM_WRITE_PROTECTION_DISABLE;//禁止寫保護
21 hsdram1.Init.SDClockPeriod = FMC_SDRAM_CLOCK_PERIOD_2;//SDRAM時鍾fpclk=108M
22 hsdram1.Init.ReadBurst = FMC_SDRAM_RBURST_ENABLE; //使能突發傳輸模式
23 hsdram1.Init.ReadPipeDelay = FMC_SDRAM_RPIPE_DELAY_1; //讀通道延時
24 /* SDRAM時序 */
25 SdramTiming.LoadToActiveDelay = 2;//加載模式寄存器命令與行有效或刷新命令之間的延遲
26 SdramTiming.ExitSelfRefreshDelay = 8;//退出自我刷新到行有效命令之間的延遲
27 SdramTiming.SelfRefreshTime = 5;//行有效與預充電命令之間的延遲
28 SdramTiming.RowCycleDelay = 7;//兩個刷新命令或兩個行有效命令之間的延遲
29 SdramTiming.WriteRecoveryTime = 2;//寫入命令到預充電命令之間的延遲
30 SdramTiming.RPDelay = 2;//預充電與行有效命令之間的延遲
31 SdramTiming.RCDDelay = 2;//行有效與列讀寫命令之間的延遲
32
33 HAL_SDRAM_Init(&hsdram1, &SdramTiming);
34 /* FMC SDRAM 設備時序初始化 */
35 SDRAM_InitSequence();
36
37 }
這個函數的執行流程如下:
(1) 初始化GPIO引腳以及FMC時鍾
函數開頭調用了前面定義的SDRAM_GPIO_Config函數對FMC用到的GPIO進行初始化,並且使用庫函數__FMC_CLK_ENABLE使能FMC外設的時鍾。
(2) 時序結構體賦值
接下來對時序結構體hsdram1和SdramTiming賦值。在前面我們了解到時序結構體各個成員值的單位是同步時鍾SDCLK的周期數,而根據我們使用的SDRAM芯片,可查詢得它對這些時序要求,見表 26-5。
表 26-5 SDRAM的延時參數(摘自《W9825G6KH》規格書)
時間參數 |
說明 |
最小值 |
單位 |
trc |
兩個刷新命令或兩個行有效命令之間的延遲 |
60 |
ns |
tras |
行有效與預充電命令之間的延遲 |
42 |
ns |
trp |
預充電與行有效命令之間的延遲 |
15 |
ns |
trcd |
行有效與列讀寫命令之間的延遲 |
15 |
ns |
twr |
寫入命令到預充電命令之間的延遲 |
2 |
cycle |
txsr |
退出自我刷新到行有效命令之間的延遲 |
72 |
ns |
tmrd |
加載模式寄存器命令與行有效或刷新命令之間的延遲 |
2 |
cycle |
部分時間參數以ns為單位,因此我們需要進行單位轉換,而以SDCLK時鍾周期數(cycle)為單位的時間參數,直接賦值到時序結構體成員里即可。
由於我們配置FMC輸出的SDCLK時鍾頻率為HCLK的1/2(在后面的程序里配置的),即FSDCLK=108MHz,可得1個SDCLK時鍾周期長度為TSDCLK=1/FSDCLK =9.26ns,然后設置各個成員的時候,只要保證時間大於以上SDRAM延時參數表的要求即可。如trc要求大於60ns,而9.26ns x 7=64.82ns,所以FMC_RowCycleDelay(TRC)成員值被設置為7個時鍾周期,依葫蘆畫瓢完成時序參數的設置。
(3) 配置FMC初始化結構體
函數接下來對FMC SDRAM的初始化結構體賦值。包括行列地址線寬度、數據線寬度、SDRAM內部Bank數量以及CL長度,這些都是根據外接的SDRAM的特性設置的,其中CL長度要與后面初始化流程中給SDRAM模式寄存器中的賦值一致。
q 設置存儲區域
Bank成員設置FMC的SDRAM存儲區域映射選擇為FMC_SDRAM_BANK2,這是由於我們的SDRAM硬件連接到FMC_CKE1和FMC_CLK1,所以對應到存儲區域2;
q 行地址、列地址、數據線寬度及內部Bank數量
這些結構體成員都是根據SDRAM芯片的特性配置的,行地址寬度為9位,列地址寬度為13位,數據線寬度為16位,SDRAM內部有4個Bank;
q CL長度
CL的長度這里被設置為2個同步時鍾周期,它需要與后面SDRAM模式寄存器中的配置一樣;
q 寫保護
WriteProtection用於設置寫保護,如果使能了這個功能是無法向SDRAM寫入數據的,所以我們關閉這個功能;
q 同步時鍾參數
SDClockPeriod成員被設置為FMC_SDRAM_CLOCK_PERIOD_2 ,所以同步時鍾的頻率就被設置為HCLK的1/2了;
q 突發讀模式及讀延遲
為了加快讀取速度,我們使能突發讀功能,且讀延遲周期為0;
q 時序參數
最后向SdramTiming賦值為前面的時序結構體,包含了我們設定的SDRAM時間參數。
q 賦值完成后調用庫函數HAL_SDRAM_Init把初始化結構體配置的各種參數寫入到FMC_SDCR控制寄存器及FMC_SDTR時序寄存器中。函數的最后調用SDRAM_InitSequence函數實現執行SDRAM的上電初始化時序。
實現SDRAM的初始化時序
在上面配置完成STM32的FMC外設參數后,在讀寫SDRAM前還需要執行前面介紹的SDRAM上電初始化時序,它就是由SDRAM_InitSequence函數實現的,見代碼清單 46-8。
代碼清單 26-7 SDRAM上電初始化時序
1 static void SDRAM_InitSequence(void)
2 {
3 uint32_t tmpr = 0;
4
5 /* Step 1 ----------------------------------------------------*/
6 /* 配置命令:開啟提供給SDRAM的時鍾 */
7 Command.CommandMode = FMC_SDRAM_CMD_CLK_ENABLE;
8 Command.CommandTarget = FMC_COMMAND_TARGET_BANK;
9 Command.AutoRefreshNumber = 1;
10 Command.ModeRegisterDefinition = 0;
11 /* 發送配置命令 */
12 HAL_SDRAM_SendCommand(&sdramHandle, &Command, SDRAM_TIMEOUT);
13
14 /* Step 2: 延時100us */
15 SDRAM_delay(1);
16
17 /* Step 3 ----------------------------------------------------*/
18 /* 配置命令:對所有的bank預充電 */
19 Command.CommandMode = FMC_SDRAM_CMD_PALL;
20 Command.CommandTarget = FMC_COMMAND_TARGET_BANK;
21 Command.AutoRefreshNumber = 1;
22 Command.ModeRegisterDefinition = 0;
23 /* 發送配置命令 */
24 HAL_SDRAM_SendCommand(&sdramHandle, &Command, SDRAM_TIMEOUT);
25
26 /* Step 4 -----------------------------------------------------*/
27 /* 配置命令:自動刷新 */
28 Command.CommandMode = FMC_SDRAM_CMD_AUTOREFRESH_MODE;
29 Command.CommandTarget = FMC_COMMAND_TARGET_BANK;
30 Command.AutoRefreshNumber = 8;
31 Command.ModeRegisterDefinition = 0;
32 /* 發送配置命令 */
33 HAL_SDRAM_SendCommand(&sdramHandle, &Command, SDRAM_TIMEOUT);
34
35 /* Step 5 ------------------------------------------------------*/
36 /* 設置sdram寄存器配置 */
37 tmpr = (uint32_t)SDRAM_MODEREG_BURST_LENGTH_1 |
38 SDRAM_MODEREG_BURST_TYPE_SEQUENTIAL |
39 SDRAM_MODEREG_CAS_LATENCY_3 |
40 SDRAM_MODEREG_OPERATING_MODE_STANDARD |
41 SDRAM_MODEREG_WRITEBURST_MODE_SINGLE;
42
43 /* 配置命令:設置SDRAM寄存器 */
44 Command.CommandMode = FMC_SDRAM_CMD_LOAD_MODE;
45 Command.CommandTarget = FMC_COMMAND_TARGET_BANK;
46 Command.AutoRefreshNumber = 1;
47 Command.ModeRegisterDefinition = tmpr;
48 /* 發送配置命令 */
49 HAL_SDRAM_SendCommand(&sdramHandle, &Command, SDRAM_TIMEOUT);
50
51 /* Step 6 -----------------------------------------------------*/
52
53 /* 設置刷新計數器 */
54 /* 刷新周期=64ms/8192行=7.8125us */
55 /* COUNT=(7.8125 us x Freq) - 20 */
56 /* 設置自刷新速率 */
57 HAL_SDRAM_ProgramRefreshRate(&sdramHandle, 824);
58 }
SDRAM的初始化流程實際上是發送一系列控制命令,利用命令結構體FMC_SDRAM_CommandTypeDef及庫函數HAL_SDRAM_SendCommand配合即可發送各種命令。函數中按次序發送了使能CLK命令、預充電命令、2個自動刷新命令以及加載模式寄存器命令。
其中發送加載模式寄存器命令時使用了一些自定義的宏,使用這些宏組合起來然后賦值到命令結構體的FMC_ModeRegisterDefinition成員中,這些宏定義見代碼清單 26-8。
代碼清單 26-8 加載模式寄存器命令相關的宏
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的刷新周期。
根據STM32F76xxx參考手冊的說明,COUNT值的計算公式如下:
刷新速率 = (COUNT + 1) x SDRAM 頻率時鍾
COUNT =( SDRAM 刷新周期/行數) – 20
而查詢我們的SDRAM芯片規格書,可知它的SDRAM刷新周期為64ms,行數為8192,可算出它的SDRAM刷新要求:
TRefresh = 64ms/8192=7.8125us
即每隔7.8125us需要收到一次自動刷新命令。
所以:
COUNTA = TRefresh/TSDCLK=7.8125x108=844
但是根據要求,如果SDRAM在接受讀請求后出現內部刷新請求,則必須將刷新速率增加 20 個 SDRAM 時鍾周期以獲得重充足的裕量。
最后計算出:COUNT=COUNTA-20=824。
以上就是函數FMC_SetRefreshCount參數值的計算過程。
使用指針的方式訪問SDRAM存儲器
完成初始化SDRAM后,我們就可以利用它存儲數據了,由於SDRAM的存儲空間是被映射到內核的尋址區域的,我們可以通過映射的地址直接訪問SDRAM,訪問這些地址時,FMC外設自動讀寫SDRAM,程序上無需額外操作。
通過地址訪問內存,最直接的方式就是使用C語言的指針方式了,見代碼清單 26-9。
代碼清單 26-9 使用指針的方式訪問SDRAM
1 /*SDRAM起始地址 存儲空間2的起始地址*/
2 #define SDRAM_BANK_ADDR ((uint32_t)0xD0000000)
3 /*SDRAM大小,32M字節*/
4 #define W9825G6KH_SIZE 0x2000000
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的首地址;宏W9825G6KH_SIZE表示SDRAM的大小,所以從地址(SDRAM_BANK_ADDR)到(SDRAM_BANK_ADDR+ W9825G6KH_SIZE)都表示在SDRAM的存儲空間,訪問這些地址,直接就能訪問SDRAM。
配合這些宏,使用指針的強制轉換以及取指針操作即可讀寫SDRAM的數據,使用上跟普通的變量無異。
直接指定變量存儲到SDRAM空間
每次存取數據都使用指針來訪問太麻煩了,為了簡化操作,可以直接指定變量存儲到SDRAM空間,見代碼清單 26-10。
代碼清單 26-10 直接指定變量地址的方式訪問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芯片讀寫校驗,見錯誤!未找到引用源。。
代碼清單 26-11 main函數
1 int main(void)
2 {
3 /* 系統時鍾初始化成216 MHz */
4 SystemClock_Config();
5 /* LED 端口初始化 */
6 LED_GPIO_Config();
7
8 /* 初始化串口 */
9 DEBUG_USART_Config();
10
11 printf("\r\n秉火STM32F67 SDRAM 讀寫測試例程\r\n");
12
13 /*初始化SDRAM模塊*/
14 SDRAM_Init();
15
16 /*藍燈亮,表示正在讀寫SDRAM測試*/
17 LED_BLUE;
18
19 /*使能RNG時鍾*/
20 __RNG_CLK_ENABLE();
21 /*初始化RNG模塊產生隨機數*/
22 hrng.Instance = RNG;
23 HAL_RNG_Init(&hrng);
24
25 printf("開始生成10000個SDRAM測試隨機數\r\n");
26 for (count=0; count<10000; count++)
27
28 {
29 RadomBuffer[count]=HAL_RNG_GetRandomNumber(&hrng);
30
31 }
32 printf("10000個SDRAM測試隨機數生成完畢\r\n");
33
34 SDRAM_Check();
35
36 while (1);
37
38
39 }
函數中初始化了系統時鍾、LED、串口,初始化隨機數發生模塊,產生10000個隨機數用於寫入SDRAM,接着調用前面定義好的SDRAM_Init函數初始化FMC及SDRAM,然后調用自定義的測試函數SDRAM_Test測試整個SDRAM填滿隨機數,進行讀寫校驗是否正確,它就是使用指針的方式存取數據並校驗而已,此處不展開。
注意對SDRAM存儲空間的數據操作都要在SDRAM_Init初始化FMC之后,否則數據是無法正常存儲的。
下載驗證
用USB線連接開發板“USB TO UART”接口跟電腦,在電腦端打開串口調試助手,把編譯好的程序下載到開發板。在串口調試助手可看到SDRAM測試的調試信息。