ESP32设备SPI主设备驱动

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhejfl/article/details/85999816

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,很详细,时序要求上面还要再看看。就这样吧,如果有什么问题,请各位看客及时提醒。谢谢。

猜你喜欢

转载自blog.csdn.net/zhejfl/article/details/85999816