I2C介绍
I2C(Inter-Integrated Circuit)总线是由 PHILIPS 公司开发的两线式串行总线,用于连接微控制器及其外围设备。是微电子通信控制领域广泛采用的一种总线标准。它是同步通信的一种特殊形式,具有接口线少,控制方式简单,器件封装形式小,通信速率较高等优点。I2C 总线只有两根双向信号线。一根是数据线 SDA,另一根是时钟线 SCL。由于其管脚少,硬件实现简单,可扩展性强等特点,因此被广泛的使用在各大集成芯片内。
I2C 内部结构
常用连接方式及结构:
相关术语及解释
主机:启动数据传送并产生时钟信号的设备;
从机:被主机寻址的器件;
多主机:同时有多于一个主机尝试控制总线但不破坏传输;
主模式:用 I2CNDAT 支持自动字节计数的模式; 位 I2CRM,I2CSTT,I2CSTP
控制数据的接收和发送;
从模式:发送和接收操作都是由 I2C 模块自动控制的;
仲裁:是一个在有多个主机同时尝试控制总线但只允许其中一个控制总线并
使传输不被破坏的过程;
同步:两个或多个器件同步时钟信号的过程;
发送器:发送数据到总线的器件;
接收器:从总线接收数据的器件;结构特点
(1)它是一个支持多设备的总线(如结构图)。“总线”指多个设备共用的信号线。在一个 I2C 通讯总线中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。
(2)一个 I2C 总线只使用两条总线线路(如结构图),一条双向串行数据线(SDA),一条串行时钟线(SCL)。数据线即用来表示数据,时钟线用于数据收发同步。
(3)每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问。
(4)总线通过上拉电阻接到电源(如结构图)。当 I2C 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。
(5)多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线。
(6)具有三种传输模式:标准模式传输速率为 100kbit/s,快速模式为400kbit/s,高速模式下可达 3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式。
I2C 协议层(I2C实现原理)
I2C 的协议定义了通信的起始和停止信号、数据有效性、响应、仲裁、时钟
同步和地址广播等环节。
数据有效性
I2C 总线进行数据传送时,时钟信号(SCL)为高电平期间,数据线上(SDA)的数据必须保
持稳定,只有在时钟线上(SCL)的信号为低电平期间,数据线上的高电平或低电平状态
才允许变化。
起始信号和终止信号
SCL 线为高电平期间,SDA 线由高电平向低电平的变化表示起始信号;SCL线为高电平期间,SDA 线由低电平向高电平的变化表示终止信号。起始和终止信号都是由主机发出的,在起始信号产生后,总线就处于被占用
的状态;在终止信号产生后,总线就处于空闲状态。
应答响应
1.每当发送器件传输完一个字节的数据后,后面必须紧跟一个校验位,这个校验位是接收端通过控制 SDA(数据线)来实现的,以提醒发送端数据我这边已经接收完成,数据传送可以继续进行。这个校验位其实就是数据或地址传输过程中的响应。
2.响应包括应答和非应答
应答(ACK):接收到数据或地址后希望对方继续发送数据,需要向对方发送应答信号(特定的低平脉冲)
非应答(NACK ):希望对方结束数据传输,发送非应答信号(特定的高平脉冲)。
3.字节的结构:每一个字节必须保证是 8 位长度(如结构图·)。数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(即一帧共有 9 位)。
数据传输原则
(1)由于某种原因从机不对主机寻址信号应答时(如从机正在进行实时性的处理工作而无法接收总线上的数据),它必须将数据线置于高电平,而由主机产生一个终止信号以结束总线的数据传送。
(2)如果从机对主机进行了应答,但在数据传送一段时间后无法继续接收更多的数据时,从机可以通过对无法接收的第一个数据字节的“非应答” 通知主机,主机则应发出终止信号以结束数据的继续传送。
(3)当主机接收数据时,它收到最后一个数据字节后,必须向从机发出一个结束
传送的信号。这个信号是由对从机的“非应答”来实现的。然后,从机释放 SDA
线,以允许主机产生终止信号。
总线的寻址方式
I2C 总线寻址按照从机地址位数可分为两种,一种是 7 位,另一种是 10位。
10 位寻址和 7 位寻址兼容,而且可以结合使用。这里讲解一下7位的。地址结构图
如图所示,该地址共有8位。高位(D7到D1位)为从机的地址,低位(D0)为数据传送方向位,为“ 0”时表示主机向从机写数据,为“1”时表示主机由从机读数据。
寻址原则
当主机发送了一个地址后,总线上的每个器件都将头 7 位与它自己的地址比较,如果一样,器件会判定它被主机寻址,其他地址不同的器件将被忽略后面的数据信号。至于是从机接收器还是从机发送器,都由 R/W 位决定的。从机的地址由固定部分和可编程部分组成。在一个系统中可能希望接入多个相同的从机,从机地址中可编程部分决定了可接入总线该类器件的最大数目。如一个从机的 7 位寻址位有 4 位是固定位,3 位是可编程位,这时仅能寻址 8 个同样的器件,即可以有 8 个同样的器件接入到该 I2C 总线系统中。
数据传输
/* 注:图中有阴影部分表示数据由主机向从机传送,无阴影部分则表示数据由从机向主机传送。横杠A 表示应答,A 非表示非应答(高电平)。S 表示起始信号,P 表示终止信号 * /
在总线的一次数据传送过程中,可以有以下几种组合方式:
1.主机向从机发送数据,数据传送方向在整个传送过程中不变
过程描述:主机发送起始信号->寻址->找到响应地址后选择写(0)或者读(1),图中为写过程(主机向从机发送数据)->从机做出应答(希望主机继续传输)或非应答(希望主机中止数据传输),图中为应答->数据继续传输->从机应答->数据再继续传输->从机非应答,请求中止->主机发送中止信号
2.主机在第一个字节后,立即从从机读数据
过程描述:主机发送起始信号->寻址->方向为1,即主机由从机读数据->从机发送数据->主机应答,希望希望从机继续发送数据->从机发送数据再继续传输->主机非应答,从机中止发送数据->主机发送中止信号
3.在传送过程中,当需要改变传送方向时,起始信号和从机地址都被重复产生一次,但两次读/写方向位正好相反。
过程描述:略,注意一点就可以了,就是在转换方向前,起始信号和从机地址都要重复产生一次。
软件模拟I2C
引入原因:
硬件 IIC 设计的比较复杂,而且稳定性不怎么好,程序移植比较麻烦,而用软件模拟 IIC,最大的好处就是移植方便,同一个代码兼容所有单片机,任何一个单片机只要有 IO 口(不需要特定 IO),都可以很快的移植过去。AT24C02 芯片介绍
AT24C01/02/04/08/16...是一个 1K/2K/4K/8K/16K 位串行 CMOS,内部含有128/256/512/1024/2048 个 8 位字节,AT24C01 有一个 8 字节页写缓冲器,AT24C02/04/08/16 有一个 16 字节页写缓冲器。该器件通过 I2C 总线接口进行操作,它有一个专门的写保护功能。此开发板使用的是 AT24C02(EEPROM)
芯片,此芯片具有 I2C 通信接口,芯片内保存的数据在掉电情况下都不丢失,所以通常用于存放一些比较重要的数据等。芯片资料
管脚及外观图
各管脚功能说明
AT24C02 芯片地址格式
AT24C02 器件地址为 7 位,高 4 位固定为 1010,低 3 位由 A0/A1/A2 信号线的电平决定。 因为传输地址或数据是以字节为单位传送的,当传送地址时,器件地址占 7 位,还有最后一位(最低位 R/W)用来选择读写方向。
A0/A1/A2 默认连接到 GND,所以器件地址为1010000,即 0x50(未计算最低位)。如果要对芯片进行写操作时,R/W 即为 0,写器件地址即为 0XA0;如果要对芯片进行读操作时,R/W 即为 1,此时读器件地址为 0XA1。开发板上我们也将 WP 引脚直接接在 GND 上,此时芯片允许数据正常读写。
典型信号模拟
为了保证数据传送的可靠性,标准的I2C总线的数 据传送有严格的时序要求。I2C总线的起始信号、终 止信号、发送“0”及发送“1”的模拟时序。
如图所示:
如图,起始信号S SDA为低电平,SCL为高电平至少>4微秒,应答需要SDA为低电平,SCL为高电平>4微秒,其他看图可知。
实验内容
0实验功能
开发板上集成了 1 个 EEPROM 模块,可实现IIC 通信。实现的功能是:系统运行时,数码管后 4 位显示 0,按 K1 将数据写入到 EEPROM 内保存,按 K2 读取 EEPROM 内保存的数据,按 K3 显示数据加 1,按 K4 显示数据清零,最大能写入的数据是 255。硬件设计
(1)独立按键(K1-K4)
(2)动态数码管
(3)EEPROM 模块电路(AT24C02)原理图
在介绍 II C 总线的时候我们说过,为了让 IIC 总线默认为高电平,通常会在IIC 总线上接上拉电阻,在图中并没有看到 SCL 和 SDA 管脚有上拉电阻,这是因为我们单片机 IO 都外接了 10K 上拉电阻,当单片机 IO 口连接到芯片的 SCL 和SDA 脚时即相当于它们外接上拉电阻,所以此处可以省去。由于该模块电路是集成的,所以 24C02 芯片的 SCL 和 SDA 管脚可以通过使用单片机的 P2^0~P2^1 管脚连接即可。
程序实现
主要分为三部分
第一部分为头文件,主要为函数和端口的定义
代码:
#ifndef __I2C_H_
#define __I2C_H_
#include sbit SCL=P2^1; sbit SDA=P2^0; void I2cStart(); void I2cStop(); unsigned char I2cSendByte(unsigned char dat); unsigned char I2cReadByte(); void At24c02Write(unsigned char addr,unsigned char dat); unsigned char At24c02Read(unsigned char addr); #endif 第二部分为I2c软件模拟实现 代码: #include"i2c.h" /* * 函数名 : Delay10us() * 函数功能 : 延时10us */ void Delay10us() //大约延时10us { unsigned char a,b; for(b=1;b>0;b--) for(a=2;a>0;a--); } /* * 函数名 : I2cStart() * 函数功能 : 起始信号:在SCL时钟信号在高电平期间SDA信号产生一个下降沿 * 备注 : 起始之后SDA和SCL都为0 */ void I2cStart() { SDA=1; Delay10us(); SCL=1; Delay10us();//SCL,SDA为高电平,建立时间是SDA保持时间>4.7us SDA=0; Delay10us();//SCL为高电平,SDA为低电平,保持时间是>4us SCL=0; Delay10us();//起始之后SCL和SDA都为0,表示总线繁忙 } /* * 函数名 : I2cStop() * 函数功能 : 终止信号:在SCL时钟信号高电平期间SDA信号产生一个上升沿 * 输入 : 无 * 输出 : 无 * 备注 : 结束之后保持SDA和SCL都为1;表示总线空闲 */ void I2cStop() { SDA=0; Delay10us(); SCL=1; Delay10us();//SCL为高电平,SDA由低电平变为高电平建立时间大于4.7us SDA=1; Delay10us(); //都为高电平,表示总线空闲 } /* * 函数名 : I2cSendByte(unsigned char dat) * 函数功能 : 通过I2C发送一个字节。在SCL时钟信号高电平期间,保持发送信号SDA保持稳定 * 输入 : num * 输出 : 0或1。发送成功返回1,发送失败返回0 * 备注 : 发送完一个字节SCL=0,SDA=1 */ unsigned char I2cSendByte(unsigned char dat) //实现数据传输 { unsigned char a=0,b=0;//最大255,一个机器周期为1us,最大延时255us。 for(a=0;a<8;a++) //要发送8位,从最高位开始,所以循环8次 { SDA=dat>>7; //起始信号之后SCL=0,所以可以直接改变SDA信号 dat=dat<<1; //左移移位下一次循环执行时,次高位就变为最高位 Delay10us(); SCL=1; //根据数据有效性原则,SCL为高电平时,SDA保持稳定,先保持稳定一段时间 Delay10us(); //建立时间>4.7us SCL=0; //稳定一段时间后,SCL为低电平,则SDA可以发生变化,高低电平的变化使得数据发送 Delay10us();//时间大于4us,继续延时,为下一字节传输准备 } SDA=1; //数据传输完成后,SDASDL都置为高电平,表示 总线空闲 Delay10us(); SCL=1; while(SDA) //等待应答,也就是等待从设备把SDA拉低 { b++; if(b>200)//如果超过2000us没有应答发送失败,或者为非应答,表示接收结束 { SCL=0; Delay10us(); return 0;//返回0代表非应答或者数据传输失败 } } SCL=0; //如果应答,则SDA为0(低电平),直接跳过循环 Delay10us(); return 1; //返回1代表数据传输成功或应答 } /* * 函数名 : I2cReadByte() * 函数功能 : 使用I2c读取一个字节 * 输入 : 无 * 输出 : dat * 备注 : 接收完一个字节SCL=0,SDA=1. */ unsigned char I2cReadByte() { unsigned char a=0,dat=0; SDA=1; //拉高表示SDA总线空闲可以等待数据 Delay10us(); for(a=0;a<8;a++)//接收8个字节 { SCL=1; //SCL置为高电平,表示数据保持稳定以保持数据的读取 Delay10us(); dat<<=1; // 数据左移1位 dat|=SDA; //位运算种的或运算,即两个数据只要有一个数据为高电平,则或运算后为高电平 Delay10us(); SCL=0; //SCL置0,数据发生翻转 Delay10us(); } return dat; } /* * 函数名 : void At24c02Write(unsigned char addr,unsigned char dat) * 函数功能 : 往24c02的一个地址写入一个数据 */ void At24c02Write(unsigned char addr,unsigned char dat) //对At24c02进行写,所以需要定义写的地址和数据 { I2cStart(); I2cSendByte(0xa0);//发送写器件地址 ,地址末位为0表示,器件地址固定,即1010,转为十六进制为a I2cSendByte(addr);//发送要写入内存地址(首地址),调用发送数据的函数 I2cSendByte(dat); //发送数据 I2cStop(); } /* * 函数名 : unsigned char At24c02Read(unsigned char addr) * 函数功能 : 读取24c02的一个地址的一个数据 */ unsigned char At24c02Read(unsigned char addr) { unsigned char num; I2cStart(); //起始信号 I2cSendByte(0xa0); //发送写器件地址 (寻址) I2cSendByte(addr); //发送要读取的地址 I2cStart(); I2cSendByte(0xa1); //发送读器件地址 num=I2cReadByte(); //读取数据 I2cStop(); return num; } 第三部分为利用I2C软件实现独立按键控制数码管 /* 实验现象:下载程序后数码管后4位显示0,按K1保存显示的数据,按K2读取上次保存的数据, 按K3显示数据加一,按K4显示数据清零。最大能写入的数据是255 */ #include "reg52.h" //此文件中定义了单片机的一些特殊功能寄存器 #include "i2c.h" typedef unsigned int u16; //对数据类型进行声明定义 typedef unsigned char u8; sbit LSA=P2^2; //138译码器端口 sbit LSB=P2^3; sbit LSC=P2^4; sbit k1=P3^1; sbit k2=P3^0; sbit k3=P3^2; sbit k4=P3^3; //定义按键端口 char num=0; u8 disp[4]; u8 code smgduan[10]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f}; //0~9的数据段 /* * 函 数 名 : delay * 函数功能 : 延时函数,i=1时,大约延时10us */ void delay(u16 i) { while(i--); } /* * 函数名 :Keypros() * 函数功能 :按键处理函数 * 输入 : 无 * 输出 : 无 */ void Keypros() //独立按键实现功能:k1按下写入存即保存数据,k2按下读取保存的数据,K3按下数据加1,k4按下数据清0 { if(k1==0) { delay(1000); //消抖处理 if(k1==0) { At24c02Write(1,num); //在地址1内写入数据num } while(!k1); } if(k2==0) { delay(1000); //消抖处理 if(k2==0) { num=At24c02Read(1); //读取EEPROM地址1内的数据保存在num中 } while(!k2); } if(k3==0) { delay(100); //消抖处理 if(k3==0) { num++; //数据加1 if(num>255)num=0; //数据不能大于255 } while(!k3); } if(k4==0) { delay(1000); //消抖处理 if(k4==0) { num=0; //数据清零 } while(!k4); } } /* * 函数名 :datapros() * 函数功能 :数据处理函数 * 输入 : 无 * 输出 : 无 */ void datapros() //将num数据处理为段选数据 { disp[0]=smgduan[num/1000];//千位 disp[1]=smgduan[num%1000/100];//百位 disp[2]=smgduan[num%1000%100/10];//个位 disp[3]=smgduan[num%1000%100%10]; } /* * 函数名 :DigDisplay() * 函数功能 :数码管显示函数 * 输入 : 无 * 输出 : 无 */ void DigDisplay() //动态显示数码管原理 { u8 i; for(i=0;i<4;i++) { switch(i) //位选,选择点亮的数码管, { case(0): LSA=1;LSB=1;LSC=0; break;//显示第0位 case(1): LSA=0;LSB=1;LSC=0; break;//显示第1位 case(2): LSA=1;LSB=0;LSC=0; break;//显示第2位 case(3): LSA=0;LSB=0;LSC=0; break;//显示第3位 } P0=disp[i];//发送数据 delay(100); //间隔一段时间扫描
这一步部分一定要对照时序图严格按照时序图的要求进行编写,为了保证都能够正常运行,延时函数为10us.
起始信号和终止信号都需要延时函数进行延时。
相关文章