一、LUT 與 SPI Flash 命令表的聯繫 LUT 全稱是 Look Up Table ,顧名思義就是查找表格,可以自定義一些 Flash 的命令序列,然後提供給 Flexspi 去調用:
看到這裡可能還是很困惑這是個什麼東西,我們從大部分人接觸的 SPI Flash 說起,以 Winbond W25Q128JVSIQ 為例。
注意這是標準 SPI 的命令表,可以看到讀取 Device ID 的命令是 90h ,h 代表十六進制,意思是代碼中發送該命令要使用 0x90,發送 0x90 之後是等待兩個 byte 的無效時鐘(可以在 MOSI 上發送兩個 byte 數據,此時 Flash 端不會理會這些數據),接下來再發送 0x00 ,發完之後就可以在 MISO 的數據線上讀取 Flash 返回的數據了,詳細過程如下圖;
以上就是對 Flash 操作的一個完整過程,其它的命令大同小異。 這裡再以一個 QSPI 的命令為例(QSPI 即 4 條數據線 + CS + CLK): 快速讀取數據的命令是 0xEB : 以上兩個命令我們可以稱它們為 2 個命令序列,一個是讀取 Device ID 的命令序列,另外一個是快速讀取數據的命令序列,可以看到每個命令序列都能以 byte 為單位分解,裡面有操作命令(0x90,0xEB 等 )、dummy、地址等 ; 回到 Flexspi 的 LUT ,它的作用就是把常用的 Flash 命令序列提前放在了 LUT 的寄存器裡面,方便後續 Flesxspi 調用,只需要給命令序列編號,就能按照裡面編輯的命令順序執行了,從晶片 RM 中可以看到 LUT 一個序列的組成:
opcode:Flexspi 外設的專有命令;
SDK 中已經很貼心的將這些參數定義好了,編寫代碼的時候只需要調用就可以 num_pads:這個參數應該很好理解,就是執行 opcode 需要用到多少數據線,比如剛剛的 0x90 這個命令只需要一根,那麼只要給 0x0 即可,快速讀取的命令 0xEB 就需要用到 4 根線,給 0x3 即可,同樣 SDK 中的定義如下: operand:這個參數代表的是執行 opcode 這個命令所需要的參數,還是以讀取 ID 的命令為例,剛剛 opcode 給的是 CMD_SDR 的命令,這個時候 Flexspi 它只知道了要發送命令過去,但是發送什麼命令過去它還不知道,這個時候就需要 operand 告訴它,所以這裡填參數 0x90 ; 根據以上描述,這裡就完成了一個序列的其中一個命令,讓我們來看看完成的是哪個部分: 接下來以 SDK 中的例程來介紹怎麼將我們熟悉的 Flash 命令序列放進 LUT 當中。 SDK 例程中用的是板載的 Flash 是 ISSI 的 IS25WP064,這裡的命令與 Winbond 有些不一樣的,在這個基礎上修改成 Winbond 的,修改之前先解釋一下為什麼前面會有 “4 * 序列號”的操作:
1 個 Sequence 規定需要有 8 個 Instruction ,規定這麼多個是為了適配一些複雜的操作 ;可以看到 1 個 Instruction 是 16bit ,那麼2 個Instruction 就是 32bit ,1 個 Sequence 就需要 4 * 32bit ,對於 i.MX RT1050 來說, LUT 寄存器只有 64 個,1 個寄存器 32bit ,那麼 1 個 Sequence 就要 4 個寄存器,所以最多只能有 16 個 Sequence ,但是這個已經遠遠夠用了,平時用的無非就是擦數據、寫數據、讀數據。 FLEXSPI_LUT_SEQ 這個宏是將兩個 Instruction 合在一起變成 32bit 的數據,方便放進一個 LUT 寄存器當中
定義的數組是直接寫進 LUT 寄存器的,長度是 64,類型是 uint32_t 的,所以其中一個數據就是 32bit ;將 64 個 LUT 寄存器分成 16 份,1 份就是 4 個寄存器,前面說過 1 個 Sequence 就需要 4 個寄存器,所以出現了 “4 * 序列號” 這個操作,那麼接下來的 “4 * 序列號 + 1” 也很好理解了,FLEXSPI_LUT_SEQ 只能合併 2 個 Instruction ,有些 Flash 命令序列 2 個 Instruction是滿足不了的,所以需要繼續往下加,“4 * 序列號 + 1” 就是往下一個相鄰的 LUT 寄存器寫,可以自己將序列號代入進去理解,這裡不再舉例說明。
2. 以讀取 ID 的命令為例,下圖是 ISSI 的:
序列號的宏定義可以自行定義,這裡沿用例程的,根據文章開頭說的,Winbond 的命令如下: (1)發送 0x90 命令 Instruction0 Opcode = kFLEXSPI Command SDR num_pads = kFLEXSPI 1PAD operand = 0x90 (2)等待兩個 dummy Instruction1 Opcode = kFLEXSPI_Command_DUMMY_SDR num_pads = kFLEXSPI 1PAD operand = 0x10 ( dummy clock 的個數) (3)發送 0x00 命令 Instruction2 Opcode = kFLEXSPI Command SDR num_pads = kFLEXSPI 1PAD operand = 0x00 (4)讀取數據 Instruction3 Opcode = kFLEXSPI_Command_READ_SDR num_pads = kFLEXSPI 1PAD operand = 0x04 (該參數無意義,非 0 即可) (5)發送 Stop 命令 Instruction4 Opcode = kFLEXSPI_Command_STOP num_pads = kFLEXSPI 1PAD operand = 0x00 以上就是全部步驟,實際上第 5 個步驟不加也不會出問題,因為 LUT 表的數值默認為 0 ,其 3 個參數值都為 0 ,故不影響,為了規範建議還是加上去,該序列如下:
那麼這裡就會有人問了,讀取數據的長度不用給嗎?這個長度是在調用庫函數裡面給的:
2. 以快速讀取數據的命令為例,下圖是 ISSI 的: 同樣,Winbond 的修改如下:
Instruction0 Opcode = kFLEXSPI Command SDR num_pads = kFLEXSPI 1PAD operand = 0xEB
(2)發送 24bit 讀取地址 Instruction1 Opcode = kFLEXSPI_Command_RADDR_SDR num_pads = kFLEXSPI 1PAD operand = 0x18 (讀取地址的長度,單位 bit, 這裡用 24bit )
(3)發送 M7-0 dummy Instruction2 Opcode = kFLEXSPI_Command_MODE8_SDR num_pads = kFLEXSPI 4PAD operand = 0xFF (4)等待 2 個 dummy Instruction3 Opcode = kFLEXSPI_Command_DUMMY_SDR num_pads = kFLEXSPI 4PAD operand = 0x04 (這裡代表發送 4 個 clock 周期)
(5)讀取數據 Instruction4 Opcode = kFLEXSPI_Command_READ_SDR num_pads = kFLEXSPI 4PAD operand = 0x04 (這裡參數同樣沒有意義,非 0 即可)
(6)發送停止命令 Instruction5 Opcode = kFLEXSPI_Command_STOP num_pads = kFLEXSPI 1PAD operand = 0x00 如下圖,最後一行加上去是為了體現一個完整的 Sequence ,會更規範
參考資料: |