普通IO口模拟实现I2C通信及应用解析

根据I2C通信规范(具体可以参考“浅谈I2C总线”),通过普通IO端口模拟可以实现单片机(主设备)与从设备的I2C通信,其中SCL通过IO口延时高低电平变化实现,SDA根据SCL状态变化产生开始信号,结束信号,以及实现发送接收数据等,以下是相关代码

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* @Purpose:  I2C Communication driver(By IO)
* @Author:  Purple
* @Version:  1.0
* @Date:  Create By Purple 2014.08.09
* 
* 
* Copyright (C) BlueWhale Tech.   
* All rights reserved.  
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */


#ifndef IICDRV_H
#define IICDRV_H


/* Include Files */


/* Macros */
#define I2C_SDA			PTT_PTT0   //举例以Freescale PT0端口为SDA线,PT1端口为SCL线
#define I2C_SDA_IO		DDRT_DDRT0
#define I2C_SCL			PTT_PTT1
#define I2C_SCL_IO		DDRT_DDRT1

#define IO_OUT_MODE		1	//Freescale:1为输出模式,0为输入模式;NEC:0为输出模式,1为输入模式
#define IO_IN_MODE		0


/* Function Prototypes */
void I2CStart(void);
void I2CStop(void);
void I2CFree(void);
void I2CSendACK(void);
void I2CSendNoACK(void);
bool I2CCheckACK(void);
void I2CNoAck(void);
void I2CSendByte(unsigned char sendData);
unsigned char I2CReceiveByte(void);


#endif
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* @Purpose:  I2C Communication driver(By IO)
* @Author:  Purple
* @Version:  1.0
* @Date:  Create By Purple 2014.08.09
* 
* 
* Copyright (C) BlueWhale Tech.   
* All rights reserved.  
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */


/* Include Files */
#include "IICDrv.h"


/* Function Prototypes */
static void I2CDelay(void);


/* Function Definitions */
/*
 * FunctionName: I2CDelay
 * Purpose: I2C时序模拟SCL时间间隔(周期),需要根据Slave性能及单片机工作频率调整
 * Parameters: 无
*/
static void I2CDelay(void)
{
	_asm("nop");
	_asm("nop");
	_asm("nop");
	_asm("nop");
	_asm("nop");
}


/*
 * FunctionName: I2CStart
 * Purpose: 模拟I2C开始信号
 * Parameters: 无
*/
void I2CStart(void)
{
	I2C_SDA_IO=IO_OUT_MODE;     //设置SDA端口为输出端口
	I2C_SCL_IO=IO_OUT_MODE;     //设置SCL端口为输出端口
	
	I2C_SCL=1;
	I2CDelay();
	I2C_SDA=1;
	I2CDelay();
	I2C_SDA=0;
	I2CDelay();
	I2C_SCL=0;
	I2CDelay();
}

/*
 * FunctionName: I2CStop
 * Purpose: 模拟I2C结束信号
 * Parameters: 无
*/
void I2CStop(void)
{
	I2C_SDA_IO=IO_OUT_MODE;     //设置SDA端口为输出端口
	I2C_SCL_IO=IO_OUT_MODE;     //设置SCL端口为输出端口
	
	I2C_SCL = 0;
	I2CDelay();
	I2C_SDA = 0;
	I2CDelay();
	I2C_SDA = 1;
	I2CDelay();
	I2C_SCL = 1;
	I2CDelay();
}

/*
 * FunctionName: I2CFree
 * Purpose: 模拟I2C空闲状态信号
 * Parameters: 无
*/
void I2CFree(void)
{
	I2C_SDA_IO=IO_OUT_MODE;     //设置SDA端口为输出端口
	I2C_SCL_IO=IO_OUT_MODE;     //设置SCL端口为输出端口
	
	I2C_SDA = 1;
	I2CDelay();
	I2C_SCL = 1;
	I2CDelay();
}

/*
 * FunctionName: I2CSendACK
 * Purpose: 模拟I2C发送ACK响应
 * Parameters: 无
*/
void I2CSendACK(void)
{
	I2C_SDA_IO=IO_OUT_MODE;     //设置SDA端口为输出端口
	I2C_SCL_IO=IO_OUT_MODE;     //设置SCL端口为输出端口
	
	I2C_SCL=0;
	I2CDelay();
	I2C_SDA=0;
	I2CDelay();
	I2C_SCL=1;
	I2CDelay();
	I2C_SCL=0;
	I2CDelay();
}

/*
 * FunctionName: I2CSendNoACK
 * Purpose: 模拟I2C无ACK响应
 * Parameters: 无
*/
void I2CSendNoACK(void)
{
	I2C_SDA_IO=IO_OUT_MODE;	  //设置SDA端口为输出端口
	I2C_SCL_IO=IO_OUT_MODE;	  //设置SCL端口为输出端口

	I2C_SCL=0;
	I2CDelay();
	I2C_SDA=1;
	I2CDelay();
	I2C_SCL=1;
	I2CDelay();
	I2C_SCL=0;
	I2CDelay();
}

/*
 * FunctionName: I2CCheckACK
 * Purpose: 检查I2C是否有ACK响应
 * Parameters: 无
*/
bool I2CCheckACK(void)
{
	bool tempACK;
	
	I2C_SDA_IO=IO_OUT_MODE;	  //设置SDA端口为输出端口
	I2C_SCL_IO=IO_OUT_MODE;	  //设置SCL端口为输出端口

	I2C_SDA=1;
	I2CDelay();
	I2C_SCL = 1;
	I2C_SDA_IO=IO_IN_MODE;	  //设置SDA端口为输入端口,检查Slave是否有响应
	I2CDelay();
	if(I2C_SDA)
		tempACK=FALSE;
	else
		tempACK=TRUE;

	I2C_SCL=0;
	I2CDelay();

	return tempACK ;
}

/*
 * FunctionName: I2CSendByte
 * Purpose: 模拟I2C发送一个字节数据
 * Parameters: sendData-发送的一个字节数据
*/
void I2CSendByte(unsigned char sendData)
{
	unsigned char serialNum = 0;

	I2C_SDA_IO=IO_OUT_MODE;	  //设置SDA端口为输出端口
	I2C_SCL_IO=IO_OUT_MODE;	  //设置SCL端口为输出端口

	for(serialNum=8;serialNum>=1;serialNum--)   //以MSB方式按位发送一个字节数据
	{
		I2C_SDA = (sendData>>(serialNum-1))&0x01;
		I2CDelay();
		I2C_SCL = 1;
		I2CDelay();
		I2C_SCL = 0;
		I2CDelay();
	}
}

/*
 * FunctionName: I2CReceiveByte
 * Purpose: 模拟I2C接收一个字节数据
 * Parameters: 无
*/
unsigned char I2CReceiveByte(void)
{	
	unsigned char serialNum = 0;
	unsigned char dataValue=0;

	I2C_SDA_IO=IO_IN_MODE;	  //设置SDA端口为输入端口
	I2C_SCL_IO=IO_OUT_MODE;	  //设置SCL端口为输出端口

	for(serialNum=0;serialNum<=7;serialNum++)//以MSB方式按位接收一个字节数据
	{
		I2C_SCL=1;
		I2CDelay();
		if(I2C_SDA) dataValue|=(0b10000000>>serialNum);
		I2C_SCL=0;
		I2CDelay();
	}

	return dataValue;
}
需要注意模拟SCL采用的延时需要根据从设备的特性来调整,延时时间不能小于从设备的最小SCL间隔时间

既然已经通过IO端口实现了I2C通信,那么,我们就可以用以上代码实现单片机与相应从设备I2C的通信了,以EEPROM 24C04为例,以下是读取和写入EEPROM数据相关函数的代码

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* @Purpose:  Communication with EEPROM 24C04
* @Author:  Purple
* @Version:  1.0
* @Date:  Create By Purple 2014.08.09
* 
* 
* Copyright (C) BlueWhale Tech.   
* All rights reserved.  
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */


/* Include Files */
#include "EEApp.h"



/* Function Definitions */
/*
 * FunctionName: Slave24C04Write
 * Purpose: 向EEPROM24C04中写入一个字节数据
 * Parameters: tarAddress-写入数据的起始地址
 *             wrNumber-待写入数据的长度(字节单位)
 *             wrPointer-待写入数据的首字节地址
*/
void Slave24C04Write(unsigned char tarAddress,unsigned char wrNumber,unsigned char* wrPointer)
{
	bool rxdACK;

	I2CStart();

	I2CSendByte(SLAVE_ADDRESS);   //发送24C04的器件地址,地址LSB最后一位为0代表写入,1代表读取
	rxdACK=I2CCheckACK();
	I2CSendByte(tarAddress);   //发送写入数据的起始地址
	rxdACK=I2CCheckACK();

	for(;wrNumber!=0;wrNumber--,wrPointer++)
	{
	  	I2CSendByte(*wrPointer);   //按字节写入数据
		rxdACK=I2CCheckACK();
	}

	I2CStop();
}

/*
 * FunctionName: Slave24C04Read
 * Purpose: 从EEPROM24C04中读取一个字节数据
 * Parameters: tarAddress-读取数据的起始地址
 *             wrNumber-读取数据的长度(字节单位)
 *             wrPointer-读取数据的首字节存放地址
*/
void Slave24C04Read(unsigned char tarAddress,unsigned char rdNumber,unsigned char* rdPointer)
{
	bool rxdACK;
	
	I2CStart();
	I2CSendByte(SLAVE_ADDRESS);   //发送24C04的器件地址
	rxdACK=I2CCheckACK();
	I2CSendByte(tarAddress);   //发送读取数据的起始地址
	rxdACK=I2CCheckACK();

	I2CStart();
	I2CSendByte(SLAVE_ADDRESS+1);  //发送24C04的器件地址,地址LSB最后一位为1代表读取
	rxdACK=I2CCheckACK();

	for(;rdNumber!=0;rdNumber--,rdPointer++)
	{
	    *rdPointer=I2CReceiveByte();   //按字节读取数据

		if(rdNumber!=1)
			I2CSendACK();
		else
			I2CSendNoACK();
	}

	I2CStop();
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* @Purpose:  Communication with EEPROM 24C04
* @Author:  Purple
* @Version:  1.0
* @Date:  Create By Purple 2014.08.09
* 
* 
* Copyright (C) BlueWhale Tech.   
* All rights reserved.  
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */


#ifndef EEAPP_H
#define EEAPP_H


/* Include Files */
#include "IICDrv.h"


/* Macros */
#define SLAVE_ADDRESS	(0xA0)


/* Function Prototypes */
void Slave24C04Write(unsigned char tarAddress,unsigned char wrNumber,unsigned char* wrPointer);
void Slave24C04Read(unsigned char tarAddress,unsigned char rdNumber,unsigned char* rdPointer);



#endif

需要注意不同的从设备要根据应用电路调整相应的从设备地址。

采用IO口来模拟I2C通信,一般仅用于单片机没有I2C功能的情况下,如果单片机本身具有I2C功能,还是应该通过配置单片机相应的寄存器,通过中断来实现I2C通信,因为模拟SCL采用的延时就是一个空等待,对于整个操作系统来说,这是一个资源浪费,同时还有可能会影响到其他任务的运行。

你可能感兴趣的:(Freescale,NEC,软件,通信,嵌入式,单片机,单片机,嵌入式,通信,c,软件)