赞
踩
目前,由于要存放本地音乐,芯片内部的Flash大小不够,所以要用到外部SPI Flash。暂时选择芯片W25Q127.因此有必要研究一下ESP32的SPI外设。
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
ESP32有四个SPI外设,分别为SPI0、SPI1、HSPI和VSPI。SPI0是专用于Flash的缓存,ESP32将连接的SPI Flash设备映射到内存中。SPI1和SPI0 使用相同的硬件线,SPI1用于写入flash芯片。HSPI和VSPI可以任意使用。SPI1、HSPI和VSPI共有三条片选线,因此作为SPI主机允许ESP32 至多驱动三个SPI设备。
SPI主机驱动可以和从机轻松通信,在多线程中也一样。它完全透明地使用DMA传输读写数据并自动处理同一主机上不同SPI从机之间的多路复用。
注意线程安全: 从不同任务访问同一总线上的多个SPI设备时,SPI驱动API是线程安全的;但是当从不同任务访问同一的SPI设备时,SPI驱动API不是线程安全的。
在这种情况下,建议要么重构应用程序程序以确保只有一个任务访问每个SPI设备;或者添加互斥锁在访问同一设备时。
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再次失活的一个实例。事务是原子操作,因为他们不会被另一个事务中断。
SPI 总线上的事务由五个阶段组成,其中任何阶段都可以跳过。这些阶段为
这里只翻译全双工模式:读写两阶段合并,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轮询型事务。每个设备选择一个类型的事务来发送。如需发送两种类型的事务,要参阅混合事务发送到同一设备的说明。
中断型事务:事务运行时,中断型事务采用中断驱动逻辑。当等待事务结束时,该路由将阻塞,允许CPU运行其他任务。
中断事务将排队到设备中,驱动将自动将他们在ISR中一个一个发送。任务可以将多个事务排队到设备中,并在事务完成之前执行其他操作。
轮询型事务:该类型的事务不依赖于中断,程序将轮询SPI外设的status bits直到事务完成。
执行中断型事务的所有任务可能会被队列阻塞,此时需要等待事务完成前运行两次ISR。 轮询型事务节省了在队列处理和上下文切换上花费的时间,因而有较小的事务间隔。缺点在于轮询型事务在传输时CPU不能运行其他任务。
当事务完成时,spi_device_polling_end()程序至少花费1us开销来接触阻塞其他任务。因此强烈建议在spi_device_acquire_bus和spi_device_release_bus
中包含一系列轮询事务以避免开销。
有时你可能需要立即发送并连续发送spi事务,获取总线越快越好。你可以调用spi_device_acquire_bus
和spi_device_release_bus
来是实现。当总线被捕获,则给其他从设备的事务(无论是轮询型的还是中断型的)在总线释放前都被挂起
步骤:
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_start和spi_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交换到正确的位置。
ESP32上的外设信号直连到指定的GPIO,这些GPIO成为IOMUX pin脚。全用IOMUX pin允许时钟频率达到80MHz。
/****************************对SPI Master的主要接口进行说明(driver/include/driver/spi_master.h)*************************************/
esp_err_t spi_bus_initialize
(spi_host_device_t host, const 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。
esp_err_t spi_bus_add_device
(spi_host_device_t host, const spi_device_interface_config_t *dev_config, spi_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:指向设备句柄
esp_err_t spi_bus_remove_device
(spi_device_handle_t handle)
从SPI总线上移除设备
参数: handle 要释放的设备句柄
esp_err_t spi_device_transmit
(spi_device_handle_t handle, spi_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.
esp_err_tspi_device_queue_trans
(spi_device_handle_t handle, spi_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不进行超时。
看了API,很详细,时序要求上面还要再看看。就这样吧,如果有什么问题,请各位看客及时提醒。谢谢。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。