NXP 的 i.MX RT 系列 MCU 離不開 Flexspi 這個外設,Flexspi 是專為存儲設備設計的,以 SPI Flash 舉例,它支持單線、 4 線以及 8 線讀寫,熟悉 Flash 的小夥伴都知道需要一些命令才能對其操作, 對此 Flexspi 設計了一個稱為 LUT 的命令查找表,這篇文章就對 LUT 進行一個詳細介紹。
一、LUT 與 SPI Flash 命令表的聯繫
1. LUT 簡介
LUT 全稱是 Look Up Table ,顧名思義就是查找表格,可以自定義一些 Flash 的命令序列,然後提供給 Flexspi 去調用:
看到這裡可能還是很困惑這是個什麼東西,我們從大部分人接觸的 SPI Flash 說起,以 Winbond W25Q128JVSIQ 為例。
2. 標準 SPI Flash 命令表介紹
如下圖,平時接觸最多的 SPI 是四條線,也就是標準 SPI:CLK 、CS、 MOSI、 MISO,
假設現在要讀取 Flash 的 ID ,需要怎麼操作呢?首先在手冊中找到命令表:
注意這是標準 SPI 的命令表,可以看到讀取 Device ID 的命令是 90h ,h 代表十六進制,意思是代碼中發送該命令要使用 0x90,發送 0x90 之後是等待兩個 byte 的無效時鐘(可以在 MOSI 上發送兩個 byte 數據,此時 Flash 端不會理會這些數據),接下來再發送 0x00 ,發完之後就可以在 MISO 的數據線上讀取 Flash 返回的數據了,詳細過程如下圖;
以上就是對 Flash 操作的一個完整過程,其它的命令大同小異。
3. 4 線 QSPI Flash 命令表介紹
這裡再以一個 QSPI 的命令為例(QSPI 即 4 條數據線 + CS + CLK):
快速讀取數據的命令是 0xEB :
首先先發送 0xEB ;
再發送 24bit 的讀取地址,分 3 個 byte 發送 ;
接著是 3 個 dummy 時鐘 ;
注意此時的時鐘個數與上一個讀取 ID 命令的是有區別的,這裡 1 個 dummy 是 2 個時鐘周期,讀取 ID 的是 8 個 CLK ,這個其實也很好理解,標準 SPI 只有一條數據線發送\接收,那麼 1 byte 就需要 8 個時鐘周期才能完整讀取;
對於QSPI 而言它有 4 跟數據線,1 個時鐘周期可以讀取 4bit 數據,2 個時鐘周期就可以讀取 8bit 也就是 1 byte,所以 dummy 的時鐘周期也就減少了,等待 3 個dummy 周期後就可以接收數據了。
4. SPI Flash 命令對應到 LUT
以上兩個命令我們可以稱它們為 2 個命令序列,一個是讀取 Device ID 的命令序列,另外一個是快速讀取數據的命令序列,可以看到每個命令序列都能以 byte 為單位分解,裡面有操作命令(0x90,0xEB 等 )、dummy、地址等 ;
回到 Flexspi 的 LUT ,它的作用就是把常用的 Flash 命令序列提前放在了 LUT 的寄存器裡面,方便後續 Flesxspi 調用,只需要給命令序列編號,就能按照裡面編輯的命令順序執行了,從晶片 RM 中可以看到 LUT 一個序列的組成:
1 個命令序列稱為 Sequence (對於 i.MX RT1050 來說,它的 LUT 最多可以有 16 個序列,後面會解釋為何只有 16 個);
1 個 Sequence 中包含了 8 個Instruction ;( 這個 Instruction 可以將它理解為前面說的—— 1 個 Flash 命令序列能以 byte 為單位進行拆解,1 個 Instruction 就代表了一個 byte 的命令 )
1 個 Instruction 裡面有三個參數,分別是 opcode,num_pads,operand,解釋一下這三個參數的含義:
opcode:Flexspi 外設的專有命令;
比如現在需要向 Flash 發送讀取 ID 的命令 0x90 ,那麼此時就需要告訴 Flexspi 要發送操作命令到 Flash ,Flexspi 能識別的命令就是 CMD_SDR / CMD_DDR,關於 SDR 和 DDR 的區別可自行上網查找,大部分使用的是 CMD_SDR ,所以在這裡這個參數就需要填寫 0x01
SDK 中已經很貼心的將這些參數定義好了,編寫代碼的時候只需要調用就可以
num_pads:這個參數應該很好理解,就是執行 opcode 需要用到多少數據線,比如剛剛的 0x90 這個命令只需要一根,那麼只要給 0x0 即可,快速讀取的命令 0xEB 就需要用到 4 根線,給 0x3 即可,同樣 SDK 中的定義如下:
operand:這個參數代表的是執行 opcode 這個命令所需要的參數,還是以讀取 ID 的命令為例,剛剛 opcode 給的是 CMD_SDR 的命令,這個時候 Flexspi 它只知道了要發送命令過去,但是發送什麼命令過去它還不知道,這個時候就需要 operand 告訴它,所以這裡填參數 0x90 ;
根據以上描述,這裡就完成了一個序列的其中一個命令,讓我們來看看完成的是哪個部分:
二 、 代碼編寫
接下來以 SDK 中的例程來介紹怎麼將我們熟悉的 Flash 命令序列放進 LUT 當中。
1. 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 的修改如下:
(1)發送 0xEB 命令
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 ,會更規範
LUT 表的介紹就到這裡,這裡分別介紹了標準 SPI 和 QSPI 的命令並用一條命令舉例,其它的 Flash 命令序列也是一樣,單線、4 線主要根據命令的詳細時序編寫即可。
參考資料:
評論