ESP32

您所在的位置:网站首页 软件模拟spi时钟 ESP32

ESP32

2023-07-23 08:20| 来源: 网络整理| 查看: 265

一、SPI主机驱动

SPI主控程序是一个控制ESP32的SPI外围设备的程序,同时它们作为主控设备发挥作用。

ESP32的SPI外围设备概述

ESP32集成了4个SPI外设。

SPI0和SPI1在内部用于访问ESP32的附加闪存。两个控制器共享相同的SPI总线信号,并且有一个仲裁器来决定哪个控制器可以访问总线。

在SPI1总线上使用SPI主驱动器时有很多限制,请看在SPI1总线上使用SPI主驱动器的注意事项。

SPI2和SPI3是通用的SPI控制器,有时分别被称为HSPI和VSPI。它们对用户是开放的。SPI2和SPI3有独立的总线信号,各自的名称相同。每条总线有三条CS线,可以驱动多达相同数量的SPI从机。 名词解释

下表给出了与SPI主驱动有关的名词解释。

术语定义HostESP32内部的SPI控制器外设,在总线上发起SPI传输,并作为一个SPI主站。DeviceSPI从属设备。一个SPI总线可以连接到一个或多个器件。每个设备共享MOSI、MISO和SCLK信号,但只有当主机发出设备的单独CS线时,才在总线上处于激活状态。Bus一个信号总线,是连接到一个主机的所有设备所共有的。一般来说,一个总线包括以下线路。MISO、MOSI、SCLK、一条或多条CS线,以及可选的QUADWP和QUADHD。所以设备都连接到相同的线路上,除了每个设备都有自己的CS线之外。如果以菊花链的方式连接,几个器件也可以共享一条CS线。MOSI主出从进,又称从主机到设备的数据传输。也是Octal/OPI模式下的data0信号。MISO主进从出,又称从设备到主机的数据传输。也是Octal/OPI模式下的data1信号。SCLK串行时钟,由主机产生的振荡信号,使数据位的传输保持同步。CS片选,允许主机选择连接到总线上的单个设备,以便发送或接收数据。QUADWP写保护信号。用于4位(qio/qout)交易。也用于Octal/OPI模式下的data2信号。QUADHD保持信号。用于4位(qio/qout)交易。也用于Octal/OPI模式下的data3信号。DATA4Octal/OPI模式下的data4信号。DATA5Octal/OPI模式下的data5信号。DATA6Octal/OPI模式下的data6信号。DATA7Octal/OPI模式下的data7信号。Assertion激活一条线的活动De-assertion将线路恢复到不活动(回到空闲)状态的动作。Transaction一个实例是主机断言CS线,向从机传输数据,并取消断言CS线。事务是原子性的,这意味着它们永远不会被另一个事务打断。Launch edge源寄存器将信号发射到线路上的时钟边缘。Latch edge目标寄存器锁存信号的时钟边缘。 驱动程序功能

SPI主驱动管理主机与设备的通信。该驱动程序支持以下功能。

多线程环境。在读和写数据时透明地处理DMA传输。对来自同一信号总线上不同设备的数据进行自动时分复用,见SPI总线锁。

SPI主机驱动的概念是将多个设备连接到一个总线上(共享一个ESP32 SPI外设)。只要每个设备只被一个任务访问,该驱动程序就是线程安全的。然而,如果多个任务试图访问同一个SPI设备,那么驱动程序就不是线程安全的。在这种情况下,我们建议采取以下措施。

重构你的应用程序,使每个SPI外围设备一次只能被一个任务访问。使用xSemaphoreCreateMutex在共享设备周围添加一个互斥锁。 SPI交易

一个SPI总线交易包括五个阶段,可以在下表中找到。这些阶段中的任何一个都可以被跳过。

阶段描述Command在这个阶段,一个命令(0-16位)由主机写到总线上。Address在这个阶段,一个地址(0-64位)由主机在总线上传输。Write主机向设备发送数据。这种数据紧随可选的命令和地址阶段,在电气层面上与它们没有区别。Dummy这个阶段是可配置的,用于满足时间要求。Read设备向其主机发送数据。

事务的属性由总线配置结构spi_bus_config_t、设备配置结构spi_device_interface_config_t和事务配置结构spi_transaction_t决定。

一个SPI主机可以发送全双工事务,在此期间,读和写阶段同时发生。总的事务长度由以下成员的总和决定。

spi_device_interface_config_t::command_bitsspi_device_interface_config_t::address_bitsspi_transaction_t::length

而成员spi_transaction_t::rxlength只决定了接收到缓冲区的数据长度。

在半双工事务中,读和写阶段不是同时进行的(一次一个方向)。写和读阶段的长度分别由struct spi_transaction_t的length和rxlength成员决定。

命令和地址阶段是可选的,因为不是每个SPI设备都需要命令和/或地址。这反映在从机的配置中:如果command_bits和/或address_bits被设置为0,就不会发生命令或地址阶段。

读和写阶段也可以是可选的,因为不是每个交易都需要同时写入和读取数据。如果rx_buffer是NULL,并且SPI_TRANS_USE_RXDATA没有被设置,那么读取阶段将被跳过。如果tx_buffer是NULL,并且SPI_TRANS_USE_TXDATA没有被设置,则写阶段被跳过。

该驱动支持两种类型的交易:中断交易和轮询交易。程序员可以选择在每个设备上使用不同的交易类型。如果你的设备需要两种交易类型,请看向同一设备发送混合交易的注意事项。

中断交易

中断事务将阻塞交易例程,直到交易完成,从而使CPU能够运行其他任务。

一个应用任务可以排队多个交易,驱动程序将在中断服务例程(ISR)中自动逐一处理这些交易。它允许任务切换到其他程序,直到所有交易完成。

轮询交易

轮询交易不使用中断。该例程一直轮询SPI主机的状态位,直到交易完成。

所有使用中断交易的任务都可以被队列阻断。这时,他们需要等待ISR运行两次,然后才完成交易。轮询交易节省了原本用于队列处理和上下文切换的时间,从而使交易持续时间更小。缺点是在这些交易进行时CPU很忙。

spi_device_polling_end()例程需要至少1个us的开销,以便在交易完成时解除对其他任务的封锁。强烈建议使用函数spi_device_acquire_bus()和spi_device_release_bus()包装一系列的轮询交易,以避免开销。欲了解更多信息,请参见总线获取。

交易行模式

ESP32支持的线路模式如下,要使用这些模式,请在结构 spi_transaction_t中设置成员标志,如交易标志栏所示。如果你想检查相应的IO引脚是否被设置,请设置spi_bus_config_t中的成员标志,如总线IO设置标志栏所示。

模式名称命令行的宽度地址行的宽度数据行的宽交易标志总线IO设置标志普通SPI11100双输出112SPI_TRANS_MODE_DIOSPICOMMON_BUSFLAG_DUAL双 I/O122SPI_TRANS_MODE_DIO | SPI_TRANS_MULTILINE_ADDR四路输出114SPI_TRANS_MODE_QIOSPICOMMON_BUSFLAG_QUAD四路 I/O144SPI_TRANS_MODE_QIO | SPI_TRANS_MULTILINE_ADDR 命令和地址阶段

在命令和地址阶段,结构spi_transaction_t中的成员cmd和addr被发送到总线上,此时没有任何东西被读取。命令和地址阶段的默认长度在spi_device_interface_config_t中通过调用spi_bus_add_device()设置。如果成员spi_transaction_t::flags中的SPI_TRANS_VARIABLE_CMD和SPI_TRANS_VARIABLE_ADDR没有被设置,驱动程序会在从机初始化时自动将这些阶段的长度设置为默认值。

如果命令和地址阶段的长度需要可变,请声明spi_transaction_ext_t结构,在成员spi_transaction_ext_t::base中设置标志SPI_TRANS_VARIABLE_CMD和/或SPI_TRANS_VARIABLE_ADDR,并像平常一样配置base的其他部分。然后每个阶段的长度将等于spi_transaction_ext_t结构中设定的command_bits和address_bits。

如果命令和地址阶段需要和数据阶段一样的行数,你需要在结构体 spi_transaction_t的flags成员中设置SPI_TRANS_MULTILINE_CMD和/或SPI_TRANS_MULTILINE_ADDR。也请看交易行模式。

写和读阶段

通常情况下,需要传输到从机设备或从从机设备传输的数据将从结构spi_transaction_t的成员rx_buffer和tx_buffer中读取或写入一个内存块。如果传输时启用了DMA,则要求缓冲区必须是。

分配在具有DMA功能的内部存储器中。如果外部PSRAM被启用,这意味着使用pvPortMallocCaps(size, MALLOC_CAP_DMA)。32位对齐(从32位边界开始盯住,长度为4字节的倍数)。

如果不满足这些要求,由于临时缓冲区的分配和复制,事务效率将受到影响。

如果使用多条数据线传输,请在struct spi_device_interface_config_t的成员标志中设置SPI_DEVICE_HALFDUPLEX标志。而结构体 spi_transaction_t中的成员标志应该按照交易行模式中的描述进行设置。

在使用DMA时,不支持同时具有读和写阶段的半双工交易。有关细节和解决方法,请参见 “已知问题”。

总线获取

有时你可能想专门和连续地发送SPI事务,以便尽可能少地花费时间。为此,你可以使用总线获取,这有助于暂停对其他设备的交易(包括轮询或中断),直到总线被释放。要获取和释放总线,请使用函数spi_device_acquire_bus()和spi_device_release_bus()。

驱动程序的使用

通过调用函数spi_bus_initialize()来初始化一个SPI总线。确保在struct spi_bus_config_t中设置正确的I/O引脚。将不需要的信号设置为-1。

通过调用函数spi_bus_add_device()注册一个连接到总线上的设备。确保用dev_config参数配置设备可能需要的任何定时要求。你现在应该已经获得了设备的句柄,在向它发送事务时将会用到。

要与设备进行交互,请将所需的任何交易参数填入一个或多个spi_transaction_t结构。然后使用轮询交易或中断交易来发送这些结构。

中断

要么通过调用函数spi_device_queue_trans()来排队所有交易,并在稍后的时间使用函数spi_device_get_trans_result()查询结果,要么通过将所有请求输入spi_device_transmit()来同步处理。

轮询

调用函数spi_device_polling_transmit()来发送轮询交易。或者,如果你想在中间插入一些东西,通过使用spi_device_polling_start()和spi_device_polling_end()发送交易。

(可选)要对一个Device执行背对背的交易,在发送交易之前调用函数spi_device_acquire_bus(),在交易发送完毕后调用spi_device_release_bus()。

(可选)要卸载某个设备的驱动程序,请调用spi_bus_remove_device(),并将设备句柄作为参数。

(可选)要移除总线的驱动,确保没有更多的驱动被连接,并调用spi_bus_free()。

查看例子:peripherals/spi_master。

数据不超过32位的交易

当交易数据大小等于或小于32位时,为数据分配一个缓冲区将是次优的。数据可以直接存储在事务结构中。对于传输的数据,可以通过使用tx_data成员并在传输时设置SPI_TRANS_USE_TXDATA标志来实现。对于接收的数据,使用rx_data并设置SPI_TRANS_USE_RXDATA。在这两种情况下,不要碰tx_buffer或rx_buffer成员,因为它们与tx_data和rx_data使用相同的内存位置。

与uint8_t以外的整数进行交易

一个SPI主机逐个字节地将数据读入和写入内存。默认情况下,数据是以最有意义的位(MSB)为先发送的,LSB先用在极少数情况下。如果需要发送一个小于8位的值,应该以MSB优先的方式将这些位写进内存。

例如,如果需要发送0b00010,应将其写入uint8_t变量中,读取的长度应设置为5位。设备仍然会收到8位,其中有3个额外的 "随机 "位,所以必须正确地进行读取。

除此之外,ESP32是一个little-endian芯片,这意味着uint16_t和uint32_t变量的最小有效字节被存储在最小的地址。因此,如果uint16_t存储在内存中,位[7:0]首先被发送,其次是位[15:8]。

对于要传输的数据的大小与uint8_t数组不同的情况,可以使用以下宏来将数据转换为可由SPI驱动直接发送的格式。

SPI_SWAP_DATA_TX 用于传输的数据SPI_SWAP_DATA_RX用于接收数据 关于向同一设备发送混合交易的说明

为了减少编码的复杂性,只向一个设备发送一种类型的事务(中断或轮询)。但是,你仍然可以交替地发送中断和轮询事务。下面的说明解释了如何做到这一点。

轮询事务应该在所有的轮询和中断事务都完成后才启动。

由于未完成的轮询事务会阻塞其他事务,请不要忘记在spi_device_polling_start()之后调用函数spi_device_polling_end()以允许其他事务或允许其他设备使用总线。记住,如果在你的轮询事务中不需要切换到其他任务,你可以用spi_device_polling_transmit()启动一个事务,这样它将自动结束。

飞行中的轮询事务被ISR操作所干扰,以适应中断事务。在你调用spi_device_polling_start()之前,一定要确保所有发送到ISR的中断事务都已经完成。为了做到这一点,你可以继续调用spi_device_get_trans_result(),直到所有的事务被返回。

为了更好地控制函数的调用顺序,只在一个任务中向同一个设备发送混合事务。

在SPI1总线上使用SPI主驱动的注意事项

尽管SPI Bus Lock功能使得在SPI1总线上使用SPI Master驱动成为可能,但它仍然很棘手,需要很多特殊处理。这是一个适合高级开发者的功能。

要在SPI1总线上使用SPI Master驱动,你必须注意两个问题。

在驱动器操作SPI1总线时,所需的代码和数据应该在内部存储器中。

SPI1总线是由设备和Flash以及PSRAM中的数据(代码)缓存共享的。在其他驱动器操作SPI1总线时,缓存应该被禁用。因此,在驱动程序获取SPI1总线的同时,闪存和PSRAM中的数据(代码)不能被获取。

在spi_device_acquire_bus()和spi_device_release_bus()之间进行显式总线获取。

在spi_device_polling_start()和spi_device_polling_end()之间(或者在spi_device_polling_transmit()里面)隐式获取总线。

在上述时间内,所有其他任务和大多数ISR将被禁用(见IRAM安全中断处理程序)。当前任务使用的应用代码和数据应该放在内部存储器(DRAM或IRAM)中,或者已经放在ROM中。对外部存储器的访问(flash代码,flash中的const数据,以及PSRAM中的静态/堆数据)将导致缓存禁用但缓存的存储器区域被访问的异常。关于IRAM、DRAM和闪存缓存之间的区别,请参考应用内存布局文档。

要把函数放到IRAM中,你可以这样做:

在函数中加入IRAM_ATTR(include “esp_attr.h”),比如:

IRAM_ATTR void foo(void) { }

请注意,当一个函数被内联时,它将跟随其调用者的段,而属性将不生效。你可能需要使用NOLINE_ATTR来避免这种情况。

在linker.lf中使用noflash位置。请看更多关于链接器脚本生成机制的内容。请注意,有些代码可能被编译器转化为常量数据中的查找表,所以noflash_text并不安全。

请注意,优化水平可能会影响编译器的内联行为,或将一些代码转化为常量数据中的查找表,等等。

为了将数据放入DRAM,你可以这样做:

在数据定义中加入DRAM_ATTR(include “esp_attr.h”),比如:

DRAM_ATTR int g_foo = 3;

在linker.lf中使用noflash放置。请看更多关于链接器脚本生成机制的内容。

查看例子:peripherals/spi_master/hd_eeprom。

GPIO矩阵和IO_MUX

ESP32的大多数外设信号都直接连接到其专用的IO_MUX引脚。然而,这些信号也可以通过不太直接的GPIO矩阵被路由到任何其他可用的引脚。如果至少有一个信号是通过GPIO矩阵路由的,那么所有的信号都将通过它路由。

GPIO矩阵引入了路由的灵活性,但也带来了以下的缺点。

增加了MISO信号的输入延迟,这使得MISO设置时间违反的可能性更大。如果SPI需要高速运行,请使用专用的IO_MUX引脚。

允许时钟频率只有40MHz的信号,而如果使用IO_MUX引脚则为80MHz。

关于MISO输入延迟对最大时钟频率的影响的更多细节,请参见 “时间考虑”。

用于SPI总线的IO_MUX引脚在下面给出。

SPI2SPI3Pin NameGPIO NumberGPIO NumberCS0*155SCLK1418MISO1219MOSI1323QUADWP222QUADHD421 只有连接到总线上的第一个器件可以使用CS0引脚。 传输速度方面的考虑

有三个因素限制了传输速度:

交易间隔。SPI时钟频率。SPI函数的缓存缺失,包括回调。

决定大交易的传输速度的主要参数是时钟频率。对于多个小交易来说,传输速度主要由交易间隔的长度决定。

交易时间

交易持续时间包括设置SPI外围寄存器,将数据复制到FIFO或设置DMA链接,以及SPI交易的时间。

中断交易允许附加额外的开销,以适应FreeRTOS队列的成本以及在任务和ISR之间切换所需的时间。

对于中断交易,CPU可以在交易进行时切换到其他任务。这节省了CPU的时间,但增加了交易的持续时间。参见中断交易。对于轮询交易,它不会阻塞任务,但允许在交易进行中进行轮询。更多信息,请看轮询交易。

如果启用了DMA,设置链接列表需要每个交易约2us。当一个主站在传输数据时,它会自动从链接列表中读取数据。如果没有启用DMA,CPU必须自己从FIFO中写和读每个字节。通常情况下,这要比2个小时快,但是写和读的交易长度都被限制在64字节。

一个字节的数据的典型交易时间如下:

通过DMA的中断交易:28µs。

通过CPU的中断交易:25µs。

通过DMA的轮询交易:10µs。

通过CPU的轮询交易:8µs。

SPI时钟频率

传输每个字节需要8倍的时钟周期8/fspi。

如果时钟频率太高,一些功能的使用可能会受到限制。请看时序考虑因素。

缓存缺失

默认配置只将ISR放入IRAM。其他与SPI相关的功能,包括驱动本身和回调,可能会遭受缓存缺失,需要等到代码从flash中读取。选择CONFIG_SPI_MASTER_IN_IRAM可以将整个SPI驱动放入IRAM中,并将整个回调(s)及其被调用函数放入IRAM中以防止缓存丢失。

对于一个中断交易,在一个交易中传输n个字节,总体成本是20+8n/Fspi[MHz] [us]。因此,传输速度是:n/(20+8n/Fspi)。下表给出了8MHz时钟速度下的传输速度的例子。

频率(MHz)交易间隔(us)交易长度(bytes)总时间(us)总速度(KBps)82512638.5825833242.48251641490.28256489719.1825128153836.6

当一个交易长度很短时,交易间隔的成本很高。如果可能的话,尽量把几个短的交易压成一个交易,以达到更高的传输速度。

请注意,在闪存操作期间,ISR默认是禁用的。为了在flash操作期间继续发送交易,请启用CONFIG_SPI_MASTER_ISR_IN_IRAM并在成员spi_bus_config_t::intr_flags中设置ESP_INTR_FLAG_IRAM。在这种情况下,所有在开始闪存操作前排队的事务都将由ISR并行处理。还要注意的是,每个Device的回调和它们的callee函数都应该在IRAM中,否则你的回调会因为缓存丢失而崩溃。更多细节,请看IRAM安全中断处理程序。

时序考虑因素

如下图所示,在SCLK启动边缘之后和信号被内部寄存器锁定之前,MISO线有一个延迟。因此,MISO引脚的设置时间是SPI时钟速度的限制性因素。当延迟过长时,设置松弛度



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3