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

news2024/11/15 17:30:20

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. 读写示例

在这里插入图片描述

一、准备工作

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。
  2. 引脚分配

    • 每个 I2C 控制器都需要两个引脚:SDA(数据线)和 SCL(时钟线)。
    • ESP32-C3 上的引脚分配可以根据硬件设计进行配置。
  3. 工作模式

    • I2C 控制器可以配置为主设备(Master)或从设备(Slave)模式。
    • 主设备模式允许 ESP32-C3 与其他 I2C 设备通信,发送和接收数据。
    • 从设备模式允许 ESP32-C3 作为从设备响应其他主设备的请求。
  4. 时钟速率

    • I2C 控制器支持多种时钟速率,通常以 Hz 为单位。时钟速率决定了数据传输的速度。
    • ESP32-C3 上的 I2C 控制器通常支持标准模式(100 kHz)和快速模式(400 kHz)。
  5. 数据缓冲区

    • I2C 控制器通常具有用于存储发送和接收数据的缓冲区。
    • 你可以在代码中填充这些缓冲区以发送数据或从中读取数据。
  6. 中断和DMA支持

    • ESP32-C3 的 I2C 控制器通常支持中断和DMA(直接内存访问),以提高数据传输的效率。
  7. 主要函数

    • 在 ESP32-C3 上,你可以使用 ESP-IDF(Espressif IoT Development Framework)中的 I2C 驱动库来初始化和操作 I2C 控制器。
    • 一些常用的函数包括 i2c_master_init()(初始化主设备模式)、i2c_slave_init()(初始化从设备模式)、i2c_master_cmd_begin()(发送 I2C 命令)等。
  8. 错误处理

    • 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字节的过程。

在这里插入图片描述
具体地说,过程是:

  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() 释放命令链接使用的资源

下图是主机读取数据的命令链接过程示例:
在这里插入图片描述

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");

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/973292.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

华为OD机试 - 找出经过特定点的路径长度 - 深度优先搜索(Java 2022 Q4 100分)

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路五、Java算法源码六、效果展示1、输入2、输出3、说明 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&#…

特征值,特征向量,SVD分解,PCD分解

特征值&#xff0c;特征向量&#xff1a; 对于n阶方阵A&#xff0c;在A张成的空间里&#xff0c;存在非零向量v&#xff0c; 该向量转换到A张成的空间时&#xff0c;方向不变&#xff0c;大小变为λ倍。 ① Av λv 变换一下&#xff1a; ② (A - λI)v 0 对于A向量&#x…

安全编程:初始化那些你忽略掉的东西

对于黑客来说&#xff0c;特权提升漏洞是令他感到非常兴奋的事情&#xff0c;而有时候这种漏洞的来源仅仅是因为开发者忘记将内存缓冲区中的垃圾数据进行初始化。此话怎讲&#xff1f; 我想&#xff0c;现在每个人都应该熟悉 SecureZeroMemory 函数的使用&#xff0c;它用来擦…

ESD实时监控监测系统包括哪些功能

ESD实时监控监测系统是一种用于监测和控制静电放电的系统。静电放电&#xff08;Electrostatic Discharge&#xff0c;ESD&#xff09;是指由于电荷的不平衡而引起的突发放电现象&#xff0c;可能对电子元器件、设备和工作环境造成损害。 ESD实时监控监测系统通常包括以下功能…

elmentui表单重置及出现的问题

一、表单&#xff1a; 二、代码——拿官方的代码举例(做了一些小改动)&#xff1a; 改动&#xff1a;model绑定的字段&#xff0c;由form改为queryParams ref绑定的字段form改为queryFrom 注&#xff1a;model绑定的这个字段用来做数据双向绑定的 注&#xff1a;ref绑定的这…

【TypeScript】一直提示 :无法重新声明块范围变量

【TypeScript】一直提示 &#xff1a;无法重新声明块范围变量 问题描述&#xff1a;在VSCode中编写ts代码时&#xff0c;编写保存完之后&#xff0c;通过tsc 文件名.ts编译就会看到变量名下面出现了红色的波浪线&#xff0c;提示的内容是无法重新声明块范围变量。 解决方法&am…

书单制作方法详细步骤,需要的小伙伴快来看看~

随着网络的发展&#xff0c;视频已经成为了人们获取信息的主要途径之一。书单视频作为一种特殊类型的视频&#xff0c;既能为观众提供阅读建议&#xff0c;又能为制作者带来收益&#xff0c;因此备受欢迎。本文将分享书单视频制作的详细步骤&#xff0c;帮助有兴趣的朋友们快速…

k8s基本概念

一、什么是Kubernetes二&#xff1a;Kubernetes部署方式的演变三、为什么要用K8S四、K8S的特性五、Kubernetes 集群架构与组件5.1 Master 组件① Kube-apiserver② Kube-controller-manager③ Kube-scheduler④ AUTH 认证模块 5.2 配置存储中心5.3 Node 组件① Kubelet② Kube-…

【校招VIP】产品分析之活动策划宣传

考点介绍&#xff1a; 产品的上线运营是非常重要的。应该来说好的产品都是运营出来的&#xff0c;在一运营过程中难免会依靠策划活动来提高产品知名度、用户数。用户粘度等等指标一&#xff0c;如何策划一个成功的活动就显得非常重要。 产品分析之活动策划宣传-相关题目及解析…

常见的几种排序算法

目录 一、插入排序 1、直接插入排序 1.1、排序方法 1.2、图解分析 1.3、代码实现 2、希尔排序 2.1、排序方法 2.2、图解分析 2.3、代码实现 二、选择排序 1、直接选择排序 1.1、排序方法 1.2、图解分析 1.3、代码实现 2、堆排序 2.1、排序方法 2.2、图解分析 …

Qt/C++音视频开发49-推流到各种流媒体服务程序

一、前言 最近将推流程序完善了很多功能&#xff0c;尤其是增加了对多种流媒体服务程序的支持&#xff0c;目前支持mediamtx、LiveQing、EasyDarwin、nginx-rtmp、ZLMediaKit、srs、ABLMediaServer等&#xff0c;其中经过大量的对比测试&#xff0c;个人比较建议使用mediamtx和…

QT DAY6

#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);socket new QTcpSocket(this);//如果连接服务器成功&#xff0c;该客户端就会发射一个connected的信号。//我们…

【TypeScript学习】—基本类型(二)

【TypeScript学习】—基本类型&#xff08;二&#xff09; 一、TypeScript基本类型 //也可以直接用字面量进行类型声明let a:10; a10;//也可以使用 |来连接多个类型&#xff08;联合类型&#xff09;let b:"male"|"female"; b"male"; b"fe…

【Java】Java新特性--Records记录类型

Java 14引入了一个新的语言特性&#xff0c;即Records。Records是一种新的数据类&#xff0c;旨在简化Java中的数据类创建过程。它们提供了一种简洁的方式来创建具有默认的getter、setter、equals、hashCode和toString方法的不可变数据类。 以下是Records的基本语法&#xff1…

一文讲透:erp系统是什么?

erp系统是什么&#xff1f;这个看似简单的问题还真不好解答。因为现在99%的人都把ERP“系统”和ERP“软件”混淆了&#xff01; ERP原本主要是专注于制造业的信息化问题&#xff0c;我把它叫真正的ERP“系统”。 但现在基本上只要是一个软件系统都可以叫ERP系统&#xff0c;什…

【动态规划】面试题 08.01. 三步问题

Halo&#xff0c;这里是Ppeua。平时主要更新C&#xff0c;数据结构算法&#xff0c;Linux与ROS…感兴趣就关注我bua&#xff01; 文章目录 0. 题目解析1. 算法原理1.1 状态表示1.2 状态转移方程1.3初始化1.4 填表顺序1.5 返回值 2.算法代码 &#x1f427; 本篇是整个动态规划的…

9.2 消息对话框 画板 定时器

#include "widget.h"Widget::Widget(QWidget *parent): QWidget(parent) {//设置定时器timernew QTimer(this);timeidthis->startTimer(1000);connect(timer,&QTimer::timeout,this,&Widget::timeout_slot);speechernew QTextToSpeech(this);//边框this-&…

天眼情报分析——编程赛道——研究对象001续

前言&#xff1a; 此次情报分析依旧会分为几大块 一、ACWING 算法基础课 1.高精度加法和减法听都没听过 1.什么是高精度加减法&#xff1f; "高精度加法"和"高精度减法"是一类编程题目&#xff0c;通常出现在算法竞赛和在线编程平台上&#xff0c;比如…

金蝶云星空和金蝶云星空单据接口对接

金蝶云星空和金蝶云星空单据接口对接 接入系统&#xff1a;金蝶云星空 金蝶K/3Cloud&#xff08;金蝶云星空&#xff09;是移动互联网时代的新型ERP&#xff0c;是基于WEB2.0与云技术的新时代企业管理服务平台。金蝶K/3Cloud围绕着“生态、人人、体验”&#xff0c;旨在帮助企业…

【科研论文配图绘制】task8 总结与回顾

task8 总结与回顾&#xff0c;这次组队学习大致掌握了常见python绘图工具包的使用&#xff0c;整体上和matlab的语法类似&#xff0c;也是用画布形式控制元素的绘制。印象深刻的是seaborn的使用&#xff0c;在之前做波士顿房价预测时候先接触了seaborn绘制的散点图、直方图和核…