大家好,我是痞子衡,是正經(jīng)搞技術(shù)的痞子。今天痞子衡給大家介紹的是
i.MXRT下改造FlexSPI driver以AHB方式去寫入NOR Flash。痞子衡前段時間寫過一篇 《串行NAND Flash的兩大特性導(dǎo)致其在i.MXRT FlexSPI下無法XiP》,文章里介紹了 NAND Flash 的 Page Read 等待特性(發(fā)完 Read 命令后需要回讀 Flash 內(nèi)部狀態(tài)寄存器 Busy 位來判斷 Page 數(shù)據(jù)是否已準(zhǔn)備好)導(dǎo)致其無法像 NOR Flash 那樣通過 AHB 方式被便捷訪問,僅能在一個 Page 空間里實現(xiàn) AHB 讀(前提是在 IPG 方式發(fā)完讀命令以及讀完狀態(tài)寄存器確保數(shù)據(jù)已經(jīng)準(zhǔn)備好后)。回到 NOR Flash 上,我們可以輕松通過 AHB 方式讀取 Flash 數(shù)據(jù),但寫入 Flash 一般都是調(diào)用 FlexSPI 驅(qū)動來實現(xiàn)(即 IPG 方式),那么有沒有可能也通過
“AHB 方式來寫入 Flash” 呢?答案是可以的,但為啥痞子衡加了個引號,且往下看:
本文以恩智浦 MIMXRT1170-EVK 開發(fā)板上主芯片 i.MXRT1176 及其配套板載 Flash 芯片 - 芯成 IS25WP128 為例。
一、Flash寫操作流程
芯成 IS25WP128 是一顆很典型的四線 QSPI NOR Flash,其寫入(編程)時序是符合 JEDEC216 標(biāo)準(zhǔn)的。簡單來說,一個完整的寫時序包含三個獨立子時序:Write Enable 時序 Page Program 時序 Read Status 時序。先來看打頭陣的 Write Enable 子時序,NOR Flash 內(nèi)部的狀態(tài)寄存器會有一個位叫 WEL (Write Enable Latch),這個位控制著 Flash 的擦寫權(quán)限,默認(rèn)值是 0(即不允許擦寫)。如果想要寫入 Flash,必須先通過 Write Enable 命令將 WEL 位臨時設(shè)為 1(這個位會隨著當(dāng)前的擦寫命令結(jié)束后自動恢復(fù)到 0)。
置位了 WEL 后,便可以傳輸 Page 數(shù)據(jù)給 Flash,這個子時序便是 Page Program。Page Program 按命令地址和數(shù)據(jù)傳輸方式不同分為三種:一線 SPI,四線 SPI,QPI,下面是常用的四線 SPI 的時序圖,命令和地址通過 IO0 傳輸,數(shù)據(jù)通過 IO[3:0] 傳輸。通常來說,在這個時序里,傳入的地址應(yīng)該是 Page 首地址,寫入數(shù)據(jù)長度應(yīng)該是一個完整的 Page 大小。但從非 Page 首地址處寫入小于一個 Page 長度的數(shù)據(jù)也是可以的,但有一個注意點就是不要在這個時序里出現(xiàn)跨頁的現(xiàn)象(如果出現(xiàn),超出當(dāng)前頁的數(shù)據(jù)會被放回到該頁起始地址處)。
Page Program 子時序結(jié)束后,數(shù)據(jù)還并未真正寫入 Flash 內(nèi)存體中,F(xiàn)lash 內(nèi)部控制器只是開始處理數(shù)據(jù),這時候會有一個等待時間(大概0.2ms),F(xiàn)lash 內(nèi)部的狀態(tài)寄存器有一個位叫 WIP (Write In Progress),這個位標(biāo)志著數(shù)據(jù)寫入狀態(tài)(默認(rèn)值是 0,當(dāng) Page Program 子時序結(jié)束后,WIP 立即跳為 1),用戶需要通過 Read Status 子時序來實時讀取狀態(tài)寄存器的值從而獲知數(shù)據(jù)處理情況。當(dāng) Flash 內(nèi)部狀態(tài)寄存器中的 WIP 位從 1 跳回到 0,便意味著一次完整的寫時序結(jié)束了,主機可以發(fā)起下一次寫時序。
二、FlexSPI對寫時序支持
痞子衡舊文 《從頭開始認(rèn)識i.MXRT啟動頭FDCB里的lookupTable》 里對 FlexSPI 讀時序介紹得非常詳細(xì),尤其是對 AHB 方式讀支持的實現(xiàn),現(xiàn)在痞子衡再介紹下 FlexSPI 對于寫時序的支持。第一節(jié)里介紹的 Flash 寫操作的三個子時序,在 FlexSPI 外設(shè)里當(dāng)然都是支持的,SEQ_CTL 組件里都預(yù)先實現(xiàn)了這些子時序,比如下面就是 Page Program 的序列:
因為 Flash 寫操作需要三個子序列,比 Flash 讀操作單序列要復(fù)雜得多,并且最關(guān)鍵的是寫操作還包含一個不確定的等待周期(Read Status 子時序與 Flash 交互),這就導(dǎo)致 FlexSPI 外設(shè)在 AHB 方式寫上沒法完美支持,這也是為什么寫入 Flash 都是通過 IPG 方式來完成的,因為 IPG 方式下,子序列可以隨意組合,由用戶代碼手動調(diào)度。原則上三個寫操作子序列可以放在 LUT 中的任何一個 Sequence 位置,因為即使按序放在一起,我們通過 FlexSPI->FLSHxCR2 寄存器(x可取A1/A2/B1/B2,具體根據(jù)Flash引腳連接來定)中的 AWRSEQID 位指明寫操作第一個子序列在 LUT 中的位置(index) 也無法自動完成 Page 數(shù)據(jù)的完整寫入操作。但也不要就此放棄,單獨 Page Program 子序列還是可以通過 AHB 方式寫來替代的,這樣也可以讓我們過一下 AHB 方式寫入 Flash 的癮,只是需要在 AHB 寫入操作前后輔助 IPG 方式下的 Write Enable 和 Read Status 動作,下一節(jié)用代碼給大家實際演示。
三、FlexSPI driver用法
例程路徑:\SDK_2.10.0_MIMXRT1170-EVK\boards\evkmimxrt1170\driver_examples\flexspi\nor\polling_transfer\cm7\iar
3.1 初始化
先來看一下 FlexSPI 初始化函數(shù) flexspi_nor_flash_init(),這個函數(shù)需要三個配置變量:分別是 flexspi_config_t 型面向 FlexSPI 外設(shè)層的配置 flexspiconfig,flexspi_device_config_t 型面向 Flash 器件端的配置 deviceconfig,以及很核心的 customLUT(這里只列出了跟 Flash
讀寫操作相關(guān)的時序)。
#define?NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD?????0
#define?NOR_CMD_LUT_SEQ_IDX_WRITEENABLE????????2
#define?NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD???4
#define?NOR_CMD_LUT_SEQ_IDX_READSTATUSREG??????12
#define?CUSTOM_LUT_LENGTH????????60
const?uint32_t?customLUT[CUSTOM_LUT_LENGTH]?=?{
????/*?Fast?read?quad?mode?-?SDR?*/
????[4?*?NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD]?=
????????FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR,?kFLEXSPI_1PAD,?0xEB,?kFLEXSPI_Command_RADDR_SDR,?kFLEXSPI_4PAD,?0x18),
????[4?*?NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD? ?1]?=?
????????FLEXSPI_LUT_SEQ(kFLEXSPI_Command_DUMMY_SDR,?kFLEXSPI_4PAD,?0x06,?kFLEXSPI_Command_READ_SDR,?kFLEXSPI_4PAD,?0x04),
????/*?Write?Enable?*/
????[4?*?NOR_CMD_LUT_SEQ_IDX_WRITEENABLE]?=
????????FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR,?kFLEXSPI_1PAD,?0x06,?kFLEXSPI_Command_STOP,?kFLEXSPI_1PAD,?0),
????/*?Page?Program?-?quad?mode?*/
????[4?*?NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD]?=
????????FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR,?kFLEXSPI_1PAD,?0x32,?kFLEXSPI_Command_RADDR_SDR,?kFLEXSPI_1PAD,?0x18),
????[4?*?NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD? ?1]?=
????????FLEXSPI_LUT_SEQ(kFLEXSPI_Command_WRITE_SDR,?kFLEXSPI_4PAD,?0x04,?kFLEXSPI_Command_STOP,?kFLEXSPI_1PAD,?0),
????/*?Read?status?register?*/
????[4?*?NOR_CMD_LUT_SEQ_IDX_READSTATUSREG]?=
????????FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR,?kFLEXSPI_1PAD,?0x05,?kFLEXSPI_Command_READ_SDR,?kFLEXSPI_1PAD,?0x04),
};
flexspi_device_config_t?deviceconfig?=?{
????.flexspiRootClk???????=?12000000,
????.flashSize????????????=?0x4000,?/*?16Mb/KByte?*/
????.CSIntervalUnit???????=?kFLEXSPI_CsIntervalUnit1SckCycle,
????.CSInterval???????????=?2,
????.CSHoldTime???????????=?3,
????.CSSetupTime??????????=?3,
????.dataValidTime????????=?0,
????.columnspace??????????=?0,
????.enableWordAddress????=?0,
????.AWRSeqIndex??????????=?0,
????.AWRSeqNumber?????????=?0,
????//?支持?AHB?讀的關(guān)鍵配置
????.ARDSeqIndex??????????=?NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD,
????.ARDSeqNumber?????????=?1,
????.AHBWriteWaitUnit?????=?kFLEXSPI_AhbWriteWaitUnit2AhbCycle,
????.AHBWriteWaitInterval?=?0,
};
void?flexspi_nor_flash_init(FLEXSPI_Type?*base)
{
????CLOCK_SetRootClockDiv(kCLOCK_Root_Flexspi1,?2);
????CLOCK_SetRootClockMux(kCLOCK_Root_Flexspi1,?0);
????/*Get?FLEXSPI?default?settings?and?configure?the?flexspi.?*/
????flexspi_config_t?flexspiconfig;
????FLEXSPI_GetDefaultConfig(