1. 原理
(1)通信特征
串行、同步、非差分、低速率、半双工
(2) 同步
同步通信就是通信双方工作在同一个时钟下,一般是通信的A方通过一根CLK信号线传输A自己的时钟给B,B工作在A传输的时钟下。同步通信的显著特征就是:通信线中有CLK
(3)低速率
I2C一般是用在同一个板子上的2个IC之间的通信,而且用来传输的数据量不大,所以本身通信速率很低(一般几百KHz,不同的I2C芯片的通信速率可能不同。在编程的时候要看自己所使用的设备允许的I2C通信最高速率,不能超过这个速率
(4)非差分
因为I2C通信速率不高,而且通信双方距离很近,所以使用电平信号通信,其实还有一根GND线,用来构成电平信号,而不是差分信号。
半双工
因为I2C通信只有一个SDA数据线用来传输通信数据,所以一个通信周期只能是由一方发送给另一方,而不能双方同时发送,所以是半双工通信模式。
2. 主控图
(1) SDA:串行数据线
(2) SCL:串行时钟线
(3) 用于芯片芯片之间通信
3. 电路
4. 时序图
(1) 起始终止信号标志
开始通信:
SDA线由高位拉到低位,此时I2C bus接口还处于从节点状态,SCL一直为高位。
设置I2C bus接口为master mode,此时产生SCL时钟信号,SDA可以传输数据、命令。
结束通信:
I2C bus 接口为master mode,是SCL一直产生高电平;
SDA由低突然拉高,即结束通信;
(2)数据读取
(3)数据帧格式
放置在SDA行上的每个字节长度应为8位。对于每次传输没有传输字节的限制。
开始条件之后的第一个字节应该有地址字段。如果i2c总线在主模式下工作,主传送地址字段。每个字节后跟一个确认(ACK)位。串行数据和地址的数据首先传输最高有效位MSB首先被发送。
主设备向从设备中发送(写)数据
主设备向从设备中接收(读)数据
5. 寄存器介绍(芯片手册p895 )
(1) 控制寄存器I2CCON
(2) 状态寄存器I2CSTAT
(3) 地址寄存器I2CADD
(4) 线控寄存器I2CLC
(5) 数据移位寄存器I2CDS
6. SDA仲裁
(1) 仲裁发生在SDA线上,以防止两个主总线之间的争用。如果一个具有高级SDA的主服务器检测到其他具有低级SDA活动的主服务器,它不会启动数据传输,因为总线上的当前级别与它自己的级别不相对应。
(2)SDA线的仲裁也是建立在总线具有线“与”逻辑功能的原理上的。节点在发送1位数据后,比较总线上所呈现的数据与自己发送的是否一致。是,继续发送;否则,退出竞争。SDA线的仲裁可以保证I2C总线系统在多个主节点同时企图控制总线时通信正常进行并且数据不丢失。总线系统通过仲裁只允许一个主节点可以继续占据总线
(3假设主节点产生低电平作为第一个地址位,而另一个主节点保持高电平。在这种情况下,两个主节点都检测总线上的低电平,在功率上,因为低电平优于高电平。如果发生这种情况,低电平(作为地址的第1位)将获得领导权,而高电平(作为地址的第1位)生成将退出领导权。如果两个主人产生低电平作为第一个位地址,仲裁会从第二个地址位再次仲裁。此仲裁继续到最后一个地址位的末尾)
7. I2C时钟
(1)I2C时钟源头来源于PCLK(PCLK_PSYS,等于65MHz),经过了2级分频后得到的。
(2)第一级分频是I2CCON的bit6,可以得到一个中间时钟I2CCLK(等于PCLK/16或者PCLK/512)。
(3)第二级分频是得到最终I2C控制器工作的时钟,以I2CCLK这个中间时钟为来源,分频系数为[1,16]。
(4)最终要得到时钟是2级分频后的时钟,比如一个可用的设置是:65000KHz/512/4=31KHz
8.I2C控制器流程图
(1)主发送模式流程
(2) 主接收模式流程
(3) 从设备发送模式
(4) 从设备接收模式
9. 读写过程分析
(1)写的过程:
开始的8个CLK中进行写的操作,SDA由主机驱动,第9个CLK,SDA由从机驱动,发出应答信号;
(2) 读的过程:
开始的8个CLK中进行读的操作,SDA由从机驱动,第9个CLK,SDA由主机驱动,发出应答信号;
10.部分代码分析
#include "lib.h"
/*s5pv210 I2C相关寄存器配置*/
/* GPIO */
#define GPD1CON (*(volatile unsigned int *)0xE02000C0)
#define GPD1PUD (*(volatile unsigned int *)0xE02000C8)/* IIC */
#define IICCON (*(volatile unsigned int *)0xE1800000)
#define IICSTAT (*(volatile unsigned int *)0xE1800004)
#define IICDS (*(volatile unsigned int *)0xE180000C)#define VIC0ADDRESS (*(volatile unsigned int *)0xF2000F00)
#define VIC1ADDRESS (*(volatile unsigned int *)0xF2100F00)
#define VIC2ADDRESS (*(volatile unsigned int *)0xF2200F00)
#define VIC3ADDRESS (*(volatile unsigned int *)0xF2300F00)void Delay(int time);#define WRDATA (1)
#define RDDATA (2)typedef struct tI2C {unsigned char *pData; /* 数据缓冲区 */volatile int DataCount; /* 等待传输的数据长度 */volatile int Status; /* 状态 */volatile int Mode; /* 模式:读/写 */volatile int Pt; /* pData中待传输数据的位置 */
}t210_I2C, *pt210_I2C;static t210_I2C g_t210_I2C;void i2c_init(void)
{/* 选择引脚功能:GPE15:IICSDA, GPE14:IICSCL */GPD1CON |= 0x22;GPD1PUD |= 0x5;/* bit[7] = 1, 使能ACK* bit[6] = 0, IICCLK = PCLK/16* bit[5] = 1, 使能中断* bit[3:0] = 0xf, Tx clock = IICCLK/16* PCLK = 66.7MHz, IICCLK = 4.1MHz*/IICCON = (1<<7) | (0<<6) | (1<<5) | (0xf); // 0xafIICSTAT = 0x10; // I2C串行输出使能(Rx/Tx)
}/** 主机发送* slvAddr : 从机地址,buf : 数据存放的缓冲区,len : 数据长度 */
void i2c_write(unsigned int slvAddr, unsigned char *buf, int len)
{g_t210_I2C.Mode = WRDATA; // 写操作g_t210_I2C.Pt = 0; // 索引值初始为0g_t210_I2C.pData = buf; // 保存缓冲区地址g_t210_I2C.DataCount = len; // 传输长度IICDS = slvAddr;IICSTAT = 0xf0; // 主机发送,启动/* 等待直至数据传输完毕 */ while (g_t210_I2C.DataCount != -1);
}/** 主机接收* slvAddr : 从机地址,buf : 数据存放的缓冲区,len : 数据长度 */
void i2c_read(unsigned int slvAddr, unsigned char *buf, int len)
{g_t210_I2C.Mode = RDDATA; // 读操作g_t210_I2C.Pt = -1; // 索引值初始化为-1,表示第1个中断时不接收数据(地址中断)g_t210_I2C.pData = buf; // 保存缓冲区地址g_t210_I2C.DataCount = len; // 传输长度IICDS = slvAddr;IICSTAT = 0xb0; // 主机接收,启动/* 等待直至数据传输完毕 */ while (g_t210_I2C.DataCount != 0);
}//----------IIC中断服务函数----------
void do_irq(void)
{unsigned int iicSt,i;wy_printf("do_irq \n");iicSt = IICSTAT; if(iicSt & 0x8){ wy_printf("Bus arbitration failed\n"); }switch (g_t210_I2C.Mode){ //模式为写时中断处理case WRDATA:{if((g_t210_I2C.DataCount--) == 0){// 下面两行用来恢复I2C操作,发出P信号IICSTAT = 0xd0;//主传输模式IICCON = 0xaf;Delay(10000); // 等待一段时间以便P信号已经发出break; }IICDS = g_t210_I2C.pData[g_t210_I2C.Pt++];// 将数据写入IICDS后,需要一段时间才能出现在SDA线上for (i = 0; i < 10; i++); IICCON = 0xaf; // 恢复I2C传输break;}//模式为读时中断处理case RDDATA:{if (g_t210_I2C.Pt == -1){// 这次中断是发送I2C设备地址后发生的,没有数据// 只接收一个数据时,不要发出ACK信号g_t210_I2C.Pt = 0;if(g_t210_I2C.DataCount == 1)IICCON = 0x2f; // 恢复I2C传输,开始接收数据,接收到数据时不发出ACKelse IICCON = 0xaf; // 恢复I2C传输,开始接收数据break;}g_t210_I2C.pData[g_t210_I2C.Pt++] = IICDS;g_t210_I2C.DataCount--;if (g_t210_I2C.DataCount == 0){// 下面两行恢复I2C操作,发出P信号IICSTAT = 0x90;IICCON = 0xaf;Delay(10000); // 等待一段时间以便P信号已经发出break; } else{ // 接收最后一个数据时,不要发出ACK信号if(g_t210_I2C.DataCount == 1)IICCON = 0x2f; // 恢复I2C传输,接收到下一数据时无ACKelse IICCON = 0xaf; // 恢复I2C传输,接收到下一数据时发出ACK}break;}default:break; }// 中断结束一定要清中断向量VIC0ADDRESS = 0x0;VIC1ADDRESS = 0x0;VIC2ADDRESS = 0x0;VIC3ADDRESS = 0x0;
} unsigned char s5pv210_read(unsigned char address)
{unsigned char val;wy_printf("at24cxx_read address = %d\r\n", address);i2c_write(0xA0, &address, 1);wy_printf("at24cxx_read send address ok\r\n");i2c_read(0xA0, (unsigned char *)&val, 1);wy_printf("at24cxx_read get data ok\r\n");return val;
}void s5pv210_write(unsigned char address, unsigned char data)
{unsigned char val[2];val[0] = address;val[1] = data;i2c_write(0xA0, val, 2);
}