第十二课JZ2440裸板开发之I2C

一、I2C基本知识

1、iic
iic的实质是串口通信的一种方式。IIC 即Inter-Integrated Circuit(集成电路总线) I2C总线是PHLIPS公司推出的一种串行总线,I2C总线只有两根双向信号线。一根是数据线SDA,另一根是时钟线SCL。
2、物理接口
(1)SCL(serial clock):时钟线,传输CLK信号,一般是I2C主设备向从设备提供时钟的通道。
(2)SDA(serial data): 数据线,通信数据都通过SDA线传输
3、通讯特征
(1)串口通讯
数据传输以字节为单位
(2)主从设备
通讯过程中,收发分主从,究竟是主设备还是从设备,这个不是IIC通信协议规定的,而是通讯双方规定的(在应用而不在协议)
一般一个硬件芯片可以只作为从设备、也可以只做主设备、或者既能够做从设备也能够做主设备
(3)通讯方式:1对1、或者1对多
1对1:一个主设备、一个从设备
1对多:一个主设备、多个从设备
(4)主设备负责总线调度
主设备决定某一时间和哪个从设备通讯,同一时刻,IIC总线上只能有一个从设备,其他从设备处在休眠状态
(5)选择通讯从设备
每一个IIC从设备在通讯的时候都有一个从设备地址(这个设备号是在同一个电路板上唯一,全球不唯一),在通讯的时候,IIC主设备必须要知道将要通讯的从设备的地址,通过发送从设备地址选中。
(6)通讯过程中改变SDA数据传输方向时,要先结束通讯,在发送从设备+方向来改变SDA方向
4、用途
IIC一般用在SOC和SOC外部设备进行通信

二、I2C通信协议

1、通信信号
【起始信号】
实质就是一个信号变化,SCL处在高电平,SDA从高电平变成低电平(下降沿)
【结束信号】
实质也是一个信号变化:SCL保持高电平不变,SDA从低电平变为高电平(上升沿)
【数据传送】
I2C总线进行数据传递过程中,SCL处在高电平期间,SDA上数据必须保持稳定,SCL处在低电平期间,才允许SDA数据变化:
(1)发送数据
在SCL处在低电平期间,向SDA输出数据,等到SCL在高电平时,稳定不变
(2)接收数据
在SCL处在高电平期间,从SDA上读取数据
【应答信号】
iic总线规定,每发送一个字节数据,都要有一个应答信号来确定数据是否被接收方收到。应答信号由接受设备产生,在SCL为高电平期间,接受设备将SDA拉低为低电平,表示数据传输正确,产生应答(ACK)。
应答信号的实质是:在发送方发完一个字节之后的第9个时钟周期,接收方发送一个bit的0(第9bit)
【非应答信号】
实质就是在单个字节之后发送一个bit位的1(第9bit)
2.数据传输格式
1.写操作:
:: 白色背景:主→从
:: 灰色背景:从→主
在这里插入图片描述
2.读操作:
:: 白色背景:主→从
:: 灰色背景:从→主
在这里插入图片描述

三、GPIO模拟I2C通信

我们这里先给大家展示一下gpio模拟I2C的程序,这是之前学习的时候,用s5p6818的板子跟一个三轴加速器通信的例子,大家主要对其通信过程参考一下,具体带码实现可以不必深究:
两个文件:I2C.c mma8451.c
I2C.c 这里主要实现I2C通信的基本函数,包括:发送起始信号,结束信号,发送一个字节数据,接受一个字节数据

#include "s5p6818.h"

extern void DelayMs(unsigned int Time);


/************SDA设置--->GPIOD7*************/
#define SDA_OUT()	(GPIOD.OUTENB |= (1<<7)) //将SDA方向设置为输出
#define SDA_IN()	(GPIOD.OUTENB &= ~(1<<7)) //将SDA方向设置为输入
#define SDA_H()		(GPIOD.OUT |= (1<<7))//将SDA引脚输出高电平
#define SDA_L()		(GPIOD.OUT &= ~(1<<7)) //将SDA引脚输出低电平

#define	SDA_DATA()	(GPIOD.PAD & (1<<7) ? 1: 0) //获取SDA数据是0还是1

/************SCL设置--->GPIOD6*************/
#define SCL_H()	(GPIOD.OUT |= (1<<6)) //设置SCL输出高电平
#define	SCL_L()	(GPIOD.OUT &= ~(1<<6)) //设置SCL输出低电平




/*
 * iic初始化函数
 * */
void iic_init(void)
{
	//1、设置引脚功能为GPIO功能
	GPIOD.ALTFN0 &= ~(0x3<<12); //GPIOD6
	GPIOD.ALTFN0 &= ~(0x3<<14); //GPIOD7

	//2、设置SCL和SDA为输出方向
	GPIOD.OUTENB |= (0x3<<6); //设置GPIOD6和GPIOD7为输出

	//3、确保SCL和SDA空闲时为高电平
	GPIOD.OUT |= (0x3<<6);

	DelayMs(1);

}

/*
 * iic起始信号发送
 * */
void iic_start(void)
{
	//1、将SDA方向设置为输出
	SDA_OUT();

	//2、SDA、SCL输出高电平
	SCL_H();
	SDA_H();

	//3、稳定电平信号
	DelayMs(1);

	//4、拉低SDA产生一个起始信号
	SDA_L();

	DelayMs(1);
}

/*
 * iic发送停止信号
 * */
void iic_stop(void)
{
	//1、确保SDA处在输出方向
	SDA_OUT();

	//2、拉高SCL
	SCL_H();

	//3、拉低SDA
	SDA_L();
	DelayMs(1);//让信号稳定

	//4、拉高SDA产生一个上升沿
	SDA_H();
	DelayMs(1);
}

/*
 * iic发送一个字节数据
 * */
char iic_send_byte(char data)
{
	int i = 0;
	char ack = 0;

	//1、设置SDA为输出方向
	SDA_OUT();

	//2、发送一个字节(8个bit)
	for(i=0; i<8; i++)
	{
		//拉低SCL,方便发送数据
		SCL_L();
		DelayMs(1);//稳定SCL的低电平

		//写数据(发数据)
		if(data & (1<<7)) //最高位为1
			SDA_H(); //发送高电平
		else
			SDA_L(); //发送低电平

		DelayMs(1);

		//拉高SCL,让接收方开始接收数据
		SCL_H();

		DelayMs(1);//让高电平持续一段时间,方便接收方有时间去读取

		data = data << 1;
	}


	//4、拉低SCL
	SCL_L(); //拉低SCL给接收方一个发送ACK的信号

	//3、让SDA输出一个高电平
	SDA_H(); //消除最后一次发送的bit是0对于接收ACK掉的影响


	//5、切换SDA方向为输入
	SDA_IN();
	DelayMs(1);

	//6、拉高SCL
	SCL_H();
	DelayMs(1);

	//7、读取数据
	if(SDA_DATA()) //非ACK
		ack = 1;
	else			//ACK应答
		ack = 0;

	//8、保持总线
	SCL_L();
	DelayMs(1);
}

/*
 * iic接收字节
 * */
char iic_read_byte(void)
{
	char data = 0;
	int i = 0;
	//1、设置SDA方向为输入
	SDA_IN();

	//2、接收字节
	for(i=7; i>=0; i--)
	{
		//1、拉低SCL,给发送方发送信号
		SCL_L();
		DelayMs(1); //稳定电平信号

		//2、拉高SCL,准备接收数据
		SCL_H();
		DelayMs(1);

		//3、读取数据
		if(SDA_DATA()) //高电平->1
			data = data | (1<<i);
	}

	//3、拉低SCL
	SCL_L();

	//4、SDA方向设置为输出
	SDA_OUT();
	DelayMs(1);

	//5、发送ACK
	SDA_L();
	DelayMs(1);

	//6、拉高SCL,让对方读取ACK
	SCL_H();
	DelayMs(1);

	//7、拉低SCL保持总线使用权限
	SCL_L();
	DelayMs(1);

	return data;
}

mma8451.c 实现对mma8451芯片的读写:

#include "iic.h"

#define MMA_ADDR	(0x1c<<1) //从设备地址
#define IIC_WRITE	(MMA_ADDR) //从设备地址 + 写标记
#define IIC_READ	(MMA_ADDR | 1) //从设备地址 + 读标记

/*
 * 写mma8451内部寄存器
 * */
void mma8451_write_reg(char reg, char data)
{
	//1、发送起始信号
	iic_start();

	//2、发送从设备地址 + 写标记(0)
	iic_send_byte(IIC_WRITE);

	//3、发送寄存器地址
	iic_send_byte(reg);

	//4、发送写入的数据
	iic_send_byte(data);

	//5、发送停止信号结束通信过程
	iic_stop();
}

/*
 * 读取mma8451寄存器值
 * */
char mma8451_read_reg(char reg)
{
	char data = 0;

	//1、发送起始信号
	iic_start();

	//2、发送从设备地址 + 写标记:因为要告知mma8451将要读取的寄存器地址
	iic_send_byte(IIC_WRITE);

	//3、发送mma8451寄存器地址
	iic_send_byte(reg);

	//4、发送起始信号
	iic_start();

	//5、发送从设备地址 + 读标记
	iic_send_byte(IIC_READ);

	//6、读取数据
	data = iic_read_byte();

	//7、发送停止信号结束
	iic_stop();

	return data;
}

/*
 * 初始化设置mma8451
 * */
void mma8451_init(void)
{
	//1、iic初始化
	iic_init();

	//2、设置mma8451工作模式为active模式:设置mma8451内部寄存器0x2a = 1
	mma8451_write_reg(0x2a, 1);

//	printf("0x2a = %d\n", mma8451_read_reg(0x2a));

	//3、设置mma8451工作状态为高性能:设置mma8451内部寄存器0x2b = 2
	mma8451_write_reg(0x2b, 2);
}

四、使用I2C控制器实现通信

从上面GPIO模拟I2C的通信我们可以看到,模拟的时候是很麻烦的,一般情况下主控芯片都会有I2C控制器,我们要发送数据时,可以把数据放到某个寄存器,它就会自动的发出时钟,并且把数据发送给从设备,同时会等待从设备返回回应信号。
在这里插入图片描述

4.1寄存器介绍

上图是我们s3c2440芯片手册中I2C控制器的逻辑图,根据上图,我们的时钟采用的是PCLK分频后的时钟,相关的寄存器功能设置下面介绍:
(1)IICCON寄存器(Multi-masterIIC-buscontrol)
在这里插入图片描述
使用IICCON寄存器时,有如下注意事项。

1.发送模式的时钟频率由位[6]、位[3:0]联合决定,另外,llCCON[6]=0,IICCON[3:0]不能取0或10
2.12c中断在以下3种情况下发生:当发出地址信息或接收到一个从机地址并且吻合时,当总线仲裁失败时,当发送/接收完一个字节的数据(包括响应位)时。
3.基于SDA、SCL线上时间特性的考虑,要发送数据时,先将数据写入IICDS寄存器,然后再清除中断。
4.如果IICCON[5]=0,IICCON将不能正常工作。所以,即使不使用12c中断,也要将IICCON[5]设为1。

(2)IICSTAT寄存器(Multi-masterIIC-buscontrol/status)
IICSTAT寄存器用于选择12c接口的工作模式,发出S信号、P信号,使能接收/发送功能,并标识各种状态,比如总线仲裁是否成功、作为从机时是否被寻址、是否接收到0地址、是否接收到ACK信号等。IICSTAT寄存器的各位如表:
在这里插入图片描述
(3)IICADD寄存器(Multi-masterIlC-busaddress)
用到IICADD寄存器的位[0:7],表示从机地址。IICADD寄存器在串行输出使能位
IICSTAT[4]为0时,才可以写入:在任何时间都可以读出。IICADD寄存器的各位如表:
在这里插入图片描述
(4)IICDS寄存器(Multi-masterIIC-busTx/Rxdatashift)
用到IICDS寄存器的位丨7:0],其中保存的是要发送或己经接收的数据。IICDS寄存器在串行输出使能位IICSTAT(1)为1时,才可以写入;在任何时间都可以读出。IICDS寄存器的各位如表:
在这里插入图片描述
4.2控制器读写流程图
在这里插入图片描述
在这里插入图片描述

4.3程序

首先我们这里把程序分为两部分,一部分是控制器代码,实现读、写的基本功能,另一部分对从设备的读写。

4.3.1 控制器部分

i2c_controller.h

#ifndef _I2C_CONTROLLER_H
#define _I2C_CONTROLLER_H

typedef struct i2c_msg {
	unsigned int addr;  /* 7bits */
	int flags;  /* 0 - write, 1 - read */
	int len;
	int cnt_transferred;
	int err;
	unsigned char *buf;
}i2c_msg, *p_i2c_msg;

typedef struct i2c_controller {
	int (*init)(void);
	int (*master_xfer)(p_i2c_msg msgs, int num);
	char *name;
}i2c_controller, *p_i2c_controller;


#endif /* _I2C_CONTROLLER_H */

i2c_controller.c

#include "i2c_controller.h"
#include "../s3c2440_soc.h"

static p_i2c_msg p_cur_msg;

int isLastData(void)
{
	if (p_cur_msg->cnt_transferred == p_cur_msg->len - 1)
		return 1;  /* 正要开始传输最后一个数据 */
	else 
		return 0;
}

void resume_iic_with_ack(void)
{
	unsigned int iiccon = IICCON;
	iiccon |= (1<<7); /* 回应ACK */
	iiccon &= ~(1<<4); /* 恢复IIC操作 */
	IICCON =  iiccon;
}

void resume_iic_without_ack(void)
{
	unsigned int iiccon = IICCON;
	iiccon &= ~((1<<7) | (1<<4)); /* 不回应ACK, 恢复IIC操作 */
	IICCON =  iiccon;
}


void i2c_interrupt_func(int irq)
{
	int index;
	unsigned int iicstat = IICSTAT;
	unsigned int iiccon;

	//printf("i2c_interrupt_func! flags = %d\n\r", p_cur_msg->flags);

	p_cur_msg->cnt_transferred++;
	
	/* 每传输完一个数据将产生一个中断 */

	/* 对于每次传输, 第1个中断是"已经发出了设备地址" */

	if (p_cur_msg->flags == 0)	/* write */
	{
		/* 对于第1个中断, 它是发送出设备地址后产生的
		 * 需要判断是否有ACK
		 * 有ACK : 设备存在
		 * 无ACK : 无设备, 出错, 直接结束传输
		 */
		if (p_cur_msg->cnt_transferred == 0)  /* 第1次中断 */
		{
			if (iicstat & (1<<0))
			{ /* no ack */
				/* 停止传输 */
				IICSTAT = 0xd0;
				IICCON &= ~(1<<4);
				p_cur_msg->err = -1;
				printf("tx err, no ack\n\r");
				delay(1000);
				return;
			}
		}

		if (p_cur_msg->cnt_transferred < p_cur_msg->len)
		{
			/* 对于其他中断, 要继续发送下一个数据
			 */
			IICDS = p_cur_msg->buf[p_cur_msg->cnt_transferred];
			IICCON &= ~(1<<4);
		}
		else
		{
			/* 停止传输 */
			IICSTAT = 0xd0;
			IICCON &= ~(1<<4);
			delay(1000);
		}
	}
	else /* read */
	{
		/* 对于第1个中断, 它是发送出设备地址后产生的
		 * 需要判断是否有ACK
		 * 有ACK : 设备存在, 恢复I2C传输, 这样在下一个中断才可以得到第1个数据
		 * 无ACK : 无设备, 出错, 直接结束传输
		 */
		if (p_cur_msg->cnt_transferred == 0)  /* 第1次中断 */
		{
			if (iicstat & (1<<0))
			{ /* no ack */
				/* 停止传输 */
				IICSTAT = 0x90;
				IICCON &= ~(1<<4);
				p_cur_msg->err = -1;
				printf("rx err, no ack\n\r");
				delay(1000);
				return;
			}
			else  /* ack */
			{
				/* 如果是最后一个数据, 启动传输时要设置为不回应ACK */
				/* 恢复I2C传输 */
				if (isLastData())
				{
					resume_iic_without_ack();
				}
				else
				{
					resume_iic_with_ack();
				}
				return;
			}
		}

		/* 非第1个中断, 表示得到了一个新数据
		 * 从IICDS读出、保存
		 */
		if (p_cur_msg->cnt_transferred < p_cur_msg->len)
		{
			index = p_cur_msg->cnt_transferred - 1;
			p_cur_msg->buf[index] = IICDS;

			/* 如果是最后一个数据, 启动传输时要设置为不回应ACK */
			/* 恢复I2C传输 */
			if (isLastData())
			{
				resume_iic_without_ack();
			}
			else
			{
				resume_iic_with_ack();
			}
		}
		else
		{
			/* 发出停止信号 */
			IICSTAT = 0x90;
			IICCON &= ~(1<<4);
			delay(1000);
		}
	}
}


void s3c2440_i2c_con_init(void)
{
	/* 配置引脚用于I2C*/
	GPECON &= ~((3<<28) | (3<<30));
	GPECON |= ((2<<28) | (2<<30));
	
	/* 设置时钟 */
	/* [7] : IIC-bus acknowledge enable bit, 1-enable in rx mode
	 * [6] : 时钟源, 0: IICCLK = fPCLK /16; 1: IICCLK = fPCLK /512
	 * [5] : 1-enable interrupt
	 * [4] : 读出为1时表示中断发生了, 写入0来清除并恢复I2C操作
	 * [3:0] : Tx clock = IICCLK/(IICCON[3:0]+1).
	 * Tx Clock = 100khz = 50Mhz/16/(IICCON[3:0]+1)
	 */
	IICCON = (1<<7) | (0<<6) | (1<<5) | (30<<0);

	/* 注册中断处理函数 */
	register_irq(27, i2c_interrupt_func);
}

int do_master_tx(p_i2c_msg msg)
{
	p_cur_msg = msg;
	
	msg->cnt_transferred = -1;
	msg->err = 0;
	
	/* 设置寄存器启动传输 */
	/* 1. 配置为 master tx mode */
	IICCON |= (1<<7); /* TX mode, 在ACK周期释放SDA */
	IICSTAT = (1<<4);
		
	/* 2. 把从设备地址写入IICDS */
	IICDS = msg->addr<<1;
	
	/* 3. IICSTAT = 0xf0 , 数据即被发送出去, 将导致中断产生 */
	IICSTAT = 0xf0;
	

	/* 后续的传输由中断驱动 */

	/* 循环等待中断处理完毕 */
	while (!msg->err && msg->cnt_transferred != msg->len);

	if (msg->err)
		return -1;
	else
		return 0;
}

int do_master_rx(p_i2c_msg msg)
{
	p_cur_msg = msg;

	msg->cnt_transferred = -1;
	msg->err = 0;
	
	/* 设置寄存器启动传输 */
	/* 1. 配置为 Master Rx mode */
	IICCON |= (1<<7); /* RX mode, 在ACK周期回应ACK */
	IICSTAT = (1<<4);
		
	/* 2. 把从设备地址写入IICDS */
	IICDS = (msg->addr<<1)|(1<<0);
	
	/* 3. IICSTAT = 0xb0 , 从设备地址即被发送出去, 将导致中断产生 */
	IICSTAT = 0xb0;
	

	/* 后续的传输由中断驱动 */

	/* 循环等待中断处理完毕 */
	while (!msg->err && msg->cnt_transferred != msg->len);

	if (msg->err)
		return -1;
	else
		return 0;
}

int s3c2440_master_xfer(p_i2c_msg msgs, int num)
{
	int i;
	int err;
	
	for (i = 0; i < num; i++)	
	{
		if (msgs[i].flags == 0)/* write */
			err = do_master_tx(&msgs[i]);
		else
			err = do_master_rx(&msgs[i]);
		if (err)
			return err;
	}
	return 0;
}

4.3.2 设备部分

#include "i2c_controller.h"

#define AT24CXX_ADDR 0x50

int at24cxx_write(unsigned int addr, unsigned char *data, int len)
{
	i2c_msg msg;
	int i;
	int err;
	unsigned char buf[2];

	
	for (i = 0; i < len; i++)
	{
		buf[0] = addr++;
		buf[1] = data[i];
		
		/* 构造i2c_msg */
		msg.addr  = AT24CXX_ADDR;
		msg.flags = 0; /* write */
		msg.len   = 2;
		msg.buf   = buf;
		msg.err   = 0;
		msg.cnt_transferred = -1;

		/* 调用i2c_transfer */
		err = s3c2440_master_xfer(&msg, 1);
		if (err)
			return err;
	}
	
	return 0;
}


int at24cxx_read(unsigned int addr, unsigned char *data, int len)
{
	i2c_msg msg[2];
	int err;
	
	/* 构造i2c_msg */
	msg[0].addr  = AT24CXX_ADDR;
	msg[0].flags  = 0; /* write */
	msg[0].len   = 1;
	msg[0].buf   = &addr;
	msg[0].err   = 0;
	msg[0].cnt_transferred = -1;

	msg[1].addr  = AT24CXX_ADDR;
	msg[1].flags  = 1; /* read */
	msg[1].len   = len;
	msg[1].buf   = data;
	msg[1].err   = 0;
	msg[1].cnt_transferred = -1;

	/* 调用i2c_transfer */
	err = s3c2440_master_xfer(&msg, 2);
	if (err)
		return err;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值