本文章主要参考自韦东山老师的新一期裸板视屏中I2C章节
IIC协议是一个板级异步双向的串行协议。
只使用一根数据一根时钟两根线,通常频率都不会很高,通常我们使用在几百Khz,目前最高的器件能达到Mhz级别。
IIC协议的缺点:数据线只有一根既要主机传输又要从机传输,所以传输速度很慢。
主器件用于启动总线传送数据,并产生时钟以开放传送的器件,此时任何被寻址的器件均被认为是从器件。
主器件同时也负责终止一次传输。
主器件的每一次启动传输都是在时钟线的高电平期间,数据线从高到低。
主器件的每一次停止传输都是在时钟线的高电平期间,数据线从低到高。
一般情况下IIC总线上是可以接多个设备的,但所接设备数量主要由电容负载来决定,因为每个器件的总线接口都有一定的等效电容,而线路的电容会影响总线传输速度,当电容过大时有可能造成传输错误 。
SDA和SCL都是双向I/O线,接口电路为开漏输出。
如下图所示为SCL和SDA的开漏输出电路:
我们可以画一个真值表来表示SDA的电平。
主SDA |
从SDA |
总线SDA |
0 |
0 |
无效 |
0 |
1 |
0 |
1 |
0 |
0 |
1 |
1 |
0 |
可以看到当主从SDA都维持低电平时,SDA线是悬空的,此时电平由外部环境决定。
所以通常我们在SCL和SDA先上都要接一个上拉电阻。让其保证在主从SDA都输出0时,总线为高电平。如下图所示。
同时如果是使用 i/o口模拟IIC的小伙伴们要注意下面几点。
1.发送数据是以高字节在前低字节在后的顺序发送。
2.数据是在时钟的高电平采样的,所以改变数据要在时钟的低电平改变。
3.主机写数据时,要在第九个时钟的高电平期间沿检查从机的应答。
4.主机读数据时,要在第九个时钟的高电平期间给从机发送应答。
注:应答信号其实就是数据线为低电平。
从上图可以看到,如果采用硬件传送数据IIC,使用中断的话,中断会发生在应答信号后面,整个中断期间时钟线会被拉低,表示总线被占用,此时从机不会再总线发送信息。
5.起始信号后面的首字节数据,是主机和从机建立通信关系的,在起始信号后面发送7位从机地址+1位读写位,其中读为1,写为0。
I2C控制器主要分为两个点,公共接口实现和平台接口实现
先看一下公共接口实现
#ifndef __I2C_CONTROLLER_H__
#define __I2C_CONTROLLER_H__
struct i2c_msg {
const char *name;
unsigned int addr; /* 7bit */
int flags; /* 0-write,1-read */
unsigned char *buf;
unsigned int len; /* buff len */
int cnt_xferred; /* 已经传输的字节数 */
int err;
};
struct i2c_controller {
const char *name;
int (*init)(void);
int (*master_xfer)(struct i2c_msg *msgs,int num);
};
int register_i2c_controller(struct i2c_controller *msg);
int select_i2c_controller(const char *name);
#endif /* __I2C_CONTROLLER_H__ */
#define I2C_CONTROLLER_NUM 10
static struct i2c_controller *s_i2c_controllers[I2C_CONTROLLER_NUM];
static struct i2c_controller *s_i2c_controller_select;
/* i2c_controller数组来存储各种不同芯片的结构体 */
int register_i2c_controller(struct i2c_controller *msg)
{
int i;
for (i=0; i<I2C_CONTROLLER_NUM; i++)
{
if(!s_i2c_controllers[i])
{
s_i2c_controllers[i] = msg;
return 0;
}
}
return -1;
}
/* 根据name来选择某款i2c控制器 */
int select_i2c_controller(const char *name)
{
int i;
for (i=0; i<I2C_CONTROLLER_NUM; i++)
{
if(s_i2c_controllers[i] && !strcmp(name, s_i2c_controllers[i]->name))
{
s_i2c_controller_select = s_i2c_controllers[i];
return 0;
}
}
return -1;
}
/* 实现iic_transfer函数 */
int i2c_transfer(struct i2c_msg *msg,int num)
{
if(s_i2c_controller_select)
{
return s_i2c_controller_select->master_xfer(msg, num);
}
return -1;
}
void i2c_init(void)
{
/* 注册下面的i2c控制器 */
s5pv210_i2c_controller_add();
/* 选择下面的i2c控制器 */
select_i2c_controller("s5pv210");
/* 调用选择好的控制器的init函数 */
if(s_i2c_controller_select)
s_i2c_controller_select->init();
}
主要有注册和选择控制器,以及公共的发送函数。
初始化函数主要是可以注册多个控制器,以名字匹配选择。
接下来看平台相关的函数
static struct i2c_msg *s_cur_msg;
/* i2c interrupt function */
void s5pv210_i2c_interrupt(void)
{
unsigned int i2cstat = __REG(I2CSTAT0);
unsigned int index;
/* 以传送字节数 */
s_cur_msg->cnt_xferred ++;
if( !s_cur_msg->flags ) /* write */
{
/* 判断设备是否存在
* 第一个中断,是发送设备地址产生的
* 需要判断是否有接受到ack
*/
if( !s_cur_msg->cnt_xferred ) /* 还未发送数据 */
{
if( i2cstat & 0x01 )
{
/* 无ack */
__REG(I2CSTAT0) = 0xd0; /* master stop */
__REG(I2CCON0) &= ~(1<<4); /* clear pend bit */
s_cur_msg->err = -1;
printf("tx no ack \r\n");
delay(10000);
return;
}
}
/* 这里没判断设备应答 */
/* .... */
if(s_cur_msg->cnt_xferred < s_cur_msg->len)
{
/* 发送数据 */
__REG(I2CDS0) = s_cur_msg->buf[s_cur_msg->cnt_xferred];
__REG(I2CCON0) &= ~(1<<4); /* clear pend bit */
}
else
{
/* 数据发送完毕 */
__REG(I2CSTAT0) = 0xd0; /* master stop */
__REG(I2CCON0) &= ~(1<<4); /* clear pend bit */
delay(10000);
}
}
else /* read */
{
/* 第一个中断,是发送设备地址产生的
* 需要判断是否有接受到ack
*/
if( !s_cur_msg->cnt_xferred )
{
if( i2cstat & 0x01 )
{
/* 无ack */
__REG(I2CSTAT0) = 0x90; /* master stop */
__REG(I2CCON0) &= ~(1<<4); /* clear pend bit */
s_cur_msg->err = -1;
delay(10000);
printf("rx no ack \r\n");
return;
}
else
{
/* 第一个中断,还未收到数据,只是启动传输 */
__REG(I2CCON0) &= ~(1<<4); /* clear pend bit */
}
}
else
{
if( s_cur_msg->cnt_xferred < s_cur_msg->len )
{
index = s_cur_msg->cnt_xferred - 1;
s_cur_msg->buf[index] = __REG(I2CDS0)&0xff;
__REG(I2CCON0) &= ~(1<<4); /* clear pend bit */
if( s_cur_msg->cnt_xferred == (s_cur_msg->len-1) )
{
/* 取消ack */
__REG(I2CCON0) &= ~(1<<7);
}
}
else
{
/* 数据接收完毕 */
s_cur_msg->buf[s_cur_msg->len-1] = __REG(I2CDS0)&0xff;
__REG(I2CSTAT0) = 0x90; /* master stop */
__REG(I2CCON0) &= ~(1<<4); /* clear pend bit */
delay(10000);
}
}
}
}
int s5pv210_i2c_controller_init(void)
{
/* 配置引脚用于i2c输出 */
__REG(GPD1CON) &= ~(0xff);
__REG(GPD1CON) |= (2<<4)|(2<<0);
/* [7] :acknowledge enable bit,1-enbale
* [6] :transmit clock prescaler selection bit,0-16prescaler,1-512
* [5] :Tx/Rx Interrupt ,1-enable
* [4] :Interrupt pending flag
* [3:0]: I2C-Bus transmit clock prescaler
* Tx clock = I2CCLK/(I2CCON[3:0]+1)
*/
__REG(I2CCON0) = (1<<7)|(1<<6)|(1<<5)|(1<0);
/* 注册中断处理函数 */
system_exception(46, s5pv210_i2c_interrupt);
}
/* */
int do_master_xfer_transmit(struct i2c_msg *msg)
{
s_cur_msg = msg;
s_cur_msg->cnt_xferred = -1;
s_cur_msg->err = 0;
/* 设置寄存器启动传输 */
/* 1.Master Tx mode
* 2.Write slave address
* 3.Write 0xF0 (M/T Start)
*/
__REG(I2CSTAT0) = (1<<4);
__REG(I2CCON0) |= (1<<7);
/* 把从设备地址写入i2cdas寄存器 */
__REG(I2CDS0) = (msg->addr<<1)|0x00;
/* 3.Write 0xF0 (M/T Start) */
__REG(I2CSTAT0) = 0xf0;
/* 剩下的操作由中断函数完成 */
/* 等待传输完成 */
while((!s_cur_msg->err) && (s_cur_msg->len != s_cur_msg->cnt_xferred));
return s_cur_msg->err;
}
int do_master_xfer_receive(struct i2c_msg *msg)
{
s_cur_msg = msg;
s_cur_msg->cnt_xferred= -1;
s_cur_msg->err = 0;
/* 设置寄存器启动传输 */
/* 1.Master Rx mode
* 2.Write slave address
* 3.Write 0xb0 (M/T Start)
*/
__REG(I2CCON0) |= (1<<7);
__REG(I2CSTAT0) = (1<<4);
/* 把从设备地址写入i2cdas寄存器 */
__REG(I2CDS0) = (msg->addr<<1)|0x01;
/* 3.Write 0xb0 (M/T Start) */
__REG(I2CSTAT0) = 0xb0;
/* 剩下的操作由中断函数完成 */
/* 等待传输完成 */
while((!s_cur_msg->err) && (s_cur_msg->len != s_cur_msg->cnt_xferred));
return s_cur_msg->err;
}
int s5pv210_i2c_master_xfer(struct i2c_msg *msgs, int num)
{
int i;
int err;
for(i=0; i<num; i++)
{
if(!msgs[i].flags)
err = do_master_xfer_transmit(&msgs[i]);
else
err = do_master_xfer_receive(&msgs[i]);
if(err)
return err;
}
return 0;
}
/* 实现i2c_controller
* .init
* .master_xfer
* .name
*/
static struct i2c_controller s5pv210_i2c_controller = {
.name = "s5pv210",
.init = s5pv210_i2c_controller_init,
.master_xfer = s5pv210_i2c_master_xfer,
};
void s5pv210_i2c_controller_add(void)
{
register_i2c_controller(&s5pv210_i2c_controller);
}
基本都有注释,就不详细说了。那几个delay函数很重要,手册中也说明了,要停止信号发送完。
应用程序我使用的AT24C02
#define AT24C02 0x50
int at24cxx_write(unsigned int addr,unsigned char *data,int len)
{
#if 0
/* 构造i2c_msg */
struct i2c_msg msg[2];
/* 发送要写的地址 */
msg[0].flags = 0;
msg[0].buf[0]= addr;
msg[0].len = 1;
msg[0].addr = AT24C02;
/* 发送数据 */
msg[1].flags = 0;
msg[1].buf = data;
msg[1].len = len;
msg[1].addr = AT24C02;
/* 调用i2c_transfer */
return i2c_transfer(msg, 2);
#else
/* 构造i2c_msg */
struct i2c_msg msg;
unsigned char buf[2];
int i;
int err;
for (i = 0; i < len; i++)
{
buf[0] = addr++;
buf[1] = data[i];
/* 构造i2c_msg */
msg.addr = AT24C02;
msg.flags = 0; /* write */
msg.len = 2;
msg.buf = buf;
/* 调用i2c_transfer */
err = i2c_transfer(&msg, 1);
if (err)
return err;
}
return 0;
#endif
}
int at24cxx_read(unsigned int addr,unsigned char *data,int len)
{
/* 构造i2c_msg */
struct i2c_msg msg[2];
/* 发送要读的地址 */
msg[0].flags = 0;
msg[0].buf[0]= addr;
msg[0].len = 1;
msg[0].addr = AT24C02;
/* 读 */
msg[1].flags = 1;
msg[1].buf = data;
msg[1].len = len;
msg[1].addr = AT24C02;
/* 调用i2c_transfer */
return i2c_transfer(msg, 2);
}
唯一要说明的是写函数,之前因为没有仔细看手册,误认为写和读一样,可以随机一次写多个。导致写函数我写多个总字节,总是有问题。
后来仔细研究了datasheet才发现问题
对于1k和2k bit的芯片每个页是8个字节 lower three
对于4k和8k和16k bit的芯片每个页是16个字节 lower four
在写页的时候,每个数据在写完后,地址会在页内自增。但超过一页的范围后,地址不会向下一页增加,反而会从该页的地址开始处放置新数据,继而覆盖该页原有的数据。