ESP32 之 ESP-IDF 教学(六)——硬件I2C总线外设(I²C)

本文章 来自原创专栏《ESP32教学专栏 (基于ESP-IDF)》,讲解如何使用 ESP-IDF 构建 ESP32 程序,发布文章并会持续为已发布文章添加新内容! 每篇文章都经过了精打细磨!

↓↓↓通过下方对话框进入专栏目录页↓↓↓
CSDN 请求进入目录       _ O x

是否进入ESP32教学导航(基于ESP-IDF)?

       确定


文章目录

    • 一、I2C 简介
    • 二、I2C 数据帧
    • 三、在 ESP-IDF 中 使用 I2C API (仅介绍主机模式)
      • 1、使用步骤(主机)
        • ① 配置驱动程序`i2c_param_config()`
          • 【1】参数配置
          • 【2】源时钟配置
        • ② 安装 I2C 驱动
        • ③ 主机模式下的写数据【重点】
        • ④ 主机模式下的读数据【重点】
      • 2、代码示例(主机写数据)【基于C++】
      • 3*、自定义时序(选学)
      • 4*、I2C 中断(选学)

一、I2C 简介

I2C是一种通过两条双向IO线:SDA(串行数据线)和SCL(串行时钟线)进行数据通信的一种通信协议

二、I2C 数据帧

由于 ESP-IDF 对于I2C通信协议的封装较为完善,开发者写程序并不需要完全了解 I2C 通信时序,只需要理解I2C通信的数据帧即可。
在这里插入图片描述

Start Slave Address R/W Ack Slave Address Ack Slave Address Ack Ack Stop
起始 从机地址 读/写 应答 8位数据 应答 8位数据 应答 应答 终止
  1. 起始信号
  2. 发送从机地址
  3. 发送读写标志:0为写,1为读取
  4. 应答位(可设置为忽略应答位)
  5. 发送8位数据
  6. loop循环执行4、5。直至发送完所有数据
  7. 终止

接下来我们将离不开I2C数据帧的格式。

三、在 ESP-IDF 中 使用 I2C API (仅介绍主机模式)

ESP-IDF 封装的 I2C API 中体现了面向对象的思想。它将 I2C 发送的数据帧当作了一个装有数据的容器。具体使用逻辑如下:

  1. 创建一个空的 I2C 命令 对象。
  2. 添加各种子数据帧,例如起始信号,从机地址,读写位,数据,终止信号等。

接下来我将用例程来展示在 ESP-IDF 中 使用 I2C API。在此之前,我总结一下使用 I2C 的步骤。

1、使用步骤(主机)

注意:
步骤①和步骤②可以颠倒!

① 配置驱动程序i2c_param_config()
【1】参数配置

我们需要传递一个 i2c_config_t 结构体指针,该结构体包含了配置i2c主机模式的参数。

int i2c_master_port = 0;
i2c_config_t conf = {
// 选择工作模式,I2C_MODE_MASTER 为本文介绍的主机模式
    .mode 			  = I2C_MODE_MASTER,
// 选择SDA管脚的GPIO编号
    .sda_io_num 	  = I2C_MASTER_SDA_IO,
// 允许上拉
    .sda_pullup_en 	  = GPIO_PULLUP_ENABLE,
// 选择SCL管脚的GPIO编号
    .scl_io_num 	  = I2C_MASTER_SCL_IO,
// 允许上拉
    .scl_pullup_en 	  = GPIO_PULLUP_ENABLE, 
// 选择一个合适的时钟频率(比如100,000)
    .master.clk_speed = I2C_MASTER_FREQ_HZ, 
/* 选择时钟源头,你可以填入I2C_SCLK_SRC_FLAG_* 来选择合适的时钟源 */
    .clk_flags = 0,//此句可以注释掉
};
【2】源时钟配置

对于.clk_flags元素:

名称 含义
I2C_SCLK_SRC_FLAG_FOR_NOMA 0 仅根据所需频率进行自动选择时钟。(不支持特殊功能,如 APB 等)
I2C_SCLK_SRC_FLAG_AWARE_DFS 1 当 APB 时钟改变时,时钟的波特率不会改变。
I2C_SCLK_SRC_FLAG_LIGHT_SLEEP 2 用于轻度睡眠模式

之后调用i2c_param_config()配置 I2C:

i2c_param_config(I2C_NUM_0, &conf);
  • 参数一:欲配置的I2C通道(I2C_NUM_0I2C_NUM_1
  • 参数二:配置参数结构体的指针
② 安装 I2C 驱动

方法是调用函数 i2c_driver_install()

函数 i2c_driver_install() 简介:
 
1、参数(手机端可能得不到完全展示,手机端可以滑动来查看右边一列)

参数名 含义 类型
"i2c_num" 【I2C 端口编号】
01,或者I2C_NUM_0I2C_NUM_1
[i2c_port_t]
"mode" 总线工作模式
主机(I2C_MODE_MASTER)还是从机(I2C_MODE_SLAVE
[i2c_mode_t]
"slv_rx_buf_len" 主机模式下此参数无效,请填 0
从机接受缓冲区大小,主机不需要。
[size_t]
"slv_tx_buf_len" 主机模式下此参数无效,请填 0
从机发送缓冲区大小,主机不需要。
[size_t]
"intr_alloc_flags" 如要忽略中断,请填 0
中断分配标志,详见[ESP32终端分配标志宏定义]
[int]

例如将I2C_NUM_0驱动配置为主机模式:

i2c_driver_install(0, I2C_MODE_MASTER, 0, 0, 0);

或者

i2c_driver_install(I2C_NUM_0, I2C_MODE_MASTER, 0, 0, 0);

主机模式下,后三个参数全为0

注意:
  当使用i2c_driver_install() 建立 I2C 通信,一段时间后不再需要 I2C 通信时,可以通过调用i2c_driver_delete()来移除驱动程序以释放分配的资源。

③ 主机模式下的写数据【重点】

ESP32 之 ESP-IDF 教学(六)——硬件I2C总线外设(I²C)_第1张图片
ESP-IDF 中的 I2C 通信数据帧是 i2c_cmd_handle_t 对象。你可以把它当作一个容器。之后向其中添加各种子数据帧(例如起始信号,从机地址,读写位,数据,终止信号等)

首先看一个发送数据的短代码

//创建i2c_cmd_handle_t对象
	i2c_cmd_handle_t cmd = i2c_cmd_link_create();
//添加各种子数据帧
	i2c_master_start(cmd);					//起始信号
   	i2c_master_write_byte(cmd, 0x78, true);	//从机地址及读写位
   	i2c_master_write(cmd, bytes, datalen, true);	//数据位(数组)
   	i2c_master_stop(cmd);					//终止信号
//向I2C_NUM_0 发送这个数据帧,timeout设置为1000毫秒
    i2c_master_cmd_begin(0, cmd, 1000 / portTICK_PERIOD_MS);
//删除i2c_cmd_handle_t对象,释放资源
    i2c_cmd_link_delete(cmd);

解析:(部分解析在 上文代码注释 中)

  • 第 4 行i2c_cmd_handle_t对象中添加一个起始信号

  • ★第 5 行i2c_cmd_handle_t对象中添加一个从机地址0x78,读写位为0
    i2c_master_write_byte()函数的作用是添加1个byte(8位)数据到cmd_link中】

  • ★ 第 6 行调用函数i2c_master_write()添加 datalen 个数据位(第三个参数),数据为是bytes数组,类型为uint8_t[],长度要 ⩾ \geqslant datalen
    i2c_master_write()函数的作用是添加多个byte数据到cmd_link中,以数组形式传入,所以这里也可以替换为i2c_master_write_byte()表示只需要添加一个数据位(8位数据),参考第 5 行】

  • 第 7 行添加一个终止信号

  • 第 9 行发送数据
    通过调用 i2c_master_cmd_begin() 来触发 I2C 控制器执行命令链接。一旦开始执行,就不能再修改命令链接。

函数 i2c_master_cmd_begin() 简介:
1、返回值

  • ESP_OK 成功
  • ESP_ERR_INVALID_ARG 参数错误
  • ESP_FAIL 发送错误,从机无应答
  • ESP_ERR_INVALID_STATE I2C 驱动为安装或非Master模式
  • ESP_ERR_TIMEOUT 总线繁忙,发送超时(TimeOut)

2、参数(手机端可能得不到完全展示,手机端可以滑动来查看右边一列)

参数名 含义 类型
"i2c_num" 【I2C 端口编号】
01或者I2C_NUM_0I2C_NUM_1
[i2c_port_t ]
"cmd_handle" 欲执行的
i2c_cmd_handle_t 对象
(I2C command handler对象)
[i2c_cmd_handle_t]
"ticks_to_wait": 最大等待的 ticks
例如:1000 / portTICK_PERIOD_MS)表示1000毫秒.
[TickType_t]
④ 主机模式下的读数据【重点】

ESP32 之 ESP-IDF 教学(六)——硬件I2C总线外设(I²C)_第2张图片
  在读取数据时,在上图的步骤 4 中,不是用 i2c_master_write...,而是用 i2c_master_read_byte() 和/或 i2c_master_read() 填充命令链接。
这两个函数和i2c_master_write_byte()以及i2c_master_write()很类似。但是参数有所不同。
  如i2c_master_read(),它的第二个参数data的含义变为用于接收数据的缓冲区地址(uint8_t数组指针即可),第三个参数datalen变为所需要接受数据的长度。第四个参数ack为主机是否发送应答信号。发送则为I2C_MASTER_ACK,若每个byte都非应答则为I2C_MASTER_NACK。若只有最后一个字节(接收到数据大于datalen之后)才非应答,则为I2C_MASTER_LAST_NACK
  再如i2c_master_read_byte(),第二个参数data也变成了用于接受数据的缓冲区地址,类型为uint8_t的变量指针即可。第三个参数ack和上者一样。
  其余操作和 ③ 一致。

2、代码示例(主机写数据)【基于C++】

为更好的体现 I2C API 的使用,本示例给出的并不是一个完整个ESP-IDF程序,而是一个涉及I2C初始化,主机发送数据等操作的部分程序。因此,这个代码示例程序中不包含"app_main()"入口函数。


在以下代码示例中:

  • i2c_init()是i2c初始化函数
  • i2c_sendData是i2c向指定7位从机地址发送多个uint8_t数据的函数
  • i2c_sendByte是i2c向指定7位从机地址发送一个uint8_t数据的函数
#include 
	...//省略
#include 
#include "driver/i2c.h"
	...//省略
#include ...

/****************************************************/
/* 函数声明 声明在命名空间中是为了避免命名冲突*/
namespace MyExample_I2C {
    esp_err_t i2c_init();
    esp_err_t i2c_sendData(uint8_t slaveAddr, uint8_t *data, std::size_t dataLen, bool ack_en);
    esp_err_t i2c_sendByte(uint8_t slaveAddr, uint8_t byte, bool ack_en);
}

/****************************************************/
esp_err_t MyExample_I2C::i2c_init(){
    esp_err_t err;
/* 安装驱动程序,并检查是否成功(err == ESP_OK) */
    err = i2c_driver_install(I2C_NUM_0, I2C_MODE_MASTER, 0, 0, 0);
    if(err != ESP_OK){
        return err;
    }
/* 配置i2c参数结构体 */
    i2c_config_t config;
    memset(&config, 0, sizeof config);
    config.mode = I2C_MODE_MASTER;
    config.scl_io_num = 16;
    config.sda_io_num = 18;
    config.scl_pullup_en = true;
    config.sda_pullup_en = true;
    config.master.clk_speed = 100000;
    config.clk_flags = 0;
/* 配置i2c参数,接收错误并返回 */
    err = i2c_param_config(I2C_NUM_0, &config);
    return err;
}
/****************************************************/
//i2c发送1位数据
esp_err_t MyExample_I2C::i2c_sendByte(uint8_t slaveAddr, uint8_t byte, bool ack_en) {
    esp_err_t err;

    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (slaveAddr << 1) | I2C_MASTER_WRITE, ack_en);
    i2c_master_write_byte(cmd, byte, ack_en);
    i2c_master_stop(cmd);

    err = i2c_master_cmd_begin(I2C_NUM_0, cmd, 1000 / portTICK_PERIOD_MS);
    i2c_cmd_link_delete(cmd);

    return err;
}
/****************************************************/
//i2c发送多位数据
esp_err_t MyExample_I2C::i2c_sendData(uint8_t slaveAddr, uint8_t *data, std::size_t dataLen, bool ack_en) {
    esp_err_t err;

    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (slaveAddr << 1) | I2C_MASTER_WRITE, ack_en);
    i2c_master_write(cmd, data, dataLen, ack_en);
    i2c_master_stop(cmd);

    err = i2c_master_cmd_begin(I2C_NUM_0, cmd, 1000 / portTICK_PERIOD_MS);
    i2c_cmd_link_delete(cmd);

    return err;
}

3*、自定义时序(选学)

如本文 使用步骤——① 配置驱动程序所述,函数 i2c_param_config() 在初始化 I2C 端口的驱动程序配置时,也会将几个 I2C 通信参数设置为 I2C 总线协议规范 规定的默认值。 其他一些相关参数已在 I2C 控制器的寄存器中预先配置。
  通过调用下表中提供的专用函数,可以将所有这些参数更改为用户自定义值。请注意,时序值是在 APB 时钟周期中定义。APB 的频率在 I2C_APB_CLK_FREQ 中指定。


要更改的参数 函数
SCL 脉冲周期的高电平和低电平 i2c_set_period()
在产生 启动 信号期间使用的 SCL 和 SDA 信号时序 i2c_set_start_timing()
在产生 停止 信号期间使用的 SCL 和 SDA 信号时序 i2c_set_stop_timing()
从机采样以及主机切换时,SCL 和 SDA 信号之间的时序关系 i2c_set_data_timing()
I2C 超时 i2c_set_timeout()
优先发送/接收最高有效位 (LSB) 或最低有效位 (MSB),可在 i2c_trans_mode_t 定义的模式中选择 i2c_set_data_mode()
表格:其他可配置的 I2C 通信参数

上述每个函数都有一个 _get_ 对应项来检查当前设置的值。例如,调用 i2c_get_timeout() 来检查 I2C 超时值。
  要检查在驱动程序配置过程中设置的参数默认值,请参考文件 driver/i2c.c并查找带有后缀 _DEFAULT 的定义。
  通过函数 i2c_set_pin() 可以为 SDA 和 SCL 信号选择不同的管脚并改变上拉配置。如果要修改已经输入的值,请使用函数 i2c_param_config()

4*、I2C 中断(选学)

I2C 工作过程会产生多种中断,安装驱动程序时会安装默认中断处理程序

当然,您可以通过调用函数 i2c_isr_register() 来注册自己的而不是默认的中断处理程序。无论何时,中断服务程序(ISR)都应保持简短!

在运行自己的中断处理程序时,可以参考 ESP32 技术参考手册 > I2C 控制器 (I2C) > 中断 [点击打开PDF链接],以获取有关 I2C 控制器触发的中断描述。

调用函数 i2c_isr_free() 删除中断处理程序。

下图为技术参考手册列出的与I2C有关的中断:

ESP32 之 ESP-IDF 教学(六)——硬件I2C总线外设(I²C)_第3张图片

你可能感兴趣的:(ESP32,教学专栏,(基于ESP-IDF),嵌入式,c++,单片机,c语言,物联网)