ESPIDF-通讯外设

SPI

SPI资源

ESP32集成了4个SPI外设。

  • SPI0和SPI1通过总线仲裁器共享一条信号总线,用于在模组内部访问FLASH(SoC FLASH),不对用户开放。
  • SPI2和SPI3是通用SPI控制器,有时也被称为HSPI和VSPI。它们拥有独立的信号总线,每条总线都有三条片选(CS)信号,也就是说每个控制器都能驱动最多3个SPI从器件。这两个SPI控制器对用户开放。

SPI类型

esp32的SPI支持三线SPI、四线标准SPI、Dual SPI和Quad SPI等工作模式。

四线标准SPI

四线标准SPI由SCK、MOSI、MISO、CS四根线组成。四线标准SPI是全双工的通讯。

名称 功能
SCLK 时钟线,决定着通讯的速度
MISO 主输入从输出。主机输入,从机输出
MOSI 主输出从输入。主机输入,从机输入
CS 片选线。当片选线被拉低总线有效,可以开始通讯

三线SPI

三线 SPI 把 MISO 和 MOSI 总线进行了合并。同一时间只能进行单方向的读或者写。是半双工的通讯。

Dual SPI

Dual SPI是四线半双工的SPI通讯。Dual SPI就是让MISO和MOSI同时进行发送或者接收的工作。因此通讯速度会得到极大的提高。此时MISO和MOSI总线名称就变成了IO0和IO1。

Quad SPI

Quad SPI是六线半双工的SPI通讯。除了SCK和CS总线以外,增加了IO0、IO1、IO2、IO3四条总线,这四条总线能同时进行并行的读写,比Dual SPI通讯速度相比,又得到了极大的提高。有时候IO2和IO3引脚与WP和HD引脚共用。WD是写保护,HD是状态保持。

SPI配置

总线初始化结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
   spi_bus_config_t spiCfg;    //总线配置结构体
spiCfg.miso_io_num = GPIO_NUM_19; //gpio12->miso
spiCfg.mosi_io_num = GPIO_NUM_23; //gpio13->mosi
spiCfg.sclk_io_num = GPIO_NUM_18; //gpio14-> sclk
spiCfg.quadhd_io_num = -1; // HD引脚不设置,这个引脚配置Quad SPI的时候才有用
spiCfg.quadwp_io_num = -1; // WP引脚不设置,这个引脚配置Quad SPI的时候才有用

spiCfg.max_transfer_sz = SOC_SPI_MAXIMUM_BUFFER_SIZE;
//设置传输数据的最大值。非DMA最大64bytes,DMA最大4096bytes
//spiCfg.intr_flags = 0; //这个用于设置SPI通讯中相关的中断函数的中断优先级,0是默认。
//这组中断函数包括SPI通讯前中断和SPI通讯后中断两个函数。

spiCfg.flags = SPICOMMON_BUSFLAG_MASTER;
//这个用于设置初始化的时候要检测哪些选项。比如这里设置的是spi初始化为主机模式是否成功。
//检测结果通过spi_bus_initialize函数的
//返回值进行返回。如果初始化为主机模式成功,就会返回esp_ok

总线初始化

1
2
3
4
5
6
7
8
9
esp_err_t spi_init_info = spi_bus_initialize(SPI3_HOST, &spiCfg, SPI_DMA_DISABLED);
if(spi_init_info != ESP_OK)
{
printf("spi initialize failed!\n");
}
else
{
printf("spi initialize successed!\n");
}

设备初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
spi_device_interface_config_t spiDevCfg={
.clock_speed_hz = 1000 * 1000,
.mode = 0,
.spics_io_num = -1,
.queue_size = 6,
};
esp_err_t spi_dev_info = spi_bus_add_device(SPI3_HOST, &spiDevCfg, &spi3Handle);
if(spi_dev_info != ESP_OK)
{
printf("device config error\n");
}
else
{
printf("device config success\n");
}

发送数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
esp_err_t spiWriteData(uint8_t *data, uint8_t len)
{
esp_err_t ret;
spi_transaction_t temp;
if (len==0) return 0;
memset(&temp, 0, sizeof(temp));
gpio_set_level(SPI3_CS, 0);
temp.length = len * 8;
temp.tx_buffer = data;
temp.user = (void*)1;
ret=spi_device_polling_transmit(spi3Handle, &temp);
gpio_set_level(SPI3_CS, 1);
return ret;
}

IIC

IIC资源

IIC 是一种串行同步半双工通信协议,总线上可以同时挂载多个主机和从机。IIC 总线由串行数据线 (SDA) 和串行时钟线 (SCL) 线构成。这些线都需要上拉电阻。
IIC 具有简单且制造成本低廉等优点,主要用于低速外围设备的短距离通信(一英尺以内)。
ESP32 有 2 个 IIC 控制器(也称为端口),负责处理在 IIC 总线上的通信。每个控制器都可以设置为主机或从机。

IIC配置驱动

建立 IIC 通信第一步是配置驱动程序,这需要设置 i2c_config_t 结构中的几个参数:

  • 设置 IIC 工作模式 - 从 i2c_mode_t 中选择主机模式或从机模式
  • 设置 通信管脚
    • 指定 SDA 和 SCL 信号使用的 GPIO 管脚
    • 是否启用 ESP32 的内部上拉电阻
  • (仅限主机模式)设置 I2C 时钟速度
  • (仅限从机模式)设置以下内容:
    • 是否应启用 10 位寻址模式
    • 定义 从机地址

然后,初始化给定 IIC 端口的配置,请使用端口号和 i2c_config_t 作为函数调用参数来调用 i2c_param_config() 函数。

SCL 的时钟频率会被上拉电阻和线上电容(或是从机电容)一起影响。因此,用户需要自己选择合适的上拉电阻去保证 SCL 时钟频率是准确的。尽管 I2C 协议推荐上拉电阻值为 1 K 欧姆到 10 K 欧姆,但是需要根据不同的频率需要选择不同的上拉电阻。
通常来说,所选择的频率越高,需要的上拉电阻越小(但是不要小于 1 K 欧姆)。这是因为高电阻会减小电流,这会延长上升时间从而使频率变慢。通常我们推荐的上拉阻值范围为 2 K 欧姆到 5 K 欧姆,但是用户可能也需要根据他们的实际情况做出一些调整。

IIC主机配置

初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void iicMasterInit()
{
i2c_config_t iicMasterConfig = {
.mode = I2C_MODE_MASTER,
.sda_io_num = (gpio_num_t)IIC_SDA,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_io_num = (gpio_num_t)IIC_SCL,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = 100000,
.clk_flags = I2C_SCLK_SRC_FLAG_FOR_NOMAL,
};
i2c_param_config(I2C_NUM_0, &iicMasterConfig);
esp_err_t iicInitInfo = i2c_driver_install(I2C_NUM_0, iicMasterConfig.mode, 0, 0, 0);
if(iicInitInfo != ESP_OK)
{
printf("iic master initialize error\n");
}
else
{
printf("iic master initialize success\n");
}
}

ESP32 的内部上拉电阻范围为几万欧姆,因此在大多数情况下,它们本身不足以用作 I2C 上拉电阻。建议用户使用阻值在 I2C 总线协议规范规定范围内的上拉电阻。计算阻值的具体方法,可参考 TI 应用说明

IIC写数据

1
2
3
4
5
6
7
8
9
10
11
12
esp_err_t iicMasterWriteData(uint8_t addr, uint8_t *writeBuffer, uint8_t bufferLength)
{
esp_err_t ret;
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, addr, true); // 启用ACK
i2c_master_write(cmd, writeBuffer, bufferLength, true);
i2c_master_stop(cmd);
ret = i2c_master_cmd_begin(I2C_NUM_0, cmd, pdMS_TO_TICKS(500)); // 发送数据
i2c_cmd_link_delete(cmd);
return ret;
}

IIC读数据

1
2
3
4
5
6
7
8
9
10
11
12
esp_err_t iicMasterReadData(uint8_t addr, uint8_t *readBUffer, uint8_t bufferLength)
{
esp_err_t ret;
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, addr, true); // 启用ACK
i2c_master_read(cmd, readBUffer, bufferLength, I2C_MASTER_LAST_NACK); // I2C_MASTER_LAST_NACK
i2c_master_stop(cmd);
ret = i2c_master_cmd_begin(I2C_NUM_0, cmd, pdMS_TO_TICKS(500)); // 发送数据
i2c_cmd_link_delete(cmd);
return ret;
}