注意:所有SPI相關的API都不能在中斷服務函數或上下文切換期間使用,因為SPI相關的API都調用了互斥量,可能會造成系統錯誤
SPI
調用#include "driver/spi_master.h"
或#include "driver/spi_slave.h"
來使用API組件
ESP32集成了4個SPI外設
SPI0和SPI1通過總線仲裁器共享一條信號總線,用於在模組內部訪問FLASH(SoC FLASH),不會對用戶開放
SPI2和SPI3是通用SPI控制器,有時也被稱為HSPI和VSPI,這里的HSPI和VSPI沒有實際區別,只是為了標識兩個SPI,他們都既可以作為主機使用也可以作為從機使用。SPI控制器擁有獨立的信號總線,每條總線都有三條片選(CS)信號,也就是說每個控制器都能驅動最多3個SPI從器件。這兩個SPI控制器對用戶開放
相關概念參考SPI協議,以下內容默認讀者學習過SPI、QSPI基礎知識。
表格翻譯自官網
概念 | 定義 |
---|---|
Host | 作為SPI主設備使用的ESP32片上SPI外設,目前只能使用SPI2或SPI3 |
Device | 片外SPI從設備 |
Bus | SPI信號總線,通常包括MISO、MOSI、SCLK、多條CS線、可選的QUADWP和QUADHD線 |
MISO | Master in Slave out |
MOSI | Master in Slave out |
SCLK | 同步時鍾 |
CS | 片選信號 |
QUADWP | 寫保護信號,當且僅當使用QSPI時啟用 |
QUADHD | 保持信號,當且僅當使用QSPI時啟用 |
Assertion | 啟用某條信號線的行為;相反的行為稱為de-assertion. |
Transaction | 啟動片選、傳輸數據、結束傳輸、斷開片選這一系列傳輸過程稱為一次傳輸。傳輸是原子性的,不能被中斷 |
Launch edge | 源寄存器觸發launches信號到總線的時鍾邊沿 |
Latch edge | 源寄存器觸發latches in信號刀縱線的時鍾邊沿 |
對應的SPI-GPIO映射表如下所示
引腳對應的GPIO | SPI2 | SPI3 |
---|---|---|
CS0* | 15 | 5 |
SCLK | 14 | 18 |
MISO | 12 | 19 |
MOSI | 13 | 23 |
QUADWP | 2 | 22 |
QUADHD | 4 | 21 |
SPI Master
ESP32內部的SPI控制器可設置為主模式(Master),基本特點如下
- 適應多線程環境
- 可配置DMA輔助傳輸
- 在同一信號線上自動分配時間處理來自不同設備的的多路數據
但是SPI控制器不是永遠安全的,用戶最好重構代碼來讓每個SPI外設在同一時間只被一個任務訪問(避免臨界區出現)或使用互斥量來處理臨界區
臨界區相關的處理參考RTOS部分
SPI傳輸時序
SPI的傳輸格式有以下五個組成部分
- 指令數據段
主機發送0-16位指令
- 地址數據段
主機發送0-64位地址
- 寫入數據
主機向外設發送數據,允許附帶可選的指令和地址數據,但這些數據無法從電器層面區分
- 空段
用於同步時序
- 讀取數據
外設向主機發送數據
物理層傳輸屬性由spi_bus_config_t結構體、spi_transaction_t結構體和spi_device_interface_config_t結構體設置
//spi_bus_config_t用於配置GPIO的SPI復用引腳和SPI控制器
//注意:如果不使用QSPI可以直接不初始化quadwp_io_num和quadhd_io_num,總線會自動關閉未被配置的信號線
//如果不使用某線應將其設置為-1
struct spi_bus_config_t={
.miso_io_num,//MISO信號線,可復用為QSPI的D0
.mosi_io_num,//MOSI信號線,可復用為QSPI的D1
.sclk_io_num,//SCLK信號線
.quadwp_io_num,//WP信號線,專用於QSPI的D2
.quadhd_io_num,//HD信號線,專用於QSPI的D3
.max_transfer_sz,//最大傳輸數據大小,單位字節,默認為4094
.intr_flags,//中斷指示位
};
//spi_transaction_t用於配置SPI的數據格式
//注意:這個結構體只定義了一種SPI傳輸格式,如果需要多種SPI傳輸則需要定義多個結構體並進行實例化
struct spi_transaction_t={
.cmd,//指令數據,其長度在spi_device_interface_config_t中的command_bits設置
.addr,//地址數據,其長度在spi_device_interface_config_t中的address_bits設置
.length,//數據總長度,單位:比特
.rxlength,//接收到的數據總長度,應小於length,如果設置為0則默認設置為length
.flags,//SPI傳輸屬性設置
.user,//用戶定義變量,可以用來存儲傳輸ID等注釋信息
.tx_buffer,//發送數據緩存區指針
.tx_data,//發送數據
.rx_buffer,//接收數據緩存區指針,如果啟用DMA則需要至少4個字節
.rx_data//如果設置了SPI_TRANS_USE_RXDATA,數據會被這個變量直接接收
};
//spi_device_interface_config_t用於配置SPI協議情況
//需要根據從設備的數據手冊進行設置
struct spi_device_interface_config_t={
.command_bits,//默認控制位長度,設置為0-16
.address_bits,//默認地址位長度,設置為0-64
.dummy_bits,//在地址和數據位段之間插入的dummy位長度,用於匹配時序,一般可以保持默認
.clock_speed_hz,//時鍾頻率,設置的是80MHz的分頻系數,單位為Hz
.mode,//SPI模式,設置為0-3
.duty_cycle_pos,//
.cs_ena_pretrans,//傳輸前CS信號的建立時間,只在半雙工模式下有用
.cs_ena_posttrans,//傳輸時CS信號的保持時間
.input_delay_ns,//從機的最大合法數據傳輸時間
.spics_io_num,//設置GPIO復用為CS引腳
.queue_size,//傳輸隊列大小,決定了等待傳輸數據的數量
.flags,//SPI設備屬性設置
.pre_cb,//傳輸開始時的回調函數
.post_cb,//傳輸結束時的回調函數
};
SPI主機可以發起全雙工/半雙工的通信,全雙工通信中,總傳輸數據長度由spi_device_interface_config_t::command_bits、spi_device_interface_config_t::address_bits、spi_transaction_t::length決定,spi_transaction_t::rxlength僅決定了緩存區接收數據的長度;但半雙工通信中,讀寫不同步,總傳輸數據長度由只spi_transaction_t::length和spi_transaction_t::rxlength決定
指令和地址數據段是可選的,不是所有SPI設備都需要指令和/或數據,所以在spi_device_interface_config_t結構體中,如果設置command_bits和address_bits為0,就不會發送指令和數據
讀寫數據段也是可選的,如果rx_buffer設置為NULL,SPI_TRANS_USE_RXDATA沒有定義,則讀取數據段會被跳過;同理,如果tx_buffer設置為NULL,SPI_TRANS_USE_TXDATA沒有定義,則寫入數據段會被跳過
ESP32的驅動提供了兩種傳輸方式:
- 中斷傳輸:發送、接收時觸發中斷
- 輪詢傳輸:輪詢SPI設備狀態,如果空閑則可以調用函數
兩種模式也可以同時使用,但是實現代碼較復雜
官網給出解釋為
Notes on Sending Mixed Transactions to the Same Device
To reduce coding complexity, send only one type of transactions (interrupt or polling) to one Device. However, you still can send both interrupt and polling transactions alternately. The notes below explain how to do this.
The polling transactions should be initiated only after all the polling and interrupt transactions are finished.
Since an unfinished polling transaction blocks other transactions, please do not forget to call the function
spi_device_polling_end()
afterspi_device_polling_start()
to allow other transactions or to allow other Devices to use the bus. Remember that if there is no need to switch to other tasks during your polling transaction, you can initiate a transaction withspi_device_polling_transmit()
so that it will be ended automatically.In-flight polling transactions are disturbed by the ISR operation to accommodate interrupt transactions. Always make sure that all the interrupt transactions sent to the ISR are finished before you call
spi_device_polling_start()
. To do that, you can keep callingspi_device_get_trans_result()
until all the transactions are returned.To have better control of the calling sequence of functions, send mixed transactions to the same Device only within a single task.
也就是說輪詢模式下的兩個API相當於開關,一個讓CPU進入輪詢狀態,一個讓CPU退出輪詢狀態,需要配合使用;中斷模式下的API相當於一個指令,讓SPI控制器在發送/接收完畢時發出中斷告知CPU,CPU接收到中斷后執行中斷服務函數;輪詢狀態下應當保證沒有中斷,否則會影響實時性,但是中斷狀態下CPU可以干別的事
SPI傳輸模式
中斷傳輸
中斷傳輸期間,CPU可以執行其他任務。傳輸結束時,SPI外設觸發中斷,CPU調用任務處理函數進行處理
注意:一個任務可以排列多個傳輸序列,驅動程序會自動在中斷服務程序(ISR)中對傳輸結果進行處理;但是中斷傳輸會導致很多中斷,如果設置中斷任務太多還會影響日常任務運行降低實時性能
輪詢傳輸
輪詢傳輸會輪詢SPI外設的狀態寄存器(官網原文為狀態位)直到傳輸完成
輪詢傳輸可以節約ISR隊列掛起等待和線程(任務)上下文切換所需時間,但是會導致CPU占用
使用API spi_device_polling_end()來進行輪詢,這個API至少需要1us時間解除對其他任務的阻塞;官方推薦使用spi_device_acquire_bus()和spi_device_release_bus()來進行調度
SPI使用
- 設定並初始化GPIO復用為SPI
調用spi_bus_initialize()來初始化SPI總線,使用spi_bus_config_t結構體設置GPIO引腳
注意不使用的信號線要設置為-1
esp_err_t spi_bus_initialize(spi_host_device_t host,//SPI設備號
const spi_bus_config_t *bus_config,//總線GPIO設置
int dma_chan)//使用的DMA通道
//總線初始化API
//如果使能了DMA通道,所有傳輸和使用的數據接收緩沖區都應該在支持DMA訪問的內存區域中申請
spi_host_device_t={
SPI1_HOST=0,
SPI2_HOST,
SPI3_HOST,
}
//dma_chan只能設置為0,1,2;設置為0則不啟用DMA
- 使用spi_bus_add_device()設置SPI控制器設備
該步驟的目的是通知FreeRTOS驅動有一個SPI設備連接到了總線上
esp_err_t spi_bus_add_device(spi_host_device_t host,//SPI設備號
const spi_device_interface_config_t *dev_config,//數據格式設置
spi_device_handle_t *handle)//設備句柄
這個API會根據spi_device_interface_config_t結構體初始化一個SPI外設並規定具體的時序
注意不要過度使用:ESP32只有2個可用的SPI控制器,一個SPI控制器只有三個CS信號線,最多能控制6個從設備
全雙工下,SPI最高速度可達80MHz,一般使用40Mhz;而半雙工下,最高只能達到26MHz
-
設置一個或多個spi_transaction_t結構體來配置傳輸的數據格式
注意:需要等待當前傳輸完成后再發起新的傳輸
- 中斷模式
使用
esp_err_t spi_device_queue_trans(spi_device_handle_t handle,//SPI設備句柄 spi_transaction_t *trans_desc,//要執行的傳輸 TickType_t ticks_to_wait)//等待時間,如果設置為MAX_DELAY則會等待到隊滿 //將要執行的傳輸放入SPI傳輸隊列 esp_err_t spi_device_get_trans_result(spi_device_handle_t handle,//SPI設備句柄 spi_transaction_t **trans_desc,//之前執行的傳輸的指針 TickType_t ticks_to_wait)//等待時間 //獲取此前由spi_device_queue_trans發起傳輸的結果
來將一個傳輸加入待傳輸隊列
可以通過spi_device_get_trans_result()查詢傳輸結果;也可以將所有結果放入以下API,使用中斷處理函數和FSM來設定各種傳輸結果對應的操作
esp_err_t spi_device_transmit(spi_device_handle_t handle,//SPI設備句柄 spi_transaction_t *trans_desc)//要執行的傳輸 //發起一次SPI傳輸,等待完成並返回結果,該函數和spi_device_queue_trans+spi_device_get_trans_result共同使用等價
- 輪詢模式
使用以下API來發起輪詢模式的傳輸
esp_err_t spi_device_polling_transmit(spi_device_handle_t handle,//SPI設備句柄 spi_transaction_t *trans_desc)//要執行的傳輸 //發起一次輪詢模式下的傳輸,等待完成后返回結果 //此函數和spi_device_polling_start+spi_device_polling_end共同使用等價
如果需要再發送傳輸中間插入其他代碼,可以使用以下兩個API
esp_err_t spi_device_polling_start(spi_device_handle_t handle,//SPI設備句柄 spi_transaction_t *trans_desc,//要執行的傳輸 TickType_t ticks_to_wait)//等待時間 //立刻發起一次輪詢模式傳輸 esp_err_t spi_device_polling_end(spi_device_handle_t handle,//SPI設備句柄 TickType_t ticks_to_wait)//等待時間 //使CPU保持輪詢直到傳輸完成,這個任務直到成功完成才能結束且是非阻塞的——其他線程(任務)可以在傳輸期間占用CPU(當然會降低效率)
-
發送/接收數據
可以使用
esp_err_t spi_device_acquire_bus(spi_device_handle_t device, TickType_t wait);//釋放總線
和
void spi_device_release_bus(spi_device_handle_t dev);//釋放總線
來讓主設備一直占用總線,可以在兩個API調用期間間斷地發送數據
可以使用
esp_err_t spi_bus_remove_device(spi_device_handle_t handle)
//將設備從SPI總線上移除
和
esp_err_t spi_bus_free(spi_host_device_t host)
//釋放總線
來解除目標設備對SPI總線的占用,並釋放系統資源
傳輸速率
SPI傳輸速率與以下因素有關:
- 傳輸間隔
- SPI時鍾頻率(主要因素)
- SPI控制函數與回調函數的執行延遲
典型的傳輸間隔如下所示:
典型傳輸時間 (us) | ||
---|---|---|
中斷模式 | 輪詢模式 | |
使用DMA | 24 | 8 |
不使用DMA | 22 | 7 |
SPI Slave
SPI從設備驅動負責處理ESP32作為從設備的情況
SPI2和SPI3也能獨立地作為從設備使用,支持全雙工四線SPI、半雙工DSPI、半雙工QSPI,支持收發64字節數據和使能DMA傳輸
從模式的基本配置
使用spi_slave_interface_config_t結構體來設置SPI從模式的物理接口
使用spi_slave_transaction_t結構體設置從模式下的數據格式和數據緩沖區大小等
使用spi_transaction_t結構體配置單獨收取/單獨發送等特殊情況的傳輸數據格式
結構體原型如下
//配置
spi_device_interface_config_t devcfg={
.command_bits=0,
.address_bits=0,
.dummy_bits=0,
.clock_speed_hz=5000000,
.duty_cycle_pos=128, //50% duty cycle
.mode=0,
.spics_io_num=GPIO_CS,
.cs_ena_posttrans=3,
//Keep the CS low 3 cycles after transaction, to stop slave from missing the last bit when CS has less propagation delay than CLK
.queue_size=3
};
//用於配置SPI從機接口的spi_slave_interface_config_t結構體
spi_slave_interface_config_t slvcfg={
.mode,//SPI模式,配置為0-3
.spics_io_num,//片選信號線復用IO
.queue_size,//傳輸隊列大小,設置同時最多有多少掛起的傳輸
.flags,//接口屬性,使用位或運算符|連接各屬性參數
.post_setup_cb,//SPI寄存器加載新數據時調用的回調函數
.post_trans_cb//傳輸完成回調函數
};
//描述一次SPI傳輸的結構體
spi_slave_transaction_t
{
.length,//總數據長度
.trans_len,//傳輸數據長度
.tx_buffer,//數據發送緩沖區指針
.rx_buffer,//數據接收緩沖區指針
.user//用戶定義變量,一般用於存儲本次傳輸的ID
}
//注意:上述長度的單位是比特
//用於配置SPI總線的spi_bus_config_t結構體
spi_bus_config_t buscfg={
.mosi_io_num=GPIO_MOSI,
.miso_io_num=GPIO_MISO,
.sclk_io_num=GPIO_SCLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
};
//未使用的參數應設置為-1
如果spi_slave_interface_config_t::rx_buffer=NULL,則跳過讀取數據段;如果spi_slave_interface_config_t::tx_buffer=NULL,則跳過寫入數據段
傳輸開始前,應當配置好一個或以上的spi_slave_transaction_t結構體
注意:如果傳輸的數據大於32字節,需要使能DMA通道1或通道2,如果不使用DMA,應將dma_chan參數設置為0
傳輸相關API與使用步驟
//所有形式的SPI設備都需要調用這個API來進行初始化
esp_err_t spi_bus_initialize(spi_host_device_t host,//SPI設備號
const spi_bus_config_t *bus_config,//總線GPIO設置
int dma_chan)//使用的DMA通道
//SPI從設備初始化
esp_err_t spi_slave_initialize(spi_host_device_t host,//SPI設備號
const spi_bus_config_t *bus_config,//SPI總線設置
const spi_slave_interface_config_t *slave_config,//SPI接口設置
int dma_chan)//使用的DMA通道
//卸載SPI從設備驅動
esp_err_t spi_slave_free(spi_host_device_t host)//SPI設備號
//發起SPI從設備隊列傳輸數據
esp_err_t spi_slave_queue_trans(spi_host_device_t host,//SPI設備號
const spi_slave_transaction_t *trans_desc,//傳輸的數據
TickType_t ticks_to_wait)//等待時間
/* 將一條SPI消息掛到SPI傳輸隊列,並等待發送 */
//獲取隊列傳輸數據的結果
esp_err_t spi_slave_get_trans_result(spi_host_device_t host,//SPI設備號
spi_slave_transaction_t **trans_desc,//傳輸數據指針
TickType_t ticks_to_wait)//等待時間
/* 在使用spi_slave_queue_trans到使用spi_slave_get_trans_result之間,CPU會輪詢傳輸結果,使用該API獲取結果 */
//發起一次SPI傳輸
esp_err_t spi_slave_transmit(spi_host_device_t host,//SPI設備號
spi_slave_transaction_t *trans_desc,//傳輸的數據
TickType_t ticks_to_wait)//等待時間
//該函數與連用spi_slave_queue_trans和spi_slave_get_trans_result效果相同
- 使用spi_bus_config_t結構體和spi_bus_initialize()函數創建SPI總線
- 使用spi_device_interface_config_t結構體和spi_bus_add_device()函數向SPI總線上添加新設備
- 使用spi_slave_queue_trans、spi_slave_get_trans_result、spi_slave_transmit三個API進行傳輸數據
- 如果需要卸載SPI從設備驅動,使用spi_slave_free
注意:如果使用了DMA,需要保證使用pvPortMallocCaps(size, MALLOC_CAP_DMA)為緩沖區開辟內存,這樣可以保障DMA能夠訪問到這些緩沖區
DMA和初始化API的配置與主模式類似,驅動函數的使用方法也類似,在此不做介紹,關注API即可
目前存在的DMA缺陷(摘自官網)
Restrictions and Known Issues
If DMA is enabled, the rx buffer should be word-aligned (starting from a 32-bit boundary and having a length of multiples of 4 bytes). Otherwise, DMA may write incorrectly or not in a boundary aligned manner. The driver reports an error if this condition is not satisfied.
Also, a Host should write lengths that are multiples of 4 bytes. The data with inappropriate lengths will be discarded.
Furthermore, DMA requires SPI modes 1 and 3. For SPI modes 0 and 2, the MISO signal has to be launched half a clock cycle earlier to meet the timing. The new timing is as follows:
If DMA is enabled, a Device’s launch edge is half of an SPI clock cycle ahead of the normal time, shifting to the Master’s actual latch edge. In this case, if the GPIO matrix is bypassed, the hold time for data sampling is 68.75 ns and no longer a half of an SPI clock cycle. If the GPIO matrix is used, the hold time will increase to 93.75 ns. The Host should sample the data immediately at the latch edge or communicate in SPI modes 1 or 3. If your Host cannot meet these timing requirements, initialize your Device without DMA.
- 啟用DMA時應當將收發緩存設定為字對齊模式(是4字節的倍數)
- 從機模式的DMA需要主機時鍾的保持時間足夠長才能工作,如果主機無法滿足只能放棄使用DMA
SPI使用例
參考esp-idf/example/peripheral/spi/部分示例
SPI FLASH組件
以下內容部分摘自官網原文,黑體部分為強調和個人理解
SPI Flash 組件提供外部 flash 數據讀取、寫入、擦除和內存映射相關的 API 函數,同時也提供了更高層級的、面向分區的 API 函數(定義在分區表部分)
注意:ESP-IDF V4.0后餓FLASH API不再是原子的,如果 flash 操作地址有重疊,且寫操作與讀操作同時執行,讀操作可能會返回一部分寫入之前的數據,返回一部分寫入之后的數據
FLASH設備的使用
初始化
設置方式類似基本的SPI API調用,具體步驟如下
- 調用spi_bus_initialize()初始化SPI總線
- 調用spi_bus_add_flash_device()將片外FLASH作為從設備掛載到SPI總線,並分配內存、填充esp_flash_t結構體、初始化CS信號線
- 調用esp_flash_init()與芯片進行通信(注意:目前多個FLASH芯片可以連接到同意總線,但尚不支持在同一個SPI總線上使用esp_flash_*和spi_device_*設備)
訪問FLASH
一般來說應盡量避免對主SPI flash芯片直接使用原始SPI flash函數,如需對主SPI flash芯片進行操作應使用分區表API
使用以下API訪問片外SPI FLASH
esp_err_t esp_flash_read(esp_flash_t *chip,//指向已識別FLASH對象地指針
void *buffer,//讀取數據緩沖區指針,當保存在RAM且字對齊時具有更好的使用性能
uint32_t address,//待讀取數據的FLASH地址,必須小於chip->size
uint32_t length);//待讀取的數據長度
//將數據無對齊地從flash讀取到RAM
//chip需要用esp_flash_init()初始化過才能使用
esp_err_t esp_flash_write(esp_flash_t *chip,//指向已識別FLASH對象地指針
const void *buffer,//寫入數據緩沖區指針
uint32_t address,//待寫入數據的FLASH地址
uint32_t length);//待寫入的數據長度
//將數據無對齊地從RAM寫入到 flash
esp_err_t esp_flash_write_encrypted(esp_flash_t *chip,//指向已識別FLASH對象地指針
uint32_t address,//待寫入數據的FLASH地址
const void *buffer,//寫入數據緩沖區指針
uint32_t length);//待寫入的數據長度
//使用片上硬件FLASH加密外設,加密寫入數據
//注意:地址和數據長度都應該是16位對齊
esp_err_t esp_flash_read_encrypted(esp_flash_t *chip,//指向已識別FLASH對象地指針
uint32_t address,//待讀取數據的FLASH地址
void *out_buffer,//讀取數據緩沖區指針
uint32_t length);//待讀取的數據長度
//使用片上硬件FLASH加密外設,加密讀取數據
esp_err_t esp_flash_erase_region(esp_flash_t *chip,
uint32_t start,//起始地址
uint32_t len);//擦除長度
//擦除 flash 中指定區域的數據
//注意:擦除的地址一定要和扇區對齊!!!
esp_err_t esp_flash_erase_chip(esp_flash_t *chip);//擦除整個 flash
esp_err_t esp_flash_get_size(esp_flash_t *chip,
uint32_t *out_size);
//根據FLASH ID檢測FLASH容量(以字節為單位)
FLASH容量存儲在引導程序映像頭不燒錄偏移量為0x1000的一個字段
默認情況下燒錄引導程序時,esptool會自動檢測SPI FLASH容量並使用正確容量更新引導程序的頭部;但是也可以設置CONFIG_ESPTOOLPY_FLASHSIZE生成固定FLASH容量
struct esp_flash_t//描述片外FLASH的結構體,應當使用esp_flash_init()進行初始化
{
spi_flash_host_driver_t *host,//SPI驅動結構體句柄
const spi_flash_chip_t *chip_drv,//FLASH驅動“適配”結構體
const esp_flash_os_functions_t *os_func,
//RTOS鈎子函數,使用esp_flash_init_os_functions()設置
os_func_data,
//RTOS鈎子函數的參數
esp_flash_io_mode_t read_mode,//配置FLASH讀取模式
uint32_t size,//FLASH容量,如果設置為0則會在初始化期間檢測
uint32_t chip_id//檢測FLASH ID
}
//SPI驅動結構體,配置主機驅動和上下文
//使用了c面向對象的方式,將方法(或者說是成員函數)用函數指針封裝在結構體對象
//上面提到的那些API基本都被歸納進了這個結構體
struct spi_flash_host_driver_t
{
void *driver_data,//驅動數據
esp_err_t (*dev_config)(spi_flash_host_driver_t *driver),//設備驅動寄存器設置方法
esp_err_t (*common_command)(spi_flash_host_driver_t *driver, spi_flash_trans_t *t),
//用戶定義傳輸指令方法
esp_err_t (*read_id)(spi_flash_host_driver_t *driver, uint32_t *id),//讀取FLASH ID的方法
void (*erase_chip)(spi_flash_host_driver_t *driver),//全片擦除方法
void (*erase_sector)(spi_flash_host_driver_t *driver, uint32_t start_address),//扇區擦除方法
void (*erase_block)(spi_flash_host_driver_t *driver, uint32_t start_address),//塊擦除方法
esp_err_t (*read_status)(spi_flash_host_driver_t *driver, uint8_t *out_sr),//讀取FLASH狀態方法
esp_err_t (*set_write_protect)(spi_flash_host_driver_t *driver, bool wp),//關閉寫保護方法
void (*program_page)(spi_flash_host_driver_t *driver,
const void *buffer,
uint32_t address,
uint32_t length),
//按頁寫入FLASH並檢查最大寫入字節數方法
bool (*supports_direct_write)(spi_flash_host_driver_t *driver, const void *p),
//檢查寫入中是否需要申請新的頁方法
bool (*supports_direct_read)(spi_flash_host_driver_t *driver, const void *p),
//檢查讀取中是否需要申請新的頁方法
int max_write_bytes,//每頁的最大寫入字節數
esp_err_t (*read)(spi_flash_host_driver_t *driver,
void *buffer,
uint32_t address,
uint32_t read_len)
//從FLASH中讀取數據並檢查最大讀取字節數方法
int max_read_bytes,//最大讀取字節數
bool (*host_idle)(spi_flash_host_driver_t *driver),//檢查SPI主機是否空閑方法
esp_err_t (*configure_host_io_mode)(spi_flash_host_driver_t *driver,
uint32_t command,
uint32_t addr_bitlen,
int dummy_bitlen_base,
esp_flash_io_mode_t io_mode),
//設置主機工作在不同的讀取模式,響應補償時間、設置IO模式方法
void (*poll_cmd_done)(spi_flash_host_driver_t *driver),//使硬件保持輪詢直到操作完畢方法
esp_err_t (*flush_cache)(spi_flash_host_driver_t *driver,
uint32_t addr,
uint32_t size)
//清空主機(如SPI1)所用的緩存區方法
}
初始化外部FLASH的示例如下:
static esp_flash_t* example_init_ext_flash(void)
{
const spi_bus_config_t bus_config = {
.mosi_io_num = VSPI_IOMUX_PIN_NUM_MOSI,
.miso_io_num = VSPI_IOMUX_PIN_NUM_MISO,
.sclk_io_num = VSPI_IOMUX_PIN_NUM_CLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
};
const esp_flash_spi_device_config_t device_config = {
.host_id = VSPI_HOST,
.cs_id = 0,
.cs_io_num = VSPI_IOMUX_PIN_NUM_CS,
.io_mode = SPI_FLASH_DIO,
.speed = ESP_FLASH_40MHZ
};
ESP_LOGI(TAG, "初始化外部SPI FLASH");
ESP_LOGI(TAG, "引腳設定:");
ESP_LOGI(TAG, "MOSI: %2d MISO: %2d SCLK: %2d CS: %2d",
bus_config.mosi_io_num, bus_config.miso_io_num,
bus_config.sclk_io_num, device_config.cs_io_num
);
//初始化SPI總線
ESP_ERROR_CHECK(spi_bus_initialize(VSPI_HOST, &bus_config, 1));
//將FLASH設備添加到SPI總線
esp_flash_t* ext_flash;
ESP_ERROR_CHECK(spi_bus_add_flash_device(&ext_flash, &device_config));
//連接、檢查FLASH並進行初始化
esp_err_t err = esp_flash_init(ext_flash);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "連接外部FLASH失敗: %s (0x%x)", esp_err_to_name(err), err);
return NULL;
}
//執行 輸出ID和大小 任務
uint32_t id;
ESP_ERROR_CHECK(esp_flash_read_id(ext_flash, &id));
ESP_LOGI(TAG, "Initialized external Flash, size=%d KB, ID=0x%x", ext_flash->size / 1024, id);
return ext_flash;
}