ESP-C3入门23. I2C读写外部存储器
  TQxd73xCzmSy 2023年11月02日 53 0



ESP-C3入门23. I2C读写外部存储器

  • 一、准备工作
  • 1. 开发环境
  • 2. ESP32-C3 I2C资源介绍
  • 二、主要函数
  • 1. 配置驱动程序
  • 2. 源时钟配置
  • 3. 安装驱动程序
  • 4. 通信
  • 5. 指示写入或读取数据
  • 二、实现步骤
  • 1. 配置 I2C 总线:
  • 2. 初始化 I2C 总线:
  • 3. 与外部存储设备通信:
  • 4. 处理读写数据:
  • 5. 关闭 I2C 总线:
  • 三、完整代码
  • 1. i2c_util.h
  • 2. i2c_util.c
  • 3. 读写示例


ESP-C3入门23. I2C读写外部存储器_初始化

一、准备工作

1. 开发环境

  • ESP32 IDF开发环境
  • 外部存储器Flash

官方文档地址: https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32c3/api-reference/peripherals/i2c.html

2. ESP32-C3 I2C资源介绍

I2C 是一种串行同步半双工通信协议,总线上可以同时挂载多个主机和从机。I2C 总线由串行数据线 (SDA) 和串行时钟线 (SCL) 线构成。这些线都需要上拉电阻。

ESP32-C3 上通常包含两个 I2C 控制器(I2C0 和 I2C1),它们可以分别用于不同的设备或任务。以下是 I2C 控制器的一些常见特性和资源:

  1. I2C 控制器数量
  • ESP32-C3 通常配备了两个独立的 I2C 控制器,分别命名为 I2C0 和 I2C1。
  1. 引脚分配
  • 每个 I2C 控制器都需要两个引脚:SDA(数据线)和 SCL(时钟线)。
  • ESP32-C3 上的引脚分配可以根据硬件设计进行配置。
  1. 工作模式
  • I2C 控制器可以配置为主设备(Master)或从设备(Slave)模式。
  • 主设备模式允许 ESP32-C3 与其他 I2C 设备通信,发送和接收数据。
  • 从设备模式允许 ESP32-C3 作为从设备响应其他主设备的请求。
  1. 时钟速率
  • I2C 控制器支持多种时钟速率,通常以 Hz 为单位。时钟速率决定了数据传输的速度。
  • ESP32-C3 上的 I2C 控制器通常支持标准模式(100 kHz)和快速模式(400 kHz)。
  1. 数据缓冲区
  • I2C 控制器通常具有用于存储发送和接收数据的缓冲区。
  • 你可以在代码中填充这些缓冲区以发送数据或从中读取数据。
  1. 中断和DMA支持
  • ESP32-C3 的 I2C 控制器通常支持中断和DMA(直接内存访问),以提高数据传输的效率。
  1. 主要函数
  • 在 ESP32-C3 上,你可以使用 ESP-IDF(Espressif IoT Development Framework)中的 I2C 驱动库来初始化和操作 I2C 控制器。
  • 一些常用的函数包括 i2c_master_init()(初始化主设备模式)、i2c_slave_init()(初始化从设备模式)、i2c_master_cmd_begin()(发送 I2C 命令)等。
  1. 错误处理
  • ESP32-C3 的 I2C 控制器通常提供了错误处理机制,允许检测和处理通信中的错误,如冲突、丢失的应答等。

要使用 ESP32-C3 上的 I2C 资源,需要配置相应的引脚、初始化 I2C 控制器,然后编写代码来进行数据传输和通信。

二、主要函数

1. 配置驱动程序

使用 i2c_config_t结构体,
配置主机示例

int i2c_master_port = 0;
i2c_config_t conf = {
    .mode = I2C_MODE_MASTER,
    .sda_io_num = I2C_MASTER_SDA_IO,         // 配置 SDA 的 GPIO
    .sda_pullup_en = GPIO_PULLUP_ENABLE,
    .scl_io_num = I2C_MASTER_SCL_IO,         // 配置 SCL 的 GPIO
    .scl_pullup_en = GPIO_PULLUP_ENABLE,
    .master.clk_speed = I2C_MASTER_FREQ_HZ,  // 为项目选择频率
    .clk_flags = 0,          // 可选项,可以使用 I2C_SCLK_SRC_FLAG_* 标志来选择 I2C 源时钟
};

配置从机示例

int i2c_slave_port = I2C_SLAVE_NUM;
i2c_config_t conf_slave = {
    .sda_io_num = I2C_SLAVE_SDA_IO,            // 配置 SDA 的 GPIO
    .sda_pullup_en = GPIO_PULLUP_ENABLE,
    .scl_io_num = I2C_SLAVE_SCL_IO,            // 配置 SCL 的 GPIO
    .scl_pullup_en = GPIO_PULLUP_ENABLE,
    .mode = I2C_MODE_SLAVE,
    .slave.addr_10bit_en = 0,
    .slave.slave_addr = ESP_SLAVE_ADDR,        // 项目从机地址
    .slave.maximum_speed = I2C_SLAVE_MAX_SPEED // 预期的最大时钟速度
    .clk_flags = 0,                            // 可选项,可以使用 I2C_SCLK_SRC_FLAG_* 标志来选择 I2C 源时钟
};

2. 源时钟配置

时钟源的配置过程如下:

  1. 确定所需的时钟源:根据应用需求,选择满足频率和能力要求的时钟源。
  2. 配置时钟分配器:根据所选的时钟源,配置时钟分配器以满足应用需求。例如,如果选择的是APB时钟,那么需要配置时钟分配器以满足APB时钟的要求。
  3. 设置时钟频率:根据应用需求,设置所需的时钟频率。例如,如果需要100,000的时钟频率,那么需要将相应的时钟频率值设置到相应的寄存器中。
  4. 设置时钟源:将所选的时钟源设置到相应的寄存器中,以便在进行I2C操作时使用。

3. 安装驱动程序

使用 i2c_driver_install() 安装驱动程序,主要设置:

  • 端口号
  • 主机 或 从机模式

4. 通信

下图是I2C 主机构建命令链接、向从机发送n字节的过程。

ESP-C3入门23. I2C读写外部存储器_I2C_02


具体地说,过程是:

  1. 使用 i2c_cmd_link_crate() 创建一个命令链接
  2. i2c_master_start() 启动位
  3. i2c_master_write_byte()提供单字节作为调用此函数的实参
  4. i2c_master_write() 写一个或多个数据
  5. i2c_master_stop() 停止
  6. 调用 i2c_master_cmd_begin() 来执行命令链接
  7. 使用 i2c_cmd_link_delete() 释放命令链接使用的资源

下图是主机读取数据的命令链接过程示例:

ESP-C3入门23. I2C读写外部存储器_初始化_03

5. 指示写入或读取数据

发送从机地址后(请参考上图中第 3 步),主机可以写入或从从机读取数据。

主机实际执行的操作信息存储在从机地址的最低有效位中。

因此,为了将数据写入从机,主机发送的命令链接应包含地址 (ESP_SLAVE_ADDR << 1) | I2C_MASTER_WRITE,如下所示:

i2c_master_write_byte(cmd, (ESP_SLAVE_ADDR << 1) | I2C_MASTER_WRITE, ACK_EN);

同理,指示从从机读取数据的命令链接如下所示:

i2c_master_write_byte(cmd, (ESP_SLAVE_ADDR << 1) | I2C_MASTER_READ, ACK_EN);

二、实现步骤

1. 配置 I2C 总线:

确保 I2C 总线的配置正确,包括引脚定义、时钟速率等。

#define CONFIG_I2C_MASTER_SDA 21 // SDA引脚
#define CONFIG_I2C_MASTER_SCL 22 // SCL引脚
#define CONFIG_I2C_MASTER_FREQ_HZ 100000 // I2C时钟速率

2. 初始化 I2C 总线:

  • 包含 ESP32 IDF 的 I2C 头文件。
  • 使用 i2c_config_t 结构体来配置 I2C 总线。
  • 使用 i2c_param_config() 初始化 I2C 总线。
i2c_config_t conf;
conf.mode = I2C_MODE_MASTER;
conf.sda_io_num = CONFIG_I2C_MASTER_SDA;
conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
conf.scl_io_num = CONFIG_I2C_MASTER_SCL;
conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
conf.master.clk_speed = CONFIG_I2C_MASTER_FREQ_HZ;

i2c_param_config(I2C_NUM_0, &conf);
i2c_driver_install(I2C_NUM_0, conf.mode, 0, 0, 0);

3. 与外部存储设备通信:

使用 i2c_cmd_handle_t 和 i2c_master_cmd_begin() 函数来发送 I2C 命令与外部存储设备通信。
需要根据外部存储设备的数据手册来构建正确的读写命令。

i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (device_address << 1) | I2C_MASTER_WRITE, ACK_CHECK_EN);
i2c_master_write_byte(cmd, register_address, ACK_CHECK_EN);
i2c_master_stop(cmd);
i2c_master_cmd_begin(I2C_NUM_0, cmd, pdMS_TO_TICKS(1000));
i2c_cmd_link_delete(cmd);

4. 处理读写数据:

根据外部存储设备的规范,可以使用 i2c_master_read() 函数读取数据或使用 i2c_master_write() 函数写入数据。
处理读取的数据或根据需要进行写入操作。

5. 关闭 I2C 总线:

当完成与外部存储设备的通信后,使用 i2c_driver_delete() 函数关闭 I2C 总线。

i2c_driver_delete(I2C_NUM_0);

三、完整代码

1. i2c_util.h

#include <string.h>
#include <esp_err.h>
#include <driver/i2c.h>

#define EEPROM_ADDR 0x57
#define START_ADDR 0x25
#define BUF_SIZE 50


esp_err_t i2c_master_init(void);
void writeEEPROM(uint16_t address, uint8_t data);
uint8_t readEEPROM(uint16_t address);

2. i2c_util.c

#include "include/i2c_utils.h"
#include "esp_log.h"

static const char* TAG = "[i2c_utils]";

esp_err_t i2c_master_init(void)
{
    i2c_config_t conf;
    conf.mode = I2C_MODE_MASTER;
    conf.sda_io_num = GPIO_NUM_5; // GPIO pin for SDA
    conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
    conf.scl_io_num =   GPIO_NUM_6; // GPIO pin for SCL
    conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
    conf.master.clk_speed = 100000; // I2C clock frequency
    conf.clk_flags = I2C_SCLK_SRC_FLAG_FOR_NOMAL;
    i2c_param_config(I2C_NUM_0, &conf);
    return i2c_driver_install(I2C_NUM_0, I2C_MODE_MASTER, 0, 0, 0);
}

void writeEEPROM(uint16_t address, uint8_t data)
{
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (EEPROM_ADDR << 1) | I2C_MASTER_WRITE, true);
    i2c_master_write_byte(cmd, address >> 8, true);
    i2c_master_write_byte(cmd, address & 0xFF, true);
    i2c_master_write_byte(cmd, data, true);
    i2c_master_stop(cmd);
    esp_err_t espRc = i2c_master_cmd_begin(I2C_NUM_0, cmd, 10 / portTICK_PERIOD_MS);
      if (espRc == ESP_OK) {
        ESP_LOGI(TAG, "i2c configured successfully");
    } else {
        ESP_LOGE(TAG, "i2c configuration failed. code: 0x%.2X", espRc);
    }
    i2c_cmd_link_delete(cmd);
    vTaskDelay(5 / portTICK_PERIOD_MS); // wait to complete the write cycle
}

uint8_t readEEPROM(uint16_t address)
{
    uint8_t data;
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (EEPROM_ADDR << 1) | I2C_MASTER_WRITE, true);
    i2c_master_write_byte(cmd, address >> 8, true);
    i2c_master_write_byte(cmd, address & 0xFF, true);
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (EEPROM_ADDR << 1) | I2C_MASTER_READ, true);
    i2c_master_read_byte(cmd, &data, I2C_MASTER_LAST_NACK);
    i2c_master_stop(cmd);
    i2c_master_cmd_begin(I2C_NUM_0, cmd, 1000 / portTICK_PERIOD_MS);
    i2c_cmd_link_delete(cmd);
    return data;
}

3. 读写示例

i2c_master_init();
    printf("Writing to Ext.EEPROM...\n");

    for (uint16_t i = 0; i < sizeof(myMessage); i++)
    {
        writeEEPROM(START_ADDR + i, myMessage[i]);
        vTaskDelay(5 / portTICK_PERIOD_MS);
    }
    printf("Write Complete.\n");

    printf("Reading Ext.EEPROM...\n");

    for (uint16_t j = 0; j < sizeof(MEM); j++){
        uint8_t data = readEEPROM(START_ADDR + j);
        printf("Address 0x%02X: 0x%02X '", START_ADDR + j, data);

        if (data >= 32 && data <= 126){
            printf("%c", (char)data);
        }  else{
            printf(".");
        }

        printf("'\n");
    }

    printf("Read Complete.\n");


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
TQxd73xCzmSy