IIC
一、构成及工作原理
I2C(Inter-Integrated Circuit)总线用于连接微控制器及其外围设备。是微电子通信控制领域广泛采用的一种总线标准。
它是同步通信的一种特殊形式,具有接口线少,控制方式简单,器件封装形式小,通信速率较高等优点。I2C 总线只有两根双向信号线。一根是数据线 SDA,另一根是时钟线 SCL。
本文采用IO口模拟IIC通信时序,下面从IIC的物理层和通信协议层解析IIC的工作原理
1.物理层
(1)它是一个支持多设备的总线。"总线"指多个设备共用的信号线。在一个 I2C 通讯总线中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。
(2)一个 I2C 总线只使用两条总线线路,一条双向串行数据线(SDA),一条串行时钟线(SCL)。数据线即用来表示数据,时钟线用于数据收发同步。
(3)每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问。
(4)总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。
(5)多个主机同时使用总线时,为了防止数据冲突,会利用仲裁(校对地址)方式决定由哪个设备占用总线
下面我们来了解下 I2C 总线常用的一些术语:
主机:启动数据传送并产生时钟信号的设备;
从机:被主机寻址的器件;
多主机:同时有多于一个主机尝试控制总线但不破坏传输;
主模式:用 I2CNDAT 支持自动字节计数的模式; 位 I2CRM,I2CSTT,I2CSTP控制数据的接收和发送;
从模式:发送和接收操作都是由 I2C 模块自动控制的;
仲裁:是一个在有多个主机同时尝试控制总线但只允许其中一个控制总线并使传输不被破坏的过程;
同步:两个或多个器件同步时钟信号的过程;
发送器:发送数据到总线的器件;
接收器:从总线接收数据的器件
2.协议层
(1)数据有效性规定
I2C 总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化,每次数据传输都以字节为单位,每次传输的字节数不受限制,如图

(2)起始和停止信号
起始和终止信号都是由主机发出的,在起始信号产生后,总线就处于被占用的状态;在终止信号产生后,总线就处于空闲状态
SCL 线为高电平期间,SDA 线由高电平向低电平的变化表示起始信号 ,SDA 线由低电平向高电平的变化表示终止信号 ,如图:
(3)应答响应
每当发送器件传输完一个字节的数据后,后面必须紧跟一个校验位,这个校验位是接收端通过控制 SDA(数据线)来实现的,以提醒发送端数据我这边已经接收完成,数据传送可以继续进行。
响应包括"应答(ACK)"和"非应答(NACK)"两种信号。
作为数据接收端时,当设备(无论主从机)接收到 I2C 传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送"应答(ACK)"信号即特定的低电平脉冲 ,发送方会继续发送下一个数据;若接收端希望结束数据传输,则向对方发送"非应答(NACK)"信号即特定的高电平脉冲,发送方接收到该信号后会产生一个停止信号,结束信号传输。应答响应时序图如下:
数据传输过程:
每一个字节必须保证是 8 位长度。数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(即一帧共有 9 位)。
由于某种原因从机不对主机寻址信号应答时(如从机正在进行实时性的处理工作而无法接收总线上的数据),它必须将数据线置于高电平,而由主机产生一个终止信号以结束总线的数据传送。如果从机对主机进行了应答,但在数据传送一段时间后无法继续接收更多的数据时,从机可以通过对无法接收的第一个数据字节的"非应答" 通知主机,主机则应发出终止信号以结束数据的继续传送。
当主机接收数据时,它收到最后一个数据字节后,必须向从机发出一个结束传送的信号。这个信号是由对从机的"非应答"来实现的。然后,从机释放 SDA线,以允许主机产生终止信号。
这些信号中,起始信号是必需的,结束信号和应答信号都可以不要。
(4)总线的寻址方式
I2C 总线寻址按照从机地址位数可分为两种,一种是 7 位,另一种是 10位。10 位寻址和 7 位寻址兼容,而且可以结合使用。10 位寻址不会影响已有的 7 位寻址,有 7 位和 10 位地址的器件可以连接到相同的 I2C 总线。
以 7 位寻址为例进行介绍:
采用 7 位的寻址字节(寻址字节是起始信号后的第一个字节)的位定义如下:
D7~D1 位组成从机的地址。D0 位是数据传送方向位,为" 0"时表示主机向从机写数据,为"1"时表示主机由从机读数据。
当主机发送了一个地址后,总线上的每个器件都将头 7 位与它自己的地址比较,如果一样,器件会判定它被主机寻址,其他地址不同的器件将被忽略后面的数据信号。至于是从机接收器还是从机发送器,都由 R/W 位决定的。
从机的地址由固定部分和可编程部分组成。在一个系统中可能希望接入多个相同的从机,从机地址中可编程部分决定了可接入总线该类器件的最大数目。如一个从机的 7 位寻址位有 4 位是固定位,3 位是可编程位,这时仅能寻址 8 个同样的器件,即可以有 8 个同样的器件接入到该 I2C 总线系统中。
(5)数据传输
I2C 总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号。
在起始信号后必须传送一个从机的地址(7 位),第 8 位是数据的方向位(R/W),用" 0"表示主机发送(写)数据(W)," 1"表示主机接收数据(R)。每次数据传送总是由主机产生的终止信号结束 。但是,若主机希望继续占用总线进行新的数据传送,则可以不产生终止信号,马上再次发出起始信号对另一从机进行寻址。
在总线的一次数据传送过程中,可以有以下几种组合方式:
注意:有阴影部分表示数据由主机向从机传送,无阴影部分则表示数据由从机向主机传送。用" 0"表示主机发送(写)数据(W)," 1"表示主机接收数据(R)A 表示应答,A 非表示非应答(高电平)。S 表示起始信号,P 表示终止信号。
a、主机向从机发送数据,数据传送方向在整个传送过程中不变

(起始信号--从机地址--写数据)-->(从机应答)-->(主机发送数据)-->(从机应答)-->(主机发送数据)-->(从机不应答)-->(主机发送终止信号)
b、主机在第一个字节后,立即从从机读数据

(起始信号--从机地址--读数据)-->(从机应答)-->(从机发送数据)-->(主机应答)-->(从机发送数据)-->(主机不应答)-->(主机发送终止信号)
c、在传送过程中,当需要改变传送方向时,起始信号和从机地址都被重复产生一次,但两次读/写方向位正好相反

(起始信号--从机地址--写数据)-->(从机应答)-->(主机发送数据)-->(从机不应答)-->(起始信号--从机地址--读数据)-->(从机应答)-->(从机发送数据)-->(主机不应答)-->(主机发送终止信号)
二、实验
1.上电自检是否检测到AT24C02 芯片,将检测结果通过串口发送给上位机
2.通过按键1控制将变量k加一写入AT24C02 芯片特定地址
3.将通过按键2读取特定地址芯片值给变量,并通过数码管显示
4.按键3将k清零
1.AT24C02
AT24C02(EEPROM)芯片,256 X 8 (2K bits),此芯片具有 I2C 通信接口,芯片内保存的数据在掉电情况下都不丢失,所以通常用于存放一些比较重要的数据等。AT24C02 芯片管脚及外观图如下图所示

AT24C02 器件地址为 7 位,高 4 位固定为 1010,低 3 位由 A0/A1/A2 信号线的电平决定。 因为传输地址或数据是以字节为单位传送的,当传送地址时,器件地址占 7 位,还有最后一位(最低位 R/W)用来选择读写方向,它与地址无关。其格式如下:
开发板已经将芯片的 A0/A1/A2 连接到 GND,所以器件地址为1010000,即 0xA0(未计算最低位)。
如果要对芯片进行写操作时,R/W 即为 0,写器件地址即为 0XA0;如果要对芯片进行读操作时,R/W 即为 1,此时读器件地址为 0XA1。开发板上我们也将 WP 引脚直接接在 GND 上,此时芯片允许数据正常读写
2.软件实现
(1)IIC配置
分以下六步:
(1.写IIC)-->(2.读I2C操作)-->(//3.IIC初始化)-->(//4.起始和终止条件)-->(//5.发送接收一个字节)-->(//6.发送应答和接收应答)
c
#include "IIC.h"
//1.写IIC
void MyI2C_W_SDA(Uint16 BitValue)
{
EALLOW;
GpioCtrlRegs.GPBDIR.bit.GPIO32=1; //Output. SDA
if(BitValue==0)
{
GpioDataRegs.GPBCLEAR.bit.GPIO32=1;
}
else
{
GpioDataRegs.GPBSET.bit.GPIO32=1;
}
DELAY_US (10);
EDIS;
}
void MyI2C_W_SCL(Uint16 BitValue)
{
EALLOW;
if(BitValue==0)
{
GpioDataRegs.GPBCLEAR.bit.GPIO33=1;
}
else
{
GpioDataRegs.GPBSET.bit.GPIO33=1;
}
DELAY_US (10);
EDIS;
}
//2.读I2C操作
Uint16 MyI2C_R_SDA(void)
{
EALLOW;
GpioCtrlRegs.GPBDIR.bit.GPIO32=0; //Input, SDA
Uint16 BitValue;
BitValue =GpioDataRegs.GPBDAT.bit.GPIO32;
DELAY_US (10);
EDIS;
return BitValue;
}
//3.IIC初始化
void IICA_Init(void)
{
EALLOW;
SysCtrlRegs.PCLKCR3.bit.GPIOINENCLK = 1;// 开启 GPIO 时钟
GpioCtrlRegs.GPBPUD.bit.GPIO32 = 0; //上拉
GpioCtrlRegs.GPBDIR.bit.GPIO32 = 1; // 输出端口
GpioCtrlRegs.GPBMUX1.bit.GPIO32 = 0; // IO 口
GpioCtrlRegs.GPBQSEL1.bit.GPIO32 = 3; // 不同步
GpioCtrlRegs.GPBPUD.bit.GPIO33 = 0; //上拉
GpioCtrlRegs.GPBDIR.bit.GPIO33 = 1; // 输出端口
GpioCtrlRegs.GPBMUX1.bit.GPIO33 = 0; // IO 口
GpioCtrlRegs.GPBQSEL1.bit.GPIO33 = 3; // 不同步
EDIS;
}
//4.起始和终止条件
void MyI2C_Start(void)
{
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
MyI2C_W_SDA(0);
MyI2C_W_SCL(0);//起始条件
}
void MyI2C_Stop(void)
{
MyI2C_W_SDA(0);
MyI2C_W_SCL(1);
MyI2C_W_SDA(1);
}
//5.发送接收一个字节
//(1)发送一个字节
//SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位
void MyI2C_SendByte(Uint16 Byte)
{
Uint16 i;
for(i=0;i<8;i++)
{
MyI2C_W_SDA(Byte & (0x80 >> i));//高位先行与Byte移位与
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
}
//(2)接收一个字节
//SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位
//!!!注意:主机在接收之前,需要释放SDA
Uint16 MyI2C_ReceiveByte(void)
{
Uint16 i,Byte = 0x00;
MyI2C_W_SDA(1);
for(i=0;i<8;i++)
{
MyI2C_W_SCL(1);
if(MyI2C_R_SDA() == 1)
{
Byte = Byte | (0x80 >> i);//若接收数据为高电平,就将Byte对应位一位置1,其余位不变
}
MyI2C_W_SCL(0);
}
return Byte;
}
//6.发送应答和接收应答
//发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答
void MyI2C_SendAck(Uint16 AckBit)
{
MyI2C_W_SDA(AckBit);
MyI2C_W_SCL(1);
MyI2C_W_SCL(0);
}
//接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,
//数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
Uint16 MyI2C_ReceiveAck(void)
{
Uint16 AckBit;
MyI2C_W_SDA(1);
MyI2C_W_SCL(1);
AckBit = MyI2C_R_SDA();
MyI2C_W_SCL(0);
return AckBit;
}
(2)AT24CXX读/写一个字节及自检函数
写:(开始信号)-->(写的从机地址)-->(从机应答)-->(发送要写入的地址)-->(从机应答)-->(发送要写入的数据)-->(从机应答)-->(终止信号)
读:(开始信号)-->(写的从机地址)-->(从机应答)-->(发送要写入的地址)-->(从机应答)-->[另外开始读的时序]-->(发送要读出的地址(最后一位变成1))-->(从机应答)-->(将读出的数据赋值给局部变量)-->(不给从机应答)-->(终止信号)
c
#include "AT24CXX.h"
#include "IIC.h"
#include "SCI.h"
#include "SMG.h"
#define AT24CXX_ADDRESS 0xA0
//指定地址写一个字节
void AT24CXX_WriteReg(Uint16 RegAddress, Uint16 Data)
{
//1.开始
MyI2C_Start();
//2.从机编号
MyI2C_SendByte(AT24CXX_ADDRESS);
MyI2C_ReceiveAck(); //从机应答
//3.发送基地址
MyI2C_SendByte(RegAddress);
MyI2C_ReceiveAck();
//4.发送数据
MyI2C_SendByte(Data);
MyI2C_ReceiveAck();
//4.发送终止
MyI2C_Stop();
}
//指定地址读一个字节
Uint16 AT24CXX_ReadReg(Uint16 RegAddress)
{
//存取接收到的数据
Uint16 Data;
//开始
MyI2C_Start();
//从机编号
MyI2C_SendByte(AT24CXX_ADDRESS);
MyI2C_ReceiveAck(); //从机应答
//发送基地址
MyI2C_SendByte(RegAddress);
MyI2C_ReceiveAck();
MyI2C_Start();
//0xD0是写地址,0xD1是读地址
MyI2C_SendByte(AT24CXX_ADDRESS | 0x01);//0xD1
MyI2C_ReceiveAck();
Data = MyI2C_ReceiveByte();
//读取一个字节,不给从机应答
MyI2C_SendAck(1);
MyI2C_Stop();
return Data;
}
void AT24CXX_Check(void)
{
Uint16 Password;
AT24CXX_WriteReg(255,0x46);
DELAY_US(10000);
Password=AT24CXX_ReadReg(255);
if(Password==0x46)
{
UARTa_SendString("AT24CXX is ready!\r\n");
AT24CXX_WriteReg(255,0);
DELAY_US(10000);
}
else
{
UARTa_SendString("AT24CXX not find!");
}
}
(3)主函数:
c
#include "DSP2833x_Device.h" // DSP2833x Headerfile Include File
#include "DSP2833x_Examples.h" // DSP2833x Examples Include File
#include "SMG.h"
#include "SCI.h"
#include "IIC.h"
#include "key.h"
#include "AT24CXX.h"
#include "LED.h"
Uint16 k=0;
char key_num = 0;
Uint16 Data=0;
void main()
{
InitSysCtrl();
Led_Init();
SCI_Init();
SMG_Init();
IICA_Init();
Key_Init();
InitPieCtrl();
IER=0x0000;
IFR=0x0000;
InitPieVectTable();
AT24CXX_Check();
while(1)
{
key_num = Key_Scan(0);
switch(key_num)
{
case 1:
{
k=k+1;
AT24CXX_WriteReg(0,k);
}break;
case 2:
{
GpioDataRegs.GPCTOGGLE.bit.GPIO67=1;
Data=AT24CXX_ReadReg(0);
}break;
case 3:
{
k=0;
}break;
}
SMG_DisplayInt(Data);
}
}
最后是附加配置:
串口:
c
#include "SCI.h"
#include "DSP2833x_Sci.h"
void SCI_Init(void)
{
EALLOW;
SysCtrlRegs.PCLKCR0.bit.SCIAENCLK = 1; // SCI-A
EDIS;
InitSciaGpio();
//FIFO配置
SciaRegs.SCIFFTX.all=0xE040;//连续发送、FIFO使能、重新使能发送FIFO、发送FIFO为空
SciaRegs.SCIFFRX.all=0x204F;//FIFO接收寄存器配置
SciaRegs.SCIFFCT.all=0x0;//FIFO控制寄存器
//SCI通信控制寄存器
SciaRegs.SCICCR.all =0x0007; //1停止位、无奇偶校验、无自测、空闲线模式、8位数据
//SCI控制寄存器
SciaRegs.SCICTL1.all =0x0003; //发送中断/缓冲寄存器使能
SciaRegs.SCICTL2.all =0x0003;//接收中断/缓冲寄存器使能
SciaRegs.SCIHBAUD =0x01; // baud set @LSPCLK = 37.5MHz.即9600
SciaRegs.SCILBAUD =0xE7;
SciaRegs.SCICTL1.bit.SWRESET = 1; // 最后使能SCI
}
void UARTa_SendByte(int a)
{
while (SciaRegs.SCIFFTX.bit.TXFFST != 0);
SciaRegs.SCITXBUF=a;
}
void UARTa_SendString(char *msg)
{
int i=0;
while(msg[i] != '\0')
{
UARTa_SendByte(msg[i]);
i++;
}
}
按键:
c
#include "key.h"
#include "led.h"
void Key_Init(void)
{
EALLOW;
SysCtrlRegs.PCLKCR3.bit.GPIOINENCLK = 1;
//行键
//12
GpioCtrlRegs.GPAMUX1.bit.GPIO12=0;
GpioCtrlRegs.GPADIR.bit.GPIO12=0;
GpioCtrlRegs.GPAPUD.bit.GPIO12=0;
//13
GpioCtrlRegs.GPAMUX1.bit.GPIO13=0;
GpioCtrlRegs.GPADIR.bit.GPIO13=0;
GpioCtrlRegs.GPAPUD.bit.GPIO13=0;
//14
GpioCtrlRegs.GPAMUX1.bit.GPIO14=0;
GpioCtrlRegs.GPADIR.bit.GPIO14=0;
GpioCtrlRegs.GPAPUD.bit.GPIO14=0;
//列键
//48
GpioCtrlRegs.GPBMUX2.bit.GPIO48=0;
GpioCtrlRegs.GPBDIR.bit.GPIO48=1;
GpioCtrlRegs.GPBPUD.bit.GPIO48=0;
//49
GpioCtrlRegs.GPBMUX2.bit.GPIO49=0;
GpioCtrlRegs.GPBDIR.bit.GPIO49=1;
GpioCtrlRegs.GPBPUD.bit.GPIO49=0;
//50
GpioCtrlRegs.GPBMUX2.bit.GPIO50=0;
GpioCtrlRegs.GPBDIR.bit.GPIO50=1;
GpioCtrlRegs.GPBPUD.bit.GPIO50=0;
EDIS;
GpioDataRegs.GPBSET.bit.GPIO48=1;
GpioDataRegs.GPBSET.bit.GPIO49=1;
GpioDataRegs.GPBSET.bit.GPIO50=1;
}
char Key_Scan(char mode)
{
static char Key11 = 1;
static char Key12 = 1;
static char Key13 = 1;
//1.第一列扫描:拉低第一列
GpioDataRegs.GPBCLEAR.bit.GPIO48 = 1;
GpioDataRegs.GPBSET.bit.GPIO49 = 1;
GpioDataRegs.GPBSET.bit.GPIO50 = 1;
if(Key11 == 1 && (Key_H1 == 0 ||Key_H2 == 0||Key_H3 == 0))
{
DELAY_US(50000);
Key11 = 0;
if(Key_H1 == 0)
{
return 1;
}
else if(Key_H2 == 0)
{
return 4;
}
else if(Key_H3 == 0)
{
return 7;
}
}
else if(Key_H1 == 1||Key_H2 == 1||Key_H3 == 1)
{
Key11 = 1;//松开按键返回Key11初始转状态
}
if(mode == 1)
{
Key11 = 1;
}
//2.第二列扫描:拉低第二列
GpioDataRegs.GPBSET.bit.GPIO48 = 1;
GpioDataRegs.GPBCLEAR.bit.GPIO49 = 1;
GpioDataRegs.GPBSET.bit.GPIO50 = 1;
if(Key12 == 1 && (Key_H1 == 0 ||Key_H2 == 0||Key_H3 == 0))
{
DELAY_US(50000);
Key12 = 0;
if(Key_H1 == 0)
{
return 2;
}
else if(Key_H2 == 0)
{
return 5;
}
else if(Key_H3 == 0)
{
return 8;
}
}
else if(Key_H1 == 1||Key_H2 == 1||Key_H3 == 1)
{
Key12 = 1;//松开按键返回Key12初始转状态
}
if(mode == 1)
{
Key12 = 1;
}
//3.第三列扫描:拉低第三列
GpioDataRegs.GPBSET.bit.GPIO48 = 1;
GpioDataRegs.GPBSET.bit.GPIO49 = 1;
GpioDataRegs.GPBCLEAR.bit.GPIO50 = 1;
if(Key13 == 1 && (Key_H1 == 0 ||Key_H2 == 0||Key_H3 == 0))
{
DELAY_US(50000);
Key13 = 0;
if(Key_H1 == 0)
{
return 3;
}
else if(Key_H2 == 0)
{
return 6;
}
else if(Key_H3 == 0)
{
return 9;
}
}
else if(Key_H1 == 1||Key_H2 == 1||Key_H3 == 1)
{
Key13 = 1;//松开按键返回Key13初始转状态
}
if(mode == 1)
{
Key13 = 1;
}
return 0;
}
interrupt void EXTI1_IRQn(void)
{
GpioDataRegs.GPCTOGGLE.bit.GPIO68=1;
PieCtrlRegs.PIEACK.bit.ACK1 = 1;
}
void EXTI_Init(void)
{
//1.时钟初始化
EALLOW;
SysCtrlRegs.PCLKCR3.bit.GPIOINENCLK = 1;
EDIS;
//2.GPIO按键配置
EALLOW;
//12
GpioCtrlRegs.GPAMUX1.bit.GPIO12=0;
GpioCtrlRegs.GPADIR.bit.GPIO12=0;//配置为输入
GpioCtrlRegs.GPAPUD.bit.GPIO12=0;
GpioCtrlRegs.GPAQSEL1.bit.GPIO12 = 0;//配置为同步模式 即无滤波
//48
GpioCtrlRegs.GPBMUX2.bit.GPIO48=0;
GpioCtrlRegs.GPBDIR.bit.GPIO48=1;
GpioCtrlRegs.GPBPUD.bit.GPIO48=0;
GpioDataRegs.GPBCLEAR.bit.GPIO48 = 1;
EDIS;
//3.设置IO与中断线关系:XINT1->GPIO12
EALLOW;
GpioIntRegs.GPIOXINT1SEL.bit.GPIOSEL = 12;
EDIS;
//4.指定中断向量表中断服务函数地址
EALLOW;
PieVectTable.XINT1 = &EXTI1_IRQn;
EDIS;
//5.使能外设对应的PIE中断
PieCtrlRegs.PIEIER1.bit.INTx4 = 1;
//6.设置下降沿触发并使能XINT1
XIntruptRegs.XINT1CR.bit.POLARITY = 1;
XIntruptRegs.XINT1CR.bit.ENABLE = 1;
//7.使能CPU级全局中断
IER|=M_INT1;
EINT;
ERTM;
}