利用linux驱动思想实现s5pv210的I2C控制器操作

本文章主要参考自韦东山老师的新一期裸板视屏中I2C章节


IIC协议是一个板级异步双向的串行协议。

只使用一根数据一根时钟两根线,通常频率都不会很高,通常我们使用在几百Khz,目前最高的器件能达到Mhz级别。

IIC协议的缺点:数据线只有一根既要主机传输又要从机传输,所以传输速度很慢。

主器件用于启动总线传送数据,并产生时钟以开放传送的器件,此时任何被寻址的器件均被认为是从器件。

主器件同时也负责终止一次传输。

主器件的每一次启动传输都是在时钟线的高电平期间,数据线从高到低。

主器件的每一次停止传输都是在时钟线的高电平期间,数据线从低到高。

 

一般情况下IIC总线上是可以接多个设备的,但所接设备数量主要由电容负载来决定,因为每个器件的总线接口都有一定的等效电容,而线路的电容会影响总线传输速度,当电容过大时有可能造成传输错误 。

SDASCL都是双向I/O线,接口电路为开漏输出。

如下图所示为SCLSDA的开漏输出电路:

 

我们可以画一个真值表来表示SDA的电平。

SDA

SDA

总线SDA

0

0

无效

0

1

0

1

0

0

1

1

0

可以看到当主从SDA都维持低电平时,SDA线是悬空的,此时电平由外部环境决定。

所以通常我们在SCLSDA先上都要接一个上拉电阻。让其保证在主从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


在写页的时候,每个数据在写完后,地址会在页内自增。但超过一页的范围后,地址不会向下一页增加,反而会从该页的地址开始处放置新数据,继而覆盖该页原有的数据。



猜你喜欢

转载自blog.csdn.net/qq_16777851/article/details/81024031