根据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采用的延时就是一个空等待,对于整个操作系统来说,这是一个资源浪费,同时还有可能会影响到其他任务的运行。