1 概述:

IIC是用两条双向的线,一条SDA(serial data line),一条SCL(serial clock).

SCL:上升沿将数据输入到每个EEPROM器件中,下降沿驱动EEPROM器件输出数据(边沿触发)

SDA:双向数据线,为OD门,与其它任意数量的OD与OC门成“线与”关系

2 输出级

每一个IIC总线器件内部的SDA、SCL引脚电路结构都是一样的,引脚的输出驱动与输入缓存连在一起。其中输出为漏极开路的场效应管,输入缓存为一只高输入阻抗的同向器,这种电路具有两个特点

1)由于SDA、SCL为漏极开路结构,因此它们必须接有上拉电阻,阻值大小为1K8(1800o)、4K7(4700o)、10K,但1K8时性能最好;当总线空闲时,两根线均为高电平。连到总线上的任一器件输出的低电平,都将使总线的信号变低,即各器件的SDA及SCL都是线与关系。

2)引脚在输出信号的同时还将引脚上的电平进行检测,检测是否与刚才输出一致,为“时钟同步”和“总线仲裁”提供了硬件条件

3 主设备与从设备

系统中的所有外围器件都具有一个7位的“从器件专用地址码”,其中高4位为器件类型,由生产厂家制定,低3位为器件引脚定义地址,由使用者定义。主控器件通过地址码建立多机通信的机制,因此IIC总线省去了外围器件的片选线,这样无论总线上挂接多少个器件,其系统仍然为简约的二线结构。终端挂载在总线上,有主端和从端之分,主端必须是带有CPU逻辑模块,在同一总线上同一时刻使能有一个主端,可以有多个从端,从端的数量受地址空间和总线的最大电容400pF的限制。

主端主要用来驱动SCL line;

从设备对主设备产生响应;

二者都可以传输数据,但是从设备不能发起传输,且传输是受到主设备控制的。

4 速率

普通模式:100kHz

快速模式:400kHz

高速模式:3.4MHz

5协议

5.1 空闲状态

IIC总线的SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个期间的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。

5.2 起始信号与停止信号

起始信号:当SCL为高期间,SDA由高到低的跳变;启动信号是一种电平跳变时序信号,而不是一个电平信号

停止信号:当SCL为高期间,SDA由低到高的跳变;停止信号也是一种电平跳变时序信号,而不是一个电平信号

5.3 ACK

发送器每发送一个字节,就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。对于反馈有效应答位ACK的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平。如果接收器是主控器,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主控接收器发送一个停止信号P。

如下图逻辑分析仪的采样结果:释放总线后,如果没有应答信号,SDA应该一直维持在高电平,但是如图中蓝色虚线部分所示,它被拉低为低电平,证明收到了应答信号。

总结:1)接收器在SCL的上升沿到来之前的低电平期间拉低SDA;

2)应答信号一直保持到SCL的下降沿结束;

5.4 数据的有效性

IIC总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。

个人理解:虽然只要求在高电平期间保持稳定,但是要有一个提前量,也就是数据在SCL的上升沿到来之前就准备好,因为数据在SCL的上升沿打入到器件(EEPROM)中的。

5.5 数据的传送:

在IIC总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据。数据位的传输是边沿触发.

6 工作过程

总线上的所有通信都是由主控器引发的。在一次通信中,主控器与被控器总是在扮演着两种不同的角色。

6.1 主设备向从设备发送数据

主设备发送起始位,这会通知总线上的所有设备传输开始了,接下来主机发送设备地址,与这一地址匹配的slave将继续这一传输过程,而其他slave将会忽略接下来的传输并等待下一次传输开始。主设备寻址到从设备后,发送它所要读取或写入的从设备的内部寄存器地址;之后,发送数据。数据发送完毕后,发送停止位。

写入过程如下:

1 发送起始位

2 发送从设备的地址和读/写选择位;释放总线,等到EEPROM拉低总线进行应答;如果EEPROM接收成功,则进行应答;若没有握手成功或者发送的数据错误时EEPROM不产生应答,此时要求重发或者终止

3 发送想要写入的内部寄存器地址,EEPROM对其发出应答

4 发送数据

5 发送停止位

6 EEPROM收到停止信号后,进入到一个内部的写入周期,大概需要10ms,此期间任何操作都不会被EEPROM响应:(因此以这种方式的两次写入之间要插入一个延时,否则会导致失败)

详细

说明:主控器通过发送地址码与对应的被控器建立了通信关系,而挂接在总线上的其他被控器虽然同时也收到了地址码,但因为与其自身的地址不相符合,因此提前退出与主控器的通信

6.2 主控器读取数据的过程

在从slave读取数据前,必须先要告诉它那个内部寄存器是你想要读取的,因此必须先对其进行写入(dummy write)

读取过程

1 发送起始位

2 发送slave地址 + write bit set

3 发送内部寄存器地址

4 重新发送起始位,即restart

5 重新发送slave地址 + write bit set

6 读取数据

主机接收器在接收到最后一个字节后,也不会发出ACK信号。于是,从机发送器释放SDA线,以允许主机发出P信号结束传输。

7 发送停止位

详细

6.3 页写

6.3.1 页写数据过程

不同的器件页写的字节数也不同,2402是按8字节/页写,2404-08-16是按16字节/页写。而2432-2464则是按32字节页写;

页写初始化和字节写相同,只是主器件不会在第一个数据后发送停止条件,而是EEPROM的ACK以后,接着发送(7个)、(15个)、(31个)数据。EEPROM在收到数据后每个都应答0,最后仍需要主器件发送停止条件。终止页写条件。

在接收或发送每个数据后,字地址的(低3位)、(低4位)、(低5位)内部自动加1,高位地址不变,维持当前页。当内部产生的字地址达到该页边界地址时,随后的数据将写入该页的页首,这是会将先前的字节覆盖掉,要注意。

6.3.2 读页数据过程

主器件接收到一个数据后,应答ACK。只要EEPROM接收到ACK,将自动增加字地址并继续随时钟发送后面的数据。若达到存储器的地址末尾,地址自动回转到0.仍可继续顺序读取数据。主器件不应答0,而发送停止条件,即可结束顺序读操作。

7 实例程序

 void SCL_Init()
{
GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
} void SDA_Out_Init()
{
GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); GPIO_InitStructure.GPIO_Pin=GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
} void SDA_In_Init()
{
GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); GPIO_InitStructure.GPIO_Pin=GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_Init(GPIOB,&GPIO_InitStructure);
} void IIC_Init() //初始化总线
{
SCL_OUT=;
SDA_OUT=;
delay_us();
} void IIC_Start() //开始发送 SCL为高,SDA由高变低
{
SDA_Out_Init();
SCL_OUT=;
SDA_OUT=; delay_us();
SDA_OUT=;
delay_us(); SCL_OUT=;
} void IIC_Stop() //停止 SCL为高,SDA由低变高
{
SDA_Out_Init();
SCL_OUT=;
SDA_OUT=;
SCL_OUT=;
delay_us(); SDA_OUT=;
delay_us();
} u8 IIC_Wait_Ack() //等待应答
{
u8 time=; SDA_OUT=;
delay_us();
SDA_In_Init();
SCL_OUT=;
delay_us();
while(SDA_IN) //#define SDA_IN PBin(11)
{
time++;
if(time>) //如果超过250还无应答,IIC停止
{
IIC_Stop();
return ;
}
}
SCL_OUT=;
return ;
} void IIC_Ack() //应答信号
{
SCL_OUT=;
SDA_Out_Init();
SDA_OUT=;
delay_us(); SCL_OUT=;
delay_us();
SCL_OUT=;
} void IIC_NAck() //非应答
{
SCL_OUT=;
SDA_Out_Init();
SDA_OUT=;
delay_us(); SCL_OUT=;
delay_us();
SCL_OUT=;
} void IIC_Write_Byte(u8 data) //写一个字节数据
{
int i,receive=;
SDA_Out_Init(); receive=data;
SCL_OUT=;
for(i=;i<;i++) //相当于SCL低电平期间准备好要写入的数据,SCL跳变为高电平进行发送
{
if(receive&0x80)
SDA_OUT=;
else
SDA_OUT=;
receive=receive<<;
delay_us(); SCL_OUT=;
delay_us();
SCL_OUT=;
delay_us();
}
} u8 IIC_Read_Byte(u8 ack) //读一个字节
{
int i,txdata=;
SDA_In_Init(); SCL_OUT=; //
delay_us();
for(i=;i<;i++)
{
SCL_OUT=; //高电平保持数据的稳定;来一个上升沿,数据就将要读取的一位放在数据线SDA上,所以接下来就得左移,才能读取下一位数据
delay_us();
txdata=txdata<<;
if(SDA_IN)
txdata++;
delay_us(); SCL_OUT=;
delay_us();
}
if(!ack)
IIC_NAck();
else
IIC_Ack();
return txdata;
} void write_addr(u8 address,u8 data)
{
IIC_Start();
IIC_Write_Byte(0xA0); //发送从器件芯片的地址,由于IIC可以挂接好多芯片,所以需要先找到该芯片
IIC_Ack(); IIC_Write_Byte(address);//发送芯片的字节地址,需要确定将数据发送到芯片的某个位置,芯片大小有2K
IIC_Ack(); IIC_Write_Byte(data); //将数据写入到芯片内
IIC_Ack(); IIC_Stop();
delay_ms(); //数据写完之后需等10ms时间才能再次发送起始信号,这个时间叫写周期
} u8 read_addr(u8 address)
{
u8 temp; IIC_Start();
IIC_Write_Byte(0xA0); //器件地址
IIC_Ack();
IIC_Write_Byte(address);//想要读取的器件中的地址0到255
IIC_Ack(); IIC_Start();
IIC_Write_Byte(0xA1); //1表示读取
IIC_Ack(); temp=IIC_Read_Byte();
IIC_Ack(); IIC_Stop(); return temp;
} void write_len(u16 address,u32 data,u8 len)
{
u8 i;
for(i=;i<len;i++)
write_addr(address+i,(data<<(*i)&0xff));
} u32 read_len(u16 address,u8 len)
{
u8 j;
u32 temp;
for(j=;j<len;j++)
{
temp=temp<<;
temp=temp+read_addr(address+len-j-);
}
return temp;
} /**************************************
将数据写入指定的地址里,num要写入数据个数
*************************************/
void write(u8 addr,u8 *buffer,u8 num)
{
while(num--)
{
write_addr(addr,*buffer); //含有了stop
addr++;
buffer++;
}
} /**************************************
在指定的地址读取数据
*******************************************/
u8 read(u8 addr,u8 *buffer,u8 num)
{
while(num)
{
*buffer++=read_addr(addr++);
num--;
}
return *buffer;
} void write_twobyte(u8 address)
{
u8 i;
u8 tx[]={,,};
IIC_Start();
IIC_Write_Byte(0xA0); //发送的是从器件芯片地址,由于IIC可以挂接好多芯片,所以需要先找到该芯片
IIC_Ack(); IIC_Write_Byte(address);//发送芯片的字节地址,需要确定将数据发送到芯片的某个位置,芯片大小2K
IIC_Ack(); for(i=;i<;i++)
{
IIC_Write_Byte(tx[i]); //将数据写入到芯片内
IIC_Ack();
} IIC_Stop();
delay_ms();
}

IIC学习的更多相关文章

  1. Linux3.5—IIC学习分析

    I2C控制器的设备对象内核已经实现并关联到platform总线. I2C控制器的驱动对象内核已经实现. 看mach-tiny4412.h /plat-samsung/目录下 /drivers/i2c/ ...

  2. 基于51单片机IIC通信的PCF8591学习笔记

    引言 PCF8591 是单电源,低功耗8 位CMOS 数据采集器件,具有4 个模拟输入.一个输出和一个串行I2C 总线接口.3 个地址引脚A0.A1 和A2 用于编程硬件地址,允许将最多8 个器件连接 ...

  3. 基于51单片机IIC通信的AT24C02学习笔记

    引言 最近在学习几种串行通信协议,感觉收获很多,这篇文章是学习IIC总线协议的第一篇文章,以后还会再写一篇关于PCF8591 IIC通信的ADDA转换芯片的文章. 关于IIC总线 IIC 即Inter ...

  4. IIC协议学习笔记

    "移植"的重要性:并非所有的电路都得自己设计,到了一定阶段,"移植"也是一种学习能力.--CrazyBingo 转眼间期末又到了,最近开始了所谓的期末总预习,比 ...

  5. IIC驱动学习笔记,简单的TSC2007的IIC驱动编写,测试

    IIC驱动学习笔记,简单的TSC2007的IIC驱动编写,测试 目的不是为了编写TSC2007驱动,是为了学习IIC驱动的编写,读一下TSC2007的ADC数据进行练习,, Linux主机驱动和外设驱 ...

  6. PX01关于手机屏IIC触摸调试学习笔记

    相关文件下载: 上位机工具:http://www.xk-image.com/download/blog/0002_TP调试/LcdTools20210605.rar 调试案例:http://www.x ...

  7. 8051学习笔记——IIC与EEPROM实验

    main.c #include <reg51.h> #include "iic.h" #define AT24C02 0xa0 //AT24C02 地址 sbit LS ...

  8. 51单片机学习笔记(郭天祥版)(9)——IIC、EEPROM

    IIC是两根线,单总线,只有一根数据线,发送数据和读取收据都是一根线,像我们之前学的AD.DA都是许多线,许多线的话,这样做系统可以少浪费资源,少浪费控制IO口的资源,这种并行的处理速度快.所以线越多 ...

  9. IIC通讯协议(非原创,转载他人,用于学习)

    I2C协议:1.空闲状态 2.开始信号 3.停止信号 4.应答信号 5.数据的有效性 6.数据传输 IIC详解 1.I2C总线具有两根双向信号线,一根是数据线SDA,另一根是时钟线SCL 2.IIC总 ...

随机推荐

  1. 线程间操作无效: 从不是创建控件“button1”的线程访问它。

    .net2后是不能跨线程访问控件的.,窗体上的控件是当前线程创建的,当用户异步执行一个方法:在该方法中给窗体上的控件赋值,记住:当执行一个异步委托的时候,其实 就是开了一个线程去执行那个方法,这样就会 ...

  2. 462. 最少移动次数使数组元素相等 II

    给定一个非空整数数组,找到使所有数组元素相等所需的最小移动数,其中每次移动可将选定的一个元素加1或减1. 您可以假设数组的长度最多为10000. 例如: 输入: [1,2,3] 输出: 2 说明: 只 ...

  3. [5]传奇3服务器源码分析一GameServer

    1. 2. 留存 服务端下载地址: 点击这里

  4. 学习笔记<4>初步控件布局

    一.控件布局基本概念 指控制控件在Activity当中的位置.大小.颜色以及其他控件样式属性 二.控件布局两种方法 1.使用布局文件完成控件布局(eclipse可视化拖拽控件实现) 2.在JAVA代码 ...

  5. 136. Single Number(位运算)

    Given a non-empty array of integers, every element appears twice except for one. Find that single on ...

  6. steam pipeGUI

    SteamPipe GUI 工具 如果您运行的是 Windows,并且更喜欢使用 GUI 工具来帮助生成配置文件并上传您的生成版本,您可以使用 Steamworks SDK 的工具文件夹中的 Stea ...

  7. word2vector 资料

    http://blog.csdn.net/garfielder007/article/details/51345201 https://cs224d.stanford.edu/lecture_note ...

  8. RSA解密解密

    #!/usr/bin/env python # -*- coding:utf-8 -*- import rsa import base64 # ######### 1. 生成公钥私钥 ######## ...

  9. python二 总结--函数-- 装饰器

    装饰器是什么? 有什么用? 为什么要用? 真的有用吗? 1.装饰器: 装饰器: 定义:本质是函数,(装饰其他函数)就是为其他函数添加附加功能. 原则:1.不能修改被装饰的函数的源代码          ...

  10. python mmap对象

    ----使用内存映射的原因 为了随机访问文件的内容,使用mmap将文件映射到内存中是一个高效和优雅的方法.例如,无需打开一个文件并执行大量的seek(),read(),write()调用,只需要简单的 ...