大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家介紹的是i.MXRT下改造FlexSPI driver以AHB方式去寫入NOR Flash。
痞子衡前段時間寫過一篇 《串行NAND Flash的兩大特性導致其在i.MXRT FlexSPI下無法XiP》,文章里介紹了 NAND Flash 的 Page Read 等待特性(發完 Read 命令后需要回讀 Flash 內部狀態寄存器 Busy 位來判斷 Page 數據是否已准備好)導致其無法像 NOR Flash 那樣通過 AHB 方式被便捷訪問,僅能在一個 Page 空間里實現 AHB 讀(前提是在 IPG 方式發完讀命令以及讀完狀態寄存器確保數據已經准備好后)。
回到 NOR Flash 上,我們可以輕松通過 AHB 方式讀取 Flash 數據,但寫入 Flash 一般都是調用 FlexSPI 驅動來實現(即 IPG 方式),那么有沒有可能也通過 “AHB 方式來寫入 Flash” 呢?答案是可以的,但為啥痞子衡加了個引號,且往下看:
本文以恩智浦 MIMXRT1170-EVK 開發板上主芯片 i.MXRT1176 及其配套板載 Flash 芯片 - 芯成 IS25WP128 為例。
一、Flash寫操作流程
芯成 IS25WP128 是一顆很典型的四線 QSPI NOR Flash,其寫入(編程)時序是符合 JEDEC216 標准的。簡單來說,一個完整的寫時序包含三個獨立子時序:Write Enable 時序 + Page Program 時序 + Read Status 時序。
先來看打頭陣的 Write Enable 子時序,NOR Flash 內部的狀態寄存器會有一個位叫 WEL (Write Enable Latch),這個位控制着 Flash 的擦寫權限,默認值是 0(即不允許擦寫)。如果想要寫入 Flash,必須先通過 Write Enable 命令將 WEL 位臨時設為 1(這個位會隨着當前的擦寫命令結束后自動恢復到 0)。
置位了 WEL 后,便可以傳輸 Page 數據給 Flash,這個子時序便是 Page Program。Page Program 按命令地址和數據傳輸方式不同分為三種:一線 SPI,四線 SPI,QPI,下面是常用的四線 SPI 的時序圖,命令和地址通過 IO0 傳輸,數據通過 IO[3:0] 傳輸。
通常來說,在這個時序里,傳入的地址應該是 Page 首地址,寫入數據長度應該是一個完整的 Page 大小。但從非 Page 首地址處寫入小於一個 Page 長度的數據也是可以的,但有一個注意點就是不要在這個時序里出現跨頁的現象(如果出現,超出當前頁的數據會被放回到該頁起始地址處)。
Page Program 子時序結束后,數據還並未真正寫入 Flash 內存體中,Flash 內部控制器只是開始處理數據,這時候會有一個等待時間(大概0.2ms),Flash 內部的狀態寄存器有一個位叫 WIP (Write In Progress),這個位標志着數據寫入狀態(默認值是 0,當 Page Program 子時序結束后,WIP 立即跳為 1),用戶需要通過 Read Status 子時序來實時讀取狀態寄存器的值從而獲知數據處理情況。
當 Flash 內部狀態寄存器中的 WIP 位從 1 跳回到 0,便意味着一次完整的寫時序結束了,主機可以發起下一次寫時序。
二、FlexSPI對寫時序支持
痞子衡舊文 《從頭開始認識i.MXRT啟動頭FDCB里的lookupTable》 里對 FlexSPI 讀時序介紹得非常詳細,尤其是對 AHB 方式讀支持的實現,現在痞子衡再介紹下 FlexSPI 對於寫時序的支持。
第一節里介紹的 Flash 寫操作的三個子時序,在 FlexSPI 外設里當然都是支持的,SEQ_CTL 組件里都預先實現了這些子時序,比如下面就是 Page Program 的序列:
因為 Flash 寫操作需要三個子序列,比 Flash 讀操作單序列要復雜得多,並且最關鍵的是寫操作還包含一個不確定的等待周期(Read Status 子時序與 Flash 交互),這就導致 FlexSPI 外設在 AHB 方式寫上沒法完美支持,這也是為什么寫入 Flash 都是通過 IPG 方式來完成的,因為 IPG 方式下,子序列可以隨意組合,由用戶代碼手動調度。
原則上三個寫操作子序列可以放在 LUT 中的任何一個 Sequence 位置,因為即使按序放在一起,我們通過 FlexSPI->FLSHxCR2 寄存器(x可取A1/A2/B1/B2,具體根據Flash引腳連接來定)中的 AWRSEQID 位指明寫操作第一個子序列在 LUT 中的位置(index) 也無法自動完成 Page 數據的完整寫入操作。
但也不要就此放棄,單獨 Page Program 子序列還是可以通過 AHB 方式寫來替代的,這樣也可以讓我們過一下 AHB 方式寫入 Flash 的癮,只是需要在 AHB 寫入操作前后輔助 IPG 方式下的 Write Enable 和 Read Status 動作,下一節用代碼給大家實際演示。
三、FlexSPI driver用法
例程路徑:\SDK_2.10.0_MIMXRT1170-EVK\boards\evkmimxrt1170\driver_examples\flexspi\nor\polling_transfer\cm7\iar
3.1 初始化
先來看一下 FlexSPI 初始化函數 flexspi_nor_flash_init(),這個函數需要三個配置變量:分別是 flexspi_config_t 型面向 FlexSPI 外設層的配置 flexspiconfig,flexspi_device_config_t 型面向 Flash 器件端的配置 deviceconfig,以及很核心的 customLUT(這里只列出了跟 Flash 讀寫操作相關的時序)。
#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 讀的關鍵配置
.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(&flexspiconfig);
/*Set AHB buffer size for reading data through AHB bus. */
flexspiconfig.ahbConfig.enableAHBPrefetch = true;
flexspiconfig.ahbConfig.enableAHBBufferable = true;
flexspiconfig.ahbConfig.enableReadAddressOpt = true;
flexspiconfig.ahbConfig.enableAHBCachable = true;
flexspiconfig.rxSampleClock = kFLEXSPI_ReadSampleClkLoopbackFromDqsPad;
FLEXSPI_Init(base, &flexspiconfig);
/* Configure flash settings according to serial flash feature. */
FLEXSPI_SetFlashConfig(base, &deviceconfig, kFLEXSPI_PortA1);
/* Update LUT table. */
FLEXSPI_UpdateLUT(base, 0, customLUT, CUSTOM_LUT_LENGTH);
/* Do software reset. */
FLEXSPI_SoftwareReset(base);
}
3.2 一般用法(IPG寫)
先來看 IPG 方式的 Flash 寫入函數,其中 Page Program 子時序是通過 FLEXSPI_TransferBlocking() 函數來完成的,這個函數就是往大小為 256 bytes 的 IP TX FIFO 寫 src 里的數據(默認 FlexSPI->MCR0[ATDFEN] = 0 情況下),SEQ_CTL 組件處理時會將緩存在 IP TX FIFO 里的數據全部發送到 Flash 端。
void flexspi_nor_flash_program(FLEXSPI_Type *base, uint32_t dstAddr, const uint32_t *src, uint32_t size)
{
// Write Enable 子時序
flexspi_nor_write_enable(base, dstAddr);
flexspi_transfer_t flashXfer;
flashXfer.deviceAddress = dstAddr;
flashXfer.port = kFLEXSPI_PortA1;
flashXfer.cmdType = kFLEXSPI_Write;
flashXfer.SeqNumber = 1;
flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD;
flashXfer.data = (uint32_t *)(void *)src;
flashXfer.dataSize = size;
// page program 子時序
FLEXSPI_TransferBlocking(base, &flashXfer);
// Read Status 子時序
flexspi_nor_wait_bus_busy(base);
FLEXSPI_SoftwareReset(base);
}
3.3 特殊用法(AHB寫)
我們現在來改造 IPG 方式的 Flash 寫入函數,首先要修改 deviceconfig 變量將 AWRSeqIndex 指向 PAGEPROGRAM_QUAD 在 LUT 中的位置,然后將 FLEXSPI_TransferBlocking() 函數換成 AHB 寫入代碼(memcpy 或者指針操作賦值),這時候 src 里的數據就會被自動放在大小為 64 bytes 的 AHB TX Buffer 里,SEQ_CTL 組件處理時會將緩存在 AHB TX Buffer 里的數據全部發送到 Flash 端。
但這里有一些限制,經實測,利用 memcpy 做 AHB 寫,一次僅能寫入 1/2/3/4/8 這五種有效長度的數據,其他數據長度不及預期(比如拷貝 5 - 7 字節,實際僅寫入前 4 字節;拷貝 8 字節以上,實際僅寫入前 8 字節),這其實跟 《i.MXRT中FlexSPI外設對AHB Burst Read特性的支持》 一文里提及的處理器 AHB Burst 策略有關,FlexSPI 每次僅會緩存一次 AHB Burst 寫數據進 AHB TX Buffer,而 SEQ_CTL 每工作一次都會使能一次 Flash 器件的數據處理流程(進入 Busy 狀態),因此連續的兩次 AHB burst 寫,后面一次的 burst 行為其實不產生實際 Flash 寫入效果。
flexspi_device_config_t deviceconfig = {
// 支持 AHB 寫的關鍵配置
.AWRSeqIndex = NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD,
.AWRSeqNumber = 1,
// ... 其他省略
};
void flexspi_nor_flash_program(FLEXSPI_Type *base, uint32_t dstAddr, const uint32_t *src, uint32_t size)
{
while (size)
{
// Write Enable 子時序
flexspi_nor_write_enable(base, 0);
uint32_t cpyBytes = 0;
if (size >= 8) { cpyBytes = 8; }
else if (size >= 4) { cpyBytes = 4; }
else { cpyBytes = size; }
memcpy((void *)dstAddr, (void *)src, cpyBytes);
__DSB();
// Read Status 子時序
flexspi_nor_wait_bus_busy(base);
dstAddr += cpyBytes;
src += cpyBytes / 4;
size -= cpyBytes;
}
FLEXSPI_SoftwareReset(base);
}
上面看似雞肋的 AHB 方式寫入 Flash 到底有什么用?底下痞子衡會講到 XECC 模塊,到時你就知道其用處了。
至此,i.MXRT下改造FlexSPI driver以AHB方式去寫入NOR Flash痞子衡便介紹完畢了,掌聲在哪里~~~
歡迎訂閱
文章會同時發布到我的 博客園主頁、CSDN主頁、知乎主頁、微信公眾號 平台上。
微信搜索"痞子衡嵌入式"或者掃描下面二維碼,就可以在手機上第一時間看了哦。