IIC协议详解,附单片机软件模拟源码
I2C协议
- 物理层
- 原理
- 总体特征
- 电气限制
- 协议层
- 起始和停止条件
- 数据有效性
- 响应/应答
- 寻址
- 读数据
- 写数据
- 单片机通讯
- 软件模拟
- 硬件外设
(一)物理层
1. 原理
I2C 总线,分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成。
通信原理是通过对SCL和SDA线高低电平时序的控制,来 产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。
2. 总体特征
连接到总线的器件输出级必须是漏极开路或集电极开路才能执行线与的功能。上拉能力即上升沿时间由外部电源和上拉电阻决定, 下降沿由器件OD产生, 速度快。因此IIC的通信速率由上拉能力决定。
I2C 总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址(可以从I2C器件的数据手册得知),主从设备之间就通过这 个地址来确定与哪个器件进行通信。
I2C 总线上数据的传输速率在标准模式下可达 100kbit/s ,在快速模式下可达 400kbit/s ,在高速模式下可达 3.4Mbit/s ,连接到总线的接口数量只由总线电容是 400pF 的限制决定。
I2C 总线上的主设备与从设备之间以字节(8位)为单位进行双向的数据传输。
3. 电气限制
最大device数量
IIC协议本身没有严格规定总线上device最大数目, 从理论上看, IIC能挂的device数目取决于能表示的最大地址空间, 在7位地址模式下, 减去0x00地址不可用, 理论上可以挂27−1=1272^7 -1 = 12727−1=127个设备。
但是IIC规定了总线电容不能超过400pF(具体与通信速率有关如下图)。
由于器件的管脚都是有输入电容的,PCB上也会有寄生电容,所以会有一个限制。实际设计中经验值大概是不超过8个器件。
规定电容大小的原因: IIC的OD(开漏)要求外部有电阻上拉,电阻和总线电容产生了一个RC延时效应,电容越大信号的边沿就越缓,有可能带来信号质量风险。
传输速度越快,信号的窗口就越小,上升沿下降沿时间要求更短更陡峭,所以RC乘积必须更小。
上拉电阻的选取
由于I2C接口采用Open Drain机制,器件本身只能输出低电平,无法主动输出高电平,只能通过外部上拉电阻Rp将信号线拉至高电平。
上拉电阻牵扯到两方面问题, 两者相互矛盾:
- 一个是功耗问题, 要求功耗低就得增大电阻(减小电流)
- 一个是速度问题, 要求速度快就得减小电阻(减小总线RC值提高上拉能力)
I2C上拉电阻确定有一个计算公式:
Rmin=Vdd(min)−0.4V/3mAR_{min}={Vdd(min)-0.4V}/3mA Rmin=Vdd(min)−0.4V/3mA
Rmax=(T/0.874)∗CR_{max}=(T/0.874) * CRmax=(T/0.874)∗C
通信速率为100KHz时T=1us, 速率为400KHz时T=0.3us , C为总线电容。
电源电压限制了上拉电阻的最小值 ; 负载电容(总线电容)限制了上拉电阻的最大值
如果上拉电阻值过小,Vcc灌入端口的电流(Ic)变大,这样会导致器件开漏输出的MOS管(三极管)不完全导通(Ib∗β<IcI_b*β<I_cIb∗β<Ic),由饱和状态变成放大状态,这样端口输出的低电平值增大(I2C协议规定,端口输出低电平的最高允许值为0.4V)。
如果上拉电阻过大,加上线上的总线电容,由于RC影响,会带来上升时间的增大(下降沿是芯片内的晶体管,是有源驱动,速度较快;上升沿是无源的外接电阻,速度慢),而且上拉电阻过大,即引起输出阻抗的增大,当输出阻抗和负载的阻抗可以比拟的时,则输出的高电平会分压而减少。
I2C协议还定义了串联在SDA、SCL线上电阻Rs。该电阻的作用是,有效抑制总线上的干扰脉冲进入从设备,提高可靠性。这个电阻的选择一般在100~200ohm左右。当然,这个电阻并不是必须的,在恶劣噪声环境中,可以选用。
(二)协议层
1. 起始和停止条件
I2C协议规定,总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。
起始和结束信号总是由主设备产生。
- 总线在空闲状态 时,SCL和SDA都保持着高电平,
- 当SCL为高而SDA由高到低的跳变,表示产生一个起始条件;
- 当SCL为高而SDA由低到高的跳变,表示产生一个 停止条件。
- 在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他I2C器件无法访问总线;
- 而在停止条件产生后,本次数据传输的主从设备将释放总线,总线再次处于空闲状态。
2. 数据有效性
SDA线上的数据必须在SCL的高电平时保持稳定。
SDA线的高或低电平状态只有在 SCL 线的时钟信号是低电平时才能改变。
发送到 SDA 线上的每个字节必须为 8 位,每次传输可以发送的字节数量不受限制 ,每个字节后必须跟一个响应位 ,首先传输的是数据的最高位(见下图MSB )
如果从机要完成一些其他功能后(如中断)才能接收或发送下一个完整的数据字节 ,可以使时钟线 SCL 保持低电平迫使主机进入等待状态 ,当从机准备好接收下一个数据字节时释放时钟线 SCL, 数据传输继续。
3. 响应
数据传输必须带响应,相关的响应SCL时钟脉冲由主机产生,在响应的时钟脉冲期间,发送器释放 SDA 线(输出高阻态使SDA线被上拉电阻拉高)。在响应的时钟脉冲期间,接收器必须将 SDA 线拉低,使它在这个时钟脉冲的高电平期间保持稳定的低电平。 必须考虑建立和保持时间。
4. 寻址
7位地址格式
在起始条件 S 后 ,发送了一个从机地址SLAVE ADDRESS, 这个地址共有 7 位,紧接着的第 8 位是数据方向位[R/W], 0 表示写,1表示读。接下来的一个bit是应答位NACK/ACK,当这个帧中前面8bits发送完后,接收端获得SDA控制权,此时接收设备应该在第9个时钟脉冲之前回复一个ACK(将SDA拉低)以表示接收正常,如果接收设备没有将SDA拉低,则说明接收设备可能没有收到数据(如寻址的设备不存在或设备忙)或无法解析收到的消息,如果是这样,则由master来决定如何处理(stop或repeated start condition)。
10位地址格式
10 位从机地址是由在起始条件 S 或重复起始条件 Sr 后的头两个字节组成。
第一个字节的头 7 位是 11110XX 的组合 ,其中:最后两位 XX 是 10 位地址的两个最高位 MSB
第一个字节的第 8 位是 R/ W 位, 决定了报文的方向 :0 表示写, 1 表示读。
如果 R/ W 位是 0 则下一个字节是 10 位从机地址剩下的 8 位;
如果 R/ W 位是 1 则下一个字节是从机发送给主机的数据。
仲裁过程
主机只能在总线空闲的时侯启动传输。两个或多个主机可能在起始条件的最小持续时间内产生一个起始条件,结果在总线上产生一个规定的起始条件。
当 SCL 线是高电平时,仲裁在 SDA 线发生。这样,在其他主机发送低电平时,发送高电平的主机将退出竞争, 因为总线上的电平与它自己的电平不相同。
仲裁可以持续多位。第一个阶段是比较地址位,如果每个主机都尝试寻址相同的器件,当主机作发送器时仲裁会继续比较数据位,当主机作接收器时仲裁会继续比较响应位。
IIC总线的地址和数据信息由赢得仲裁的主机决定,因此在仲裁过程中不会丢失信息。
丢失仲裁的主机可以产生时钟脉冲,直到丢失仲裁的该字节末尾。
可以看到在起始信号的第3个时钟周期低电平时,DATA1的SDA输出高电平,而DATA2向SDA输出低电平,SDA总线保持低电平,仲裁给到DATA2,即DATA1的主机退出竞争,不再发送数据。DATA1主机赢得仲裁,SDA总线已传送的数据和DATA1发送的数据保持一致。
5. 读数据
7位寻址为例 CPU作为主接收器
第一步,主机发送一个起始信号S。
第二步,主机发送7bit从机地址。此处需要注意,发送数据时,无法发送7bit数据,此处发送了7bit地址+1bit读写选择位,即发送7bit+R/W。最低位为1表示读,为0表示写。
第三步,从机产生一个ACK应答信号。
//第四步,主机发送寄存器地址。
//第五步,从机产生一个ACK应答信号。
//第六步,主机再次发送一个起始信号。
//第七步,主机发送7bit从机地址,即7bit+R/W。最低位为1表示读,为0表示写。
//第八步,从机产生一个ACK应答信号。
第九步,主机读取一个字节(8bit)的数据(相当于从机发送一个字节)。
第十步,CPU产生一个ACK应答信号。
//第十一步,读取一个CRC校验码。
第十二步,CPU产生一个NACK无应答信号。
第十三步,主机产生一个停止信号。
常规的使用就是第一、二、三、九、十、十二、十三步。 注释掉的是单片机继承的硬件IIC整个完整的通讯过程。
6. 写数据
CPU作为主发送器
第一步,主机发送一个起始信号。
第二步,发送7bit从机地址。此处需要注意,发送数据时,无法发送7bit数据,此处发送了7bit地址+1bit读写选择位,即发送7bit+R/W。最低位为1表示读,为0表示写。
第三步,从机产生一个ACK应答信号。
//第四步,主机发送寄存器地址,8bit数据。
//第五步,从机产生一个ACK应答信号。
第六步,主机发送一个字节(8bit)数据。
第七步,从机产生一个ACK应答信号。
//第八步,主机发送一个CRC校验码,此CRC校验值为2、4、6步数据产生的校验码。
第九步,从机既可以发送一个应答信号,也可以发送一个无应答信号。
第十步,主机发送一个停止信号。
(三)单片机IIC通讯
1. 软件模拟
CPU与EEPROM通讯为例
相关函数:
void I2C_Delay(void); //延时,以防单片机速度太快
void I2C_Start(void); //起始信号
void I2C_Stop(void); //终止信号
u8 I2C_SendByte(u8 data); //写一个字节
u8 I2C_ReadByte(void); //读一个字节
void EEPROM_Write(u8 address, u8 data); //往EEPROM的一个地址写一个数据
u8 EEPROM_Read (u8 address); //读EEPROM的一个地址的数据
stm32硬件I2C配置流程:
初始化GPIO
void I2c_Init(void); //初始化GPIO
模拟总线SDA, SCL ,GPIO引脚开漏输出
写起始信号函数
void I2C_Start(void); //起始信号
SDA, SCL拉高 , 延时
SDA = 1; SCL = 1; delay();
SDA由高变低 , 延时
SDA = 0; delay();
SCL拉低,延时等待发送/接收
SCL = 0; delay();
写终止信号函数
void I2C_Stop(void); //终止信号
SCL拉低后将SDA拉低 (SCL低电平时SDA可变),延时
SCL = 0; SDA = 0; delay();
SCL拉高 , 延时
SCL = 1; delay();
SDA由低变高 , 延时
SDA = 1; delay();
等待应答信号函数
u8 I2C_WaitToAck(void); //读取器件ACK应答
产生/不产生ACK应答
void I2C_Ack(void);
void I2C_NoAck(void);
写一个字节
读一个字节
//延时
static void I2c_Delay(void)
{/*CPU主频72MHz10: SCL频率205KHz7: SCL频率347KHz,高电平1.5us,低电平2.87us;5: SCL频率421KHz,高电平1.25us,低电平2.375us;*/u8 i;for(i = 0; i < 10; i++);
}
//起始信号
void I2C_Start(void)
{I2C_SDA_High(); //SDA=1I2C_SCL_High(); //SCL=1I2C_Delay();I2C_SDA_Low();I2C_Delay();I2C_SCL_Low();I2C_Delay();
}
//终止信号
void I2C_Stop(void)
{I2C_SDA_Low();I2C_SCL_High();I2C_Delay();I2C_SDA_High();I2C_Delay();
}
//写一个字节
u8 I2C_SendByte(uint8_t Byte)
{uint8_t i;/* 先发送高位字节 */for(i = 0 ; i < 8 ; i++){if(Byte & 0x80){I2C_SDA_High();}else{I2C_SDA_Low();}I2C_Delay();I2C_SCL_High();I2C_Delay();I2C_SCL_Low();I2C_Delay();if(i == 7){I2C_SDA_High(); /* 释放SDA总线 */}Byte <<= 1; /* 左移一位 */I2C_Delay();}
}
//读取一个字节
u8 I2C_ReadByte(void)
{uint8_t i;uint8_t value;/* 先读取最高位即bit7 */value = 0;for(i = 0 ; i < 8 ; i++){value <<= 1;I2C_SCL_High();I2C_Delay();if(I2C_SDA_READ()){value++;}I2C_SCL_Low();I2C_Delay();}return value;
}
//产生一个ACK应答信号
void I2C_Ack(void)
{I2C_SDA_Low();I2C_Delay();I2C_SCL_High(); //cpu产生一个时钟I2C_Delay();I2C_SCL_Low();I2C_Delay();I2C_SDA_High();
}
//产生一个非ACK信号
void I2C_NoAck(void)
{I2C_SDA_High();I2C_Delay();I2C_SCL_High(); //cpu产生一个时钟I2C_Delay();I2C_SCL_Low();I2C_Delay();
}
//产生时钟读取器件ACK应答信号
u8 I2C_WaitToAck(void)
{u8 redata;I2C_SDA_High(); //CPU释放SDA总线I2C_Delay();I2C_SCL_High(); //SCL=1此时器件会返回ACK应答I2C_Delay();if(I2C_SDA_READ()) //CPU读取SDA口线状态{redata = 1;}else{redata = 0;}I2C_SCL_Low();I2C_Delay();return redata;
}
2. 硬件外设(stm32)
(1)主发送器
(2)主接收器
(3)配置流程
与EEPROM通讯为例
- 初始化IIC相关GPIO
- 配置IIC外设的工作模式
- 编写IIC写入EEPROM的函数
- 编写IIC读取EEPROM的函数
- 使用read函数及write函数进行读写校验
- 编写page write及seq read函数并校验(页写)
(4)重要函数
- IIC初始化结构体
typedef struct{uint32_t I2C_ClockSpeed; //设置SCL时钟频率 <40,000 uint32_t I2C_Mode; //工作模式 I2C模式或SMBUS模式uint32_t I2C_DutyCycle; //指定时钟占空比 低/高=2/1或16/9uint32_t I2C_OwnAddress1; //指定自身的I2C设备地址uint32_t I2C_Ack; //使能或关闭响应(一般使能)uint32_t I2C_AcknowledgedAddress; //指定地址长度 7位/10位
} I2C_InitTypeDef;
参考资料:
原理:《IIC总线协议中文版PDF》
设备数量: 百度知道
上拉电阻: CSDN
代码实现:博客园
学习:野火、正点原子、普中科技
修改时间:2022年01月07日
IIC协议详解,附单片机软件模拟源码相关推荐
- Python 进阶之路 (八) 最用心的推导式详解 (附简单实战及源码)
什么是推导式 大家好,今天为大家带来问我最喜欢的Python推导式使用指南,让我们先来看看定义~ 推导式(comprehensions)是Python的一种独有特性,推导式是可以从一个数据序列构建另一 ...
- STM32常用协议之IIC协议详解
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 IIC协议详解 前言 一.IIC协议简介 1.1 简介 1.2 IIC物理层 1.3 协议层 1.3.1 IIC基本读写过程 1.3. ...
- C++文件操作详解,实用文件辅助类源码分享,建议收藏自用!
学习C++的小伙伴,应该会经常遇见对文件进行操作的需求,例如读写文件,作为一个使用频率较高的操作,我们每次重复地编写代码,就是浪费劳动力了,所以作者将自己常用的文件操作封装成了一个类,需要的小伙伴自取 ...
- AIDL在Telephony中的应用 —— ITelephony 详解 (以Android 9.0源码讲解)
转载请注明出处:https://blog.csdn.net/turtlejj/article/details/84861020,谢谢- Telephony模块中大量的使用了AIDL,但网上却很少有文章 ...
- 分布式事务详解,并带有lcn源码解析。
文章目录 1):为什么需要分布式事务? 2):常见的解决方案如下? 2)1):二阶段提交(2PC) 2)2):TXC逆向SQL 2)3):TCC(Try.Confirm.Cancel) 2)4):增量 ...
- ADSP-21489的开发详解:SPIflash的硬件设计及程序烧写详解(含Flash驱动源码)
硬件准备 ADSP-21489EVB:ADI 21489处理器的开发板 AD-HP530ICE:ADI DSP专用仿真器 USBi:ADI SigmaDSP和SHARC DSP的图形化编程调试器 软件 ...
- 【异步编程学习笔记】JDK中的FutureTask和CompletableFuture详解(使用示例、源码)
文章目录 FutureTask概述 使用实例 类图结构 FutureTask的run()方法 FutureTask的局限性 CompletableFuture概述 CompletableFuture代 ...
- 一分钟详解PCL-1.8.1从源码搭建开发环境四(VTK库的编译)
- java tomcat源码_详解Tomcat系列(一)-从源码分析Tomcat的启动
在整个Tomcat系列文章讲解之前, 我想说的是虽然整个Tomcat体系比较复杂, 但是Tomcat中的代码并不难读, 只要认真花点功夫, 一定能啃下来. 由于篇幅的原因, 很难把Tomcat所有的知 ...
- 【Java入门提高篇】Day26 Java容器类详解(八)HashSet源码分析
前面花了好几篇的篇幅把HashMap里里外外说了个遍,大家可能对于源码分析篇已经讳莫如深了.别慌别慌,这一篇来说说集合框架里最偷懒的一个家伙--HashSet,为什么说它是最偷懒的呢,先留个悬念,看完 ...
最新文章
- 机器学习(11)线性回归(1)理论:损失函数(含最小二乘法)、正规方程、梯度下降、回归性能评估(均方差)
- 开源:Angularjs示例--Sonar中项目使用语言分布图(CoffeeScript版)
- java获取pdf的页数、内容和缩略图
- WPF中Auto与*的区别
- HTML5新特性基础学习笔记上
- dpkg(deb)和python(setup.py)安装与卸载
- jsp dbbean mysql_Servlet+JSP+MySQL实现用户管理模块之二、实现用户注册
- java中v怎么用_Vue - 如何使用v-for =“item in 3”创建唯一键
- johnson 算法 贪心
- 写c++好的软件_族谱家谱制作怎么写?专业的家谱族谱编辑制作软件哪个好
- 团队项目计划、人员安排、方法流程
- 一个轻量的Linux运维监控脚本
- 系统的设计一个指标体系
- SUPERMAP大数据平台安装
- 无忧·企业文档2.1.4版本更新清单说明来啦
- 查找代码文件中的非 ASCII 字符
- OPENGL-学习计算机图形学
- 几个常用的免费高清无版权图片网站
- 第四讲:详谈波分设备在双活方案中应用
- Unity3D 动态加载本地/网络GLB模型