I2C总线只有两根双向信号线。一根是数据线SDA,另一根是时钟线SCL。
1、总线空闲:I2C总线空闲的时候,两条线SDA和SCL都是高电平。
2、开始信号 S 信号:SCL 为高电平时,SDA由高电平向低电平跳变,开始传送数据。
3、结束信号 P 信号:SCL 为高电平时,SDA由低电平向高电平跳变,结束传送数据。
4、响应信号 ACK:接收器在接收到8位数据后,在第9个时钟周期,拉低 SDA 电平。
(注意:在第9个时钟周期,发送器保持SDA为高,如果有ACK,那么第9个时钟周期SDA为 低电平,如果没有为高电平,发送器根据电平高低分辨是否有ACK信号。)
5、正常数据传输时:SDA 在 SCL 为低电平时改变,在 SCL 为高电平时保持稳定。
如果使能了IIC中断,发送完8bit数据后,主机自动进入中断处理函数,此时SCL被发送器拉低,让接收器被迫等待。恢复传输只需要清除中断挂起。
接收器件收到一个完整的数据字节后,有可能需要完成一些其它工作,如处理内部中断服务等,可能无法立刻接收下一个字节,这时接收器件可以将SCL线拉成低电平,从而使主机处于等待状态。直到接收器件准备好接收下一个字节时,再释放SCL线使之为高电平,从而使数据传送可以继续进行。
发送模式中当发送了数据时,在 IIC 总线数据移位(IICDS)寄存器收到新数据之前 IIC 总线接口将会一直等待。在新数据写入到寄存器之前,SCL 线将会保持为低,然后在其写入后释放。S3C2440A 应该等待中断来确定当前数据发送的完成。在 CPU 收到中断请求后,需要再次写一个新数据到 IICDS 寄存器中。
接收模式中当收到了数据时,在读取 IICDS 寄存器前 IIC 接口将会一直等待。在新数据读出前,SCL 线将会保持为低,然后在其读取后释放。S3C2440A 应该等待中断来确定当前数据接收的完成。在 CPU 收到中断请求后,需要从 IICDS 寄存器中读取数据。
特别注意:
IIC 总线中断发生在:
1、当完成了 1 字节发送或接收操作;
2、当广播呼叫或从地址匹配发生时;
3、如果总线仲裁失败。
s3c2440的IIC总线控制器:
s3c2440提供4个寄存器来完成所有IIC的操作:
IICDS:
IICADD:
IICCON:ACK信号使能,发送模式时钟源选择,发送、接收中断使能,中断标记,发送模式时钟分频系数。
IICSTAT:工作模式,忙状态,发出S信号,P信号等,串行输出使能(IICDS输出使能),最后一位的状态(是否接收到ACK)。当发出S信号后,IICDS寄存器中的数据被自动发送。
S3C2440A 的 IIC 总线接口有 4 种工作模式:
– 主机发送模式
– 主机接收模式
– 从机发送模式
– 从机接收模式
这里只要介绍主机发送模式和主机接收模式流程:
主机发送模式流程图:
主机接收模式流程图:
结合at24cxx的读写来学习吧:
at24cxx.c:
实现两个函数at24cxx_read()和at24cxx_write(),这两个函数直接提供给用户操作at24cxx。
#include <string.h>
#include "i2c.h"
unsigned char at24cxx_read(unsigned char address)
{
unsigned char val;
printf("at24cxx_read address = %d\r\n", address);
i2c_write(0xA0, &address, 1);
printf("at24cxx_read send address ok\r\n");
i2c_read(0xA0, (unsigned char *)&val, 1);
printf("at24cxx_read get data ok\r\n");
return val;
}
void at24cxx_write(unsigned char address, unsigned char data)
{
unsigned char val[2];
val[0] = address;
val[1] = data;
i2c_write(0xA0, val, 2);
}
i2c.c:
注意:控制器读/写一字节后,会自动更新下一个读/写的地址,所以发送一次读/写地址,可连续读写多个数据。
同时注意,EEPROM 接口, Rx 模式中为了产生停止条件在读取最后数据之前会禁止产生应答。
在次强调一下:IIC 总线中断发生的时机:1、当完成了 1 字节发送或接收操作;2、当广播呼叫或从地址匹配发生时;3、如果总线仲裁失败。
/*
* FILE: i2c.c
* 用于主机发送/接收
*/
#include <stdio.h>
#include "s3c24xx.h"
#include "i2c.h"
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中待传输数据的位置 */
}tS3C24xx_I2C, *ptS3C24xx_I2C;
static tS3C24xx_I2C g_tS3C24xx_I2C;
/*
* I2C初始化
*/
void i2c_init(void)
{
GPEUP |= 0xc000; // 禁止内部上拉
GPECON |= 0xa0000000; // 选择引脚功能:GPE15:IICSDA, GPE14:IICSCL
INTMSK &= ~(BIT_IIC);
/* bit[7] = 1, 使能ACK
* bit[6] = 0, IICCLK = PCLK/16
* bit[5] = 1, 使能中断
* bit[3:0] = 0xf, Tx clock = IICCLK/16
* PCLK = 50MHz, IICCLK = 3.125MHz, Tx Clock = 0.195MHz
*/
IICCON = (1<<7) | (0<<6) | (1<<5) | (0xf); // 0xaf
IICADD = 0x10; // S3C24xx slave address = [7:1]
IICSTAT = 0x10; // I2C串行输出使能(Rx/Tx)
}
/*
* 主机发送
* slvAddr : 从机地址,buf : 数据存放的缓冲区,len : 数据长度
*/
void i2c_write(unsigned int slvAddr, unsigned char *buf, int len)
{
g_tS3C24xx_I2C.Mode = WRDATA; // 写操作
g_tS3C24xx_I2C.Pt = 0; // 索引值初始为0
g_tS3C24xx_I2C.pData = buf; // 保存缓冲区地址
g_tS3C24xx_I2C.DataCount = len; // 传输长度
IICDS = slvAddr;
IICSTAT = 0xf0; // 主机发送模式,发送从机地址,使能串行传输,发出S信号启动
/* 等待直至数据传输完毕 */
while (g_tS3C24xx_I2C.DataCount != -1);
}
/*
* 主机接收
* slvAddr : 从机地址,buf : 数据存放的缓冲区,len : 数据长度
*/
void i2c_read(unsigned int slvAddr, unsigned char *buf, int len)
{
g_tS3C24xx_I2C.Mode = RDDATA; // 读操作
g_tS3C24xx_I2C.Pt = -1; // 索引值初始化为-1,表示第1个中断时不接收数据(地址中断)
g_tS3C24xx_I2C.pData = buf; // 保存缓冲区地址
g_tS3C24xx_I2C.DataCount = len; // 传输长度
IICDS = slvAddr;
IICSTAT = 0xb0; // 主机接收,启动
/* 等待直至数据传输完毕 */
while (g_tS3C24xx_I2C.DataCount != 0);
}
/*
* I2C中断服务程序
* 根据剩余的数据长度选择继续传输或者结束
*/
void I2CIntHandle(void)
{
unsigned int iicSt,i;
// 清中断
SRCPND = BIT_IIC;
INTPND = BIT_IIC;
iicSt = IICSTAT;
if(iicSt & 0x8){ printf("Bus arbitration failed\n\r"); }
switch (g_tS3C24xx_I2C.Mode)
{
case WRDATA:
{
if((g_tS3C24xx_I2C.DataCount--) == 0)
{
// 下面两行用来恢复I2C操作,发出P信号
IICSTAT = 0xd0;
IICCON = 0xaf;
Delay(10000); // 等待一段时间以便P信号已经发出
break;
}
//数据的格式是,地址 数据1 数据2 数据3...,对于各个数据的写地址,控制器会自动+1更新
//想要改为读模式,需要在发出P信号之后,发出读模式地址。
IICDS = g_tS3C24xx_I2C.pData[g_tS3C24xx_I2C.Pt++];
// 将数据写入IICDS后,需要一段时间才能出现在SDA线上
for (i = 0; i < 10; i++);
IICCON = 0xaf; // 恢复I2C传输
break;
}
case RDDATA:
{
if (g_tS3C24xx_I2C.Pt == -1)
{
// 这次中断是发送I2C设备地址后发生的,没有数据
// 只接收一个数据时,不要发出ACK信号
g_tS3C24xx_I2C.Pt = 0;
if(g_tS3C24xx_I2C.DataCount == 1)
IICCON = 0x2f; // 恢复I2C传输,开始接收数据,接收到数据时不发出ACK
else
IICCON = 0xaf; // 恢复I2C传输,开始接收数据
break;
}
g_tS3C24xx_I2C.pData[g_tS3C24xx_I2C.Pt++] = IICDS;
g_tS3C24xx_I2C.DataCount--;
if (g_tS3C24xx_I2C.DataCount == 0)
{
// 下面两行恢复I2C操作,发出P信号
IICSTAT = 0x90;
IICCON = 0xaf;
Delay(10000); // 等待一段时间以便P信号已经发出
break;
}
else
{
// 接收最后一个数据时,不要发出ACK信号
if(g_tS3C24xx_I2C.DataCount == 1)
IICCON = 0x2f; // 恢复I2C传输,接收到下一数据时无ACK
else
IICCON = 0xaf; // 恢复I2C传输,接收到下一数据时发出ACK
}
break;
}
default:
break;
}
}
/*
* 延时函数
*/
void Delay(int time)
{
for (; time > 0; time--);
}
本文到此为止,谢谢阅读。