目录
一、关于I2C
I2C通讯协议 (Inter Integrated Circuit) 是由 Phiilps 公司开发的一种简单、双向二线制同步串行总线,只需要两根线即可在连接于总线上的器件之间传送信息。由于它引脚少,硬件实现简单,可扩展性强,不需要 USART 、 CAN 等通讯协议的外部收发设备,现在被广泛地使用在系统内多个集成电路 ( IC)间的通讯。I2C是半双工!
上拉电阻可直接使用经典值4.7kΩ。使用I2C设备的灌电流不得超过3mA。逻辑0电压不得高于0.4V。
I2C相关术语的定义如下。
STM32的 I2C 架构剖析如下图。
- 通讯引脚
- 时钟控制逻辑:在快速模式下可选择 SCL 时钟的占空比,可选 T low /T high =2 或
T low /T high =16/9 模式(随意选即可)。I2C 外设输入时钟源为 PCLK1。 - 数据控制逻辑
- 整体控制逻辑
为了避免总线信号的混乱,要求各设备连接到总线的输出端时必须是漏极开路(OD)输出或集电极开路(OC)输出。(见下)
二、硬件结构
I2C总线器件内部的SDA、SCL引脚电路结构都是一样的,引脚的输出驱动与输入缓冲连在一起。
其中输出为漏极开路的场效应管、输入缓冲为一只高输入阻抗的同相器。
SCLKN1 OUT:串行时钟输出
SCLKN IN:串行时钟输入
DATAN1 OUT:串行数据输出
DATAN1 IN:串行数据输出
(1)由于 SDA、SCL 为漏极开路
结构,借助于外部的上拉电阻实现了信号的“线与
”逻辑;
(2)引脚在输出信号的同时还将引脚上的电平进行检测,检测是否与刚才输出一致。为 “时钟同步”和“总线仲裁”提供硬件基础。
三、I2C协议
3.1、I2C的三种状态
- 空闲状态:SCL与SDA同时为高电平。
- 工作开始状态:SDA由高电平到低电平,SCL由高电平到低电平。(开始信号)
- 工作停止状态:SDA由低电平到高电平,SCL由高电平到低电平。(结束信号)
3.2、数据有效性
- SCL为高电平时,SDA表示的数据有效。
- SCL为低电平时,SDA表示的数据无效,此时正在“DATA CHANGE”。
3.3、应答信号
含“应答(ACK)”与“非应答(NACK)”。
在第9个时钟时,数据发送端会释放 SDA 的控制权,由数据接收端控制 SDA ,若 SDA 为高电平,表示非应答信号(NACK),低电平表示应答信号(ACK)。
3.4、总线时序
每一帧数据由9bit组成,
- 如果是发送数据,则包含8bit数据+1bit ACK;
- 如果是设备地址数据,则8bit包含7bit设备地址 1bit方向。
3.5、读写时序
- 主机发送开始信号给从机,唤醒所有主从机
- 主机发送
7位的地址位+1位的读写位
给从机 - 等待地址匹配从机的应答信号
- 主机发送或接收数据到从机
- 在传输完每个数据帧后,接收设备将另一个ACK位返回给发送方,以确认已成功接收到该帧
- 结束之后主机发出停止信号
读写位:
- 0表示主设备向从设备(write)写数据
- 1表示主设备向从设备(read)读数据
3.5.1、读
3.5.2、写
3.6、死锁
I2C总线写操作过程中,主机在产生启动信号后控制SCL产生8个时钟脉冲,然后拉低SCL信号为低电平,这时从机输出应答信号,将SDA信号拉为低电平。
如果这时主机异常复位,SCL就会被释放为高电平。此时,如果从机没有复位,就会继续I2C的应答,将SDA一直拉为低电平,直到SCL变为低电平,才会结束应答信号。对于主机来说,复位后检测SCL和SDA信号,如果发现SDA信号为低电平,则会认为I2C总线被占用,会一直等待SCL和SDA信号变为高电平。这样,主机等待从机释放SDA信号,而同时从机又在等待主机将SCL信号拉低以释放应答信号,两者相互等待,I2C总线进人一种死锁状态。同样,当I2C进行读操作时,从机应答后输出数据,如果在这个时刻主机异常复位而此时从机输出的数据位正好为0,也会导致I2C总线进入死锁状态。
解决方法:
(1)从机电源设计为可控,总线死锁时将从机复位。
(2)从机程序添加监测功能,总线长时间被拉低则释放对总线控制。
(3)主机中增加I2C总线恢复程序。每次主机复位后,如果检测到SDA被拉低,则控制SCL产生<=9个时钟脉冲(针对8位数据的情况),每发送一个时钟脉冲就检测SDA是否被释放,如果SDA已经被释放就再模拟产生一个停止信号,这样从机就可以完成被挂起的读写操作,从死锁状态中恢复过来。(有局限性——软件并不能够直接控制SCL信号模拟产生需要时钟脉冲)
3.7、仲裁
当SCL 线是高
电平时,仲裁在SDA 线发生;在其他主机发送低电平时,发送高电平的主机将断开它的数据输出级,因为总线上的电平与它自己的电平不相同。
四、软件I2C&硬件I2C
- 软件IIC:指用单片机的两个I/O端口模拟出来的IIC,用软件控制管脚状态以模拟I2C通信波形,软件模拟寄存器的工作方式。
- 硬件IIC:一块硬件电路,硬件I2C对应芯片上的I2C外设,有相应I2C驱动电路,其所使用的I2C管脚也是专用的,直接调用内部寄存器进行配置。
相对来说,硬件I2C效率更高,软件I2C接口更灵活。
五、AT24C02
基本描述:2k bit串行EEPROM存储器,内含256字节。里面有一个8字节页写缓冲器。
从器件地址如下图。(前四位固定为1010)
- 芯片寻址:
A2~A0为由管脚电平(为000),最后一位表示读写操作。所以AT24Cxx的读地址为0xA1,写地址为0xA0。
也即
写24C02的时候,从器件地址为10100000(0xA0);
读24C02的时候,从器件地址为10100001(0xA1)。 - 片内地址寻址:
芯片寻址可对内部256B中的任一个进行读/写操作,其寻址范围为00~FF,共256个寻址单位。
对应修改A2A1A0即可。
六、代码编写
6.1、I2C初始化
void IIC_init()//IIC初始化
{
SCL=1; //首先把时钟线拉高
delay_us(4);//延时函数
SDA=1; //在SCL为高的情况下把SDA拉高
delay_us(4); //延时函数
}
6.2、开始信号
//产生IIC起始信号
//首先拉高SDA,再拉高SCL,空闲状态
//最后拉低SDA
void IIC_Start()//启动信号
{
SDA=1; //确保SDA线为高电平
delay_us(5);
SCL=1; //确保SCL高电平
delay_us(5);
//以下这一步产生了开始信号的脉冲
SDA=0; //在SCL为高时拉低SDA线,即为起始信号
delay_us(5);
}
6.3、ACK/NACK信号
//CPU产生ACK信号
void I2C_Ack(void)
{
I2C_SDA_Low();
I2C_Delay();
I2C_SCL_High();
I2C_Delay();
I2C_SCL_Low();
I2C_Delay();
I2C_SDA_High();
}
//CPU产生NACK信号
void I2C_NoAck(void)
{
I2C_SDA_High();
I2C_Delay();
I2C_SCL_High();
I2C_Delay();
I2C_SCL_Low();
I2C_Delay();
}
//CPU产生一个时钟,并读取器件的ACK应答信号
uint8_t I2C_WaitToAck(void)
{
uint8_t redata;
I2C_SDA_High();
I2C_Delay();
I2C_SCL_High();
I2C_Delay();
if(I2C_SDA_READ())
{
redata = 1;
}
else
{
redata = 0;
}
I2C_SCL_Low();
I2C_Delay();
return redata;
}
6.4、发送1byte数据
//IIC发送一个字节
//返回: 从机有无应答
//0,无应答
//1,有应答
//只有当SCL被拉低后,SDA才能被改变
//总结:在SCL为低电平期间,发送数据,发送8次数据,数据为1,SDA被拉高,数据为0,SDA被拉低。
//传输期间保持传输稳定,所以数据线仅可以在时钟SCL为低电平时改变。
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT();
IIC_SCL=0;//拉低时钟开始数据传输
for(t=0;t<8;t++)
{
//IIC_SDA=(txd&0x80)>>7; //获取最高位
//获取数据的最高位,然后左移7位
//如果某位为1,则SDA为1,否则相反
if((txd&0x80)>>7)
IIC_SDA=1;
else
IIC_SDA=0;
txd<<=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
}
6.5、读取1字节
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN(); //SDA设置为输入
for(i=0;i<8;i++ )
{
IIC_SCL=0;
delay_us(2);
IIC_SCL=1;
receive<<=1;
if(READ_SDA)
receive++;
delay_us(1);
}
if (!ack)
IIC_NAck(); //发送NACK,表示不再接收数据
else
IIC_Ack(); //发送ACK
return receive;
}
6.5、停止信号
//产生IIC停止信号
//1.先拉低SDA,再拉低SCL
//2.拉高SCL
//3.拉高SDA
//4.停止接收数据
void IIC_Stop(void)
{
IIC_SCL=0;
IIC_SDA=0; //STOP:当SCL高时,数据由低变高
delay_us(4);
IIC_SCL=1;
IIC_SDA=1; //发送I2C总线结束信号
delay_us(4);
}