STM32 I2C通信协议说明

目录

背景

I2C协议

数据的有效性

I2C通信开始和停止条件

I2C数据传输

发送

响应

正常情况:

异常情况:

主机结束接收

写寄存器的标准流程

读寄存器的标准流程

仲裁机制

时钟同步

SDA线的仲裁

程序


背景

对单片机的三大通信中的I2C通信进行说明。

I2C协议

协议采用双线结构传输数据,包括一个数据线和一个时钟线(即 SDA 和 SCL 线),其中 SDA(Serial Data)线用于双向数据传输,而 SCL(Serial Clock)线则用于同步数据传输的时钟信号。通信始终由主设备(Master)控制,从设备(Slave)被动接收和回应。这种简单的线路连接方式使得设备之间的互连变得非常容易。

数据的有效性

SDA 线上的数据必须在时钟的高电平周期保持稳定 (PS:在SCL为高电平的时候,SDA发生发生变化是作为I2C通信开始和结束的信号)数据线的高或低电平状态只有在 SCL 线的时钟
信号是低电平时才能改变。也就是说在 SCL为高电平时,SDA上的信号保持稳定 只有在SCL为低电平时,SDA上的信号才能改变 。数据的接收方会在每个时钟周期的高电平期间读取数据(SDA),因此数据是在SCL为高电平时进行读取的。

I2C通信开始和停止条件

表示起始条件:SCL 是高电平时 SDA 线从高电平向低电平切换
表示结束条件: SCL 是高电平时 SDA 线由低电平向高电平切换表示停止条件
总结:I2C的SCL为高电平时候,SDA发生变化是作为开始/结束的条件
PS:起始和停止条件一般由主机产生 总线在起始条件后被认为处于忙的状态。在停止条件的某段时间后总线被认为再次处于空闲状态。如果产生 重复起始 Sr条件而不产生停止条件总线会一直处于忙的状态,此时的起始条件 S和重复起始 Sr条件在功能上是一样的。

I2C数据传输

发送

发送到 SDA 线上的每个字节必须为 8 位,每次传输可以发送的字节数量不受限制 每个字节后必须跟 一个响应位 ,首先传输的是数据的最高位 MSB,如果从机要完成一些其他功能后 例如一个
内部中断服务程序,才能接收或发送下一个完整的数据字节, 可以使时钟线 SCL 保持低电平迫使主机进入 等待状态. 当从机准备好接收下一个数据字节并释放时钟线 SCL 后数据传输继续.

响应

正常情况:

数据传输必须带响应。 相关的响应时钟脉冲由主机产生。 在响应的时钟脉冲期间, 发送器释放 SDA 线高 在响应的时钟脉冲期间 接收器必须将 SDA 线拉低!  使它在这个时钟脉冲的高电平期间保持稳定的低电平。

异常情况:

从机不能响应从机地址时 例如它正在执行一些实时函数不能接收或发送 从机必须使数据线保持 高电平(NACK) 主机然后产生一个停止条件终止传输或者产生重复起始条件开始新的传输。
如果从机 接收器响应了从机地址但是在传输了一段时间后不能接收更多数据字节 主机必须再一次
终止传输 这个情况用从机在第一个字节后没有产生响应来表示 从机使数据线保持高电平 主机产生一 个停止或重复起始条件。

主机结束接收

如果传输中有主机(作为接收者) 它必须通过在 从机不产生时钟的 最后一个字节不产生一个响应向从机 (发送器)通知数据结束! 从机 发送器必须释放数据线,允许主机产生一个停止或重复起始条件

写寄存器的标准流程

  1. Master发起START
  2. Master发送I2C addr(7bit)和w操作0(1bit),等待ACK
  3. Slave发送ACK
  4. Master发送reg addr(8bit),等待ACK
  5. Slave发送ACK
  6. Master发送data(8bit),即要写入寄存器中的数据,等待ACK
  7. Slave发送ACK
  8. 第6步和第7步可以重复多次,即顺序写多个寄存器
  9. Master发起STOP

读寄存器的标准流程

  1. Master发送I2C addr(7bit)和w操作1(1bit),等待ACK
  2. Slave发送ACK
  3. Master发送reg addr(8bit),等待ACK
  4. Slave发送ACK
  5. Master发起re -START
  6. Master发送I2C addr(7bit)和r操作1(1bit),等待ACK
  7. Slave发送ACK
  8. Slave发送data(8bit),即寄存器里的值
  9. Master发送ACK
  10. 第8步和第9步可以重复多次,即顺序读多个寄存器
  11. 主机想结束接收时,最后一个数据不再需要ACK应答,保持为高电平(NACK)。
  12. 主机发出 STOP 信号,停止 I2C 通信

 

主机想结束接收时,最后一个数据不再需要ACK应答,保持为高电平(NACK)。

仲裁机制

如果存在多主机的情况下,才要考虑仲裁机制。I2C总线上的仲裁分两部分:SCL线的同步和SDA线的仲裁。

时钟同步

在 I2C 总线上传送信息时的时钟同步信号是由挂接在 SCL 线上的所有器件的 逻辑“与” (线与,所以需要SCL也是开漏输出)完成的。即如果有多个主机同时产生时钟,那么只有所有主机都发送高电平时,SCL 上才表现为高电平,否则 SCL 都表现为低电平。

SDA线的仲裁

总线仲裁是为了解决多设备同时竞争中线控制权的问题,通过一定的裸机来决定哪个设备能够获得最终的总线控制权。

SDA线的仲裁也是建立在总线具有线“与”逻辑功能的原理上的。节点在发送1位数据后,比较总线上所呈现的数据与自己发送的是否一致(回读机制)。

  • 是,继续发送;
  • 否则,退出竞争;

I2C总线的控制逻辑:低电平优先

SDA线的仲裁可以保证I2C总线系统在多个主节点同时企图控制总线时通信正常进行并且数据不丢失,总线系统通过仲裁只允许一个主节点可以继续占据总线

程序

void I2C_GPIO_Init(void){ //I2C接口初始化
	GPIO_InitTypeDef  GPIO_InitStructure; 	
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC,ENABLE);       
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); //启动I2C功能 
    GPIO_InitStructure.GPIO_Pin = I2C_SCL | I2C_SDA; //选择端口号                      
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; //选择IO接口工作方式       
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置IO接口速度(2/10/50MHz)    
	GPIO_Init(I2CPORT, &GPIO_InitStructure);
}


void I2C_Configuration(void){ //I2C初始化
	I2C_InitTypeDef  I2C_InitStructure;
	I2C_GPIO_Init(); //先设置GPIO接口的状态
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;//设置为I2C模式
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
	I2C_InitStructure.I2C_OwnAddress1 = HostAddress; //主机地址(从机不得用此地址)
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;//允许应答
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; //7位地址模式
	I2C_InitStructure.I2C_ClockSpeed = BusSpeed; //总线速度设置 	
	I2C_Init(I2C1,&I2C_InitStructure);
	I2C_Cmd(I2C1,ENABLE);//开启I2C					
}


void I2C_SAND_BUFFER(u8 SlaveAddr,u8 WriteAddr,u8* pBuffer,u16 NumByteToWrite){ //I2C发送数据串(器件地址,寄存器,内部地址,数量)
	I2C_GenerateSTART(I2C1,ENABLE);//产生起始位
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT)); //清除EV5
	I2C_Send7bitAddress(I2C1,SlaveAddr,I2C_Direction_Transmitter);//发送器件地址
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));//清除EV6
	I2C_SendData(I2C1,WriteAddr); //内部功能地址
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED));//移位寄存器非空,数据寄存器已空,产生EV8,发送数据到DR既清除该事件
	while(NumByteToWrite--){ //循环发送数据	
		I2C_SendData(I2C1,*pBuffer); //发送数据
		pBuffer++; //数据指针移位
		while (!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED));//清除EV8
	}
	I2C_GenerateSTOP(I2C1,ENABLE);//产生停止信号
}
void I2C_SAND_BYTE(u8 SlaveAddr,u8 writeAddr,u8 pBuffer){ //I2C发送一个字节(从地址,内部地址,内容)
	I2C_GenerateSTART(I2C1,ENABLE); //发送开始信号
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT)); //等待完成	
	I2C_Send7bitAddress(I2C1,SlaveAddr, I2C_Direction_Transmitter); //发送从器件地址及状态(写入)
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); //等待完成	
	I2C_SendData(I2C1,writeAddr); //发送从器件内部寄存器地址
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //等待完成	
	I2C_SendData(I2C1,pBuffer); //发送要写入的内容
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //等待完成	
	I2C_GenerateSTOP(I2C1,ENABLE); //发送结束信号
}
void I2C_READ_BUFFER(u8 SlaveAddr,u8 readAddr,u8* pBuffer,u16 NumByteToRead){ //I2C读取数据串(器件地址,寄存器,内部地址,数量)
	while(I2C_GetFlagStatus(I2C1,I2C_FLAG_BUSY));
	I2C_GenerateSTART(I2C1,ENABLE);//开启信号
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT));	//清除 EV5
	I2C_Send7bitAddress(I2C1,SlaveAddr, I2C_Direction_Transmitter); //写入器件地址
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));//清除 EV6
	I2C_Cmd(I2C1,ENABLE);
	I2C_SendData(I2C1,readAddr); //发送读的地址
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED)); //清除 EV8
	I2C_GenerateSTART(I2C1,ENABLE); //开启信号
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT)); //清除 EV5
	I2C_Send7bitAddress(I2C1,SlaveAddr,I2C_Direction_Receiver); //将器件地址传出,主机为读
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); //清除EV6
	while(NumByteToRead){
		if(NumByteToRead == 1){ //只剩下最后一个数据时进入 if 语句
			I2C_AcknowledgeConfig(I2C1,DISABLE); //最后有一个数据时关闭应答位
			I2C_GenerateSTOP(I2C1,ENABLE);	//最后一个数据时使能停止位
		}
		if(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_RECEIVED)){ //读取数据
			*pBuffer = I2C_ReceiveData(I2C1);//调用库函数将数据取出到 pBuffer
			pBuffer++; //指针移位
			NumByteToRead--; //字节数减 1 
		}
	}
	I2C_AcknowledgeConfig(I2C1,ENABLE);
}
u8 I2C_READ_BYTE(u8 SlaveAddr,u8 readAddr){ //I2C读取一个字节
	u8 a;
	while(I2C_GetFlagStatus(I2C1,I2C_FLAG_BUSY));
	I2C_GenerateSTART(I2C1,ENABLE);
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT));
	I2C_Send7bitAddress(I2C1,SlaveAddr, I2C_Direction_Transmitter); 
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
	I2C_Cmd(I2C1,ENABLE);
	I2C_SendData(I2C1,readAddr);
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_BYTE_TRANSMITTED));
	I2C_GenerateSTART(I2C1,ENABLE);
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT));
	I2C_Send7bitAddress(I2C1,SlaveAddr, I2C_Direction_Receiver);
	while(!I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
	I2C_AcknowledgeConfig(I2C1,DISABLE); //最后有一个数据时关闭应答位
	I2C_GenerateSTOP(I2C1,ENABLE);	//最后一个数据时使能停止位
	a = I2C_ReceiveData(I2C1);
	return a;
}

PS:因为总线的线与特性,SCL和SDA都要设置为开漏输出

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Unity学习part2

为bilibili教程【【Unity教程】零基础带你从小白到超神】 https://www.bilibili.com/video/BV1gQ4y1e7SS/?p50&share_sourcecopy_web&vd_source6e7a3cbb802eb986578ad26fae1eeaab的笔记 1、灯光的使用 定向光模拟太阳,是平行光。旋转定向光,光…

Vue 实现主题切换(明暗)

项目地址:https://gitee.com/abcdfdewrw/vue3_xiaohongshu_project 效果展示: 步骤1:定义明暗scss样式 // 浅色模式 html[data-theme"light"]:root {--header-height: 72px;--color-border-bottom: #eef2f9;--color-primary-lab…

rabbitmq五种模式的总结——附java-se实现(详细)

rabbitmq五种模式的总结 完整项目地址:https://github.com/9lucifer/rabbitmq4j-learning 一、简单模式 (一)简单模式概述 RabbitMQ 的简单模式是最基础的消息队列模式,包含以下两个角色: 生产者:负责发…

数据结构 day02

3. 线性表 3.1. 顺序表 3.1.3. 顺序表编程实现 操作:增删改查 .h 文件 #ifndef __SEQLIST_H__ #define __SEQLIST_H__ #define N 10 typedef struct seqlist {int data[N];int last; //代表数组中最后一个有效元素的下标 } seqlist_t;//1.创建一个空的顺序表 seq…

STM32的HAL库开发---ADC

一、ADC简介 1、ADC,全称:Analog-to-Digital Converter,指模拟/数字转换器 把一些传感器的物理量转换成电压,使用ADC采集电压,然后转换成数字量,经过单片机处理,进行控制和显示。 2、常见的AD…

25/2/16 <算法笔记> DirectPose

DirectPose 是一种直接从图像中预测物体的 6DoF(位姿:6 Degrees of Freedom)姿态 的方法,包括平移和平面旋转。它在目标检测、机器人视觉、增强现实(AR)和自动驾驶等领域中具有广泛应用。相比于传统的位姿估…

企业级API集成方案:基于阿里云函数计算调用DeepSeek全解析

解决方案链接:https://www.aliyun.com/solution/tech-solution/deepseek-r1-for-platforms?utm_contentg_1000401616 何为DeepSeek R1 DeepSeek R1模型有诸多技术优势。高效架构设计使其能更高效提取特征,减少冗余计算,提升数据处理速度、…

137,【4】 buuctf web [SCTF2019]Flag Shop

进入靶场 都点击看看 发现点击work会增加¥ 但肯定不能一直点下去 抓包看看 这看起来是一个 JWT(JSON Web Token)字符串。JWT 通常由三部分组成,通过点(.)分隔,分别是头部(Header&…

ThinkPHP8视图赋值与渲染

【图书介绍】《ThinkPHP 8高效构建Web应用》-CSDN博客 《2025新书 ThinkPHP 8高效构建Web应用 编程与应用开发丛书 夏磊 清华大学出版社教材书籍 9787302678236 ThinkPHP 8高效构建Web应用》【摘要 书评 试读】- 京东图书 在控制器操作中,使用view函数可以传入视图…

渗透利器:YAKIT 工具-基础实战教程.

YAKIT 工具-基础实战教程. YAKIT(Yak Integrated Toolkit)是一款基于Yak语言开发的集成化网络安全单兵工具,旨在覆盖渗透测试全流程,提供从信息收集、漏洞扫描到攻击实施的自动化支持。其核心目标是通过GUI界面降低Yak语言的使用…

Fiori APP配置中的Semantic object 小bug

在配置自开发程序的Fiori Tile时,需要填入Semantic Object。正常来说,是需要通过事务代码/N/UI2/SEMOBJ来提前新建的。 但是在S4 2022中,似乎存在一个bug,即无需新建也能输入自定义的Semantic Object。 如下,当我们任…

shell——分支语句

文章目录 基本语法常用判断条件(1)两个整数之间比较(2)按照文件权限进行判断(3)按照文件类型进行判断(4)多条件判断(&& 表示前一条命令执行成功时,才执行后一条命令&#xf…

Ubuntu 连接 air pods

1. sudo vim /etc/bluetooth/main.conf , 修改蓝牙模式为blder 2.sudo /etc/init.d/bluetooth restart, 重启蓝牙,即可连接成功

机器学习:k近邻

所有代码和文档均在golitter/Decoding-ML-Top10: 使用 Python 优雅地实现机器学习十大经典算法。 (github.com),欢迎查看。 K 邻近算法(K-Nearest Neighbors,简称 KNN)是一种经典的机器学习算法,主要用于分类和回归任务…

低空经济:开启未来空中生活的全新蓝海

引言 随着科技的进步,我们不再仅仅依赖地面交通和传统物流。你是否曾幻想过,未来的某一天,快递、外卖可以像魔法一样直接从空中送到你手中?或者,你能乘坐小型飞行器,快速穿梭于城市之间,告别拥堵…

DeepSeek核心算法解析:如何打造比肩ChatGPT的国产大模型

注:此文章内容均节选自充电了么创始人,CEO兼CTO陈敬雷老师的新书《自然语言处理原理与实战》(人工智能科学与技术丛书)【陈敬雷编著】【清华大学出版社】 文章目录 DeepSeek大模型技术系列一DeepSeek核心算法解析:如何…

苍穹外卖day4 redis相关简单知识 店铺营业状态设置

内存存储 键值对 key-value 一般用于处理突发性大量请求数据操作(暂时浅显理解) 读写速度极快,常用于缓存数据,减少对数据库的访问压力,提高系统性能。例如,可以缓存用户会话、商品信息、页面数据 设置默…

API 接口自动化

HTTP协议 - 白月黑羽 HTTP协议简介 如果客户端是浏览器,如何在chrome浏览器中查看 请求和响应的HTTP消息?按f12-》network 清除当前信息 响应的消息体在Response里看 点preview,可以看响应的消息体展开的格式 HTTP请求消息 请求头 reques…

Oracle序列(基础操作)

序列概念 序列是用于生成唯一、连续序号的对象。 序列可以是升序的,也可以是降序的。 使用CREATE SEQUENCE语句创建序列。 start with 1 指定第一个序号从1开始 increment by 1 指定序号之间的间隔为1 increment by -1 降序1000 999 998这样 maxvalue 2000 表…

【pytorch】weight_norm和spectral_norm

apply_parametrization_norm 和spectral_norm是 PyTorch 中用于对模型参数进行规范化的方法,但它们在实现和使用上有显著的区别。以下是它们的主要区别和对比: 实现方式 weight_norm: weight_norm 是一种参数重参数化技术,将权…