1. 程式人生 > >ESP32裝置SPI主裝置驅動

ESP32裝置SPI主裝置驅動

1、背景

目前,由於要存放本地音樂,晶片內部的Flash大小不夠,所以要用到外部SPI Flash。暫時選擇晶片W25Q127.因此有必要研究一下ESP32的SPI外設。

1.1 參考文獻

ESP-IDF程式設計指南 https://docs.espressif.com/projects/esp-idf/zh_CN/latest/index.html

ESP32 學習筆記(八)SPI - SPI Master https://blog.csdn.net/qq_27114397/article/details/81612693

1.2 ESP32 SPI MASTER的簡介翻譯

1.2.1 ESP32的SPI外設

ESP32有四個SPI外設,分別為SPI0、SPI1、HSPI和VSPI。SPI0是專用於Flash的快取,ESP32將連線的SPI Flash裝置對映到記憶體中。SPI1和SPI0 使用相同的硬體線,SPI1用於寫入flash晶片。HSPI和VSPI可以任意使用。SPI1、HSPI和VSPI共有三條片選線,因此作為SPI主機允許ESP32 至多驅動三個SPI裝置。

1.2.2 SPI 主機驅動

SPI主機驅動可以和從機輕鬆通訊,在多執行緒中也一樣。它完全透明地使用DMA傳輸讀寫資料並自動處理同一主機上不同SPI從機之間的多路複用。

注意執行緒安全: 從不同任務訪問同一總線上的多個SPI裝置時,SPI驅動API是執行緒安全的;但是當從不同任務訪問同一的SPI裝置時,SPI驅動API不是執行緒安全的。

在這種情況下,建議要麼重構應用程式程式以確保只有一個任務訪問每個SPI裝置;或者新增互斥鎖在訪問同一裝置時。

1.2.3 可能涉及到的術語

Host(主裝置):ESP32內部啟動SPI傳輸的SPI外設(稱為主裝置)。SPI或HSPI或VSPI之一(就目前而言,驅動實際只支援HSPI或VSPI,未來會三種外設都支援)。

Bus(匯流排): SPI匯流排,與連線到同一個主機上的所有SPI裝置所共用。通常,匯流排由miso、mosi、sclk和可選的quadwp和quadhd訊號組成。SPI從裝置並聯到這些訊號上。

Device(裝置):SPI從裝置。每個SPI從裝置都有它自己的片選線。當傳送到/從SPI從裝置的傳輸時,該片選線被選中。

Transaction(事務): CS啟用,傳送到/從SPI從裝置的資料傳輸,以及CS再次失活的一個例項。事務是原子操作,因為他們不會被另一個事務中斷。

1.2.4 詳細的SPI 事務

SPI 總線上的事務由五個階段組成,其中任何階段都可以跳過。這些階段為

  • 命令階段. 在此階段,命令(0-16 位)被輸出.
  • 地址階段. 在此階段,地址(0-64 位)被輸出.
  • 寫階段. 主裝置將資料傳送到從裝置.
  • 虛擬階段. 該階段是可配置的,用於滿足時序要求.
  • 閱讀階段. 從裝置將資料傳送給主裝置.

這裡只翻譯全雙工模式:讀寫兩階段合併,SPI主機同時讀寫裝置。事務總長度command_bits + address_bits + trans_conf.length決定;而trans_conf.length只決定了接收到緩衝區中資料大小。

命令和地址階段是可選的因為並不是每個SPI從裝置需要傳送命令和地址。這些都反應在裝置配置中:將command_bits和address_bit 欄位設定為0,則命令階段和地址階段就跳過了。

並不是每個事務都同時需要讀寫資料。當rx_buffer是NULL(SPI_USE_RXDATA沒有設定),則讀階段就跳過了。同樣。當tx_buffer是NULL(SPI_USE_TXDATA沒有設定),則寫階段就跳過了。

事務型別:中斷型事務和polling輪詢型事務。每個裝置選擇一個型別的事務來發送。如需傳送兩種型別的事務,要參閱混合事務傳送到同一裝置的說明。

 

1.2.5 SPI 事務型別

中斷型事務:事務執行時,中斷型事務採用中斷驅動邏輯。當等待事務結束時,該路由將阻塞,允許CPU執行其他任務。

中斷事務將排隊到裝置中,驅動將自動將他們在ISR中一個一個傳送。任務可以將多個事務排隊到裝置中,並在事務完成之前執行其他操作。

輪詢型事務:該型別的事務不依賴於中斷,程式將輪詢SPI外設的status bits直到事務完成。

執行中斷型事務的所有任務可能會被佇列阻塞,此時需要等待事務完成前執行兩次ISR。 輪詢型事務節省了在佇列處理和上下文切換上花費的時間,因而有較小的事務間隔。缺點在於輪詢型事務在傳輸時CPU不能執行其他任務。

當事務完成時,spi_device_polling_end()程式至少花費1us開銷來接觸阻塞其他任務。因此強烈建議在spi_device_acquire_busspi_device_release_bus 中包含一系列輪詢事務以避免開銷。

1.2.6 匯流排捕獲---有搶佔的意思

有時你可能需要立即傳送並連續傳送spi事務,獲取匯流排越快越好。你可以呼叫spi_device_acquire_bus 和spi_device_release_bus 來是實現。當匯流排被捕獲,則給其他從裝置的事務(無論是輪詢型的還是中斷型的)在匯流排釋放前都被掛起

2、SPI Master的API彙總

2.0 SPI Master的常用操作步驟

步驟:

1、呼叫spi_bus_initialize())初始化SPI匯流排。用結構體spi_bus_config_t來設定正確IO管腳,注意對於不用的訊號則設定為-1。

2、呼叫spi_bus_add_device()來通知驅動有一個spi從裝置連線到總線上。

確保在spi_device_interface_config_t結構體中配置設定的時序要求。對於每個裝置要求一個控制代碼,可以在傳輸一個事務時使用。

3、和從裝置互動時,填充一個或多個spi_transaction_t結構體引數,然後以輪詢或中斷方式傳送它們。

中斷方式:要麼通過呼叫spi_device_queue_trans對所有事物進行佇列處理,稍後呼叫spi_device_get_trans_result查詢結果;或者要麼通過將所有請求傳送到spi_device_transmission來同步處理它們。

輪詢方式: 呼叫spi_device_polling_transmit傳送輪詢事物。另外, 如果要在它們之間插入內容,可以通過spi_device_polling_startspi_device_polling_end傳送輪詢事務。

4、可選:對裝置進行back-to-back事務時,先呼叫spi_device_acquire_bus; 在事務完成後呼叫spi_device_release_bus

5、可選:呼叫spi_bus_remove_device以device handle為引數來解除安裝裝置驅動;

6、可選:從總線上移除驅動,為了確保沒有任何驅動連線呼叫spi_bus_freee;

技巧:

1、小資料的事務

有時,資料量非常小,不足以給他分配單獨緩衝區,如要傳輸的資料量小於32bit或更少的資料,則可以將其儲存在事務結構spi_transaction_t本身中。對於傳輸的資料,請使用tx_data成員並在flags成員上設定SPI_TRANS_USE_TXDATA;對於接受資料,使用rx_data成員並設定flags成員為SPI_TRANS_USE_RXDATA。這兩種情況下,就不要用到tx_buffer和rx_buffer成員,聯合體是共用地址的。

2、傳送非uint8_t型別的整數事務

SPI外設逐個位元組地讀寫儲存器。預設情況下,SPI工作在MSB優先模式,每個位元組傳送或接送從MSB到LSB。然而,若果你想傳送的陣列的長度不是8bit倍數的資料,則會發送未使用的位。

如: uint8_t data = 0x15(00010101B),並設定length = 5,則傳送的是00010b,而不是想要的10101B。

另外,ESP32是一個小端晶片,其最低位元組儲存在uint16-t和uint32_t變數的起始地址。因此,如果uint16_t儲存在儲存器中,則首先發送的是第7bit,然後傳送第6到第0bit,接著再傳輸第15bit到第8bit。

因此要傳送uint8_t陣列以外的資料,要設定成員flags為SPI_SWAP_DATA_TX以將資料轉移到MSB,並將資料MSB交換到最低地址;而SPI_SWAP_DATA_RX可用於將接受到的資料從MSB交換到正確的位置。

2.0.1GPIO 矩陣和IOMUX

ESP32上的外設訊號直連到指定的GPIO,這些GPIO成為IOMUX pin腳。全用IOMUX pin允許時鐘頻率達到80MHz。

/****************************對SPI Master的主要介面進行說明(driver/include/driver/spi_master.h)*************************************/

2.1 匯流排初始化介面

esp_err_t spi_bus_initialize(spi_host_device_t hostconst spi_bus_config_t *bus_config, int dma_chan)

注意:

目前只支援HSPI和VSPI。

如果支援DMA通道,任何傳輸和接收buffer 用支援DMA的記憶體。

引數表列

1、host:控制匯流排的SPI外設

typedef enum {
    SPI_HOST=0,                     ///< SPI1, SPI
    HSPI_HOST=1,                    ///< SPI2, HSPI
    VSPI_HOST=2                     ///< SPI3, VSPI
} spi_host_device_t;

2、bus_confgi: 是指向結構體spi_bus_config_t的指標,指定如何初始化這個主機。

typedef struct {
    int mosi_io_num;                ///< GPIO pin for Master Out Slave In (=spi_d) signal, or -1 if not used.
    int miso_io_num;                ///< GPIO pin for Master In Slave Out (=spi_q) signal, or -1 if not used.
    int sclk_io_num;                ///< GPIO pin for Spi CLocK signal, or -1 if not used.
    int quadwp_io_num;              ///< GPIO pin for WP (Write Protect) signal which is used as D2 in 4-bit communication modes, or -1 if not used.
    int quadhd_io_num;              ///< GPIO pin for HD (HolD) signal which is used as D3 in 4-bit communication modes, or -1 if not used.
    int max_transfer_sz;            ///< Maximum transfer size, in bytes. Defaults to 4094 if 0.
    uint32_t flags;                 ///< Abilities of bus to be checked by the driver. Or-ed value of ``SPICOMMON_BUSFLAG_*`` flags.
} spi_bus_config_t;

結構體spi_bus_config_t 是SPI匯流排的配置結構體。可以用這個結構體對匯流排的GPIO pin腳進行指定。通常,驅動程式將使用GPIO矩陣來路由訊號。當所有訊號通過GPIO矩陣路由或設定為-1,則會出現異常。在這種情況IO_MUX巨集被設定,則允許大於40MHz速度。

3、dma_chan: 通道1或者0,不使用DMA則設定0,。為SPI匯流排選擇DMA通道則總線上的傳輸大小隻受到內部記憶體大小限制;而選擇no DMA通道,則限制最大傳輸的位元組數為32。

2.2 向匯流排新增裝置

esp_err_t spi_bus_add_device(spi_host_device_t hostconst spi_device_interface_config_t *dev_configspi_device_handle_t *handle)

在SPI總線上分配一個裝置。這個介面初始化裝置的內部結構,並在指定的SPI 主裝置上分配一個CS pin腳,並將其路由到指定的GPIO上。所有SPI主裝置共計3個CS引腳,因此最多可以控制多達三個裝置。

注意:通常,專用的SPI引腳速度上可以多達80MHz的速度,GPIO矩陣路由的支援40MHz速度。而在GPIO矩陣路由全雙工傳輸只能支援26MHz的速度。

引數表列:

host: SPI外設

dev_config:配置的SPI 介面協議配置

typedef struct {
    uint8_t command_bits;           ///< Default amount of bits in command phase (0-16), used when ``SPI_TRANS_VARIABLE_CMD`` is not used, otherwise ignored.
    uint8_t address_bits;           ///< Default amount of bits in address phase (0-64), used when ``SPI_TRANS_VARIABLE_ADDR`` is not used, otherwise ignored.
    uint8_t dummy_bits;             ///< Amount of dummy bits to insert between address and data phase
    uint8_t mode;                   ///< SPI mode (0-3)
    uint8_t duty_cycle_pos;         ///< Duty cycle of positive clock, in 1/256th increments (128 = 50%/50% duty). Setting this to 0 (=not setting it) is equivalent to setting this to 128.
    uint8_t cs_ena_pretrans;        ///< Amount of SPI bit-cycles the cs should be activated before the transmission (0-16). This only works on half-duplex transactions.
    uint8_t cs_ena_posttrans;       ///< Amount of SPI bit-cycles the cs should stay active after the transmission (0-16)
    int clock_speed_hz;             ///< Clock speed, divisors of 80MHz, in Hz. See ``SPI_MASTER_FREQ_*``.
    int input_delay_ns;             /**< Maximum data valid time of slave. The time required between SCLK and MISO 
        valid, including the possible clock delay from slave to master. The driver uses this value to give an extra 
        delay before the MISO is ready on the line. Leave at 0 unless you know you need a delay. For better timing 
        performance at high frequency (over 8MHz), it's suggest to have the right value.
        */
    int spics_io_num;               ///< CS GPIO pin for this device, or -1 if not used
    uint32_t flags;                 ///< Bitwise OR of SPI_DEVICE_* flags
    int queue_size;                 ///< Transaction queue size. This sets how many transactions can be 'in the air' (queued using spi_device_queue_trans but not yet finished using spi_device_get_trans_result) at the same time
    transaction_cb_t pre_cb;        ///< Callback to be called before a transmission is started. This callback is called within interrupt context.
    transaction_cb_t post_cb;       ///< Callback to be called after a transmission has completed. This callback is called within interrupt context.
} spi_device_interface_config_t;

hangle:指向裝置控制代碼

2.3 從SPI總線上移除裝置

esp_err_t spi_bus_remove_device(spi_device_handle_t handle)

從SPI總線上移除裝置

引數: handle 要釋放的裝置控制代碼

2.4 傳送SPI transaction,並等待完成返回結果。

esp_err_t spi_device_transmit(spi_device_handle_t handlespi_transaction_t  *trans_desc)

這個介面等效於呼叫 spi_device_queue_trans() 之後呼叫spi_device_get_trans_result().如果仍有來自spi_device_queue_trans()的transmit或者polling_start_transmit()還沒有結束,則不能呼叫此介面。

注意: 這個介面不是執行緒安全的當多個任務訪問同一個SPI裝置時。通常裝置不能同時啟動(佇列)輪詢和中斷transmit。

引數表列:

handle: 用spi_host_add_dev包括的裝置控制代碼

trans_desc:待執行的transcation詳細內容,其結構體spi_transaction_t如下所示。

/**
 * This structure describes one SPI transaction. The descriptor should not be modified until the transaction finishes.
 */
struct spi_transaction_t {
    uint32_t flags;                 ///< Bitwise OR of SPI_TRANS_* flags
    uint16_t cmd;                   /**< Command data, of which the length is set in the ``command_bits`` of spi_device_interface_config_t.
                                      *
                                      *  <b>NOTE: this field, used to be "command" in ESP-IDF 2.1 and before, is re-written to be used in a new way in ESP-IDF 3.0.</b>
                                      *
                                      *  Example: write 0x0123 and command_bits=12 to send command 0x12, 0x3_ (in previous version, you may have to write 0x3_12).
                                      */
    uint64_t addr;                  /**< Address data, of which the length is set in the ``address_bits`` of spi_device_interface_config_t.
                                      *
                                      *  <b>NOTE: this field, used to be "address" in ESP-IDF 2.1 and before, is re-written to be used in a new way in ESP-IDF3.0.</b>
                                      *
                                      *  Example: write 0x123400 and address_bits=24 to send address of 0x12, 0x34, 0x00 (in previous version, you may have to write 0x12340000).
                                      */
    size_t length;                  ///< Total data length, in bits
    size_t rxlength;                ///< Total data length received, should be not greater than ``length`` in full-duplex mode (0 defaults this to the value of ``length``).
    void *user;                     ///< User-defined variable. Can be used to store eg transaction ID.
    union {
        const void *tx_buffer;      ///< Pointer to transmit buffer, or NULL for no MOSI phase
        uint8_t tx_data[4];         ///< If SPI_USE_TXDATA is set, data set here is sent directly from this variable.
    };
    union {
        void *rx_buffer;            ///< Pointer to receive buffer, or NULL for no MISO phase. Written by 4 bytes-unit if DMA is used.
        uint8_t rx_data[4];         ///< If SPI_USE_RXDATA is set, data is received directly to this variable
    };
} ;        //the rx data should start from a 32-bit aligned address to get around dma issue.

3、其他spi master介面說明

3.1 為中斷transation執行進行SPI transaction入佇列

esp_err_tspi_device_queue_trans(spi_device_handle_t handlespi_transaction_t * trans_desc, TickType_t ticks_to_wait)

為中斷transation執行進行SPI transaction入佇列。通過呼叫spi_device_trans_result 來獲得結果。

注意:一個裝置怒能同時開啟中斷和輪詢transactions。

引數表列:

handle: 用spi_host_add_dev包括的裝置控制代碼

trans_desc:待執行的transcation詳細內容

ticks_to_wait:直到在佇列中有空間的等待滴答數,可使用portMAX_DELAY不進行超時。

4、W25Q127晶片使用

4.1 初始化

4.2 寫命令

4.3 讀取資料

5、原始碼下載連結

 

6、結束

看了API,很詳細,時序要求上面還要再看看。就這樣吧,如果有什麼問題,請各位看客及時提醒。謝謝。