I2C(Inter-Integrated Circuit)总线是一种由 Philips 公司开发的两线式串行总线,用于连接微控制器及其外围设备。I2C 总线只有两根连接接口(串行数据 SDA 和串行时钟 SCL
线) ,具有使用方便,连接简单,占用空间小,并且支持多主控等优点。因此,在嵌入式系统中,I2C 总线被广泛应用于许多重要外扩设备的连接.
总线上发送数据的器件被称作发送器,接收数据的器件被称作接收器。控制信息交换的器件被称作主器件(CPU) ,受主器件控制的器件则被称作从器件(AT24Cxx 芯片在总
线中作为从器件工作) 。主器件产生串行时钟 SCL,控制总线的访问状态、产生 S 和 P 信号.只有当总线处于空闲状态时才可以启动数据传输,每次数据传输都始于 S 信号,
结束于P 信号,二者之间传输的数据字节没有限制,由总线上的主器件决定,数据以字节(8bit)为单位传输,第 9 位时由接收器产生应答.
I2C 总线的信号类型
② 结束信号(P): SCL 为高电平时, SDA 由低电平向高电平跳变,结束传送数据。
③ 响应信号(ACK):接收设备在接收到 8 位的数据后,在第 9 个时钟周期,拉低 SDA 电平。(注意:谁接收数据,就由谁发出 ACK 信号)
SDA 上传送的数据必须在 SCL 为高电平期间保持稳定,SDA 上的数据只能在SCL 为低电平期间变化
S5PV210 I2C
相关寄存器
多主设备 I2C 总线控制寄存器--I2CCON
多主设备 I2C 总线控制状态寄存器 -- I2CSTAT
多主设备 I2C 总线接收/发送数据移位寄存器 -- I2CDS
多主设备 I2C 总线地址寄存器 -- I2CADD.
当 I2C 总线为空闲时,SDA 和 SCL 总线都应被置为高电平。SDA 从高到低的变化能够初始化一个起始条件。当 SCL 保持稳定在高电平,SDA 从低到高的变化可以初始化一个停止条件,起始和停止条件都是由主设备生成的。 在第一个字节中的一个 7 位地址的值可以决定一个由主设备选择的从设备,其地址在起始条件初始化后被放到总线上。第 8 位决定的是传输方向(读或写),放到 SDA 线上的每个数据字节总共应该是 8 位。在总线传输期间,该字节可以被无限制地发送或接收。数据发送总是先对 MSB,每个字节应该紧跟一个应答位(ACK bit)。
数据传输格式:
开始信号 | 地址(7 位) | 读/写控制信号 | 应答 | 数据(8 位) | 应答 | 停止信号
S5PV210 I2C 读写操作
在接收模式下,如果数据收到后,I2C 总线接口会等待直到 I2C 总线数据移位寄存器被读出。在新的数据被读出寄存器之前,SCL 线将保持低电平,然后再数据被读出后再释
放。S5PV210 应该保持中断来识别当前数据接收是否完成。在CPU 收到中断请求后,它应该从 I2CDS 寄存器读取数据。
如果一个从接收器不能应答从属设备地址的确认, 它应该保持 SDA 线的电平为高。在这种情况下,主设备应该生成一个停止条件并终止数据的传输。如果主设备的接收器也参
与了被终止传输, 它应该通过在从设备收到最后自己后取消 ACK 信号的生成, 给从设备传输操作的最后发信号。 从设备发送器应该释放 SDA 线以允许主设备产生一个停止条件。
#include "i2c.h"
#include "lib.h"
#include "led.h"
#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)
{
/* 选择引脚功能:GPD1_0:IIC0_SDA, GPD1_1:IIC0_SCL */
GPD1CON |= 0x22; // GPD1CON[7:4] = 0b0010,GPD1CON[3:0] = 0b0010
GPD1PUD |= 0x5; // GPD1PUD[3:0] = 0b0101 禁止上拉
/* 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); // 0xaf
IICSTAT = 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; // 索引值初始为0
g_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);
}
/* 真正的I2C中断服务函数 */
void do_i2c_irq(void)
{
unsigned int i; //用于简单延时
switch (g_t210_I2C.Mode)
{
case WRDATA: //写中断
{
if((g_t210_I2C.DataCount--) == 0)
{
// 下面两行用来恢复I2C操作,发出P信号
IICSTAT = 0xd0; // 发出P信号,但由于IICCON[4]仍为1,P信号实际还没有发出
IICCON = 0xaf; //当清除IICCON[4]后,P信号才真正发出信号
delay(10000); // 等待一段时间以便P信号已经发出
break;
}
IICDS = g_t210_I2C.pData[g_t210_I2C.Pt++];
// 将数据写入IICDS后,需要一段时间才能出现在SDA线上
for (i = 0; i < 10; i++);
IICCON = 0xaf; // 恢复I2C传输,IICCON[4] = 0
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传输,开始接收数据,接收到数据时不发出ACK
else
IICCON = 0xaf; // 恢复I2C传输,开始接收数据,接收到数据时发出ACK
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传输,接收到下一数据时无ACK
else
IICCON = 0xaf; // 恢复I2C传输,接收到下一数据时发出ACK
}
break;
}
default:
break;
}
// 清中断向量
VIC0ADDRESS = 0x0;
VIC1ADDRESS = 0x0;
VIC2ADDRESS = 0x0;
VIC3ADDRESS = 0x0;
IICCON &= ~(1<<4); //第4位用于中断的标志,当接收或发送数据后一定要对该位,进行清零,以清除中断标志
}
/* 需要根据AT24Cxx芯片手册的读字节协议 */
unsigned char at24cxx_read(unsigned char address)
{
unsigned char val;
i2c_write(0xA0, &address, 1); //AT24C02的设备地址为0xa0
i2c_read(0xA0, (unsigned char *)&val, 1);
return val;
}
/* 需要根据AT24Cxx芯片手册的写字节协议 */
void at24cxx_write(unsigned char address, unsigned char data)
{
unsigned char val[2];
val[0] = address;
val[1] = data;
i2c_write(0xA0, val, 2);
}