ESP32学习笔记(19) 您所在的位置:网站首页 spi全双工是什么意思 ESP32学习笔记(19)

ESP32学习笔记(19)

2024-06-02 04:53| 来源: 网络整理| 查看: 265

一、SPI简介

SPI(Serial Peripheral Interface) 协议是由摩托罗拉公司提出的通讯协议,即串行外围设备接口,是一种高速全双工的通信总线。它被广泛地使用在 ADC、LCD 等设备与 MCU 间,要求通讯速率较高的场合。

芯片的管脚上只占用四根线。 MISO: 主器件数据输入,从器件数据输出。 MOSI:主器件数据输出,从器件数据输入。 SCK: 时钟信号,由主设备控制发出。 NSS(CS): 从设备选择信号,由主设备控制。当NSS为低电平则选中从器件。

1.1 ESP32中SPI

ESP32集成了4个SPI外设。

SPI0和SPI1在内部用于访问ESP32所连接的闪存。两个控制器共享相同的SPI总线信号,并且有一个仲裁器来确定哪个可以访问该总线。 在SPI1总线上使用SPI Master驱动程序时有很多限制,请参阅《在SPI1总线 上使用SPI Master驱动程序的注意事项》。 SPI2和SPI3是通用SPI控制器,有时分别称为HSPI和VSPI。它们向用户开放。SPI2和SPI3具有独立的总线信号,分别具有相同的名称。每条总线具有3条CS线,最多能控制6个SPI从设备。

ESP32内部的SPI控制器可设置为主模式(Master),基本特点如下

适应多线程环境 可配置DMA辅助传输 在同一信号线上自动分配时间处理来自不同设备的的多路数据,请参见SPI总线锁定。

注意: SPI主驱动程序的概念是将多个设备连接到一条总线(共享一个ESP32 SPI外设)。只要仅通过一个任务访问每个设备,驱动程序就是线程安全的。但是,如果多个任务尝试访问同一SPI设备,则驱动程序不是线程安全的。在这种情况下,建议执行以下任一操作: * 重构您的应用程序,以便每个SPI外围设备一次只能由一个任务访问。 * 使用围绕共享设备添加互斥锁xSemaphoreCreateMutex。

ESP-IDF 编程指南——SPI主驱动

1.2 SPI传输

SPI总线通信包含五个阶段,可以在下表中找到。这些阶段中的任何一个都可以跳过。

阶段 描述 命令 在此阶段,主机将命令(0-16位)写入总线。 地址 在此阶段,主机通过总线发送地址(0-64位)。 写 主机将数据发送到设备。该数据遵循可选的命令和地址阶段,并且在电气级别上与它们是无法区分的。 空 此阶段是可配置的,用于满足时序要求。 读 设备将数据发送到其主机。

命令和地址段是可选的,因为并非每个SPI设备都需要命令和/或地址。这反映在spi_device_interface_config_t中:如果command_bits和/或address_bits设置为零,则不会发送命令或地址段。

读取和写入段也可以是可选的,因为并非每个通信都需要写入和读取数据。如果rx_buffer为NULLSPI_TRANS_USE_RXDATA且未设置,则跳过读取阶段。如果tx_buffer为NULLSPI_TRANS_USE_TXDATA且未设置,则跳过写阶段。

配置GPIO的SPI复用引脚和SPI控制器spi_bus_config_t

//spi_bus_config_t用于配置GPIO的SPI复用引脚和SPI控制器 //注意:如果不使用QSPI可以直接不初始化quadwp_io_num和quadhd_io_num,总线会自动关闭未被配置的信号线 //如果不使用某线应将其设置为-1 struct spi_bus_config_t={ .miso_io_num,//MISO信号线,可复用为QSPI的D0 .mosi_io_num,//MOSI信号线,可复用为QSPI的D1 .sclk_io_num,//SCLK信号线 .quadwp_io_num,//WP信号线,专用于QSPI的D2 .quadhd_io_num,//HD信号线,专用于QSPI的D3 .max_transfer_sz,//最大传输数据大小,单位字节,默认为4094 .intr_flags,//中断指示位 };

配置SPI协议情况spi_transaction_t

//spi_transaction_t用于配置SPI的数据格式 //注意:这个结构体只定义了一种SPI传输格式,如果需要多种SPI传输则需要定义多个结构体并进行实例化 struct spi_transaction_t={ .cmd,//指令数据,其长度在spi_device_interface_config_t中的command_bits设置 .addr,//地址数据,其长度在spi_device_interface_config_t中的address_bits设置 .length,//数据总长度,单位:比特 .rxlength,//接收到的数据总长度,应小于length,如果设置为0则默认设置为length .flags,//SPI传输属性设置 .user,//用户定义变量,可以用来存储传输ID等注释信息 .tx_buffer,//发送数据缓存区指针 .tx_data,//发送数据 .rx_buffer,//接收数据缓存区指针,如果启用DMA则需要至少4个字节 .rx_data//如果设置了SPI_TRANS_USE_RXDATA,数据会被这个变量直接接收 };

配置SPI的数据格式spi_device_interface_config_t

//spi_device_interface_config_t用于配置SPI协议情况 //需要根据从设备的数据手册进行设置 struct spi_device_interface_config_t={ .command_bits,//默认控制位长度,设置为0-16 .address_bits,//默认地址位长度,设置为0-64 .dummy_bits,//在地址和数据位段之间插入的dummy位长度,用于匹配时序,一般可以保持默认 .clock_speed_hz,//时钟频率,设置的是80MHz的分频系数,单位为Hz .mode,//SPI模式,设置为0-3 .duty_cycle_pos,// .cs_ena_pretrans,//传输前CS信号的建立时间,只在半双工模式下有用 .cs_ena_posttrans,//传输时CS信号的保持时间 .input_delay_ns,//从机的最大合法数据传输时间 .spics_io_num,//设置GPIO复用为CS引脚 .queue_size,//传输队列大小,决定了等待传输数据的数量 .flags,//SPI设备属性设置 .pre_cb,//传输开始时的回调函数 .post_cb,//传输结束时的回调函数 };

SPI主机可以发送全双工通信,在此期间读和写阶段会同时发生。总传输时间由以下成员的总和决定:

spi_device_interface_config_t::command_bits spi_device_interface_config_t::address_bits spi_transaction_t::length

而成员spi_transaction_t::rxlength仅确定接收到缓冲区的数据长度。

在半双工通信中,读取和写入阶段不是同时的(一次是一个方向)。写入和读取阶段的长度分别由spi_transaction_t的length和rxlength成员确定。

1.2.1 中断传输

中断传输期间,CPU可以执行其他任务。传输结束时,SPI外设触发中断,CPU调用任务处理函数进行处理

注意:一个任务可以排列多个传输序列,驱动程序会自动在中断服务程序(ISR)中对传输结果进行处理;但是中断传输会导致很多中断,如果设置中断任务太多还会影响日常任务运行降低实时性能。

1.2.2 轮询传输

轮询传输会轮询SPI主机的状态位直到传输完成。

轮询传输可以节约ISR队列挂起等待和线程(任务)上下文切换所需时间,但是会导致CPU占用。

使用spi_device_polling_end()传输完成后,至少需要1us时间解除对其他任务的阻塞;强烈建议使用spi_device_acquire_bus()并spi_device_release_bus()进行轮询传输,避免开销。

1.3 GPIO矩阵和IO_MUX

ESP32的大多数外设信号都直接连接到其专用的IO_MUX引脚。但是,也可以使用GPIO矩阵将信号转换到任何其他可用的引脚。如果至少一个信号通过GPIO矩阵转换,则所有信号都将通过GPIO矩阵转换。

GPIO矩阵引入了转换灵活性,但也带来了以下缺点:

增加了MISO信号的输入延迟,这更可能违反MISO设置时间。如果SPI需要高速运行,请使用专用的IO_MUX引脚。 如果使用IO_MUX引脚,则允许信号的时钟频率最多为40 MHz,而时钟频率最高为80 MHz。

SPI总线的IO_MUX引脚如下所示

引脚对应的GPIO SPI2 SPI3 CS0 * 15 5 SCLK 14 18 MISO 12 19 MOSI 13 23 QUADWP 2 22 QUADHD 4 21 仅连接到总线的第一个设备可以使用CS0引脚。 二、API说明

以下 SPI 主机接口位于 driver/include/driver/spi_master.h。

2.1 spi_bus_initialize 2.2 spi_bus_add_device 2.3 spi_device_polling_transmit 2.4 spi_device_acquire_bus 2.5 spi_device_release_bus 2.6 spi_bus_remove_device 三、编程流程 3.1 设置通信参数

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

3.2 驱动程序安装

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

3.3 运行SPI通信

要与设备进行交互,请使用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()。

四、SPI主机代码

根据 esp-idf\examples\peripherals\spi_master\spi_eeprom 中的例程

注意:在SPI接收中,如果定义了t.flags = SPI_TRANS_USE_RXDATA,则使用t.rx_data接收数据,否则使用t.rx_buffer=data来接收数据

#include #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/spi_master.h" #include "driver/gpio.h" #include "sdkconfig.h" #include "esp_log.h" #define DMA_CHAN 2 #define PIN_NUM_MISO 12 #define PIN_NUM_MOSI 13 #define PIN_NUM_CLK 14 #define PIN_NUM_CS 15 static const char TAG[] = "main"; esp_err_t spi_write(spi_device_handle_t spi, uint8_t *data, uint8_t len) { esp_err_t ret; spi_transaction_t t; if (len==0) return; //no need to send anything memset(&t, 0, sizeof(t)); //Zero out the transaction gpio_set_level(PIN_NUM_CS, 0); t.length=len*8; //Len is in bytes, transaction length is in bits. t.tx_buffer=data; //Data t.user=(void*)1; //D/C needs to be set to 1 ret=spi_device_polling_transmit(spi, &t); //Transmit! assert(ret==ESP_OK); //Should have had no issues. gpio_set_level(PIN_NUM_CS, 1); return ret; } esp_err_t spi_read(spi_device_handle_t spi, uint8_t *data) { spi_transaction_t t; gpio_set_level(PIN_NUM_CS, 0); memset(&t, 0, sizeof(t)); t.length=8; t.flags = SPI_TRANS_USE_RXDATA; t.user = (void*)1; esp_err_t ret = spi_device_polling_transmit(spi, &t); assert( ret == ESP_OK ); *data = t.rx_data[0]; gpio_set_level(PIN_NUM_CS, 1); return ret; } void app_main(void) { esp_err_t ret; spi_device_handle_t spi; ESP_LOGI(TAG, "Initializing bus SPI%d...", SPI2_HOST+1); spi_bus_config_t buscfg={ .miso_io_num = PIN_NUM_MISO, // MISO信号线 .mosi_io_num = PIN_NUM_MOSI, // MOSI信号线 .sclk_io_num = PIN_NUM_CLK, // SCLK信号线 .quadwp_io_num = -1, // WP信号线,专用于QSPI的D2 .quadhd_io_num = -1, // HD信号线,专用于QSPI的D3 .max_transfer_sz = 64*8, // 最大传输数据大小 }; spi_device_interface_config_t devcfg={ .clock_speed_hz = SPI_MASTER_FREQ_10M, // Clock out at 10 MHz, .mode = 0, // SPI mode 0 /* * The timing requirements to read the busy signal from the EEPROM cannot be easily emulated * by SPI transactions. We need to control CS pin by SW to check the busy signal manually. */ .spics_io_num = -1, .queue_size = 7, // 传输队列大小,决定了等待传输数据的数量 }; //Initialize the SPI bus ret = spi_bus_initialize(SPI2_HOST, &buscfg, DMA_CHAN); ESP_ERROR_CHECK(ret); ret = spi_bus_add_device(SPI2_HOST, &devcfg, &spi); ESP_ERROR_CHECK(ret); gpio_pad_select_gpio(PIN_NUM_CS); // 选择一个GPIO gpio_set_direction(PIN_NUM_CS, GPIO_MODE_OUTPUT);// 把这个GPIO作为输出 const char test_str[] = "Hello!"; uint8_t test_buf[4] = ""; while (1) { spi_write(spi, test_str, 13); ESP_LOGI(TAG, "Write: %s", test_str); vTaskDelay(100); for (int i = 0; i < sizeof(test_buf); i++) { ret = spi_read(spi, &test_buf[i]); ESP_ERROR_CHECK(ret); } ESP_LOGI(TAG, "Read: %s", test_buf); memset(test_buf, 0, 4); vTaskDelay(100); } }

ESP32做主机,NRF52832做从机,查看打印:

• 由 Leung 写于 2021 年 5 月 26 日

• 参考:ESPIDF开发ESP32学习笔记【SPI与片外FLASH基础】     ESP32 SPI驱动Li3dh&&kx203     【ESP32-IDF】 02-4 外设-SPI         ESP32设备SPI主设备驱动



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有