赞
踩
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即串行同步接口
其中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传输可以包含五个阶段,包括命令、地址、空周期、写、读阶段,每个阶段都是可选的。
阶段 | 描述 |
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传输并配置使用的传输通道
传输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。
如果在初始化时设置了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结构体的具体使用细节下篇讲解。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。