ESP32-S3学习笔记<5>:SPI的应用
ESP32-S3学习笔记<5>:SPI的应用
- 1. 头文件包含
- 2. SPI的配置
- 2.1 配置SPI外设
- 2.1.1 host_id/选择外设
- 2.1.2 spi_bus_config_t/外设部分参数
- 2.1.3 dma_chan/是否使能DMA功能,以及DMA通道信息
- 2.2 向SPI总线添加设备
- 2.2.1 host_id/选择外设
- 2.2.2 dev_config/外围设备的设定
- 2.2.3 handle/外围设备句柄
- 3. 数据的传输
- 3.1 handle/外围设备句柄
- 3.2 trans_desc/传输信息描述
- 4. 应用示例
1. 头文件包含
#include "driver/spi_common.h"
#include "driver/spi_master.h"
2. SPI的配置
SPI配置,分两步进行。第一步是配置外设。第二步是添加挂载在SPI总线上的片外设备(如外部ADC等)。ESP32-S3有4个SPI外设,但是SPI0和SPI1保留给Flash或者PSRAM使用。因此一般可用的是SPI2和SPI3。
SPI设备是点对点传输的。但是通过多个CS片选线,可以将一条SPI总线应用在多个外围设备上。不同的外围设备受控于不同的SPI CS片选控制线,并且可能需要不同的速率。因此,第一步配置外设时,主要是设定SPI总线的SCLK、MOSI和MISO线,因为不论外围有几个设备, 这几个信号线是不变的。第二步配置时,则可以在该SPI外设上添加多个设备,该步骤则可指定CS控制线、速率等,并生成一个专用于该外围设备的句柄。
2.1 配置SPI外设
通过以下函数配置SPI外设:
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);
其中各个参数释义如下:
2.1.1 host_id/选择外设
host_id 设置要使用芯片的哪一个SPI外设。可用的选项有:
typedef enum {
//SPI1 can be used as GPSPI only on ESP32SPI1_HOST=0, ///< SPI1SPI2_HOST=1, ///< SPI2
#if SOC_SPI_PERIPH_NUM > 2SPI3_HOST=2, ///< SPI3
#endifSPI_HOST_MAX, ///< invalid host value
} spi_host_device_t;
可以看到,SPI0是完全选择不了的。SPI1可能是受限使用,应用中最好使用SPI2和SPI3。
2.1.2 spi_bus_config_t/外设部分参数
spi_bus_config_t 设定外设的几个参数。其定义如下:
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.esp_intr_cpu_affinity_t isr_cpu_id; ///< Select cpu core to register SPI ISR.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:指定MOSI的GPIO。由于SPI外设最多支持8数据线线模式,所以也可能是data0_io_num。对于一般的4线SPI,则只有MOSI线和MISO线。以下相同。
- miso_io_num:指定MISO的GPIO。
- sclk_io_num:指定SCLK的GPIO。
- quadwp_io_num:HOLD信号。一般SPI总线中不需要这个,则设置为-1。一些Flash(如25Q Flash)带有这个引脚。
- quadhd_io_num:同上,不需要时设置为-1。
- data4_io_num、data5_io_num、data6_io_num、data7_io_num:在8数据线时使用。4线SPI,均设置为-1。
- max_transfer_sz:设置最大传输大小。
- flags:指定驱动的一些特殊功能,定义在文件 spi_common.h 文件中,以前缀 SPICOMMON_BUSFLAG_ 开头。
- isr_cpu_id:指定中断服务程序注册到哪个CPU核心。一般设置为 ESP_INTR_CPU_AFFINITY_AUTO。不过如果带有WIFI应用等,则可能需要按情况考虑。
- intr_flags:指定中断优先级等。
2.1.3 dma_chan/是否使能DMA功能,以及DMA通道信息
可用的选项有:
typedef enum {SPI_DMA_DISABLED = 0, ///< Do not enable DMA for SPI
#if CONFIG_IDF_TARGET_ESP32SPI_DMA_CH1 = 1, ///< Enable DMA, select DMA Channel 1SPI_DMA_CH2 = 2, ///< Enable DMA, select DMA Channel 2
#endifSPI_DMA_CH_AUTO = 3, ///< Enable DMA, channel is automatically selected by driver
} spi_common_dma_t;
对于ESP32-S3而言,可选择 SPI_DMA_DISABLED 或者 SPI_DMA_CH_AUTO。
2.2 向SPI总线添加设备
使用以下函数来向SPI总线添加设备:
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);
2.2.1 host_id/选择外设
host_id 设置要使用芯片的哪一个SPI外设。应该跟上一步选择的保持一致。
2.2.2 dev_config/外围设备的设定
dev_config 的定义为:
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 phaseuint8_t mode; /**< SPI mode, representing a pair of (CPOL, CPHA) configuration:- 0: (0, 0)- 1: (0, 1)- 2: (1, 0)- 3: (1, 1)*/spi_clock_source_t clock_source;///< Select SPI clock source, `SPI_CLK_SRC_DEFAULT` by default.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; ///< SPI clock speed in Hz. Derived from `clock_source`.int input_delay_ns; /**< Maximum data valid time of slave. The time required between SCLK and MISOvalid, including the possible clock delay from slave to master. The driver uses this value to give an extradelay before the MISO is ready on the line. Leave at 0 unless you know you need a delay. For better timingperformance 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 useduint32_t flags; ///< Bitwise OR of SPI_DEVICE_* flagsint 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 timetransaction_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;
在许多SPI传输中,总线向外围器件发送一些命令、地址等信息,然后才能向这些器件发送数据,或者从这些器件获取数据。而另外一些应用,则没有命令、地址等信息。ESP32-S3的驱动针对这些情况,定义了灵活的处理方式。
- command_bits:指定命令有多少个bit。如果单纯传输数据,则设置为0。这样,传输的开始就不会传输命令。实际上,即使不使用驱动传输命令,使用者也完全可以在发送缓存中加入命令、地址等信息。不过使用驱动提供的命令和地址发送,更灵活一些。
- address_bits:指定地址位数。
- dummy_bits:指定在地址位发送完毕之后,数据传输开始之前,插入多少个无效间隔位。某些外围器件对此可能有具体要求。
- mode:指定SPI的采样模式。SPI传输中,有CPOL设定和CPHA设定,前者指定时钟在空闲时是高电平还是低电平;后者指定数据采样在时钟的第一边沿进行还是第二边沿进行。具体需要根据外围器件的DATASHEET来确定。这个如果设置错误,则数据传输有相当概率会出错。
- clock_source:指定外设时钟源。一般使用 SPI_CLK_SRC_DEFAULT 即可。
- duty_cycle_pos:设定时钟高电平的占空比。根据实际需要调节。
- cs_ena_pretrans:指定在CS信号线拉低开始,到数据传输正式开始之前,需要插入多少个间隔。间隔以SPI时钟周期为单位(但不是说SCLK会输出时钟)。许多外围器件都有这个参数要求,需要根据DATASHEET设定。
- cs_ena_posttrans:指定在传输完成后,到CS信号拉高,需要插入多少个间隔。
- clock_speed_hz:指定SPI时钟频率。需要注意的是,比较 高的速率,如超过40MHz,则通过IO MUX的GPIO是不支持的。换而言之,如果需要高速传输,需要查询一下ESP32-S3的datasheet,查看哪些GPIO能支持这个速率。同时硬件设计上也要事先设计好。
- input_delay_ns:含义是MISO和SCLK之间的建立时间关系。除非特殊应用要求,否则填0。
- spics_io_num:指定该外围设备使用哪一个GPIO作为CS。
- flags:指定一些特殊功能,如LSB开始采样等(SPI默认MSB采样)。
- queue_size:指定传输队列的大小。SPI传输数据时,将传输信息打包,并放入到队列中。SPI驱动和外设相当于在后台执行这些由应用代码打包的传输请求。一般情况下,我们的应用在启动SPI传输时都是要等待传输完成的,所以这个队列处于常空状态。不用设置很大的值,个位数就可以了。
- pre_cb、post_cb:分别传入两个回调函数,用于在传输开始前和传输完成后执行一些用户需要的特殊操作。不需要的话可以设置为NULL。
2.2.3 handle/外围设备句柄
这里指向一个外围设备句柄。后续针对该外围设备的传输,都需要这个句柄。
3. 数据的传输
数据的传输,使用如下函数进行。注意,SPI驱动提供了多种传输方式。这里的传输方式,函数会等待传输完成后返回。还有一些方法,将传输命令压入到队列即返回,由用户去查询一个传输是否完成等。
esp_err_t spi_device_transmit(spi_device_handle_t handle, spi_transaction_t *trans_desc);
参数解释如下。
3.1 handle/外围设备句柄
填入外围设备句柄。
3.2 trans_desc/传输信息描述
结构体 spi_transaction_t 定义如下:
struct spi_transaction_t {uint32_t flags; ///< Bitwise OR of SPI_TRANS_* flagsuint16_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 bitssize_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 phaseuint8_t tx_data[4]; ///< If SPI_TRANS_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_TRANS_USE_RXDATA is set, data is received directly to this variable};
} ;
- flags:指定传输的一些特殊功能。
- cmd:要传输的命令。如果前面配置了传输命令位数的话,这里则填写对应的命令。否则就不用填写了。
- addr:要传输的地址。设置同上。
- length:要传输的数据总位数。注意不是字节数。
- rxlength:要接收的数据总位数。注意不是字节数。这个值必定小于或者等于 length 。
- tx_buffer:发送数据缓存。如果前面 flags 设置了 SPI_TRANS_USE_TXDATA,则使用 tx_data来代替。这适合传输少于或等于4个字节的数据,应用代码可以填写立即数,而不必传递指针了。
- rx_buffer:接收数据存储区。如果前面 flags 设置了 SPI_TRANS_USE_RXDATA,则使用 rx_data来代替。这适合传输少于或等于4个字节的数据,应用代码可以填写立即数,而不必传递指针。
4. 应用示例
以下应用,配置一个SPI总线,并添加一个外围设备。然后,传输16个字节;传输每个字节的同时也读取一个字节,最后打印这些接收到的字节。
test_spi.h文件:
#include "driver/gpio.h"#define TEST_SPI_GPIO_NSS (GPIO_NUM_4)
#define TEST_SPI_GPIO_SCLK (GPIO_NUM_5)
#define TEST_SPI_GPIO_MISO (GPIO_NUM_6)
#define TEST_SPI_GPIO_MOSI (GPIO_NUM_7) void TEST_SPI_SPIConfig(void) ;
void TEST_SPI_SPITransfer(unsigned char *pucTxData, unsigned char *pucRxData, unsigned int uiSize) ;
test_spi.c文件:
#include "stdio.h"
#include "string.h"
#include "driver/spi_common.h"
#include "driver/spi_master.h"#include "test_spi.h"spi_device_handle_t g_pstSPIDevHandle ;void TEST_SPI_SPIConfig(void)
{esp_err_t iErrCode ;spi_bus_config_t stSPIConfig ;spi_device_interface_config_t stDevConfig ;memset(&stSPIConfig, 0, sizeof(stSPIConfig)) ;stSPIConfig.mosi_io_num = TEST_SPI_GPIO_MOSI ;stSPIConfig.miso_io_num = TEST_SPI_GPIO_MISO ;stSPIConfig.sclk_io_num = TEST_SPI_GPIO_SCLK ;stSPIConfig.quadwp_io_num = -1 ;stSPIConfig.quadhd_io_num = -1 ;stSPIConfig.data4_io_num = -1 ;stSPIConfig.data5_io_num = -1 ;stSPIConfig.data6_io_num = -1 ;stSPIConfig.data7_io_num = -1 ;stSPIConfig.max_transfer_sz = 1024 ;stSPIConfig.flags = SPICOMMON_BUSFLAG_MASTER ;iErrCode = spi_bus_initialize(SPI2_HOST, &stSPIConfig, SPI_DMA_CH_AUTO) ;if(ESP_OK != iErrCode){printf("spi_bus_initialize failed. Return value is %d\n", iErrCode) ;}memset(&stDevConfig, 0, sizeof(stDevConfig)) ;stDevConfig.mode = 0 ;stDevConfig.clock_source = SPI_CLK_SRC_DEFAULT ;stDevConfig.clock_speed_hz = 1000000 ;stDevConfig.spics_io_num = TEST_SPI_GPIO_NSS ;stDevConfig.queue_size = 8 ;iErrCode = spi_bus_add_device(SPI2_HOST, &stDevConfig, &g_pstSPIDevHandle) ;if(ESP_OK != iErrCode){printf("spi_bus_add_device failed. Return value is %d\n", iErrCode) ;}return ;
}void TEST_SPI_SPITransfer(unsigned char *pucTxData, unsigned char *pucRxData, unsigned int uiSize)
{esp_err_t iErrCode ;spi_transaction_t stTrans ;stTrans.flags = 0 ;stTrans.cmd = 0 ;stTrans.addr = 0 ;stTrans.length = (uiSize * 8) ;stTrans.rxlength = (uiSize * 8) ;stTrans.rx_buffer = pucRxData ;stTrans.tx_buffer = pucTxData ;iErrCode = spi_device_transmit(g_pstSPIDevHandle, &stTrans) ;if(ESP_OK != iErrCode){printf("spi_device_transmit failed. Return value is %d\n", iErrCode) ;}return ;
}
main.c文件:
#include <stdint.h>
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"#include "test_spi.h"void app_main(void)
{unsigned char aucTxBuf[16] = { 0x00, 0x11, 0x22, 0x33, \0x44, 0x55, 0x66, 0x77, \0x88, 0x99, 0xAA, 0xBB, \0xCC, 0xDD, 0xEE, 0xFF} ;unsigned char aucRxBuf[16] ;TEST_SPI_SPIConfig() ;while (true){memset(aucRxBuf, 0, sizeof(aucRxBuf)) ;TEST_SPI_SPITransfer(aucTxBuf, aucRxBuf, sizeof(aucTxBuf)) ;printf("Rx data : %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x \n",aucRxBuf[0], aucRxBuf[1], aucRxBuf[2], aucRxBuf[3], aucRxBuf[4], aucRxBuf[5], aucRxBuf[6], aucRxBuf[7], aucRxBuf[8], aucRxBuf[9], aucRxBuf[10], aucRxBuf[11], aucRxBuf[12], aucRxBuf[13], aucRxBuf[14], aucRxBuf[15]) ;vTaskDelay(1000) ;}
}