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

2019-04-15 15:05发布

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