ESP32 I2C 总线主模式通信程序

一、概述

这里主要是记录 ESP32 中进行 I2C 通行的基本程序,也可以说是 I2C 总线驱动程序,当然这里只是作为主模式,从模式我还没需要这个需求,以后有机会贴上。此笔记的主要目的是防止以后写 I2C 通信时,忘记代码而记录的,需要的小伙伴可以收藏一下。

  1. I2C 通信原理
    在了解程序之前需要先了解 I2C 的通信原理,这里我就不记录了,网上有很多大佬已经完成了这个事,下面是两位大佬的博客,可以参考一下。
    一文搞懂I2C通信:https://zhuanlan.zhihu.com/p/282949543
    一文搞懂I2C通信总线:https://blog.csdn.net/m0_38106923/article/details/123673285

  2. I2C总线仲裁机制
    通过了解 I2C 通信原理后,可以知道 I2C 支持 一主多从和多主多从 的通行方式,这里需要注意一下多主多从模式,需要了解一下 I2C 的仲裁机制,有需要的可以看下面两位大佬的博客。
    I2C总线仲裁机制:https://www.cnblogs.com/yuanqiangfei/p/15781416.html
    I2C总线仲裁机制:https://blog.csdn.net/lpwsw/article/details/121778724

  3. I2C 遵循3个机制

    • “线与”机制:
      多主机时,总线具有“线与”的逻辑功能,即只要有一个节点发送低电平时,总线上就表现为低电平。

    • SDA回读机制
      总线被启动后,多个主机在每发送一个数据位时都要对自己的输出电平进行检测,只要检测的电平与自己发出的电平一致,就会继续占用总线。

    • 低电平优先机制
      由于线与的存在,当多主机发送时,谁先发送低电平谁就会掌握对总线的控制权。

二、I2C程序分析

一般总线的驱动程序,多数是两个套路,首先初始化,然后完成读写功能即可。

  1. 初始化
    这里的初始化,主要是配置 I2C 的功能定义,见代码分析

    esp_err_t esp32_i2c_init()
    {   
        // 初始化I2C配置
        i2c_config_t i2c_config=
        {
            .mode = I2C_MODE_MASTER,                        // 设置i2c模式
            .sda_io_num = I2c_SDA_IO,                       // 设置SDA引脚
            .scl_io_num = I2c_SCL_IO,                       // 设置SCL引脚
            .sda_pullup_en = GPIO_PULLUP_ENABLE,            // 设置上拉使能
            .scl_pullup_en = GPIO_PULLUP_ENABLE,            // 设置上拉使能
            .master.clk_speed = I2C_MASTER_FREQ_HZ,         // 设置时钟频率400kbit
        };
        // 设置I2C
        i2c_param_config(I2c_NUM,&i2c_config);
        // 注册I2C服务及使能
        esp_err_t  err =  i2c_driver_install(I2c_NUM, i2c_config.mode, 0, 0, 0);
        return err;
    }
    
  2. I2C 的读取程序
    首先看看怎么读取一个字节数据

    static esp_err_t i2c_read8(uint8_t dev_addr, uint8_t reg_addr, uint8_t* read_data)
    {
        esp_err_t err = 0;
    
        /* 写地址 */
        i2c_cmd_handle_t cmd = i2c_cmd_link_create();                       // 新建操作I2C句柄
        i2c_master_start(cmd);                                              // 启动I2C
        i2c_master_write_byte(cmd, ( TCS34725_address << 1 ) | I2C_MASTER_WRITE, ACK_CHECK_EN);     // 发送地址+写+检查ack
        i2c_master_write_byte(cmd, reg_addr, ACK_CHECK_EN);                 // 发送ID寄存器地址
        i2c_master_stop(cmd);                                               // 关闭发送I2C
        err = i2c_master_cmd_begin(I2c_NUM, cmd, 1000 / portTICK_RATE_MS);
        i2c_cmd_link_delete(cmd); 
    
        /* 读取数据 */
        cmd = i2c_cmd_link_create();
        i2c_master_start(cmd);
        i2c_master_write_byte(cmd, ( dev_addr << 1 ) | I2C_MASTER_READ, ACK_CHECK_EN);
        i2c_master_read_byte(cmd, read_data, NACK_VAL);
        i2c_master_stop(cmd);
        err = i2c_master_cmd_begin(I2c_NUM, cmd, 1000 / portTICK_RATE_MS);
        i2c_cmd_link_delete(cmd);
        return err;
    }
    
    

    读取两个字节数据

    static esp_err_t i2c_read16(uint8_t dev_addr, uint8_t reg_addr, uint16_t* read_data)
    {
        esp_err_t err = 0;
        uint8_t read_data_h, read_data_l;
        /* 写地址 */
        i2c_cmd_handle_t cmd = i2c_cmd_link_create();                       // 新建操作I2C句柄
        i2c_master_start(cmd);                                              // 启动I2C
        i2c_master_write_byte(cmd, ( TCS34725_address << 1 ) | I2C_MASTER_WRITE, ACK_CHECK_EN);     // 发送地址+写+检查ack
        i2c_master_write_byte(cmd, reg_addr, ACK_CHECK_EN);                 // 发送ID寄存器地址
        i2c_master_stop(cmd);                                               // 关闭发送I2C
        err = i2c_master_cmd_begin(I2c_NUM, cmd, 1000 / portTICK_RATE_MS);
        i2c_cmd_link_delete(cmd); 
    
        /* 读取数据 */
        cmd = i2c_cmd_link_create();
        i2c_master_start(cmd);
        i2c_master_write_byte(cmd, ( dev_addr << 1 ) | I2C_MASTER_READ, ACK_CHECK_EN);
        i2c_master_read_byte(cmd, &read_data_l, ACK_VAL);
        i2c_master_read_byte(cmd, &read_data_h, NACK_VAL);
        i2c_master_stop(cmd);
        err = i2c_master_cmd_begin(I2c_NUM, cmd, 1000 / portTICK_RATE_MS);
        i2c_cmd_link_delete(cmd);
        *read_data = read_data_h << 8 | read_data_l;
        return err;
    }
    
    

    读取多个字节
    是不是发现像上面这样读取比较麻烦,不利于代码的复用,下面是一个读取函数搞定 I2C 的读取需求

    static esp_err_t i2c_read(uint8_t dev_addr, uint8_t* data_rd, size_t size)
    {
        esp_err_t err;
        if (size == 0) {
            return ESP_OK;
        }
        i2c_cmd_handle_t cmd = i2c_cmd_link_create();
        i2c_master_start(cmd);
        i2c_master_write_byte(cmd, ( dev_addr << 1 ) | I2C_MASTER_READ, ACK_CHECK_EN);
        if (size > 1) {
            i2c_master_read(cmd, data_rd, size - 1, ACK_VAL);
        }
        i2c_master_read_byte(cmd, data_rd + size - 1, NACK_VAL);
        i2c_master_stop(cmd);
        /* I2C 发送函数 */
        err = i2c_master_cmd_begin(I2c_NUM, cmd, 1000 / portTICK_RATE_MS);
        i2c_cmd_link_delete(cmd);
        return err;
    }
    
  3. I2C的写程序
    写一个字节数据

    static esp_err_t i2c_write8(uint8_t dev_addr, uint8_t reg_addr, uint8_t write_data)
    {
        esp_err_t err = 0;
        i2c_cmd_handle_t cmd = i2c_cmd_link_create();                       // 新建操作I2C句柄
        i2c_master_start(cmd);                                              // 启动I2C
        i2c_master_write_byte(cmd, ( dev_addr << 1 ) | I2C_MASTER_WRITE, ACK_CHECK_EN);     // 发送地址+写+检查ack
        i2c_master_write_byte(cmd, reg_addr, ACK_CHECK_EN);                 // 发送ID寄存器地址
        i2c_master_write_byte(cmd, write_data, ACK_CHECK_EN);               // 将数据写入寄存器中
        i2c_master_stop(cmd);                                               // 关闭发送I2C
        err = i2c_master_cmd_begin(I2c_NUM, cmd, 1000 / portTICK_RATE_MS);
        i2c_cmd_link_delete(cmd);                                           // 删除句柄   
        return err;
    }
    

    写多个字节

    static esp_err_t i2c_write(uint8_t dev_addr, uint8_t* write_data, size_t size)
    {
        esp_err_t err;
        i2c_cmd_handle_t cmd = i2c_cmd_link_create();                       // 新建操作I2C句柄
        i2c_master_start(cmd);                                              // 启动I2C
        i2c_master_write_byte(cmd, ( dev_addr << 1 ) | I2C_MASTER_WRITE, ACK_CHECK_EN);     // 发送地址+写+检查ack
        i2c_master_write(cmd, write_data, size, ACK_CHECK_EN);              // 发送ID寄存器地址
        i2c_master_stop(cmd);                                               // 关闭发送I2C
        err = i2c_master_cmd_begin(I2c_NUM, cmd, 100/portTICK_PERIOD_MS);   // I2C 发送函数
        i2c_cmd_link_delete(cmd);                                           // 删除句柄   
        return err;
    }
    
    

三、程序源码

头文件

/**
 * @file esp32_i2c_drive.h
 *
 */

#ifndef _ESP32_I2C_DRIVE_H_
#define _ESP32_I2C_DRIVE_H_

#ifdef __cplusplus
extern "C" {
#endif

/*********************
 *      INCLUDES
 *********************/
#include "driver/i2c.h"

/*********************
 *      DEFINES
 *********************/
#define CODE_SIMPLIFY                           // I2C 驱动代码精简模式

// I2C配置
#define I2c_SDA_IO              (21)
#define I2c_SCL_IO              (22)
#define I2c_NUM                 (I2C_NUM_0)
#define I2C_MASTER_FREQ_HZ      (400000)
#define ACK_CHECK_EN            (0x1)           // 主机检查从机的ACK
#define ACK_CHECK_DIS 			(0x0)
#define ACK_VAL                 (0x0)			// 主机读取时的应答信号
#define NACK_VAL                (0x1)

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 * GLOBAL PROTOTYPES
 **********************/
esp_err_t esp32_i2c_init();
esp_err_t esp32_i2c_write8(uint8_t dev_addr, uint8_t reg_addr, uint8_t write_data);
esp_err_t esp32_i2c_read8(uint8_t dev_addr, uint8_t reg_addr, uint8_t* read_data);
esp_err_t esp32_i2c_read16(uint8_t dev_addr, uint8_t reg_addr, uint16_t* read_data);


// esp_err_t esp32_i2c_write(uint8_t* write_data, size_t size);
// esp_err_t esp32_i2c_read(uint8_t* data_rd, size_t size);

/**********************
 *      MACROS
 **********************/

#ifdef __cplusplus
} /* extern "C" */
#endif

#endif /* _ESP32_I2C_DRIVE_H_ */

源文件

#include "esp32_i2c_drive.h"
#include "TCS34725.h"

/***************************************************************
文件名 : esp32_i2c_drive.c
作者 : jiaozhu
版本 : V1.0
描述 : esp32_i2c_drive esp32的i2c驱动文件。
其他 : 无
日志 : 初版 V1.0 2022/12/30
***************************************************************/

/**
 * @brief 初始化I2C
 * 
 * @return esp_err_t 初始化成功返回 0
 */
esp_err_t esp32_i2c_init()
{   
    // 初始化I2C配置
    i2c_config_t i2c_config=
    {
        .mode = I2C_MODE_MASTER,                        // 设置i2c模式
        .sda_io_num = I2c_SDA_IO,                       // 设置SDA引脚
        .scl_io_num = I2c_SCL_IO,                       // 设置SCL引脚
        .sda_pullup_en = GPIO_PULLUP_ENABLE,            // 设置上拉使能
        .scl_pullup_en = GPIO_PULLUP_ENABLE,            // 设置上拉使能
        .master.clk_speed = I2C_MASTER_FREQ_HZ,         // 设置时钟频率400kbit
    };
    // 设置I2C
    i2c_param_config(I2c_NUM,&i2c_config);
    // 注册I2C服务及使能
    esp_err_t  err =  i2c_driver_install(I2c_NUM, i2c_config.mode, 0, 0, 0);
    return err;
}

#if defined(CODE_SIMPLIFY)
/**
 * @brief 通过I2C写入多个数据
 * 
 * @param write_data 数据地址,类型为 [寄存器地址] [数据地址] [寄存器地址] [数据地址] ......
 * @param size 写入的数据长度
 * @return esp_err_t 操作成功返回 0
 */
static esp_err_t i2c_write(uint8_t dev_addr, uint8_t* write_data, size_t size)
{
    esp_err_t err;
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();                       // 新建操作I2C句柄
    i2c_master_start(cmd);                                              // 启动I2C
    i2c_master_write_byte(cmd, ( dev_addr << 1 ) | I2C_MASTER_WRITE, ACK_CHECK_EN);     // 发送地址+写+检查ack
    i2c_master_write(cmd, write_data, size, ACK_CHECK_EN);              // 发送ID寄存器地址
    i2c_master_stop(cmd);                                               // 关闭发送I2C
    err = i2c_master_cmd_begin(I2c_NUM, cmd, 100/portTICK_PERIOD_MS);   // I2C 发送函数
    i2c_cmd_link_delete(cmd);                                           // 删除句柄   
    return err;
}

/**
 * @brief 通过I2C读取设备数据,但是需要配合 i2c_write 使用才能读取
 * 
 * @param data_rd 数据存储地址
 * @param size 需要读取的数据长度
 * @return esp_err_t 操作成功返回 0
 */
static esp_err_t i2c_read(uint8_t dev_addr, uint8_t* data_rd, size_t size)
{
    esp_err_t err;
    if (size == 0) {
        return ESP_OK;
    }
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, ( dev_addr << 1 ) | I2C_MASTER_READ, ACK_CHECK_EN);
    if (size > 1) {
        i2c_master_read(cmd, data_rd, size - 1, ACK_VAL);
    }
    i2c_master_read_byte(cmd, data_rd + size - 1, NACK_VAL);
    i2c_master_stop(cmd);
    /* I2C 发送函数 */
    err = i2c_master_cmd_begin(I2c_NUM, cmd, 1000 / portTICK_RATE_MS);
    i2c_cmd_link_delete(cmd);
    return err;
}

#else
/**
 * @brief 往 I2C 中写入一个8位的数据
 * 
 * @param dev_addr 设备地址
 * @param reg_addr 寄存机地址
 * @param write_data 需要写入的数据
 * @return esp_err_t 无错误时返回 0
 */
static esp_err_t i2c_write8(uint8_t dev_addr, uint8_t reg_addr, uint8_t write_data)
{
    esp_err_t err = 0;
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();                       // 新建操作I2C句柄
    i2c_master_start(cmd);                                              // 启动I2C
    i2c_master_write_byte(cmd, ( dev_addr << 1 ) | I2C_MASTER_WRITE, ACK_CHECK_EN);     // 发送地址+写+检查ack
    i2c_master_write_byte(cmd, reg_addr, ACK_CHECK_EN);                 // 发送ID寄存器地址
    i2c_master_write_byte(cmd, write_data, ACK_CHECK_EN);               // 将数据写入寄存器中
    i2c_master_stop(cmd);                                               // 关闭发送I2C
    err = i2c_master_cmd_begin(I2c_NUM, cmd, 1000 / portTICK_RATE_MS);
    i2c_cmd_link_delete(cmd);                                           // 删除句柄   
    return err;
}

/**
 * @brief 读取一个8位的数据
 * 
 * @param dev_addr 设备地址
 * @param reg_addr 寄存器地址
 * @param read_data 数据存储地址
 * @return esp_err_t 无错误时返回 0
 */
static esp_err_t i2c_read8(uint8_t dev_addr, uint8_t reg_addr, uint8_t* read_data)
{
    esp_err_t err = 0;

    /* 写地址 */
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();                       // 新建操作I2C句柄
    i2c_master_start(cmd);                                              // 启动I2C
    i2c_master_write_byte(cmd, ( TCS34725_address << 1 ) | I2C_MASTER_WRITE, ACK_CHECK_EN);     // 发送地址+写+检查ack
    i2c_master_write_byte(cmd, reg_addr, ACK_CHECK_EN);                 // 发送ID寄存器地址
    i2c_master_stop(cmd);                                               // 关闭发送I2C
    err = i2c_master_cmd_begin(I2c_NUM, cmd, 1000 / portTICK_RATE_MS);
    i2c_cmd_link_delete(cmd); 

    /* 读取数据 */
    cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, ( dev_addr << 1 ) | I2C_MASTER_READ, ACK_CHECK_EN);
    i2c_master_read_byte(cmd, read_data, NACK_VAL);
    i2c_master_stop(cmd);
    err = i2c_master_cmd_begin(I2c_NUM, cmd, 1000 / portTICK_RATE_MS);
    i2c_cmd_link_delete(cmd);
    return err;
}

/**
 * @brief 读取一个16位的数据
 * 
 * @param dev_addr 设备地址
 * @param reg_addr 寄存器地址
 * @param read_data 数据存储地址
 * @return esp_err_t 无错误时返回 0
 */
static esp_err_t i2c_read16(uint8_t dev_addr, uint8_t reg_addr, uint16_t* read_data)
{
    esp_err_t err = 0;
    uint8_t read_data_h, read_data_l;
    /* 写地址 */
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();                       // 新建操作I2C句柄
    i2c_master_start(cmd);                                              // 启动I2C
    i2c_master_write_byte(cmd, ( TCS34725_address << 1 ) | I2C_MASTER_WRITE, ACK_CHECK_EN);     // 发送地址+写+检查ack
    i2c_master_write_byte(cmd, reg_addr, ACK_CHECK_EN);                 // 发送ID寄存器地址
    i2c_master_stop(cmd);                                               // 关闭发送I2C
    err = i2c_master_cmd_begin(I2c_NUM, cmd, 1000 / portTICK_RATE_MS);
    i2c_cmd_link_delete(cmd); 

    /* 读取数据 */
    cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, ( dev_addr << 1 ) | I2C_MASTER_READ, ACK_CHECK_EN);
    i2c_master_read_byte(cmd, &read_data_l, ACK_VAL);
    i2c_master_read_byte(cmd, &read_data_h, NACK_VAL);
    i2c_master_stop(cmd);
    err = i2c_master_cmd_begin(I2c_NUM, cmd, 1000 / portTICK_RATE_MS);
    i2c_cmd_link_delete(cmd);
    *read_data = read_data_h << 8 | read_data_l;
    return err;
}
#endif

esp_err_t esp32_i2c_write8(uint8_t dev_addr, uint8_t reg_addr, uint8_t write_data)
{
#if defined(CODE_SIMPLIFY)
    uint8_t write_buf[2];
    write_buf[0] = reg_addr;
    write_buf[1] = write_data;
    return i2c_write(dev_addr, write_buf, 2);
#else
    return i2c_write8(dev_addr, reg_addr, write_data);
#endif
}

esp_err_t esp32_i2c_read8(uint8_t dev_addr, uint8_t reg_addr, uint8_t* read_data)
{
#if defined(CODE_SIMPLIFY)
    esp_err_t err = 0;
    err = i2c_write(dev_addr, &reg_addr, 1);
    err = i2c_read(dev_addr, read_data, 1);
    return err;
#else
    return i2c_read8(dev_addr, reg_addr, read_data);
#endif
}

esp_err_t esp32_i2c_read16(uint8_t dev_addr, uint8_t reg_addr, uint16_t* read_data)
{
#if defined(CODE_SIMPLIFY)
    uint8_t buf[2];
    esp_err_t err = 0;
    err = i2c_write(dev_addr, &reg_addr, 1);
    err = i2c_read(dev_addr, buf, 2);
    *read_data = buf[1] << 8 | buf[0];
    return err;
#else
    return i2c_read16(dev_addr, reg_addr, read_data);
#endif
}

注意:到此笔记已经结束了,上面程序各位可以直接拿去使用,但是拒绝商用,出现任何问题概不负责。

参考链接

一文搞懂I2C通信:https://zhuanlan.zhihu.com/p/282949543
一文搞懂I2C通信总线:https://blog.csdn.net/m0_38106923/article/details/123673285
I2C总线仲裁机制:https://www.cnblogs.com/yuanqiangfei/p/15781416.html
I2C总线仲裁机制:https://blog.csdn.net/lpwsw/article/details/121778724

posted @ 2023-01-06 09:28  浇筑菜鸟  阅读(739)  评论(0编辑  收藏  举报