I2C协议靠这16张图彻底搞懂(超详细)
文章目录
- 背景
- 硬件层
- 数据传输协议
- 实际上如何工作?
- 单个主设备连接多个从机
- 多个主设备连接多个从机
- 如何编程?
- 总结
背景
I²C(Inter-Integrated Circuit),中文应该叫集成电路总线,它是一种串行通信总线,使用多主从架构,是由飞利浦公司在1980年代初设计的,方便了主板、嵌入式系统或手机与周边设备组件之间的通讯。由于其简单性,它被广泛用于微控制器与传感器阵列,显示器,IoT设备,EEPROM等之间的通信。
I²C最重要的功能包括:
- 只需要两条总线;
- 没有严格的波特率要求,例如使用RS232,主设备生成总线时钟;
- 所有组件之间都存在简单的主/从关系,连接到总线的每个设备均可通过唯一地址进行软件寻址;
- I²C是真正的多主设备总线,可提供仲裁和冲突检测;
- 传输速度;
- 标准模式:Standard Mode = 100
Kbps
- 快速模式:Fast Mode = 400
Kbps
- 高速模式: High speed mode = 3.4
Mbps
- 超快速模式: Ultra fast mode = 5
Mbps
- 标准模式:Standard Mode = 100
- 最大主设备数:无限制;
- 最大从机数:理论上是127;
以上是I²C的一些重要特点,下面会进一步对I²C进行介绍。
硬件层
I²C协议仅需要一个SDA和SCL引脚。SDA是串行数据线的缩写,而SCL是串行时钟线的缩写。这两条数据线需要接上拉电阻。
设备间的连接如下所示:
使用I²C,可以将多个从机(Slave
)连接到单个主设备(Master
),并且还可以有多个主设备(Master
)控制一个或多个从机(Slave
)。
假如希望有多个微控制器(
MCU
)将数据记录到单个存储卡或将文本显示到单个LCD时,这个功能就非常有用。
I²C总线(SDA
,SCL
)内部都使用漏极开路驱动器(开漏驱动),因此SDA
和SCL
可以被拉低为低电平,但是不能被驱动为高电平,所以每条线上都要使用一个上拉电阻,默认情况下将其保持在高电平;
上拉电阻的值取决于许多因素。德州仪器TI 建议 使用以下公式来计算正确的上拉电阻值:
Rp(min)=VDD−VOL(max)IOLR_p(min)=\cfrac{V_{DD}-V_{OL}(max)}{I_{OL}}Rp(min)=IOLVDD−VOL(max)
Rp(min)=tr0.8473xCbR_p(min)=\cfrac{t_r}{0.8473xC_b}Rp(min)=0.8473xCbtr
其中 VOLV_{ OL}VOL是逻辑低电压;
IOLI_{OL}IOL是逻辑低电流;
trt_rtr是信号的最大上升时间;
CbC_bCb是总线(电线)电容;
具体如下所示:
从上表可知,使用I2C设备必须在3mA3mA3mA的灌电流下工作,
这里不难发现需要在做选型需要满足几个条件;
- 灌电流 最大值为3mA3mA3mA;
- 另外I2C总线规范和用户手册还为低电平输出电压VOLV_{OL}VOL设置了最大值为0.4V
Rp(min)=VCC−0.4V3mAR_p(min) = \cfrac{VCC - 0.4V}{3mA}Rp(min)=3mAVCC−0.4V
所以根据上述公式可以计算,对于5V的电源,每个上拉电阻必须至少具有1.53kΩ,而对于3.3V的电源,每个电阻必须至少具有967Ω。
如果觉得计算电阻值比较麻烦,也可以使用典型值 4.7kΩ。
上述推导过程可以参考 TI的文档《I2C Bus Pullup Resistor Calculation》 https://www.ti.com/lit/an/slva689/slva689.pdf
最终在调试的时候,当我们测量SDA或SCL信号并且逻辑LOW上的电压高于0.4V时,我们就知道可以知道灌电流太高了;
当然,这并不意味着每当灌电流超过3mA时,设备就会立即停止工作。但是,在操作超出其规格的设备时,应始终小心,因为它可能导致通信故障,缩短其使用寿命甚至甚至永久损坏设备。
数据传输协议
主设备和从设备进行数据传输时遵循以下协议格式。数据通过一条SDA数据线在主设备和从设备之间传输0
和1
的串行数据。串行数据序列的结构可以分为,开始条件,地址位,读写位,应答位,数据位,停止条件,具体如下所示;
开始条件
当主设备决定开始通讯时,需要发送开始信号,需要执行以下动作;
- 先将SDA线从高压电平切换到低压电平;
- 然后将
SCL
从高电平切换到低电平;
在主设备发送开始条件信号之后,所有从机即使处于睡眠模式也将变为活动状态,并等待接收地址位。
具体如下图所示;
地址位
通常地址位占7位数据,主设备如果需要向从机发送/接收数据,首先要发送对应从机的地址,然后会匹配总线上挂载的从机的地址;
I2C还支持10位寻址;
读写位
该位指定数据传输的方向;
- 如果主设备需要将数据发送到从设备,则该位设置为
0
; - 如果主设备需要往从设备接收数据,则将其设置为
1
。
ACK / NACK
主机每次发送完数据之后会等待从设备的应答信号ACK
;
- 在第9个时钟信号,如果从设备发送应答信号
ACK
,则SDA
会被拉低; - 若没有应答信号
NACK
,则SDA
会输出为高电平,这过程会引起主设备发生重启或者停止;
数据块
传输的数据总共有8位,由发送方设置,它需要将数据位传输到接收方。
发送之后会紧跟一个ACK
/ NACK
位,如果接收器成功接收到数据,则设置为0。否则,它保持逻辑“ 1”。
重复发送,直到数据完全传输为止。
停止条件
当主设备决定结束通讯时,需要发送开始信号,需要执行以下动作;
- 先将SDA线从低电压电平切换到高电压电平;
- 再将SCL线从高电平拉到低电平;
具体如下图所示;
实际上如何工作?
第一步:起始条件
主设备通过将SDA线从高电平切换到低电平,再将SCL线从高电平切换到低电平,来向每个连接的从机发送启动条件 :
第二步:发送从设备地址
主设备向每个从机发送要与之通信的从机的7位或10位地址,以及相应的读/写位;
第三步:接收应答
每个从设备将主设备发送的地址与其自己的地址进行比较。如果地址匹配,则从设备通过将SDA线拉低一位以表示返回一个ACK位;
如果来自主设备的地址与从机自身的地址不匹配,则从设备将SDA线拉高,表示返回一个NACK位;
第四步:收发数据
主设备发送或接收数据到从设备;
第五步:接收应答
在传输完每个数据帧后,接收设备将另一个ACK位返回给发送方,以确认已成功接收到该帧:
第六步:停止通信
为了停止数据传输,主设备将SCL切换为高电平,然后再将SDA切换为高电平,从而向从机发送停止条件;
单个主设备连接多个从机
I2C总线上的主设备使用7位地址对从设备进行寻址,可以使用128(272^727)个从机地址。
请使用4.7K上拉电阻将SDA和SCL线连接到Vcc;
多个主设备连接多个从机
多个主设备可以连接到一个或多个从机;
当两个主设备试图通过SDA线路同时发送或接收数据时,同一系统中的多个主设备就会出现问题。
为了解决这个问题,每个主设备都需要在发送消息之前检测SDA线是低电平还是高电平;
如果SDA线为低电平,则意味着另一个主设备可以控制总线,并且主设备应等待发送消息。
如果SDA线为高电平,则可以安全地发送消息。
要将多个主设备连接到多个从机,请使用下图,其中4.7K上拉电阻将SDA和SCL线连接到Vcc:
如何编程?
Talk is cheap. Show me the code.
参考了STM32的HAL库中I2C驱动,主设备发送函数HAL_I2C_Master_Transmit()
具体如下:
/*** @brief Transmits in master mode an amount of data in blocking mode.* @param hi2c Pointer to a I2C_HandleTypeDef structure that contains* the configuration information for the specified I2C.* @param DevAddress Target device address: The device 7 bits address value* in datasheet must be shifted to the left before calling the interface* @param pData Pointer to data buffer* @param Size Amount of data to be sent* @param Timeout Timeout duration* @retval HAL status*/
HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout){uint32_t tickstart = 0x00U;/* Init tickstart for timeout management*/tickstart = HAL_GetTick();if(hi2c->State == HAL_I2C_STATE_READY){/* Wait until BUSY flag is reset */if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET, I2C_TIMEOUT_BUSY_FLAG, tickstart) != HAL_OK){return HAL_BUSY;}/* Process Locked */__HAL_LOCK(hi2c);/* Check if the I2C is already enabled */if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE){/* Enable I2C peripheral */__HAL_I2C_ENABLE(hi2c);}/* Disable Pos */hi2c->Instance->CR1 &= ~I2C_CR1_POS;hi2c->State = HAL_I2C_STATE_BUSY_TX;hi2c->Mode = HAL_I2C_MODE_MASTER;hi2c->ErrorCode = HAL_I2C_ERROR_NONE;/* Prepare transfer parameters */hi2c->pBuffPtr = pData;hi2c->XferCount = Size;hi2c->XferOptions = I2C_NO_OPTION_FRAME;hi2c->XferSize = hi2c->XferCount;/* Send Slave Address */if(I2C_MasterRequestWrite(hi2c, DevAddress, Timeout, tickstart) != HAL_OK){if(hi2c->ErrorCode == HAL_I2C_ERROR_AF){/* Process Unlocked */__HAL_UNLOCK(hi2c);return HAL_ERROR;}else{/* Process Unlocked */__HAL_UNLOCK(hi2c);return HAL_TIMEOUT;}}/* Clear ADDR flag */__HAL_I2C_CLEAR_ADDRFLAG(hi2c);while(hi2c->XferSize > 0U){/* Wait until TXE flag is set */if(I2C_WaitOnTXEFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK){if(hi2c->ErrorCode == HAL_I2C_ERROR_AF){/* Generate Stop */hi2c->Instance->CR1 |= I2C_CR1_STOP;return HAL_ERROR;}else{return HAL_TIMEOUT;}}/* Write data to DR */hi2c->Instance->DR = (*hi2c->pBuffPtr++);hi2c->XferCount--;hi2c->XferSize--;if((__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BTF) == SET) && (hi2c->XferSize != 0U)){/* Write data to DR */hi2c->Instance->DR = (*hi2c->pBuffPtr++);hi2c->XferCount--;hi2c->XferSize--;}/* Wait until BTF flag is set */if(I2C_WaitOnBTFFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK){if(hi2c->ErrorCode == HAL_I2C_ERROR_AF){/* Generate Stop */hi2c->Instance->CR1 |= I2C_CR1_STOP;return HAL_ERROR;}else{return HAL_TIMEOUT;}}}/* Generate Stop */hi2c->Instance->CR1 |= I2C_CR1_STOP;hi2c->State = HAL_I2C_STATE_READY;hi2c->Mode = HAL_I2C_MODE_NONE;/* Process Unlocked */__HAL_UNLOCK(hi2c);return HAL_OK;}else{return HAL_BUSY;}
}
总结
本文主要介绍I2C的入门基础知识,从I2C协议的硬件层,协议层进行了简单介绍;作者能力有限,难免存在错误和纰漏,请大佬不吝赐教。
I2C协议靠这16张图彻底搞懂(超详细)相关推荐
- javascript 不让成为nan_一张图彻底搞懂JavaScript的==运算
大家知道,==是JavaScript中比较复杂的一个运算符.它的运算规则奇怪,容易让人犯错,从而成为JavaScript中"最糟糕的特性"之一. 在仔细阅读了ECMAScript规 ...
- 四张图彻底搞懂CNN反向传播算法(通俗易懂)
点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 本文转自:机器学习算法那些事 阅读本文之前,可以先阅读之前讲述的全 ...
- synchronized 分布式时为什么会失效_10张图,搞懂索引为什么会失效?
MySQL数据是如何存储的? 聚集索引 我们先建如下的一张表 CREATE TABLE `student` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT ...
- 13 张图彻底搞懂分布式系统服务注册与发现原理
作者 | 雷架 来源 | 爱笑的架构师(ID:DancingOnYourCode) 头图 | CSDN 下载自东方IC 在微服务架构或分布式环境下,服务注册与发现技术不可或缺,这也是程序员进阶之路必 ...
- 原来10张图就可以搞懂分布式链路追踪系统原理
分布式系统为什么需要链路追踪? 随着互联网业务快速扩展,软件架构也日益变得复杂,为了适应海量用户高并发请求,系统中越来越多的组件开始走向分布式化,如单体架构拆分为微服务.服务内缓存变为分布式缓存.服务 ...
- 13张图彻底搞懂分布式系统服务注册与发现原理
在微服务架构或分布式环境下,服务注册与发现技术不可或缺,这也是程序员进阶之路必须要掌握的核心技术之一,本文通过图解的方式带领大家轻轻松松掌握. 引入服务注册与发现组件的原因 先来看一个问题,假如现在我 ...
- 16 张图带你搞懂 Java 数据结构,从此想不飘都难!
CSDN 的小伙伴们,大家好,我是沉默的王二. 假期结束了,需要快速切换到工作的状态投入到新的一天当中.放假的时候痛快地玩耍,上班的时候积极的工作,这应该是我们大多数"现代人"该有 ...
- 哪吒社区技能树16 张图带你搞懂 Java 数据结构,从此想不飘都难
目录 一.数组 二.链表 三.栈 四.队列 五. 树 1.普通树:对子节点没有任何约束. 2.二叉树:每个节点最多含有两个子节点的树. 二叉树按照不同的表现形式又可以分为多种. 3.二叉查找树:英文 ...
- 「八大排序算法」16张图带你搞懂基数排序
前言 在排序算法中,大家可能对桶排序.计数排序.基数排序不太了解,不太清楚其算法的思想和流程,也可能看过会过但是很快就忘记了,但是不要紧,幸运的是你看到了本篇文章.本文将通俗易懂的给你讲解基数排序. ...
最新文章
- 【HZOI2015】帕秋莉的超级多项式
- 1088 最长回文子串
- 程序员的自我修养六可执行文件的装载与进程
- 自定义Gradle插件(十)
- 位地址和字节地址换算_面试常考,项目易错,长文详解C/C++中的字节对齐
- 机器学习实战(五)——Logistic 回归
- vue-resource jsonp跨域问题解决方法
- 苏州软件测试11k工资要什么水平,3个月从机械转行软件测试,他的入职薪资是11K...
- qt中QHBoxLayout或QVBoxLayout布局内控件的动态生成与显示
- java程序设计课程简介_《JAVA语言程序设计》课程简介
- 互联网黑产:那些职业羊毛党到底如何月赚几十万?
- 浙江正泰中自 DCS系统PCS1800系统介绍
- 联通光纤服务器没有响应怎么办,联通光纤猫断线无法上网等问题不一样的解决方案...
- pythonobject转int_python – Pandas:将date’object’转换为int
- APP注册名称的一些问题
- 浅析小程序云原生数据库的设计与应用
- 一级计算机浏览器题加收藏夹,2016年计算机一级考试上机操作重难点突破
- 你的用户珍贵么?杀鸡吃肉和养鸡吃蛋的选择……
- GNN论文周报 | 来自北航、中科院、MSRA、新加坡国立大学、慕尼黑工业大学等机构前沿论文研究...
- scp 拷贝文件夹到远程服务器被拒绝ssh: connect to host xxx.xxx.xxx.xxx port xxxxx: Connection refused