IIC(三)

前面两节分别介绍了IIC协议的硬件接口和jz2440开发板上的IIC控制器,这一节就来写程序进一步熟悉IIC协议的操作过程,首先来看一下本节程序的结构
1、程序结构
在这里插入图片描述

上面的图是本节程序的结构图,采用这种分层的方式编写程序,每一层都负责具体的一部分,这使得程序的结构性紧凑,降低程序之间的耦合性,在添加新设备的时候也是非常方便的
1、测试程序:测试程序它主要是提供测试菜单,并进行初始化,一般是由最上层的应用程序调用
2、at24cxx是使用的i2c设备的名称,这一层包含一个文件,主要是对下层操作接口的封装,直接供测试程序调用
3、i2c_controller:这一层主要是为下层提供规范,定义结构体类型,定义接口类型等,由下层直接继承,并填充相应的成员。这里来看,好像和我们学过的"面向对象编程"思想一致,^_^
4、s3c2440_i2c_controller:这一层就是最底层了,它包含直接对设备的操作接口,填充继承下来的数据结构,由上层统一调用

采用分层的思想编程有很多好处,整个程序的结构看起来就十分的清楚,而且各司其职,对后面的扩展性也是非常好的
2、硬件配置
在这里插入图片描述

IICCON寄存器
[7] : 在接收模式设置为1,接收ACK信号
[6] : 时钟源IICCLK的设置,可以设置成pclk / 16 或者pclk / 512
[5] :  IIC中断,要使能
[4] : 中断标记位,读出1表示中断发生,清零恢复i2c操作
[3:0] : 	配合设置i2c的时钟
		IICCON[3:0] = x
		clock = IICCLK/(x+1)
		100 kHz = IICCLK / (x + 1) 
		100000 = 50000000 / 512 / (x+1)    x = 0.97 - 1不合适,所以取IICCLK = pclk / 16,计算出[3:0] = 30

3、编程
先来看看为底层定义规范的i2c_controller,几个重要的结构体

/* i2c传输消息结构体 */
typedef struct i2c_msg {
 unsigned int addr;			/* 从机地址 */
 int flags;				/* 读写标记 */
 int len;					/* 数据长度 */
 int err;					/* 错误标记 */
 int cnt_transfered;		/* 已传输的字节数 */
 unsigned char *buf;		/* 数据缓冲区 */
}i2c_msg,*p_i2c_msg;

/* i2c_controller结构体 */
typedef struct i2c_controller {
 int (*init)(void);					/* 初始化函数指针 */
 int (*master_xfer)(p_i2c_msg,int);	/* 传输函数指针 */
 char *name;						/* 控制器名称 */
}i2c_controller,*p_i2c_controller;
static p_i2c_controller i2c_controllers[I2C_CONTROLLER_NUM];		/* 定义i2c控制器数组,用来存储各种设备的i2c控制器对象 */
static p_i2c_controller i2c_controller_selected;				/* 被选择的i2c控制器的对象 */

/* i2c控制器的注册函数 */
void register_i2c_controller(p_i2c_controller controller)
{
	int i;
	for (i = 0; i < I2C_CONTROLLER_NUM; i++) {
		if (!i2c_controllers[i]) {
			i2c_controllers[i] = controller;
			break;
		}
	}
}
/* 根据名字选择i2c控制器 */
int select_i2c_controller(char *name)
{
	int i;
	for (i = 0; i < I2C_CONTROLLER_NUM; i++) {
		if (i2c_controller[i] && !strcmp(i2c_controller[i]->name,name)) {
			i2c_controller_selected = i2c_controller[i];
			return 0;
		}
	}	
	return -1;
}
/* i2c传输函数,直接调取底层的传输函数 */
int i2c_transfer(p_i2c_msg msgs,int num)
{
 return i2c_controller_selected->master_xfer(msgs,num);
}
/* 初始化 */
void i2c_init(void)
{
 /* 注册i2c控制器 */
 s3c2440_i2c_add();
 /* 根据名字选择i2c控制器 */
 select_i2c_controller("s3c2440");
 /* 调用初始化函数 */
 i2c_controller_selected->init();
}
//OK。到这里为提供接口规范的i2c_controller的功能就已经全部完成,接下来根据这些规范来编写针对设备s3c2440_i2c_controller吧!

s3c2440_i2c_controller.c

static i2c_controller s3c2440_i2c_controller = {
 .name = "s3c2440",
 .init = s3c2440_i2c_init,
 .master_xfer = s3c2440_master_xfer
};
//注册i2c控制器到数组中
void s3c2440_i2c_add(void)
{
 register_i2c_controller(&s3c2440_i2c_controller);
}
void s3c2440_i2c_init(void)
{
	//将使用的引脚配置为i2c模式
	 GPECON &= ~((3 << 28) | (3 << 30));
	 GPECON |= ((2 << 28) | (2 << 30));
	 //i2c控制器的设置
	 IICCON = (30 << 0) | (1 << 5) | (0 << 6) | (1 << 7);
	  /* 注册中断 */
 	register_irq(27,i2c_interrupt_func);
}
//传输函数
int s3c2440_master_xfer(p_i2c_msg msgs,int num)
{
	 int i;
	 int err;	
	 for(i = 0; i < num; i++) {
	 	if (msgs[i] == 0) /* 写 */ {
	 		err = master_transmit(&msgs[i]);
	 	} else {
	 		err = master_receive(&msgs[i]);
	 	}
	 	if (err)
	 		return err;
	 }
}	 

//发送数据函数
int master_transmit(p_i2c_msg msg)
{
 p_cur_mesg = msg;		/* 指向当前需要发送的消息结构体 */
 msg->cnt_transfered = -1;	/*  已传输初始值为-1 */
 msg->err = 0;
IICSTAT = (1 << 4);
IICDS = (msg->addr << 1); /* 高7位为设备地址,最低位为0时表示写 */
 IICSTAT  = 0xf0;		 /* 包含了配置为发送模式,start */
 /* 循环等待,后续由中断完成 */
 while (!msg->err && msg->cnt_transfered != msg->len);
 if (msg->err == -1)
  return -1;
 return 0;
 } 
 //接收数据模式
int master_receive(p_i2c_msg msg)
{	 
 p_cur_mesg = msg;
 msg->cnt_transfered = -1;
 msg->err = 0;
 IICSTAT |= (1 <<4);
 IICDS = (msg->addr << 1) | (1 << 0);/* 高7位为设备地址,最低位为1时表示读*/
 IICSTAT  = 0xb0; //start
 while (!msg->err && msg->cnt_transfered != msg->len);
  if (msg->err == -1)
  	return -1;
  return 0;
 }
//数据传输都在中断函数里实现,所以中断函数是重点
void i2c_interrupt_func(int irq)
{
	int index;
 	unsigned int iicstat = IICSTAT;
 	p_cur_mesg->cnt_transfered++;
 	/* 第一个中断,是已经发出设备地址 */
 	if (p_cur_mesg->flags == 0) /* 写 */ {
		if (p_cur_mesg->cnt_transfered == 0) { /* 第一次中断 */
		 if (IICSTAT & 1) {    		/* 没有ACK,停止传输 */
  			 IICSTAT = 0xd0;		/*停止信号*/
			IICCON &= ~(1 << 4);   /* 清除中断 */
			p_cur_mesg->err = -1;	/* 结束函数中的循环 */
    			delay(1000);
    			return;
    		}
    		if (p_cur_mesg->cnt_transfered < p_cur_mesg->len) {
   			/* 对于其他中断,表明数据发送出去,发送下一个数据 */
   			   IICDS = p_cur_mesg->buf[p_cur_mesg->cnt_transfered];
			   IICCON &= ~(1 << 4);		/* 清除中断,恢复操作 */
		} else {	/* 数据发送完 */
			    IICSTAT = 0xd0;  /* stop */
 			    IICCON &= ~(1 << 4); /* 清除中断 */	
 			    delay(1000);
		}	  
	} else {	/* 读 */
		if (IICSTAT & 1) { /* 没有ACK */
		    IICSTAT = 0x90;
		    IICCON &= ~(1 << 4); /* 清除中断 */
        		p_cur_mesg->err = -1;
  		    delay(1000);
    			return;
		} else {   /* 如果是最后一个数据,启动传输时要设置不回应ACK */
			 if (isLastData())
    				 resume_without_ack();
    			 else  
    			  	resume_with_ack();
    			  return;
    		}
    		//正常传输
		if (p_cur_mesg->cnt_transfered < p_cur_mesg->len) {
			index = p_cur_mesg->cnt_transfered - 1;
			p_cur_mesg->buf[index] = IICDS;
		   if (isLastData()) 
			    resume_without_ack();
   		   else 
    				resume_with_ack();
		} else {
			   IICSTAT = 0x90;
			   IICCON &= ~(1 << 4);
			   delay(1000);
		}

	}	 
}

int isLastData(void)
{
 if (p_cur_mesg->cnt_transfered == p_cur_mesg->len - 1)
  return 1;
 return 0;
}
   void resume_with_ack(void)
{
 unsigned int iiccon = IICCON;
 iiccon |= (1 << 7); /* 回应ACK */
 iiccon &= ~(1 << 4);/* 恢复传输 */
 IICCON = iiccon;
}
void resume_without_ack(void)
{
 unsigned int iiccon = IICCON;
 iiccon &= ~((1 << 7) | (1 << 4));/* 恢复传输 */
 IICCON = iiccon;
}

		

好了,关于at24cxx设备的i2c控制器的程序也已经完成,接下来继续编写at24cxx.c,去调用底层封装好的程序

//写函数,参数为要写的地址,数据缓冲区,以及数据长度
int at24cxx_write(unsigned int addr,unsigned char *dat,int len)
{
	 /* 构造i2c_message */
	 i2c_msg msg;
	 char buf[2];
	 int i;
	 int err;
	  for (i = 0; i < len; i++) {
		  buf[0] = addr++;
		  buf[1] = dat[i];
		  msg.addr = AT24CXXADDR;
  		msg.flags = 0;
  		msg.len = 2;
  		msg.buf = buf;
  		err = i2c_transfer(&msg, 1);
  		if (err)
  			return err;
  	}
  	return 0;
  }
  /*
  	几点说明:构造用于传输的i2c_msg结构体,每次只发送一个字节的数据,0表示写,先将buf[0]的设备内部地址发送,再发送buf[1]处要写
  	的数据,for循环里面每次让地址加一,在写一个地址处写下一个数据
  */
 
int at24cxx_read(unsigned int addr,unsigned char *dat,int len)
{
	 /* 构造i2c_message */
	 i2c_msg msg[2];
	  int i;
	 int err;
	  msg[0].addr = AT24CXXADDR;
	 msg[0].flags = 0;
 	msg[0].len = 1;
 	msg[0].buf = &addr;
  	msg[1].addr = AT24CXXADDR;
 	msg[1].flags = 1;
 	msg[1].len = len;
 	msg[1].buf = dat;
 	 err = i2c_transfer(&msg, 2);
 	if (err)
  	return err;
 return 0;
 }
 /*
	对于读需要构造两个i2c_msg结构体,第一个i2c_msg是要我们要读的地址给发送,所以对应的i2c_msg应该是写,第2个i2c_msg才是要读的消息结构体,保存我们的缓冲区
 */

好了,对于i2c的代码基本上就已经完成了,在调试的时候遇到了很多的问题,有一部分是由于自己的粗心大意造成的,也是对整个过程还不太清楚吧,最后,再对整个过程进行总结。

1、对于写操作来说,上层应用调用到at24cxx.c中的at24cxx_write函数,在该函数中每次构造一个i2c_msg结构体,
结构体中包含从设备的地址,以及我们要写的设备地址和数据,调用i2c_transfer函数,调用到最底层的s3c2440_master_xfer函数,
在该函数中判断是写操作之后就会调用master_transmit函数进行发送,在该函数中设置基本项之后发出start信号,
在发出start信号之后从设备地址以及被发送出去,这一步不需要人为来控制,所以在中断服务函数中的第一次中断就是来
自从设备接收从设备地址之后的相应信号,所以,中断程序中接下来的操作还是针对第一个([0])i2c_msg来操作的,
如果它有多个字节的数据,则一个字节一个字节的发送完毕,再回去at24cxx_write发送下一个i2c_msg,流程和第一个一样,
每一次开始之前都要把从设备地址发送出去,再进行操作
2、对于读操作,每一次定义两个i2c_msg,在s3c2440_master_xfer中总共有两个i2c_msg结构体,而第一个i2c_msg是要把
要读的地址写入从设备内部,所以第一个i2c_msg走的是写的流程,和上面一样
	a、它在把从设备地址发出去之后第一次进入中断函数,在中断函数中接着把这个i2c_msg结构体中的设备内部地址发送出去,
	在第二次进入到中断程序中进行判断,因为只有一个数据(就是设备内部地址),所以不需要再次进行发送数据,发出stop信号结束发送即可。
	b、在操作完第一个i2c_msg之后,第二个i2c_msg结构体是进行读的消息,在s3c2440_master_xfer中走的是读的流程,
	同样,先发出start信号,同时把从设备地址发送出去,在第一次进入到中断程序中,判断有没有ACK,若有则读取数据,
	同时给从设备回应ACK,这里要注意,如果只读取一个数据直接发送stop信号即可,如果有多个数据要读取,需要给从设备回应ACK,
	在下一次进入中断后再做判断。
发布了33 篇原创文章 · 获赞 2 · 访问量 1025

猜你喜欢

转载自blog.csdn.net/weixin_41791581/article/details/103076880
IIC
今日推荐