文章目录
- 前言
- 通信流程图
- SPI总线初始化
- `spi_bus_config_t` 结构体声明及成员含义
- `spi_bus_initialize` 函数的作用、原型及参数和返回值的含义
- 函数原型:
- 参数含义:
- 返回值:
- 示例:
- 添加SPI设备
- `spi_bus_add_device` 函数介绍
- 函数原型:
- 参数说明:
- 返回值:
- `spi_device_interface_config_t` 结构体
- 成员含义:
- 示例代码:
- 参数含义:
- 返回值:
- `spi_transaction_t` 结构体声明及成员含义
- 示例:
- spi_device_release_bus
- 函数原型:
- 参数含义:
- 返回值:
- 示例:
- spi_bus_remove_device
- 函数原型:
- 参数含义:
- 返回值:
- 示例:
- 示例
- 总结
前言
ESP32 是一款功能强大的微控制器,广泛应用于物联网(IoT)设备和嵌入式系统中。SPI(Serial Peripheral Interface)是 ESP32 常用的一种通信协议,用于在微控制器和外部设备之间进行高速数据传输。SPI 通信具有简单、高效和可靠的特点,非常适合需要快速响应的应用场景。在本文中,我们将探讨如何在 ESP32 上使用 SPI 总线,实现与外部传感器、存储设备和其他外设的通信。
通信流程图
+---------------------------+
| 开始 |
+---------------------------+|v
+---------------------------+
| 初始化 SPI 总线 |
| - 配置引脚 |
| - 设置参数 |
+---------------------------+|v
+---------------------------+
| 添加 SPI 设备 |
| - 配置设备参数 |
| - 设置 CS 引脚 |
+---------------------------+|v
+---------------------------+
| 配置 SPI 传输 |
| - 设置 TX 缓冲区 |
| - 设置 RX 缓冲区 |
+---------------------------+|v
+---------------------------+
| 执行 SPI 传输 |
| - 发送数据 |
| - 接收数据 |
+---------------------------+|v
+---------------------------+
| 移除 SPI 设备 |
| - 释放资源 |
+---------------------------+|v
+---------------------------+
| 释放 SPI 总线 |
| - 释放引脚 |
| - 清理资源 |
+---------------------------+|v
+---------------------------+
| 结束 |
+---------------------------+
SPI总线初始化
spi_bus_config_t
结构体声明及成员含义
在 ESP32 中,spi_bus_config_t
结构体用于配置 SPI 总线的参数。下面是该结构体的声明及其成员的含义:
typedef struct {int mosi_io_num; // MOSI(主设备输出,从设备输入)引脚编号int miso_io_num; // MISO(主设备输入,从设备输出)引脚编号int sclk_io_num; // SCLK(时钟信号)引脚编号int quadwp_io_num; // WP(写保护)引脚编号,用于四线制 SPI,如果不使用则设置为 -1int quadhd_io_num; // HD(保持数据)引脚编号,用于四线制 SPI,如果不使用则设置为 -1int max_transfer_sz; // 最大传输数据大小,以字节为单位uint32_t flags; // 标志位,用于设置 SPI 总线的特定属性(通常设置为 0)int intr_flags; // 中断标志,用于配置中断的优先级
} spi_bus_config_t;
spi_bus_initialize
函数的作用、原型及参数和返回值的含义
spi_bus_initialize
函数用于初始化 SPI 总线。它配置 SPI 总线的引脚和其他参数,以便 SPI 设备可以在总线上通信。
函数原型:
esp_err_t spi_bus_initialize(spi_host_device_t host, const spi_bus_config_t *bus_config, spi_dma_chan_t dma_chan);
参数含义:
host
:指定要将设备添加到的 SPI 主机(总线)。可以是SPI1_HOST
、SPI2_HOST
或SPI3_HOST
(在 ESP32 上),以及适用于其他 ESP32 变体的主机接口。bus_config
:指向spi_bus_config_t
结构体的指针,该结构体包含了 SPI 总线的配置信息。dma_chan
:指定要使用的 DMA 通道(通常是 1 或 2,你可以使用自动SPI_DMA_CH_AUTO
,如果不使用 DMA,则设置为 0)。
返回值:
ESP_OK
:表示初始化成功。- 其他错误代码(如
ESP_ERR_INVALID_ARG
、ESP_ERR_INVALID_STATE
等):表示初始化过程中发生了错误。
示例:
spi_bus_config_t bus_config = {.mosi_io_num = 23,.miso_io_num = 19,.sclk_io_num = 18,.quadwp_io_num = -1,.quadhd_io_num = -1,.max_transfer_sz = 4096,.flags = 0,.intr_flags = 0
};esp_err_t ret = spi_bus_initialize(HSPI_HOST, &bus_config, 1);
if (ret == ESP_OK) {printf("SPI bus initialized successfully.\n");
} else {printf("Failed to initialize SPI bus: %s\n", esp_err_to_name(ret));
}
以上示例演示了如何使用 spi_bus_initialize
函数初始化一个 SPI 总线。通过配置 spi_bus_config_t
结构体并调用 spi_bus_initialize
函数,我们可以轻松地设置 SPI 总线,以便与外部设备进行通信。
添加SPI设备
spi_bus_add_device
函数介绍
spi_bus_add_device
函数用于将一个 SPI 设备添加到已经初始化的 SPI 总线上。它会创建一个 SPI 设备句柄,用于后续的 SPI 通信操作。
函数原型:
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);
参数说明:
-
host
:指定要将设备添加到的 SPI 主机(总线)。可以是SPI1_HOST
、SPI2_HOST
或SPI3_HOST
(在 ESP32 上),以及适用于其他 ESP32 变体的主机接口。 -
dev_config
:指向spi_device_interface_config_t
结构体的指针,该结构体包含了 SPI 设备的配置参数,如时钟频率、SPI 模式、片选引脚等。 -
handle
:指向spi_device_handle_t
类型的指针,用于接收设备句柄。这个句柄用于后续的 SPI 传输操作。
返回值:
ESP_OK
:表示成功将设备添加到 SPI 总线上。- 其他错误代码(如
ESP_ERR_INVALID_ARG
、ESP_ERR_NO_MEM
等):表示添加设备过程中发生了错误。
spi_device_interface_config_t
结构体
spi_device_interface_config_t
结构体用于配置 SPI 设备的参数。以下是结构体的定义及其成员的含义:
typedef struct {uint32_t clock_speed_hz; // SPI 时钟频率,以赫兹为单位uint8_t mode; // SPI 模式(0、1、2 或 3)int spics_io_num; // 片选引脚的编号uint8_t queue_size; // 事务队列的大小spi_device_pre_cb_t pre_cb; // 传输前的回调函数(可以设置为 NULL)spi_device_post_cb_t post_cb; // 传输后的回调函数(可以设置为 NULL)void* user; // 用户定义的数据uint8_t flags; // 设备标志,指定设备的一些特殊特性(如 DMA 支持)int address_bits; // 设备地址位数(用于某些设备的地址模式)int dummy_bits; // 假数据位数(用于 SPI 设备的空闲周期)spi_device_handle_t handle; // 设备句柄(自动设置,无需用户配置)
} spi_device_interface_config_t;
成员含义:
-
clock_speed_hz
:设置 SPI 时钟频率,以赫兹(Hz)为单位。该值决定了 SPI 总线的速度。 -
mode
:指定 SPI 模式,取值为 0、1、2 或 3。SPI 模式决定了数据传输的极性和相位。 -
spics_io_num
:指定 SPI 设备的片选(CS)引脚的编号。片选引脚用于选择具体的 SPI 设备进行通信。 -
queue_size
:指定事务队列的大小。事务队列用于存储待处理的 SPI 传输事务,确保数据传输的顺序和可靠性。 -
pre_cb
:传输前的回调函数。可以在传输前执行一些特定的操作,比如设置设备状态。可以设置为NULL
表示不使用回调函数。 -
post_cb
:传输后的回调函数。可以在传输完成后执行一些特定的操作,比如处理传输结果。可以设置为NULL
表示不使用回调函数. -
user
:用户定义的数据,可以在回调函数中使用。用于存储与设备相关的额外信息。 -
flags
:设备标志,指定设备的一些特殊特性。例如,是否启用 DMA 支持等。 -
address_bits
:设备地址位数(用于某些设备的地址模式)。设置设备地址的位数。 -
dummy_bits
:假数据位数,设置在 SPI 传输中需要插入的假数据位数,用于某些 SPI 设备的空闲周期。 -
handle
:SPI 设备句柄,自动设置,用于后续的 SPI 传输操作。用户无需手动配置。
示例代码:
以下是如何使用 spi_bus_add_device
函数将一个 SPI 设备添加到 SPI 总线的示例代码:
#include "driver/spi_master.h"// 定义 SPI 总线配置
spi_bus_config_t bus_config = {.mosi_io_num = 23,.miso_io_num = 19,.sclk_io_num = 18,.quadwp_io_num = -1,.quadhd_io_num = -1,.max_transfer_sz = 4096,
};// 初始化 SPI 总线
esp_err_t ret = spi_bus_initialize(SPI2_HOST, &bus_config, 1);
if (ret != ESP_OK) {// 处理初始化失败的情况
}// 定义 SPI 设备配置
spi_device_interface_config_t dev_config = {.clock_speed_hz = 1*1000*1000, // 时钟频率 1 MHz.mode = 0, // SPI 模式 0.spics_io_num = 5, // CS 引脚.queue_size = 7, // 事务队列大小.pre_cb = NULL, // 传输前的回调函数.post_cb = NULL, // 传输后的回调函数
};// 设备句柄
spi_device_handle_t handle;// 添加 SPI 设备
ret = spi_bus_add_device(SPI2_HOST, &dev_config, &handle);
if (ret == ESP_OK) {printf("SPI 设备成功添加。\n");
} else {printf("添加 SPI 设备失败: %s\n", esp_err_to_name(ret));
}
通过 spi_bus_add_device
函数,你可以将 SPI 设备成功地添加到 SPI 总线上,并配置其操作参数,为后续的 SPI 数据传输做好准备。
## spi_device_polling_transmit`spi_device_polling_transmit` 函数用于通过 SPI 总线发送和接收数据,并在完成传输前阻塞。该函数适用于需要确保数据立即传输且无延迟的场景,因为它在数据传输完成前不会返回。#### 函数原型:```c
esp_err_t spi_device_polling_transmit(spi_device_handle_t handle, spi_transaction_t *trans_desc);
参数含义:
handle
:SPI 设备的句柄,用于标识哪个 SPI 设备进行传输。该句柄在调用spi_bus_add_device
函数时获得。trans_desc
:指向spi_transaction_t
结构体的指针,该结构体包含了 SPI 传输的信息,如发送和接收缓冲区、数据长度等。
返回值:
ESP_OK
:表示传输成功。- 其他错误代码(如
ESP_ERR_INVALID_ARG
、ESP_ERR_INVALID_STATE
等):表示传输过程中发生了错误。
spi_transaction_t
结构体声明及成员含义
在调用 spi_device_polling_transmit
函数时,需要提供 spi_transaction_t
结构体,该结构体包含了 SPI 传输的具体信息。下面是该结构体的声明及其成员的含义:
typedef struct {uint32_t flags; // 传输的标志位size_t length; // 传输的总长度(以位为单位)size_t rxlength; // 接收数据的长度(以位为单位),通常与 length 相同void *user; // 用户定义的指针,可以在回调中使用union {const uint8_t *tx_buffer; // 指向发送缓冲区的指针uint8_t *tx_data[4]; // 小于 32 位的数据传输直接通过此数组};union {uint8_t *rx_buffer; // 指向接收缓冲区的指针uint8_t *rx_data[4]; // 小于 32 位的数据接收直接通过此数组};
} spi_transaction_t;
示例:
以下是如何使用 spi_device_polling_transmit
函数进行 SPI 数据传输的示例代码:
spi_device_handle_t handle; // 假设该句柄已通过 spi_bus_add_device 函数获取spi_transaction_t trans_desc = {.flags = 0,.length = 8 * sizeof(data_to_send), // 传输数据长度,以位为单位.tx_buffer = data_to_send, // 发送缓冲区.rx_buffer = data_received, // 接收缓冲区
};esp_err_t ret = spi_device_polling_transmit(handle, &trans_desc);
if (ret == ESP_OK) {printf("SPI data transmitted successfully.\n");// 在 data_received 中处理接收到的数据
} else {printf("Failed to transmit SPI data: %s\n", esp_err_to_name(ret));
}
在这个示例中,spi_device_polling_transmit
函数用于发送和接收数据。通过配置 spi_transaction_t
结构体并调用该函数,我们可以实现高效的 SPI 数据传输。该函数会在传输完成后返回,因此适用于需要立即获取传输结果的应用场景。
spi_device_release_bus
spi_device_release_bus
函数用于释放当前 SPI 设备对 SPI 总线的控制权。在多任务环境或多设备环境中,如果某个任务或设备占用了 SPI 总线,那么其他任务或设备就无法使用该总线。通过调用 spi_device_release_bus
,可以释放总线的控制权,让其他任务或设备能够使用 SPI 总线。
函数原型:
esp_err_t spi_device_release_bus(spi_device_handle_t handle);
参数含义:
handle
:SPI 设备的句柄,用于标识哪个 SPI 设备要释放总线控制权。该句柄在调用spi_bus_add_device
函数时获得。
返回值:
ESP_OK
:表示成功释放总线。- 其他错误代码(如
ESP_ERR_INVALID_ARG
等):表示释放总线过程中发生了错误。
示例:
以下是如何使用 spi_device_release_bus
函数释放 SPI 总线控制权的示例代码:
spi_device_handle_t handle; // 假设该句柄已通过 spi_bus_add_device 函数获取// 进行一些 SPI 传输操作
// ...// 释放 SPI 总线
esp_err_t ret = spi_device_release_bus(handle);
if (ret == ESP_OK) {printf("SPI bus released successfully.\n");
} else {printf("Failed to release SPI bus: %s\n", esp_err_to_name(ret));
}
在这个示例中,spi_device_release_bus
函数用于释放 SPI 总线的控制权,使得其他任务或设备可以使用该总线。这个函数对于协调多个 SPI 设备或任务之间的总线使用非常有用。
spi_bus_remove_device
spi_bus_remove_device
函数用于从 SPI 总线上移除一个已添加的 SPI 设备。调用该函数可以释放与该设备相关的资源,从而让 SPI 总线可以被其他设备使用或进行重新配置。
函数原型:
esp_err_t spi_bus_remove_device(spi_device_handle_t handle);
参数含义:
handle
:SPI 设备的句柄,用于标识要移除的 SPI 设备。该句柄在调用spi_bus_add_device
函数时获得。
返回值:
ESP_OK
:表示成功移除设备。- 其他错误代码(如
ESP_ERR_INVALID_ARG
等):表示移除设备过程中发生了错误。
示例:
以下是如何使用 spi_bus_remove_device
函数从 SPI 总线上移除一个设备的示例代码:
spi_device_handle_t handle; // 假设该句柄已通过 spi_bus_add_device 函数获取// 进行一些 SPI 传输操作
// ...// 移除 SPI 设备
esp_err_t ret = spi_bus_remove_device(handle);
if (ret == ESP_OK) {printf("SPI device removed successfully.\n");
} else {printf("Failed to remove SPI device: %s\n", esp_err_to_name(ret));
}
在这个示例中,spi_bus_remove_device
函数用于从 SPI 总线上移除指定的设备。通过调用该函数,可以释放与该设备相关的资源,从而为其他设备的使用或重新配置 SPI 总线提供便利。这个函数在需要动态管理 SPI 设备的应用中非常有用。
示例
#include <stdio.h>
#include "driver/spi_master.h"
#include "esp_log.h"
#include "esp_system.h"#define SPI_BUS_SPEED_HZ 1000000 // SPI 时钟频率 1 MHz
#define SPI_DEVICE_MODE 0 // SPI 模式 0
#define SPI_DEVICE_CS_PIN 5 // 片选引脚
#define SPI_DEVICE_QUEUE_SIZE 7 // 事务队列大小static const char *TAG = "spi_example";// SPI 总线配置
spi_bus_config_t bus_config = {.mosi_io_num = 23, // MOSI 引脚.miso_io_num = 19, // MISO 引脚.sclk_io_num = 18, // SCLK 引脚.quadwp_io_num = -1, // 不使用 Quad WP.quadhd_io_num = -1, // 不使用 Quad HD.max_transfer_sz = 4096, // 最大传输大小
};// SPI 设备配置
spi_device_interface_config_t dev_config = {.clock_speed_hz = SPI_BUS_SPEED_HZ, // 时钟频率.mode = SPI_DEVICE_MODE, // SPI 模式.spics_io_num = SPI_DEVICE_CS_PIN, // 片选引脚.queue_size = SPI_DEVICE_QUEUE_SIZE, // 事务队列大小.pre_cb = NULL, // 传输前的回调函数.post_cb = NULL, // 传输后的回调函数
};void app_main(void)
{esp_err_t ret;// 初始化 SPI 总线ret = spi_bus_initialize(SPI2_HOST, &bus_config, 1);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to initialize SPI bus: %s", esp_err_to_name(ret));return;}// 添加 SPI 设备spi_device_handle_t spi_handle;ret = spi_bus_add_device(SPI2_HOST, &dev_config, &spi_handle);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to add SPI device: %s", esp_err_to_name(ret));spi_bus_free(SPI2_HOST);return;}// 准备要发送的数据uint8_t tx_data[] = {0xAA, 0xBB, 0xCC, 0xDD};uint8_t rx_data[sizeof(tx_data)] = {0};spi_transaction_t trans = {.length = sizeof(tx_data) * 8, // 数据长度(以位为单位).tx_buffer = tx_data, // 发送缓冲区.rx_buffer = rx_data, // 接收缓冲区};// 执行 SPI 传输ret = spi_device_transmit(spi_handle, &trans);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to transmit SPI data: %s", esp_err_to_name(ret));} else {ESP_LOGI(TAG, "SPI data transmitted successfully");// 打印接收到的数据printf("Received data: ");for (int i = 0; i < sizeof(rx_data); i++) {printf("0x%02X ", rx_data[i]);}printf("\n");}// 移除 SPI 设备ret = spi_bus_remove_device(spi_handle);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to remove SPI device: %s", esp_err_to_name(ret));}// 释放 SPI 总线ret = spi_bus_free(SPI2_HOST);if (ret != ESP_OK) {ESP_LOGE(TAG, "Failed to free SPI bus: %s", esp_err_to_name(ret));}
}
总结
通过本文的介绍,我们了解了如何在 ESP32 上使用 SPI 总线,实现与各种外部设备的高速数据通信。SPI 通信的简单性和高效性,使其成为物联网和嵌入式系统中常用的通信方式。掌握了 SPI 的使用方法后,开发者可以轻松地将 ESP32 与多种外设集成,为自己的项目提供更多功能和可能性。无论是读取传感器数据,还是控制显示屏,SPI 都能提供可靠的解决方案,帮助开发者实现他们的设计目标。