ESP32S3系列--SPI主机驱动详解(一)

一、目的

SPI是一种串行同步接口,可用于与外围设备进行通信。

ESP32S3自带4个SPI外设,其中SPI0/SPI1内部专用,共用一组信号线,通过一个仲裁器访问外部Flash和PSRAM;SPI2/3各自使用一组独立的信号线;开发者可以使用SPI2/3控制外部SPI从设备(Slave device);其中SPI2作为主设备有6个片选,数据线最多可以有八根,SPI3作为主设备有3个片选,数据线最多可以有四根。SPI2/3既可以作为主机使用,也可以作为从机使用。

本篇主要介绍SPI主机驱动的基本知识,包括标准SPI(MISO/MOSI)/Dual SPI/Quad SPI以及Octal SPI的配置和使用。

关于标准SPI/Dual SPI/Quad SPI以及Octal SPI的区别请阅读《理解SPI/Dual SPI/Quad SPI/QPI之间的区别》。

关于SPI2/3作为从机使用的知识点会在后续博文中详细介绍。


为了方便开发者使用SPI外设,ESP-IDF SDK中将SPI外设抽象为BUS(总线),一条总线上只有一个主设备,但是可以挂接多个从设备,每个从设备各自有独立的一条片选线(CS),其他信号线共用;片选线用于选中设备进行通信。

总线框图

上图中slave A和B共用一组信号线,使用的是标准SPI模式;每个设备有自己独立的片选控制线;某个时刻只能控制一个设备,要么控制A,要么控制B;A和B分时使用SPI总线。

如果觉得博客写得不错,或对你有帮助,接受打赏哦

二、介绍

参考资料

SPI Master Driver - ESP32-S3 - — ESP-IDF Programming Guide latest documentation (espressif.com)


SPI外设框图

SPI即串行同步接口

其中FSPI即SPI2,SPI2可以通过IOMUX或者GPIO Matrix进行引脚配置,SPI3只能通过GPIO Matrix进行引脚配置。

SPI2/3都支持DMA传输。


全双工和半双工区别

模式

说明

全双工

主机与从机之间的发送线和接收线各自独立,发送数据和接收数据同时进行。

半双工

主机和从机只能有一方先发送数据,另一方接收数据。发送数据和接收数据不能同时进行。

四线全双工

四线包括:时钟线、片选线和两条数据线。其中,可使用两条数据线同时发送和接收数据;一根数据线用于发送(MOSI),一根数据线用于接收(MISO)

四线半双工

四线包括:时钟线、片选线和两条数据线。其中,分时使用两条数据线,不可同时使用。

三线半双工

三线包括:时钟线、片选线和一条数据线。使用数据线分时发送和 接收数据。

专业术语

术语

描述

Host(主机)

芯片内部的SPI控制器外设,用于主动发起SPI传输

Device(设备)

SPI从设备,一条SPI总线可以连接多个从设备;每个设备分时共享信号线;每个设备都有一根独立的片选控制线;当主机需要控制某个设备时,选中对应的片选线即可(一般是拉低CS线)

Bus(总线)

多个设备共享的SPI信号线

MOSI(主机输出从机输入)

主机从此信号线输出数据,从设备从此信号线接收数据;在四线/八线模式下作为Data0

MISO(主机输入从机输出)

主机从此信号线接收数据,从设备从此信号线发送数据;在四线/八线模式下作为Data1

SCLK(串行同步时钟)

串行同步时钟,数据的发送接收依赖此信号来同步

CS(片选)

片选信号,每个从设备都有一个片选线

QUADWP(写保护)

写保护信号;在四线/八线模式下作为Data2

QUADHD(保持)

保持信号;在四线/八线模式下作为Data3

DATA4

在八线模式下作为Data4

DATA5

在八线模式下作为Data5

DATA6

在八线模式下作为Data6

DATA7

在八线模式下作为Data7

Assertion

发起SPI传输(一般是拉低片选线),总线进入忙状态

De-assertion

SPI传输结束(一般是拉高片选线),总线进入空闲状态

Transaction(传输事务)

一次完整的传输事务:主机拉低从机的 CS 线,开始传输数据,然 后再拉高从机的 CS 线。传输事务为原子操作,即不可打断。

Transfer(传输)

SPI 主机与从机完成数据交换的一次完整过程。一次 SPI 传输可以 包含一个或多个 SPI 传输事务。

单次传输

在这种传输模式下,一次SPI传输仅包含一次传输事务。

Launch edge(发射边沿)

数据源寄存器(发送端)在此边沿将数据比特发送至信号线上

Latch edge(锁存边沿)

数据目的寄存器(接收端)在此边沿将数据比特锁存下来


特性
  • 支持多线程环境使用

  • 支持CPU控制的传输模式以及DMA控制的传输模式

  • DMA读写过程用户无感知

  • 自动时分复用(不同时刻对不同设备进行读写)

  • 时钟最高80MHz(主机模式)


注意点
  • 同一个设备尽量在一个线程中操作

  • 同一个设备在不同线程中操作需要通过互斥锁进行保护


SPI事务描述

每次SPI传输可以包含五个阶段,包括命令、地址、空周期、写、读阶段,每个阶段都是可选的。

阶段

描述

Command(命令)

在此阶段主机可以发送命令字段,长度最多16bit

Address(地址)

在此阶段主机可以发送地址字段,长度最多32bit

Dummy(空周期)

此阶段用于适配时序要求

Write(写)

此阶段主机发送数据给从设备

Read(读)

此阶段主机读取从设备数据

     

传输线模式配置(重要)

通过设置传输模式标记可以控制每个阶段的数据线的数目

模式

命令线宽度

地址线宽度

数据线宽度

Transaction Flag

Bus IO setting Flag

Normal SPI

1

1

1

0

0

Dual Output

(DOUT)

1

1

2

SPI_TRANS_MODE_DIO

SPICOMMON_BUSFLAG_DUAL

Dual I/O

(DIO)

1

2

2

SPI_TRANS_MODE_DIO | SPI_TRANS_MULTILINE_ADDR

Quad Output

(QOUT)

1

1

4

SPI_TRANS_MODE_QIO

SPICOMMON_BUSFLAG_QUAD

Quad I/O

(QIO)

1

4

4

SPI_TRANS_MODE_QIO | SPI_TRANS_MULTILINE_ADDR

Octal Output

1

1

8

SPI_TRANS_MODE_OCT

SPICOMMON_BUSFLAG_OCTAL

OPI

8

8

8

SPI_TRANS_MODE_OCT | SPI_TRANS_MULTILINE_ADDR | SPI_TRANS_MULTILINE_CMD

传输标记说明
#define SPI_TRANS_MODE_DIO            (1<<0)  ///< Transmit/receive data in 2-bit mode

收发数据阶段使用双线模式

#define SPI_TRANS_MODE_QIO            (1<<1)  ///< Transmit/receive data in 4-bit mode

收发数据阶段使用四线模式

#define SPI_TRANS_MODE_DIOQIO_ADDR    (1<<4)  ///< Also transmit address in mode selected by SPI_MODE_DIO/SPI_MODE_QIO
#define SPI_TRANS_MULTILINE_ADDR      SPI_TRANS_MODE_DIOQIO_ADDR ///< The data lines used at address phase is the same as data phase (otherwise, only one data line is used at address phase)

地址阶段使用和数据阶段一样的模式,如果未设置此标记则地址阶段使用一线模式

#define SPI_TRANS_MULTILINE_CMD       (1<<9)  ///< The data lines used at command phase is the same as data phase (otherwise, only one data line is used at command phase)

命令阶段使用和数据阶段一样的模式,如果未设置此标记则命令阶段使用一线模式

#define SPI_TRANS_MODE_OCT            (1<<10) ///< Transmit/receive data in 8-bit mode

收发数据阶段使用八线模式,配合SPI_TRANS_MULTILINE_CMD/SPI_TRANS_MULTILINE_ADDR可以所有阶段都是八线模式

总线的初始化

在使用SPI总线之前必须先初始化

/**
 * @brief Initialize a SPI bus
 *
 * @warning SPI0/1 is not supported
 *
 * @param host_id       SPI peripheral that controls this bus
 * @param bus_config    Pointer to a spi_bus_config_t struct specifying how the host should be initialized
 * @param dma_chan      - Selecting a DMA channel for an SPI bus allows transactions on the bus with size only limited by the amount of internal memory.
 *                      - Selecting SPI_DMA_DISABLED limits the size of transactions.
 *                      - Set to SPI_DMA_DISABLED if only the SPI flash uses this bus.
 *                      - Set to SPI_DMA_CH_AUTO to let the driver to allocate the DMA channel.
 *
 * @warning If a DMA channel is selected, any transmit and receive buffer used should be allocated in
 *          DMA-capable memory.
 *
 * @warning The ISR of SPI is always executed on the core which calls this
 *          function. Never starve the ISR on this core or the SPI transactions will not
 *          be handled.
 *
 * @return
 *         - ESP_ERR_INVALID_ARG   if configuration is invalid
 *         - ESP_ERR_INVALID_STATE if host already is in use
 *         - ESP_ERR_NOT_FOUND     if there is no available DMA channel
 *         - ESP_ERR_NO_MEM        if out of memory
 *         - ESP_OK                on success
 */
esp_err_t spi_bus_initialize(spi_host_device_t host_id, const spi_bus_config_t *bus_config, spi_dma_chan_t dma_chan);

函数参数的说明:

host_id:SPI外设索引号

bus_config:SPI总线参数

dma_chan:是否使能DMA传输并配置使用的传输通道


使能DMA传输的注意点
  • 传输buffer需要时dma-capable的内部RAM

  • 必须32bit对齐并且长度必须是4字节对齐

如果这两个条件不满足,驱动内部会自动分配内存然后进行拷贝后再传输,故效率会降低;所以建议使能DMA传输时外部传入的buffer尽可能是DMA属性的内存

// Region of memory accessible via DMA in internal memory. See esp_ptr_dma_capable().
#define SOC_DMA_LOW  0x3FC88000
#define SOC_DMA_HIGH 0x3FD00000

/**
 * @brief Check if the pointer is dma capable
 *
 * @param p pointer
 *
 * @return true: capable; false: not capable
 */
__attribute__((always_inline))
inline static bool esp_ptr_dma_capable(const void *p)
{
    return (intptr_t)p >= SOC_DMA_LOW && (intptr_t)p < SOC_DMA_HIGH;
}

static SPI_MASTER_ISR_ATTR esp_err_t setup_priv_desc(spi_transaction_t *trans_desc, spi_trans_priv_t* new_desc, bool isdma)
{
    *new_desc = (spi_trans_priv_t) { .trans = trans_desc, };

    // rx memory assign
    uint32_t* rcv_ptr;
    if ( trans_desc->flags & SPI_TRANS_USE_RXDATA ) {
        rcv_ptr = (uint32_t *)&trans_desc->rx_data[0];
    } else {
        //if not use RXDATA neither rx_buffer, buffer_to_rcv assigned to NULL
        rcv_ptr = trans_desc->rx_buffer;
    }
    if (rcv_ptr && isdma && (!esp_ptr_dma_capable(rcv_ptr) || ((int)rcv_ptr % 4 != 0))) {  //①
        //if rxbuf in the desc not DMA-capable, malloc a new one. The rx buffer need to be length of multiples of 32 bits to avoid heap corruption.
        ESP_LOGD(SPI_TAG, "Allocate RX buffer for DMA" );
        rcv_ptr = heap_caps_malloc((trans_desc->rxlength + 31) / 8, MALLOC_CAP_DMA);  //②
        if (rcv_ptr == NULL) goto clean_up;
    }
    new_desc->buffer_to_rcv = rcv_ptr;

    // tx memory assign
    const uint32_t *send_ptr;
    if ( trans_desc->flags & SPI_TRANS_USE_TXDATA ) {
        send_ptr = (uint32_t *)&trans_desc->tx_data[0];
    } else {
        //if not use TXDATA neither tx_buffer, tx data assigned to NULL
        send_ptr = trans_desc->tx_buffer ;
    }
    if (send_ptr && isdma && !esp_ptr_dma_capable( send_ptr )) {  //③
        //if txbuf in the desc not DMA-capable, malloc a new one
        ESP_LOGD(SPI_TAG, "Allocate TX buffer for DMA" );
        uint32_t *temp = heap_caps_malloc((trans_desc->length + 7) / 8, MALLOC_CAP_DMA);
        if (temp == NULL) goto clean_up;

        memcpy( temp, send_ptr, (trans_desc->length + 7) / 8 );
        send_ptr = temp;
    }
    new_desc->buffer_to_send = send_ptr;

    return ESP_OK;

clean_up:
    uninstall_priv_desc(new_desc);
    return ESP_ERR_NO_MEM;
}

spi_device_queue_trans和spi_device_polling_start函数中通过setup_priv_desc这个函数检查rx_buffer/tx_buffer的属性

①如果要接收则检查rcv_ptr是否是DMA属性的(在地址区间内)并且rcv_ptr指针的值是否是4字节对齐

②rcv_ptr不满足dma要求则重新分配

③如果要发送则检查send_ptr是否是DMA属性的(注意发送的首地址可以不是四字节对齐)


SPI外设索引号

/**
 * @brief Enum with the three SPI peripherals that are software-accessible in it
 */
typedef enum {
//SPI1 can be used as GPSPI only on ESP32
    SPI1_HOST=0,    ///< SPI1
    SPI2_HOST=1,    ///< SPI2
    SPI3_HOST=2,    ///< SPI3
    SPI_HOST_MAX,   ///< invalid host value
} spi_host_device_t;

在ESP32S3上可以使用SPI2_HOST和SPI3_HOST


SPI总线参数说明

/**
 * @brief This is a configuration structure for a SPI bus.
 *
 * You can use this structure to specify the GPIO pins of the bus. Normally, the driver will use the
 * GPIO matrix to route the signals. An exception is made when all signals either can be routed through
 * the IO_MUX or are -1. In that case, the IO_MUX is used, allowing for >40MHz speeds.
 *
 * @note Be advised that the slave driver does not use the quadwp/quadhd lines and fields in spi_bus_config_t refering to these lines will be ignored and can thus safely be left uninitialized.
 */
typedef struct {
    union {
      int mosi_io_num;    ///< GPIO pin for Master Out Slave In (=spi_d) signal, or -1 if not used.
      int data0_io_num;   ///< GPIO pin for spi data0 signal in quad/octal mode, or -1 if not used.
    };
    union {
      int miso_io_num;    ///< GPIO pin for Master In Slave Out (=spi_q) signal, or -1 if not used.
      int data1_io_num;   ///< GPIO pin for spi data1 signal in quad/octal mode, or -1 if not used.
    };
    int sclk_io_num;      ///< GPIO pin for SPI Clock signal, or -1 if not used.
    union {
      int quadwp_io_num;  ///< GPIO pin for WP (Write Protect) signal, or -1 if not used.
      int data2_io_num;   ///< GPIO pin for spi data2 signal in quad/octal mode, or -1 if not used.
    };
    union {
      int quadhd_io_num;  ///< GPIO pin for HD (Hold) signal, or -1 if not used.
      int data3_io_num;   ///< GPIO pin for spi data3 signal in quad/octal mode, or -1 if not used.
    };
    int data4_io_num;     ///< GPIO pin for spi data4 signal in octal mode, or -1 if not used.
    int data5_io_num;     ///< GPIO pin for spi data5 signal in octal mode, or -1 if not used.
    int data6_io_num;     ///< GPIO pin for spi data6 signal in octal mode, or -1 if not used.
    int data7_io_num;     ///< GPIO pin for spi data7 signal in octal mode, or -1 if not used.
    int max_transfer_sz;  ///< Maximum transfer size, in bytes. Defaults to 4092 if 0 when DMA enabled, or to `SOC_SPI_MAXIMUM_BUFFER_SIZE` if DMA is disabled.
    uint32_t flags;       ///< Abilities of bus to be checked by the driver. Or-ed value of ``SPICOMMON_BUSFLAG_*`` flags.
    int intr_flags;       /**< Interrupt flag for the bus to set the priority, and IRAM attribute, see
                           *  ``esp_intr_alloc.h``. Note that the EDGE, INTRDISABLED attribute are ignored
                           *  by the driver. Note that if ESP_INTR_FLAG_IRAM is set, ALL the callbacks of
                           *  the driver, and their callee functions, should be put in the IRAM.
                           */
} spi_bus_config_t;

各个字段含义:

mosi_io_num/data0_io_num:主机输出从机输入或者data0信号线IO引脚编号

miso_io_num/data1_io_num:主机输入从机输出或者data1信号线IO引脚编号

sclk_io_num:时钟信号IO引脚编号

quadwp_io_num/data2_io_num:写保护信号或者data2信号线IO引脚编号

quadhd_io_num/data3_io_num:保持信号或者data3信号线IO引脚编号

data4_io_num:data4信号线IO引脚编号

data5_io_num:data5信号线IO引脚编号

data6_io_num:data6信号线IO引脚编号

data7_io_num:data7信号线IO引脚编号

max_transfer_size:一次SPI传输最大的长度;使能DMA传输时,如果设置为0,则默认限制为4092字节;未使能DMA时,如果设置为0,则默认限制为SOC_SPI_MAXIMUM_BUFFER_SIZE(64字节)。

flags:总线特征标志,具体说明请看下文

intr_flags:中断特征标志,如果设置为ESP_INTR_FLAG_IRAM则驱动中涉及到的回调以及回调调用的函数也要是IRAM属性的

其中数据线根据实际硬件需要设置,如果某些引脚不需要,设置为-1即可。

关于flags字段的说明

#define SPICOMMON_BUSFLAG_SLAVE         0          ///< Initialize I/O in slave mode

设置从设备模式,内部使用;通过spi_slave_initialize接口初始化默认为从设备模式

#define SPICOMMON_BUSFLAG_MASTER        (1<<0)     ///< Initialize I/O in master mode

设置主设备模式,内部使用;通过spi_bus_initialize接口初始化默认为主设备模式

#define SPICOMMON_BUSFLAG_IOMUX_PINS    (1<<1)     ///< Check using iomux pins. Or indicates the pins are configured through the IO mux rather than GPIO matrix.

检查是否使用IOMUX作为IO输入输出或者指示IO引脚是否是使用IOMUX进行配置的

#define SPICOMMON_BUSFLAG_GPIO_PINS     (1<<2)     ///< Force the signals to be routed through GPIO matrix. Or indicates the pins are routed through the GPIO matrix.

强制使用GPIO Matrix作为IO输入输出或者指示IO引脚是否是使用GPIO Matrix进行配置的

默认情况下(SPICOMMON_BUSFLAG_IOMUX_PINS/SPICOMMON_BUSFLAG_GPIO_PINS标志都不设置),SPI主机驱动会使用GPIO矩阵来配置外设引脚;但是如果所有的信号都可以通过IOMUX进行配置(或者某些信号不需要使用,设置为-1),那么就会使用IOMUX进行引脚配置,优点是传输时钟频率可以大于40MHz。

IO引脚配置说明

如果在初始化时设置了SPICOMMON_BUSFLAG_GPIO_PINS标记,那么内部就使用IO矩阵进行IO配置(即使需要的IO引脚可以通过IOMUX配置)如果设置了SPICOMMON_BUSFLAG_IOMUX_PINS,则内部会检查设置的IO引脚编号是否可以通过IOMUX配置,如果可以则初始化成功,否则初始化失败如果两个标记都没有设置,那么内部会检查设置的IO引脚编号是否可以通过IOMUX配置,如果可以则使用IOMUX,否则使用IO Matrix

一般情况下SPICOMMON_BUSFLAG_GPIO_PINS和SPICOMMON_BUSFLAG_IOMUX_PINS都不需要设置,只要我们设置的IO引脚是可以通过IOMUX配置的,默认就会使用IOMUX配置;否则都是通过IO Matrix配置。

具体的内部代码逻辑如下

 //check if the selected pins correspond to the iomux pins of the peripheral
    bool use_iomux = !(flags & SPICOMMON_BUSFLAG_GPIO_PINS) && bus_uses_iomux_pins(host, bus_config);
    if (use_iomux) {
        temp_flag |= SPICOMMON_BUSFLAG_IOMUX_PINS;
    } else {
        temp_flag |= SPICOMMON_BUSFLAG_GPIO_PINS;
    }

    uint32_t missing_flag = flags & ~temp_flag;
    missing_flag &= ~SPICOMMON_BUSFLAG_MASTER;//don't check this flag

    if (missing_flag != 0) {
    //check pins existence
        if (missing_flag & SPICOMMON_BUSFLAG_SCLK) {
            ESP_LOGE(SPI_TAG, "sclk pin required.");
        }
        if (missing_flag & SPICOMMON_BUSFLAG_MOSI) {
            ESP_LOGE(SPI_TAG, "mosi pin required.");
        }
        if (missing_flag & SPICOMMON_BUSFLAG_MISO) {
            ESP_LOGE(SPI_TAG, "miso pin required.");
        }
        if (missing_flag & SPICOMMON_BUSFLAG_DUAL) {
            ESP_LOGE(SPI_TAG, "not both mosi and miso output capable");
        }
        if (missing_flag & SPICOMMON_BUSFLAG_WPHD) {
            ESP_LOGE(SPI_TAG, "both wp and hd required.");
        }
        if (missing_flag & SPICOMMON_BUSFLAG_IOMUX_PINS) {
            ESP_LOGE(SPI_TAG, "not using iomux pins");
        }
#if SOC_SPI_SUPPORT_OCT
        if (missing_flag & SPICOMMON_BUSFLAG_IO4_IO7) {
            ESP_LOGE(SPI_TAG, "spi data4 ~ spi data7 are required.");
        }
#endif
        SPI_CHECK(missing_flag == 0, "not all required capabilities satisfied.", ESP_ERR_INVALID_ARG);
    }

通过IOMUX配置的引脚配置

Pin Name

SPI2

SPI3

GPIO Number

CS0*

10

N/A

SCLK

12

N/A

MISO

13

N/A

MOSI

11

N/A

QUADWP

14

N/A

QUADHD

9

N/A

只有SPI2支持IOMUX进行引脚配置,除非时钟频率很高,否则没有必要纠结是使用IOMUX还是GPIO Matrix。

一般情况下际初始化总线上不需要设置SPICOMMON_BUSFLAG_GPIO_PINS和SPICOMMON_BUSFLAG_IOMUX_PINS这两个标志。


#define SPICOMMON_BUSFLAG_SCLK          (1<<3)     ///< Check existing of SCLK pin. Or indicates CLK line initialized.

检查是否配置SCLK引脚,一般不使用此标记;如果设置了此标志,但是sclk_io_num字段为-1,则初始化出错。

#define SPICOMMON_BUSFLAG_MISO          (1<<4)     ///< Check existing of MISO pin. Or indicates MISO line initialized.
#define SPICOMMON_BUSFLAG_MOSI          (1<<5)     ///< Check existing of MOSI pin. Or indicates MOSI line initialized.

检查是否配置MOSI/MISO引脚,一般不使用此标记;如果设置了这些标志,但是mosi_io_num/mosi_io_num字段为-1,则初始化出错。

#define SPICOMMON_BUSFLAG_DUAL          (1<<6)     ///< Check MOSI and MISO pins can output. Or indicates bus able to work under DIO mode.

检查MOSI和MISO引脚可以输出或者指示总线能够工作在DIO模式,一般不使用此标记;如果设置了这些标记,但是mosi_io_num/mosi_io_num未都设置且合法,则初始化报错。

#define SPICOMMON_BUSFLAG_WPHD          (1<<7)     ///< Check existing of WP and HD pins. Or indicates WP & HD pins initialized.

检查WP/HD信号的存在或者指示WP/HD引脚已经初始化,一般不使用此标记;如果设置了这些标记,但是quadwp_io_num/quadhd_io_num未都设置且合法,则初始化报错。

#define SPICOMMON_BUSFLAG_QUAD          (SPICOMMON_BUSFLAG_DUAL|SPICOMMON_BUSFLAG_WPHD)     ///< Check existing of MOSI/MISO/WP/HD pins as output. Or indicates bus able to work under QIO mode.

检查MOSI/MISO/WP/HD引脚可以输出或者指示总线能够工作在QIO模式

#define SPICOMMON_BUSFLAG_IO4_IO7       (1<<8)     ///< Check existing of IO4~IO7 pins. Or indicates IO4~IO7 pins initialized.

检查IO4-IO7引脚存在或者指示IO4-IO7引脚已经初始化

#define SPICOMMON_BUSFLAG_OCTAL         (SPICOMMON_BUSFLAG_QUAD|SPICOMMON_BUSFLAG_IO4_IO7)  ///< Check existing of MOSI/MISO/WP/HD/SPIIO4/SPIIO5/SPIIO6/SPIIO7 pins as output. Or indicates bus able to work under octal mode.

检查MOSI/MISO/WP/HD/SPIIO4/SPIIO5/SPIIO6/SPIIO7引脚可以输出或者指示可以工作在八线模式


SPI DMA通道说明

/**
 * @brief SPI DMA channels
 */
typedef enum {
  SPI_DMA_DISABLED = 0,     ///< Do not enable DMA for SPI
#if CONFIG_IDF_TARGET_ESP32
  SPI_DMA_CH1      = 1,     ///< Enable DMA, select DMA Channel 1
  SPI_DMA_CH2      = 2,     ///< Enable DMA, select DMA Channel 2
#endif
  SPI_DMA_CH_AUTO  = 3,     ///< Enable DMA, channel is automatically selected by driver
} spi_common_dma_t;

使能DMA传输时传输的数据量不限制,max_transfer_sz如果设置为0则默认为4092字节;禁用DMA传输时会限制最大可传输的数据量,max_transfer_sz如果设置为0则默认为64字节。

一般建议如果只是操作外部flash可以禁用DMA传输。

往总线添加设备

总线初始化成功后,就可以往总线上添加从设备

typedef struct spi_device_t *spi_device_handle_t;  ///< Handle for a device on a SPI bus
/**
 * @brief Allocate a device on a SPI bus
 *
 * This initializes the internal structures for a device, plus allocates a CS pin on the indicated SPI master
 * peripheral and routes it to the indicated GPIO. All SPI master devices have three CS pins and can thus control
 * up to three devices.
 *
 * @note While in general, speeds up to 80MHz on the dedicated SPI pins and 40MHz on GPIO-matrix-routed pins are
 *       supported, full-duplex transfers routed over the GPIO matrix only support speeds up to 26MHz.
 *
 * @param host_id SPI peripheral to allocate device on
 * @param dev_config SPI interface protocol config for the device
 * @param handle Pointer to variable to hold the device handle
 * @return
 *         - ESP_ERR_INVALID_ARG   if parameter is invalid
 *         - ESP_ERR_NOT_FOUND     if host doesn't have any free CS slots
 *         - ESP_ERR_NO_MEM        if out of memory
 *         - ESP_OK                on success
 */
esp_err_t spi_bus_add_device(spi_host_device_t host_id, const spi_device_interface_config_t *dev_config, spi_device_handle_t *handle);

各个参数说明:

host_id:SPI外设索引号

dev_config:从设备配置信息

handle:从设备句柄

注意点

通过IOMUX配置外设引脚时钟最大可以为80MHz(只有SPI2);通过GPIO Matrix的时钟只能40MHz,并且全双工通信时只能设置为26MHz.

设备配置信息
/**
 * @brief This is a configuration for a SPI slave device that is connected to one of the SPI buses.
 */
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, representing a pair of (CPOL, CPHA) configuration:
                                         - 0: (0, 0)
                                         - 1: (0, 1)
                                         - 2: (1, 0)
                                         - 3: (1, 1)
                                     */
    uint16_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.
    uint16_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 should be in IRAM for best
                                 *  performance, see "Transferring Speed"
                                 *  section in the SPI Master documentation for
                                 *  full details. If not, the callback may crash
                                 *  during flash operation when the driver is
                                 *  initialized with ESP_INTR_FLAG_IRAM.
                                 */
    transaction_cb_t post_cb;  /**< Callback to be called after a transmission has completed.
                                 *
                                 *  This callback is called within interrupt
                                 *  context should be in IRAM for best
                                 *  performance, see "Transferring Speed"
                                 *  section in the SPI Master documentation for
                                 *  full details. If not, the callback may crash
                                 *  during flash operation when the driver is
                                 *  initialized with ESP_INTR_FLAG_IRAM.
                                 */
} spi_device_interface_config_t;

各个字段含义:

command_bits:命令阶段传输的bit数,最大16bit(可以通过SPI_TRANS_VARIABLE_CMD进行覆盖)

address_bits:地址阶段传输的bit数,最大64bit(可以通过SPI_TRANS_VARIABLE_ADDR进行覆盖)

dummy_bits:地址阶段和数据阶段的空周期数(可以通过SPI_TRANS_VARIABLE_DUMMY进行覆盖)

由于并不是每一个传输都需要命令、地址、空周期这些阶段,一般情况下在添加从设备时这三个字段都是设置为0,在真正发起SPI传输时通过SPI_TRANS_VARIABLE_CMD、SPI_TRANS_VARIABLE_ADDR、SPI_TRANS_VARIABLE_DUMMY这些标志位进行设置。

mode:SPI时钟的极性和相位关系,请阅读《理解SPI/Dual SPI/Quad SPI/QPI之间的区别》中的时钟极性和时钟相位章节

/**< SPI mode, representing a pair of (CPOL, CPHA) configuration:
                                         - 0: (0, 0)
                                         - 1: (0, 1)
                                         - 2: (1, 0)
                                         - 3: (1, 1)
                                     */

duty_cycle_pos:时钟占空比,默认50%

cs_ena_pretrans:传输开始前CS信号提前有效的时间,只用在半双工传输中(0-16)

cs_ena_posttrans:传输结束后CS保持有效的时间(0-16)

clock_speed_hz:时钟频率

input_delay_ns:从设备的数据有效时间,一般不设置

spics_io_num:片选IO引脚编号,未使用时设置-1即可

flags:设备标志

queue_size:传输队列大小,使用spi_device_queue_trans接口可以将传输请求进行排队处理

pre_cb:传输开始前的回调函数,在中断中执行

post_cb:传输结束后的回调函数,在中断中执行

设备标志说明
#define SPI_DEVICE_TXBIT_LSBFIRST          (1<<0)  ///< Transmit command/address/data LSB first instead of the default MSB first

地址/命令/数据阶段发送的比特位的顺序为先发送低比特位(默认是先发送高比特位)

#define SPI_DEVICE_RXBIT_LSBFIRST          (1<<1)  ///< Receive data LSB first instead of the default MSB first

接收数据的比特位顺序为先接收低比特位(默认是先接收高比特位)

#define SPI_DEVICE_BIT_LSBFIRST            (SPI_DEVICE_TXBIT_LSBFIRST|SPI_DEVICE_RXBIT_LSBFIRST) ///< Transmit and receive LSB first

收发都使用LSB

#define SPI_DEVICE_POSITIVE_CS             (1<<3)  ///< Make CS positive during a transaction instead of negative

默认片段信号拉低有效,此标记设置为拉高为选中设备

#define SPI_DEVICE_HALFDUPLEX              (1<<4)  ///< Transmit data before receiving it, instead of simultaneously

设置为半双工模式,如果需要使用多线进行数据收发,必须在添加从设备时设置此字段


总线获取

有些情况下需要独占的使用总线,这个时候可以使用spi_device_acquire_bus()独占总线;通过spi_device_release_bus()释放总线


中断传输和轮询传输

通过spi_device_transmit()发起的传输是中断传输,会阻塞当前线程直到传输完成,此时CPU调度其他线程执行;

通过spi_device_queue_trans可以发起多个传输,每个传输进行排队,中断中一个接一个的处理。

调用此接口后,当前线程可以继续执行其他任务,通过spi_device_get_trans_result()获取传输结果。

示例代码

    esp_err_t ret;
    int x;
    //Transaction descriptors. Declared static so they're not allocated on the stack; we need this memory even when this
    //function is finished because the SPI driver needs access to it even while we're already calculating the next line.
    static spi_transaction_t trans[6];

    //In theory, it's better to initialize trans and data only once and hang on to the initialized
    //variables. We allocate them on the stack, so we need to re-init them each call.
    for (x=0; x<6; x++) {
        memset(&trans[x], 0, sizeof(spi_transaction_t));
        if ((x&1)==0) {
            //Even transfers are commands
            trans[x].length=8;
            trans[x].user=(void*)0;
        } else {
            //Odd transfers are data
            trans[x].length=8*4;
            trans[x].user=(void*)1;
        }
        trans[x].flags=SPI_TRANS_USE_TXDATA;
    }
    trans[0].tx_data[0]=0x2A;           //Column Address Set
    trans[1].tx_data[0]=0;              //Start Col High
    trans[1].tx_data[1]=0;              //Start Col Low
    trans[1].tx_data[2]=(320)>>8;       //End Col High
    trans[1].tx_data[3]=(320)&0xff;     //End Col Low
    trans[2].tx_data[0]=0x2B;           //Page address set
    trans[3].tx_data[0]=ypos>>8;        //Start page high
    trans[3].tx_data[1]=ypos&0xff;      //start page low
    trans[3].tx_data[2]=(ypos+PARALLEL_LINES)>>8;    //end page high
    trans[3].tx_data[3]=(ypos+PARALLEL_LINES)&0xff;  //end page low
    trans[4].tx_data[0]=0x2C;           //memory write
    trans[5].tx_buffer=linedata;        //finally send the line data
    trans[5].length=320*2*8*PARALLEL_LINES;          //Data length, in bits
    trans[5].flags=0; //undo SPI_TRANS_USE_TXDATA flag

    //Queue all transactions.
    for (x=0; x<6; x++) {
        ret=spi_device_queue_trans(spi, &trans[x], portMAX_DELAY);
        assert(ret==ESP_OK);
    }

上述代码片段采用排队方式的中断传输,一次发起6个SPI传输事务。

轮询传输通过CPU查询标志位判断传输是否完成,通过spi_device_polling_transmit()进行

示例代码

    spi_transaction_t t;
    memset(&t, 0, sizeof(t));
    t.length=8*3;
    t.flags = SPI_TRANS_USE_RXDATA;
    t.user = (void*)1;

    esp_err_t ret = spi_device_polling_transmit(spi, &t);
    assert( ret == ESP_OK );

至此我们介绍了SPI主机驱动的基本知识,关于spi_transaction_t结构体的具体使用细节下篇讲解。

  • 11
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值