FPGA 20个例程篇:7.FLASH读写断电存储
三、數據斷電存儲,工程必備
7.FLASH讀寫斷電存儲
? ? ? ?這個例程主要是說明怎么設計FPGA時序邏輯來實現FLASH的頁面數據擦除、讀寫等,例程本身有著非常深刻地學習價值,大家不妨回想一些嵌入式工作中的常見開發場景:1.做單片機STM32開發,KEIL編譯過后下載直接通過ST-LINK,J-LINK等把bin文件燒寫到單片機的內部FLASH里,斷電重啟后STM32即運行剛才燒寫過的程序;2.同樣地我們也可以把一些希望斷電存儲的大批量數據保存到STM32內部FLASH里,用到后面程序段未使用的扇區空間即可;3.STM32外掛一顆FLASH,把需要顯示的圖像二進制數據預先存入FLASH里,上電后再把數據讀出,重新繪制到顯示屏的指定坐標上即可;4.FPGA外掛一顆QSPI-FLASH,通過下載器把mcs文件燒寫到FLASH里,開機上電后FPGA即讀出FLASH的配置,再按照該配置去重新綜合其內部的電路來達到預期設計等,所以毫不夸張地說掌握FLASH的讀寫在嵌入式開發中是一項必備技能。
? ? ? ?這里和朋友們簡單討論一些題外話,對于很多工程項目因為拆機不便,都會有著遠程更新程序的現實性需求,例如上位機對STM32的IAP串口程序升級;4G網絡遠程升級ARM LIUNX等,雖然具體的升級接口方式隨著硬件變化而變化,但其實核心內容不變依舊是進入Bootloader,通過各類通信協議接收到bin文件的二級制數據,逐一寫入FLASH的對應扇區內,最后從Bootloader跳轉到Application。
? ? ? ? 大家有沒有想過對FPGA而言,本身只是時序邏輯,那么它怎樣才能實現遠程升級呢?這是一個很有意思也很有價值的問題,現實項目中如果需要對FPGA遠程設計,一般需要通過上位機或者ARM端的協助,上位機可以通過常見的LAN、USB2.0、RS232等不同的外設接口把bin文件打報數據發送給FPGA,FPGA接收后按照時序邏輯去把數據寫入外掛QSPI-FLASH的對應頁面(此時FPGA已上電從QSPI-FLASH中讀出電路配置內容,按照程序給定的時序邏輯工作),也可以預先把FPGA的bin文件預先拷入U盤里,ARM或STM32通過移植FATS32,把U盤里的bin文件讀出并打報數據扔給FPGA,同樣的FPGA再去寫入數據到外掛的QSPI-FLASH里,這個例程先暫且不去討論具體FPGA遠程升級的實現方案,而是提供一套擦除、讀寫FPGA外掛的鎂光QSPI-FLASH的設計方法,如圖1所示是豌豆開發板板載的一顆鎂光N25Q128芯片,利用這顆QSPI-FLASH芯片,既可以支持mcs文件燒寫即完成程序的斷電保存,也可以實現其他大批量數據的存儲記憶。
圖1 豌豆開發板Artix7上N25Q128電路
? ? ? ?談過FLASH的實戰背景意義后,在這個例程里,筆者想詳細地向大家介紹FPGA驅動IC設計的大體方法,只有當理解并掌握一般性方法后,再去做其他相關IC驅動研發時才能舉一反三、靈活自如。但可能正應了那句老話,一萬個讀者就有一萬個哈姆雷特,這里只是筆者的多年工作積累供參考,并沒有像數學考試一樣一個標準的答案,仁者見仁智者見智,可能不是最優解,但確實是合理解。
? ? ? ?這里我們還是先來仔細閱讀N25Q128的芯片手冊,在提取程序設計中的用到關鍵信息后,再來和朋友們探討FPGA去驅動IC芯片的一般性方法!本例程的代碼是從轉產項目中摘錄的也具有很大參考價值,需要特殊說明的是QSPI-FLASH的引腳應該接到Artix7芯片的固定IO上,否則無法用下載器下載mcs文件。
? ? ? ?寫到這里,筆者不由自主地想和大家分享一些親身研發經歷,也是這個例程代碼的原型,當時項目開發需要實現Artix7斷電存儲一些數據,XILINX官方手冊也推薦鎂光的外擴QSPI-FLASH芯片,PCB投板并焊接回來,卻發現QSPI-FLASH芯片不管怎么驅動都好像不工作,就不斷地用讀取ID號做測試始終沒任何反應,因為物料是立創上面買的沒有懷疑,所以花了大量精力去排查其他地方,索性買來一塊低端外掛FLASH的Spartan6開發板,經過測試讀取ID號成功了,但型號是W25Q128,所以仍舊無法確定具體原因,當時感到束手無策,仔細核對了兩個FLASH的芯片手冊差別不大,但相同的邏輯代碼放在Artix7板上依舊不行,最后沒有辦法直接把Artix7板上的N25Q128芯片用烙鐵拆了,也把Spartan6開發板的W25Q128拆了換上鎂光的N25Q128芯片再試,結果再試居然好了!于是矛頭直指Artix7芯片,當然現在看來確實走了很多彎路,因為過于相信XILINX下的ILA在線調試工具,實際沒有用示波器或邏輯分析儀去實際測量SPI總線協議上的信號,雖然在ILA下觀察SPI總線的時鐘一直作為輸出在線觀察有波形產生,但該引腳事實上沒有任何輸出只是被ILA調試欺騙,最后查閱了XILINX的官方手冊證明是連接該引腳的CCLK_0是特殊引腳,即使在.xdc文件里定義,也不能被直接驅動,需要參考XILINX手冊用特殊的原語去驅動該引腳!
? ? ? ?如圖2所示是芯片手冊上對N25Q128的引腳說明,大家可以對照圖1的電路來看,除了VCC和GND兩個引腳外,SO8W 封裝的N25Q128芯片,還有D0-D3,SCK時鐘信號和CS#片選信號六個引腳,朋友們可以注意到對于這顆QSPI-FLASH芯片有extended spi、dual spi、quad spi三種讀寫模式,即單線SPI、雙線SPI、四線SPI通信,其中單線SPI的一根數據線只用來發送,另一根數據線只用來接收,即為全雙工;雙線 SPI的兩根線都具有收發功能,但在同一時刻只能是發送或者是接收,即為半雙工;而四線 SPI與雙線 SPI相類似,只是數據線數量上有所區別,也為半雙工。
? ? ? ?其實筆者對單線SPI、雙線SPI、四線SPI通信模式下驅動N25Q128芯片都有嘗試過,平心而論單線SPI則更具有廣泛地實用性,因為一方面在雙線SPI、四線SPI通信中均為半雙工,即引腳需要定義成inout類型這也增加了程序設計的復雜性,另一方面芯片手冊對雙線SPI、四線SPI通信做了詳細說明,不難發現在這兩種模式下的命令發送和數據接收都有一些很容易大意的地方。
? ? ? ?當然在這里也有朋友會反問,如果用雙線SPI、四線SPI通信不也提高了數據讀寫速度嗎?答案是肯定會有所提高,但大家想一想在整個設計,首先我們對SPI總線協議使用25Mhz的時鐘,對現實項目中讀寫FLASH數據的需求已經完全可以滿足了;其次對FLASH的讀寫頻率不會很頻繁,不管是遠程更新程序還是斷電記憶數據;再次在項目設計上,往往FPGA是作為計算、接口、采集后端的,而ARM作為顯示、工控、通信的前端,這時還需要通過各種總線、協議等把數據報文發送給FPGA,FPGA才會觸發讀寫FLASH操作,而通常情況下25Mhz的SPI通信相對于Modbus 485總線、CAN總線等通信來說,速度方面已經足夠用了,即讀寫FLASH速度通常情況下要快于報文接收的速度,所以在這里再去糾結雙線SPI、四線SPI通信來提升FLASH讀寫速度,實際上現實意義并不大。
?圖2 N25Q128引腳的說明
? ? ? ?如圖3所示,是芯片手冊對N25Q128存儲空間的詳細描述,大家可以看到N25Q128標明的是128Mb空間,即128*1024*1024=134217728位,也就是手冊中所寫的16777216字節,對FLASH以及下一個例程的SD卡里都有扇區的概念,手冊說明了一顆N25Q128芯片含有256個扇區,每個扇區是64K字節,即256*64*1024=16777216字節;含有4096個子扇區,每個子扇區是4K字節,即4096*4*1024=16777216字節;含有65536頁,每頁是256字節,即65536*256=16777216字節,所以要搞清楚這些基本概念,對于FLASH擦除、讀寫最小的操作是以頁為單位,當然也能以扇區和子扇區為單位,在這個例程設計中我們以頁為操作的基本單位。
?圖3 N25Q128的存儲空間示意圖
? ? ? ?如下圖4所示是N25Q128芯片的操作指令表格,筆者把程序上常用的指令用紅圈標注了出來。在表格中非常詳細地說明了Command具體內容,Code對應指令,是否支持Extended、Dual I/O、Quad I/O即單線SPI、雙線SPI、四線SPI通信和注意事項,這里因為程序設計中選擇了單線SPI,所以我們更加關注Extended欄中的項目。大家注意到表格下面的Notes,只需要看2、4、8三條即可,其中有的指令需要發送1字節的指令信息后再發送3字節的地址信息,有的指令則直接發送1字節的指令信息即可,有的指令還需要在發送前再發送write enable指令,下面我們結合芯片手冊上每種操作指令的時序邏輯圖,來為大家展開更加細致的說明,這些信息也關系到程序設計。
圖4 N25Q128芯片的操作指令表格
? ? ??筆者在這里按照芯片手冊的排版順序,為大家介紹程序設計上需要用到指令的時序邏輯,如圖5到圖10所示,分別是SPI單線模式下N25Q128芯片的讀取狀態寄存器指令、讀取ID指令、頁讀取指令、頁寫入指令、寫使能和寫禁止指令、頁擦除指令時序圖。
?圖5 SPI單線模式下N25Q128芯片的讀取狀態寄存器指令時序圖
?圖6 SPI單線模式下N25Q128芯片的讀取ID指令時序圖
?圖7 SPI單線模式下N25Q128芯片的頁讀取指令時序圖
圖8 SPI單線模式下N25Q128芯片的頁寫入指令時序圖
圖9 SPI單線模式下N25Q128芯片的寫使能和寫禁止指令時序圖
?圖10 SPI單線模式下N25Q128芯片的頁擦除指令時序圖
? ? ? ?如圖11所示是N25Q128芯片的狀態寄存器的說明,這里特別應該去注意的是,在頁寫入和頁擦除操作后,需要輪詢地發送讀狀態寄存器指令以確定N25Q128是否執行完成、準備就緒,當讀到一字節的狀態寄存器最低位是0時,即代表芯片準備就緒可以執行后續的其他操作,否則當讀到最低位是1則代表尚未準備就緒,這時再發送其他操作指令則會被視為無效操作,感興趣的同學可以在看看芯片手冊對其他寄存器的描述,N25Q128芯片確實有不止一個寄存器,例如有Status Register、Nonvolatile Configuration Register、Enhanced Volatile Configuration Register、Flag Status Register等,對這些寄存器的讀寫也會產生不同的效果,但在本例程中就用到最常見的一個即可。
?圖11 N25Q128芯片的狀態寄存器(Status Register)的說明
? ? ? ?如表1所示,是筆者總結的N25Q128芯片驅動指令列表,在這個列表中注明了指令的具體內容和編號,和在各個指令下FPGA與FLASH通信時是否需要發送指令和地址內容、是否需要在此后繼續讀寫數據等,在這里也定義了一個空指令CMD_NULL便于程序設計。(“√”代表該指令下通信需要發送命令、地址、寫數據或者接收數據等)搞清楚整個表格的內容,對于flash_driver模塊的狀態機設計很有幫助。
| commad | instruction | cmd | addr | rd | wr |
| CMD_RD_DEVICE_ID 讀芯片信息 | 9F | √ | √ | ||
| CMD_WR_DISABLE/WR_ENABLE 寫禁止/寫使能 | 04/06 | √ | |||
| CMD_PAGE_PROGRAM 頁編程(寫數據) | 02 | √ | √ | √ | |
| CMD_RD_DATA 讀數據 | 03 | √ | √ | √ | |
| CMD_SECTOR_ERASE 扇區擦除 | 20 | √ | √ | ||
| CMD_RD_STATUS_REGISTER 讀狀態寄存器 | 05 | √ | √ | ||
| CMD_NULL 空指令 | 00 |
表1 N25Q128芯片驅動指令列表
? ? ? ?詳細閱讀過N25Q128芯片手冊并提取出關鍵信息后,就如前面所說筆者想通過這個例程和朋友們一起去探討使用FPGA驅動一顆IC芯片的一般性程序設計方法。這個例程中,我們去實現如下功能:通過按下豌豆開發板上的按鍵,觸發一次讀寫FLASH的過程,為了驗證相關的操作指令,我們依次去進行如下操作:讀取ID、寫禁止、寫使能、寫擦除、讀取狀態寄存器、寫使能、頁寫入、讀取狀態寄存器、頁讀取,為了方便測試,這里選擇在FLASH的頁地址(每頁是256字節)0下的,依次寫入0-255的256個字節數據,然后再去讀取頁地址0下的256個數據通過串口打印到上位機。
? ? ? 這時朋友們可以想一想應該怎么去實現整個代碼設計,當然FPGA設計本身具有很大的靈活性,可能每個人都有不同的看法,筆者在這里給大家分享一種一般性的驅動IC的方法,也是從多年工作經驗中總結歸納而來的,比如在這個例程中,通過對例程功能的描述,對于操作FLASH則需要一個狀態機去實現指令之間的跳轉;通過對芯片手冊的分析,表3-3也詳細總結了不同指令下的數據發送和接收數量上不完全相同;通過對協議總線的學習,SPI底層還需要驅動邏輯的支持才能正常工作,這樣一想就會感覺整個設計起來會很復雜、很困難,那么我們應該如何去化繁為簡呢?
? ? ? ?通過模塊劃分的思想,我們可以把很多復雜煩瑣的設計做得簡單明了,一般性地我們可以把FPGA對IC驅動細劃分成驅動層、控制層、邏輯層,比如在本例程中可以對應地劃分成flash_driver、flash_control、qspi_flash_rw_top三個層,這樣就可以把很多東西簡化。驅動層實現對FLASH不同指令下的SPI總線上數據收發的時序邏輯;控制層則把驅動層例化到本模塊,根據工程需求實現操作FLASH指令之間的跳轉;邏輯層再把控制層例化過來,簡單修改就可以滿足整個代碼邏輯上對FLASH的控制,這樣就很大程度上減少編碼的復雜度,相反如果所有的驅動層、控制層、邏輯層的時序邏輯都放到一個模塊實現,那么肯定很復雜易出錯,而且也不易排查問題。
? ? ? ?其次這里還有個小技巧,細心的同學會發現幾乎大部分IC芯片都有支持讀取ID操作,用讀取ID來作為驗證驅動層時序邏輯設計是否正確,往往在實際項目中被證明是最為簡單快捷的驗證方法。
? ? ? ?如上表1所示,對于flash_driver驅動模塊,需要用到FLASH操作指令如下:讀取ID(9F);寫禁止(04);寫使能(06);頁編程(02);頁讀取(03);頁擦除(20);讀取狀態寄存器(05),對于這些指令操作,可以把它歸納成幾個步驟:發送CMD命令、發送ADDR地址、讀取數據和寫入數據,針對到具體指令操作有些步驟是跳過的,但不失一般性我們把flash_driver模塊劃分成6個狀態的狀態機,即IDLE、CMD_SEND、ADDR_SEND、RD_DATA、WR_DATA、DONE。
? ? ? ?flash_driver驅動模塊根據上游flash_control控制模塊發來的具體指令做狀態機的跳轉即可,如表2所示是flash_driver模塊信號列表,設計非常直觀明了給出詳細代碼供參考,對照代碼和說明很容易就可以動手還原出來,這里強調幾個設計上容易忽略的地方:
1. 對于FLASH的SPI時鐘的驅動設計,直接在本模塊從50Mhz時鐘分頻到25Mhz,但這里需要通過XILINX原語驅動CCLK_0引腳,CCLK_0引腳是特殊引腳,即使在xdc文件中定義也不生效;
2. 這個模塊中筆者使用的是單線SPI讀寫,感興趣的同學可以嘗試雙線SPI和四線SPI讀寫,從而結合具體工程練習,提高了FPGA的設計能力;
3. 市面上各種教程對于SPI總線的設計,其實很多是有些瑕疵的地方,筆者在項目當中之前也參考別的代碼SPI設計,存在一些實際問題,在這個例程中也詳細分享給大家,確實很多細節真的需要自己靜下心來思考才能體會到。
? ? ? ? 通過第4個例程“ 串行DAC輸出模擬電壓控制LED亮度的學習實踐 ”,朋友們也都了解到SPI是有極性和相位一說的,即在上升沿和下降沿時接收或者發送數據,當然接收和發送數據通過兩根獨立的數據線,即mosi和miso實現全雙工通信,對于SPI時序邏輯筆者見過主流的幾種設計方式:1. 從上游模塊產生一個SPI總線時鐘和一個相對偏移180度時鐘,再用這兩個時鐘的上升沿分別做數據的收發時鐘參考,這樣猛地一看貌似沒有問題,但實際增加了代碼的復雜度,同時因為兩個時鐘存在相位差,在和上游模塊進行數據交互的時候,人為引入了跨時鐘域的問題;2. 直接用always模塊的posedge clk和negedge clk兩個時鐘去收發數據,因為FPGA里的D觸發器默認是用時鐘上升沿,雖然語法上支持用時鐘下降沿的方法當從底層配置上來說不太友好;3. 通過時序邏輯產生一個時鐘,再通過時序邏輯去向mosi總線上發送數據,因為時序邏輯是存在一拍的延時誤差,這樣做在分頻大的情況下,比如50Mhz分頻到6.25Mhz時,SPI的mosi和miso總線的建立和保持時間是足夠的,但在50Mhz分頻到25Mhz時,也是不可行的,所以筆者在flash_driver模塊中直接去產生SPI總線時鐘,并用組合邏輯從SPI總線上收發數據,這樣從代碼層面上就合理規避了可能出現的問題和人為增加的設計復雜度,如圖12所示是本模塊詳細代碼設計。
| 信號列表 | ||
| 信號名 | I/O | 位寬 |
| clk | I | 1 |
| rst_n | I | 1 |
| flash_driver_en | I | 1 |
| flash_cmd | I | 8 |
| flash_addr | I | 24 |
| flash_dqout | I | 1 |
| flash_dqin | O | 1 |
| flash_cs | O | 1 |
| flash_cmd_done | O | 1 |
| flash_dout | O | 8 |
| flash_dout_vld | O | 1 |
表2 flash_driver模塊信號列表
圖12 FLASH驅動模塊的代碼設計
? ? ? ? 在完成FLASH驅動模塊的編寫后,我們就需要再動手編寫FLASH控制模塊,前面也提起過在控制模塊中例化驅動模塊,再用一個狀態機完成各個指令之間的切換,依次去進行如下操作:讀取ID、寫禁止、寫使能、寫擦除、讀取狀態寄存器、寫使能、頁寫入、讀取狀態寄存器、頁讀取,即對應程序設計中的狀態機:IDLE、RD_DEVICE_ID、WR_DISABLE、WR_ENABLE_FIRST、SECTOR_ERASE、RD_STATUS_REGISTER_FIRST、WR_ENABLE_SECOND、PAGE_PROGRAM、RD_STATUS_REGISTER_SECOND、RD_FLASH_DATA,如表3所示是flash_ control模塊的信號列表,其中flash_control_en信號由按鍵產生。
| 信號列表 | ||
| 信號名 | I/O | 位寬 |
| clk | I | 1 |
| rst_n | I | 1 |
| flash_control_en | I | 1 |
| flash_rdfifo_rdy | I | 8 |
| flash_dqout | I | 1 |
| flash_dqin | O | 1 |
| flash_cs | O | 1 |
| flash_cmd | O | 8 |
| flash_rd_dout | O | 8 |
| flash_rd_dout_vld | O | 1 |
表3 flash_ control模塊信號列表
? ? ? ? 如圖13所示是FLASH控制模塊的代碼設計,和上個例程讀寫EEPROM類似,這里也需要一個FIFO用來緩存從FLASH的0地址頁內讀出的256字節數據,本模塊中例化了FLASH驅動模塊,通過向下游模塊發送flash_driver_en使能和flash_cmd命令和FLASH驅動模塊數據交互,狀態機的跳轉按照前面芯片手冊提取出的信息設計即可,沒有太多復雜的東西。
圖13 FLASH控制模塊的代碼設計
? ? ? ? 如圖14所示是FLASH讀寫頂層文件的例化,這里只需要把幾個模塊相關信號例化到一起即可,如圖15所示是串口助手讀取FLASH頁的數據實驗截圖,用戶去按下豌豆開發板上的按鍵,就會觸發FLASH控制模塊的一系列指令操作,通過先擦除再寫入后讀取的方式,即首先擦除0地址頁內的數據,接著從0-255依次寫入0地址頁內256字節數據,最后從0地址頁內讀取256字節的數據打印到串口助手上顯示。
圖14 FLASH讀寫頂層文件的例化
圖15 串口助手讀取FLASH頁的數據
源工程代碼下載鏈接(含datasheet):
鏈接:https://pan.baidu.com/s/1R_Bsakz545CfCZgco-vzwg?
提取碼:cdug?
總結
以上是生活随笔為你收集整理的FPGA 20个例程篇:7.FLASH读写断电存储的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: LearnOpenGL - 纹理贴图 源
- 下一篇: Android: Android源码下载