【ESP32】ESP-IDF开发 | I2C控制器+I2C主从收发例程

1. 简介

        I2C(Inter-Integrated Circuit),是由Philips公司在1980年代初开发的一种半双工的同步串行总线,它利用一根时钟线和一根数据线在连接总线的两个器件之间进行信息的传递,为设备之间数据交换提供了一种简单高效的方法。每个连接到总线上的器件都有唯一的地址,任何器件既可以作为主机也可以作为从机,但同一时刻只允许有一个主机。

        I2C在硬件上是采用了开漏输出,在这种模式下GPIO没有输出负载的能力,因此总线上的每根管脚都需要接上拉电阻才能正常通信。

        I2C每次通信都由主机发起,主机发出起始信号(在 SCL 为高电平时拉低 SDA 线),则通讯开始;接下来主机会发送从机地址(7bit或10bit)和读写位(1bit),如果从机地址匹配,从机会发生应答信号(拉低 SDA 线);接下来,根据读写位,主机和从机可以发送/接收更多的数据;最后主机发出停止信号(在 SCL 为高电平时,拉高 SDA 线),终止通信。

        速度方面,ESP32支持100kbps(标准模式)和400kbps(快速模式)两种速度。

2. 控制器

        每个I2C控制器都有一块32字节的RAM用于存放数据;SCL_FSM和SDA_FSM用于控制SCL和SDA信号线;DATA_Shifter用于将字节一位一位的发送出去。

2.1 主机控制器

        主机控制器会有16个命令寄存器和一个命令控制器,命令寄存器的内容决定了数据如何传输。

  • CMD_DONE:判断一条命令是否执行完毕;
  • op_code:命令编码;
    • RSTART:op_code 等于 0 时,用于控制 I2C 协议中 START 位以及 RESTART 位的
    • 发送;
    • WRITE:op_code等于1时,该命令表示当前主机将发送数据;
    • READ:op_code等于2时,该命令表示当前主机将要接收数据;
    • STOP:op_code等于3时,该命令用于控制协议中停止位的发送;
    • END:op_code等于4时,该命令用于主机模式下连续发送数据,主要实现方式为关闭SCL 时钟,当数据准备完毕,继续上次传输。
  • ack_value:当接收数据时,在字节被接收后,该位用于表示接收方将发送一个ACK位;
  • ack_exp:该位用于设置发送方期望的ACK值;
  • ack_check_en:该位用于控制发送方是否对ACK位进行检测;1:检测ACK值,0:不检测ACK值;
  • byte_num:该寄存器用于说明读写数据的数据长度(单位字节),最大为255,最小为1。

 2.2 从机控制器

        从机控制器的SCL和SDA管脚会各自有一个滤波器,用于过滤通信中的噪声。

3. 例程

        例程使用两个I2C外设互连,并相互收发数据。

#include "driver/i2c.h"
#include "driver/i2c_master.h"
#include "driver/i2c_slave.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "esp_log.h"#include <string.h>#define TAG "app"#define WRITE_CMD 0x12
#define READ_CMD 0x13static i2c_master_bus_handle_t bus_handle;
static i2c_master_dev_handle_t dev_handle;
static i2c_slave_dev_handle_t slave_handle;static QueueHandle_t slave_recv_queue;
static QueueHandle_t slave_recv_notify;static const char *str = "Hello, World!";static IRAM_ATTR bool i2c_slave_rx_done_callback(i2c_slave_dev_handle_t slave, const i2c_slave_rx_done_event_data_t *edata, void *user_data)
{BaseType_t high_task_wakeup = pdFALSE;xQueueSendFromISR(slave_recv_queue, edata, &high_task_wakeup);return high_task_wakeup == pdTRUE;
}static void master_task(void *args)
{uint8_t *data_rd = (uint8_t *) malloc(128);while (1) {/* 主机写从机 */uint8_t data_sd[128] = {0};data_sd[0] = WRITE_CMD;strcpy((char *) &data_sd[1], str);ESP_ERROR_CHECK(i2c_master_transmit(dev_handle, data_sd, strlen(str) + 1, -1));ESP_LOGI(TAG, "[Master] Send %d bytes of data", strlen(str));xSemaphoreTake(slave_recv_notify, portMAX_DELAY);  // 等待从机处理完数据接收vTaskDelay(1000 / portTICK_PERIOD_MS);/* 主机读从机 */uint8_t read_reg = READ_CMD;memset(data_rd, 0, 128);ESP_ERROR_CHECK(i2c_master_transmit_receive(dev_handle, &read_reg, 1, data_rd, strlen(str), -1));ESP_LOGI(TAG, "[Master] Receive %d bytes of data: %s", strlen((char *) data_rd), data_rd);xSemaphoreTake(slave_recv_notify, portMAX_DELAY);  // 等待从机处理完数据接收vTaskDelay(1000 / portTICK_PERIOD_MS);}
}static void slave_task(void *args)
{uint8_t *data_rd = (uint8_t *) malloc(128);while (1) {/* 从机接收数据 */i2c_slave_rx_done_event_data_t rx_data;memset(data_rd, 0, 128);ESP_ERROR_CHECK(i2c_slave_receive(slave_handle, data_rd, 128));if (pdTRUE == xQueueReceive(slave_recv_queue, &rx_data, portMAX_DELAY)) {uint8_t *data = rx_data.buffer;if (data[0] == WRITE_CMD) {/* 主机写命令 */ESP_LOGI(TAG, "[Slave] Receive %d bytes of data: %s", strlen((char *) &data[1]), &data[1]);} else if (data[0] == READ_CMD) {/* 主机读命令 */ESP_ERROR_CHECK(i2c_slave_transmit(slave_handle, (const uint8_t *) str, strlen(str), -1));ESP_LOGI(TAG, "[Slave] Send %d bytes of data", strlen(str));}xSemaphoreGive(slave_recv_notify);}}
}void app_main()
{/* 初始化I2C主机 */i2c_master_bus_config_t i2c_bus_config = {0};i2c_bus_config.clk_source = I2C_CLK_SRC_DEFAULT;  // 默认时钟,APB时钟i2c_bus_config.i2c_port = I2C_NUM_0;i2c_bus_config.scl_io_num = 17;i2c_bus_config.sda_io_num = 18;i2c_bus_config.glitch_ignore_cnt = 7;i2c_bus_config.flags.enable_internal_pullup = 1;  // 使用内部上拉电阻ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_config, &bus_handle));i2c_device_config_t dev_cfg = {0};dev_cfg.dev_addr_length = I2C_ADDR_BIT_LEN_7;  // 7位地址dev_cfg.device_address = 0x58;dev_cfg.scl_speed_hz = 400000;  // 400kHzESP_ERROR_CHECK(i2c_master_bus_add_device(bus_handle, &dev_cfg, &dev_handle));slave_recv_notify = xSemaphoreCreateBinary();xSemaphoreTake(slave_recv_notify, 0);/* 初始化I2C从机 */i2c_slave_config_t i2c_slv_config = {0};i2c_slv_config.addr_bit_len = I2C_ADDR_BIT_LEN_7;i2c_slv_config.clk_source = I2C_CLK_SRC_DEFAULT;  // 默认时钟,APB时钟i2c_slv_config.i2c_port = I2C_NUM_1;i2c_slv_config.send_buf_depth = 256;  // 发送队列深度i2c_slv_config.scl_io_num = 21;i2c_slv_config.sda_io_num = 22;i2c_slv_config.slave_addr = 0x58;/* 注册从机接收回调 */ESP_ERROR_CHECK(i2c_new_slave_device(&i2c_slv_config, &slave_handle));slave_recv_queue = xQueueCreate(1, sizeof(i2c_slave_rx_done_event_data_t));i2c_slave_event_callbacks_t cbs = {.on_recv_done = i2c_slave_rx_done_callback,};ESP_ERROR_CHECK(i2c_slave_register_event_callbacks(slave_handle, &cbs, NULL));/* 创建任务 */xTaskCreate(slave_task, "slave_task", 2048, NULL, 5, NULL);xTaskCreate(master_task, "master_task", 2048, NULL, 5, NULL);
}

1. I2C主机初始化

        第一步,初始化I2C总线,初始化结构体如下。

typedef struct {i2c_port_num_t i2c_port;gpio_num_t sda_io_num;gpio_num_t scl_io_num;union {i2c_clock_source_t clk_source;
#if SOC_LP_I2C_SUPPORTEDlp_i2c_clock_source_t lp_source_clk;
#endif};uint8_t glitch_ignore_cnt;int intr_priority;size_t trans_queue_depth;struct {uint32_t enable_internal_pullup: 1;} flags;
} i2c_master_bus_config_t;
  • i2c_port:I2C外设(0-1);
  • sda_io_num:SDA管脚号;
  • scl_io_num:SCL管脚号;
  • clk_source:时钟源;
  • lp_clk_source:低功耗时钟源;
  • glitch_ignore_cnt:毛刺过滤周期,一般默认为7就好;
  • intr_priority:中断优先级;
  • trans_queue_depth:发送队列深度;
  • enable_internal_pullup:内部上拉。

        第二步,添加总线设备,初始化结构体如下。

typedef struct {i2c_addr_bit_len_t dev_addr_length;uint16_t device_address;uint32_t scl_speed_hz;uint32_t scl_wait_us;struct {uint32_t disable_ack_check:1;} flags;
} i2c_device_config_t;
  • dev_addr_length:从机地址长度;
  • device_address:从机地址;
  • scl_speed_hz:通信速率;
  • scl_wait_us:时钟线超时时间;
  • disable_ack_check:禁用ACK检查。

2. I2C从机初始化

        第一步,初始化从机设备,初始化结构体如下。

typedef struct {i2c_port_num_t i2c_port;gpio_num_t sda_io_num;gpio_num_t scl_io_num;i2c_clock_source_t clk_source;uint32_t send_buf_depth;uint16_t slave_addr;i2c_addr_bit_len_t addr_bit_len;int intr_priority;struct {
#if SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSEuint32_t stretch_en: 1;
#endif
#if SOC_I2C_SLAVE_SUPPORT_BROADCASTuint32_t broadcast_en: 1;
#endif
#if SOC_I2C_SLAVE_SUPPORT_I2CRAM_ACCESSuint32_t access_ram_en: 1;
#endif
#if SOC_I2C_SLAVE_SUPPORT_SLAVE_UNMATCHuint32_t slave_unmatch_en: 1;
#endif} flags;
} i2c_slave_config_t;
  • i2c_port:I2C外设(0-1);
  • sda_io_num:SDA管脚号;
  • scl_io_num:SCL管脚号;
  • clk_source:时钟源;
  • send_buf_depth:发送队列深度;
  • slave_addr:从机地址;
  • addr_bit_len:从机地址长度;
  • intr_priority:中断优先级;
  • stretch_en:使能stretch功能;
  • broadcast_en:使能从机广播;
  • access_ram_en:使能I2C RAM直接访问;
  • slave_unmatch_en:使能地址匹配错误中断。

        第二步,注册从机接收回调。回调函数的内容比较简单,就是把接收到的内容通过消息队列发送给从机任务。

        这个例程我创建了两个任务线程,一个主机任务,一个从机任务。

        主机任务先向从机发送数据,再从从机中读数据,每次操作都通过二值信号量等待从机完成数据处理。

        从机任务负责接收主机发来的数据,根据第一个字节(命令字节)的内容(读或写)执行相应的操作。

这里要注意的是,目前IDF的最新版本(v5.3.1)存在一个未修复的BUG,当我们调用i2c_slave_receive函数接收主机数据时会导致系统崩溃,我在另一篇置顶文章中提供了解决思路。

         编译烧录程序,就能看到以下log。

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

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

相关文章

提高交换网络可靠性之端口安全配置

转载请注明出处 此实验为配置交换机端口安全&#xff0c;当非法设备接入接口时自动触发安全措施 1.查看PC1和PC2的MAC地址&#x1f447; 2.交换机改名为S1&#xff0c;同时启用端口安全 3.配置允许接入设备 4.设置违规处理方式&#xff1a;即违规则关闭端口 5.查看安全端口相关…

UE5 第三人称学习之动画 control rig

这个东西和建模软件里有的是一个东西&#xff0c;然后IK就是你动脚&#xff0c;他帮你算出小腿大腿该怎么动&#xff0c;FK就是你自己动了大腿&#xff0c;摆小腿&#xff0c;然后再摆脚 就是给每一根骨骼搞一个控制器&#xff0c;给他一个容易选中和操作更明显的图形作为控制…

宏处理将多个excel文件的指定sheet页合并到一个excel文件中

背景了解&#xff1a;有个同事问我&#xff1a;现在他要处理一千多个文件&#xff0c;每个excel文件都有3个sheet页签&#xff0c;想把所有的excel文件的第二个sheet页签复制一份放到一个新的excel文件中。如果是手动去操作一个个文件的复制&#xff0c;也没什么不可&#xff0…

Linux系列-进程的概念

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 这篇文章&#xff0c;我们主要分析一下进程。 之前&#xff0c;我们讲过了冯诺依曼体系架构&#xff0c; 我们常见的计算机&#xff0c;像笔记本&#xff0c;或者不常见的计算机…

SQL优化经验大全(表设计优化,索引优化,索引创建规则、索引失效场景,sql语句优化,主从复制,分库分表)面试题

目录 1.表的设计优化 2.索引优化 2.1 索引创建的规则 2.2 索引失效的场景 3.SQL语句优化 4.主从复制、读写分离 5.分库分表 5.1.怎么判断项目是需要分库还是要分表&#xff1f; 5.2 分库分表有哪些拆分方案&#xff1f; 5.2.1 垂直分库 5.2.2 垂直分表 5.2.3 水平分…

css边框修饰

一、设置线条样式 通过 border-style 属性设置&#xff0c;可选择的一些属性如下&#xff1a; dotted&#xff1a;点线 dashed&#xff1a;虚线 solid&#xff1a;实线 double&#xff1a;双实线 效果如下&#xff1a; 二、设置边框线宽度 ① 通过 border-width 整体设置…

OpenGL入门002——顶点着色器和片段着色器

文章目录 一些概念坐标转换阶段顶点着色器片段着色器VBOVAO 实战简介main.cppCMakeLists.txt最终效果 一些概念 坐标转换阶段 概述&#xff1a; 模型空间、世界空间、视图空间和裁剪空间是对象在3D场景中经历的不同坐标变换阶段。每个空间对应渲染管道的一个步骤&#xff0c;…

LeetCode 684.冗余连接:拓扑排序+哈希表(O(n)) 或 并查集(O(nlog n)-O(nα(n)))

【LetMeFly】684.冗余连接&#xff1a;拓扑排序哈希表&#xff08;O(n)&#xff09; 或 并查集&#xff08;O(nlog n)-O(nα(n))&#xff09; 力扣题目链接&#xff1a;https://leetcode.cn/problems/redundant-connection/ 树可以看成是一个连通且 无环 的 无向 图。 给定往…

C语言指针与一维数组 Java动态初始化与常见问题(越界问题)

1./*int main(void) { int a[5] { 10,20,30,40,50 };//数组间的元素地址相连的 int* p; printf("a代表的地址&#xff1a;%d\n", a);//数组首个元素的地址 printf("a1代表的地址&#xff1a;%d\n", a 1);//偏移数组 printf("a2…

2023年SEO趋势分析与未来发展展望

内容概要 在2023年的数字营销环境中&#xff0c;搜索引擎优化&#xff08;SEO&#xff09;依然扮演着关键角色。随着技术的不断演进和用户需求的变化&#xff0c;SEO趋势也在不断变化。首先&#xff0c;核心算法的更新已开始影响网站排名&#xff0c;搜索引擎越来越注重网站内…

面试经典 150 题:189、383

189. 轮转数组 【参考代码】 class Solution { public:void rotate(vector<int>& nums, int k) {int size nums.size();if(1 size){return;}vector<int> temp(size);//k k % size;for(int i0; i<size; i){temp[(i k) % size] nums[i];}nums temp; }…

Linux云计算 |【第五阶段】CLOUD-DAY8

主要内容&#xff1a; 掌握DaemonSet控制器、污点策略&#xff08;NoSchedule、Noexecute&#xff09;、Job / CronJob资源对象、掌握Service服务、服务名解析CluterIP&#xff08;服务名自动发现&#xff09;、&#xff08;Nodeport、Headless&#xff09;、Ingress控制器 一…

智能网联汽车:人工智能与汽车行业的深度融合

内容概要 在这个快速发展的时代&#xff0c;智能网联汽车已经不再是科幻电影的专利&#xff0c;它正在悄然走进我们的日常生活。如今&#xff0c;人工智能&#xff08;AI&#xff09;技术与汽车行业的结合犹如一场科技盛宴&#xff0c;让我们看到了未来出行的新方向。通过自动…

Iceoryx2:高性能进程间通信框架(中间件)

文章目录 0. 引言1. 主要改进2. Iceoryx2 的架构3. C示例代码3.1 发布者示例&#xff08;publisher.cpp&#xff09;3.2 订阅者示例&#xff08;subscriber.cpp&#xff09; 4. 机制比较5. 架构比较6. Iceoryx vs Iceoryx2参考资料 0. 引言 Iceoryx2 是一个基于 Rust 实现的开…

NumPy安装

1.NumPy简介 NumPy(Numerical Python) 是 Python 语言的扩展程序库&#xff0c;支持大量维度数组与矩阵运算&#xff0c;此外也针对数组运算提供大量的数学函数库。 NumPy 的前身 Numeric 最早由 Jim Hugunin 与其它协作者共同开发&#xff0c;2005 年&#xff0c;Travis Oliph…

全自动一键批量创建站群网站插件 | Z-BlogPHP 堆词起站工具

在当今竞争激烈的数字营销世界&#xff0c;如何快速提升网站曝光率和流量&#xff1f;答案就是智能站群系统。 本文将结合实际效果&#xff0c;介绍一款功能强大的站群系统&#xff0c;重点讲述其堆词功能、泛目录管理、一键批量创建、内容转码、自定义标签和GPT内容生成与发布…

【深度学习】时间序列预测、分类、异常检测、概率预测项目实战案例

说明&#xff1a;本专栏内容来自于个人学习笔记、以及相关项目的实践与总结。写作目的是为了让读者体会深度学习的独特魅力与无限潜力&#xff0c;以及在各行各业之中的应用与实践。因作者时间精力有限&#xff0c;难免有疏漏之处&#xff0c;期待与读者共同进步。 前言 在当今…

第十五章 Vue工程化开发及Vue CLI脚手架

目录 一、引言 二、Vue CLI 基本介绍 三、安装Vue CLI 3.1. 安装npm和yarn 3.2. 安装Vue CLI 3.3. 查看 Vue 版本 四、创建启动工程 4.1. 创建项目架子 4.2. 启动工程 五、脚手架目录文件介绍 六、核心文件讲解 6.1. index.html 6.2. main.js 6.3. App.vue 一、…

Linux 进程终止 进程等待

目录 进程终止 退出码 错误码 代码异常终止(信号详解) exit _exit 进程等待 概念 等待的原因 wait 函数原型 参数 返回值 监控脚本 waitpid 概念 函数原型 参数 返回值 WIFEXITED(status) WEXITSTATUS(status) 问题 为什么不用全局变量获得子进程的退出信…

[MySQL]DQL语句(一)

查询语句是数据库操作中最为重要的一系列语法。查询关键字有 select、where、group、having、order by、imit。其中imit是MySQL的方言&#xff0c;只在MySQL适用。 数据库查询又分单表查询和多表查询&#xff0c;这里讲一下单表查询。 基础查询 # 查询指定列 SELECT * FROM …