I2C协议—读写EEPROM(24Cxx为例)
STM32 I2C协议详解与应用实践-CSDN博客
实际项目中移植IIC协议及读写EEprom代码:
/* AT24C08,写次数达100万次
SOC ,2500次充放电次数,需要500万次记录,需存储均衡
每页
*/
#include "FreeRTOS.h"
#include "task.h"
#include "24C08.h"
#include "CortrolMain.h"
//#define OP_READ 0xa1 // ?÷?tμ??·ò??°?áè?2ù×÷,0xa1?′?a1010 0001B
#define EE_ADDR 0xa0 // ?÷?tμ??·ò??°D′è?2ù×÷,0xa1?′?a1010 0000B
#include "include.h"
#define EE_SCL_PIN PTB2 //模拟IIC的SCL信号 1.修改引脚即可修改IIC接口
#define EE_SDA_PIN PTC13 //模拟IIC的SDA信号
#define EE_SDA_IN() GPIO_PDDR_REG(GPIOX[EE_SDA_PIN>> 5]) &= ~(1 << (EE_SDA_PIN&0x1f)); //GPIO_PinSetDir(EE_SDA_PIN, 0); //输入
#define EE_SDA_OUT() GPIO_PDDR_REG(GPIOX[EE_SDA_PIN>> 5]) |= (1 << (EE_SDA_PIN&0x1f)); //GPIO_PinSetDir(EE_SDA_PIN, 1); //输出
#define EE_SCK_OUT() GPIO_PDDR_REG(GPIOX[EE_SCL_PIN>> 5]) |= (1 << (EE_SCL_PIN&0x1f)); //GPIO_PinSetDir(EE_SDA_PIN, 1); //输出
#define EE_IIC_SCL PTB2_OUT //SCL 2.修改引脚即可修改IIC接口
#define EE_IIC_SDA PTC13_OUT //SDA
#define EE_READ_SDA PTC13_INT //输入SDA
/******************************************************************************
*函 数:void EE_IIC_Delay(void)
*功 能:IIC延时
*参 数:无
*返回值:无
*备 注: 移植时只需要将EE_IIC_Delay()换成自己的延时即可
*******************************************************************************/
void EE_IIC_Delay(uint8_t us)
{
for(int i = 0; i < 20; i++)
{
__asm("NOP");//core bus 160M 情况下大概IIC速率 400K
}
}
/******************************************************************************
*函 数:void IIC_Init(void)
*功 能:IIC初始化
*参 数:无
*返回值:无
*备 注:无
*******************************************************************************/
void EE_IIC_Init(void)
{
gpio_init(EE_SCL_PIN,GPO,1) ;
gpio_init(EE_SDA_PIN,GPO,1) ;
EE_SCK_OUT();
EE_SDA_OUT();
EE_IIC_SCL=1;
EE_IIC_SDA=1;
}
void EE_IIC_Start(void)
{
EE_SDA_OUT(); //sda线输出
EE_IIC_SDA=1;
EE_IIC_SCL=1;
EE_IIC_Delay(4);
EE_IIC_SDA=0; //START:when CLK is high,DATA change form high to low
EE_IIC_Delay(4);
EE_IIC_SCL=0; //钳住I2C总线,准备发送或接收数据
}
void EE_IIC_Stop(void)
{
EE_SDA_OUT(); //sda线输出
EE_IIC_SCL=0;
EE_IIC_SDA=0; //STOP:when CLK is high DATA change form low to high
EE_IIC_Delay(4);
EE_IIC_SCL=1;
EE_IIC_SDA=1; //发送I2C总线结束信号
EE_IIC_Delay(4);
}
uint8_t EE_IIC_WaitAck(void)
{
uint8_t ucErrTime=0;
EE_SDA_IN(); //SDA设置为输入 (从机给一个低电平做为应答)
EE_IIC_SDA=1;EE_IIC_Delay(1);
EE_IIC_SCL=1;EE_IIC_Delay(1);;
while(EE_READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
EE_IIC_Stop();
return 1;
}
}
EE_IIC_SCL=0; //时钟输出0
return 0;
}
void EE_IIC_Ack(void)
{
EE_IIC_SCL=0;
EE_SDA_OUT();
EE_IIC_SDA=0;
EE_IIC_Delay(1);
EE_IIC_SCL=1;
EE_IIC_Delay(2);
EE_IIC_SCL=0;
}
void EE_IIC_NAck(void)
{
EE_IIC_SCL=0;
EE_SDA_OUT();
EE_IIC_SDA=1;
EE_IIC_Delay(1);
EE_IIC_SCL=1;
EE_IIC_Delay(1);
EE_IIC_SCL=0;
}
void EE_IIC_SendByte(uint8_t data)
{
uint8_t t;
EE_SDA_OUT();
EE_IIC_SCL=0; //拉低时钟开始数据传输
for(t=0;t<8;t++)
{
EE_IIC_SDA=(data&0x80)>>7;
EE_IIC_Delay(1);
EE_IIC_SCL=1;
data<<=1;
EE_IIC_Delay(1);
EE_IIC_SCL=0;
}
EE_IIC_Delay(1);
}
void EE_IIC_PageSendByte(uint16_t data)
{
uint8_t t,DataIn;
EE_SDA_OUT();
EE_IIC_SCL=0; //拉低时钟开始数据传输
DataIn=data&0xff;
for(t=0;t<8;t++)
{
EE_IIC_SDA=(DataIn&0x80)>>7;
EE_IIC_Delay(1);
EE_IIC_SCL=1;
data<<=1;
EE_IIC_Delay(1);
EE_IIC_SCL=0;
}
DataIn=(data>>8)&0xff;
for(t=0;t<8;t++)
{
EE_IIC_SDA=(DataIn&0x80)>>7;
EE_IIC_Delay(1);
EE_IIC_SCL=1;
data<<=1;
EE_IIC_Delay(1);
EE_IIC_SCL=0;
}
EE_IIC_Delay(1);
}
uint8_t EE_IIC_ReadByte(uint8_t ack)
{
uint8_t i,receive=0;
EE_SDA_IN(); //SDA设置为输入模式 等待接收从机返回数据
for(i=0;i<8;i++ )
{
EE_IIC_SCL=0;
EE_IIC_Delay(1);
EE_IIC_SCL=1;
receive<<=1;
if(EE_READ_SDA)receive++; //从机发送的电平
EE_IIC_Delay(1);
}
if(ack)
EE_IIC_Ack(); //发送ACK
else
EE_IIC_NAck(); //发送nACK
return receive;
}
uint8_t EE_IIC_ReadByteFromSlave(uint8_t I2C_Addr,uint8_t reg,uint8_t *buf)
{
EE_IIC_Start();
EE_IIC_SendByte(I2C_Addr); //发送从机地址
if(EE_IIC_WaitAck()) //如果从机未应答则数据发送失败
{
EE_IIC_Stop();
return 1;
}
EE_IIC_SendByte(reg); //发送寄存器地址
EE_IIC_WaitAck();
EE_IIC_Start();
EE_IIC_SendByte(I2C_Addr+1); //进入接收模式
EE_IIC_WaitAck();
*buf=EE_IIC_ReadByte(0);
EE_IIC_Stop(); //产生一个停止条件
return 0;
}
uint8_t EE_EE_IIC_SendByteToSlave(uint8_t I2C_Addr,uint8_t reg,uint8_t data)
{
EE_IIC_Start();
EE_IIC_SendByte(I2C_Addr); //发送从机地址
if(EE_IIC_WaitAck())
{
EE_IIC_Stop();
return 1; //从机地址写入失败
}
EE_IIC_SendByte(reg); //发送寄存器地址
EE_IIC_WaitAck();
EE_IIC_SendByte(data);
if(EE_IIC_WaitAck())
{
EE_IIC_Stop();
return 1; //数据写入失败
}
EE_IIC_Stop(); //产生一个停止条件
return 0;
}
uint8_t EE_IIC_WriteByteToSlave(uint8_t I2C_Addr,uint8_t reg,uint8_t data)
{
EE_IIC_Start();
EE_IIC_SendByte(I2C_Addr); //发送从机地址
if(EE_IIC_WaitAck())
{
EE_IIC_Stop();
return 1; //从机地址写入失败
}
EE_IIC_SendByte(reg); //发送寄存器地址
EE_IIC_WaitAck();
EE_IIC_SendByte(data);
if(EE_IIC_WaitAck())
{
EE_IIC_Stop();
return 1; //数据写入失败
}
EE_IIC_Stop(); //产生一个停止条件
return 0;
}
uint8_t EE_IIC_ReadMultByteFromSlave(uint8_t dev, uint8_t reg, uint8_t length, uint8_t *data)
{
uint8_t count = 0;
uint8_t temp;
EE_IIC_Start();
EE_IIC_SendByte(dev); //发送从机地址
if(EE_IIC_WaitAck())
{
EE_IIC_Stop();
return 1; //从机地址写入失败
}
EE_IIC_SendByte(reg); //发送寄存器地址
EE_IIC_WaitAck();
EE_IIC_Start();
EE_IIC_SendByte(dev+1); //进入接收模式
EE_IIC_WaitAck();
for(count=0;count<length;count++)
{
if(count!=(length-1))
temp = EE_IIC_ReadByte(1); //带ACK的读数据
else
temp = EE_IIC_ReadByte(0); //最后一个字节NACK
data[count] = temp;
}
EE_IIC_Stop(); //产生一个停止条件
return 0;
}
/******************************************************************************
*函 数:uint8_t IICwriteBytes(uint8_t dev, uint8_t reg, uint8_t length, uint8_t* data)
*功 能:将多个字节写入指定设备 指定寄存器
*参 数:dev 目标设备地址
reg 寄存器地址
length 要写的字节数
*data 要写入的数据将要存放的指针
*返回值:1成功 0失败
*备 注:无
*******************************************************************************/
uint8_t EE_IIC_WriteMultByteToSlave(uint8_t dev, uint8_t reg, uint8_t length, uint8_t* data)
{
uint8_t count = 0;
EE_IIC_Start();
EE_IIC_SendByte(dev); //发送从机地址
if(EE_IIC_WaitAck())
{
EE_IIC_Stop();
return 1; //从机地址写入失败
}
EE_IIC_SendByte(reg&0xff); //发送寄存器地址,就是页地址
EE_IIC_WaitAck();
for(count=0;count<length;count++)
{
EE_IIC_SendByte(data[count]);
if(EE_IIC_WaitAck()) //每一个字节都要等从机应答
{
EE_IIC_Stop();
return 1; //数据写入失败
}
}
EE_IIC_Stop(); //产生一个停止条件
return 0;
}
uint8_t EEPROM_PageWrite(uint8 PageNum, uint8_t length, uint8_t* data)
{
uint8 Addr,PageAddr,i;
uint16 j;
//每页16个字节,24c08一共64页,页地址是页号*16
/*
if(PageNum<16)//0~15
{
PageAddr=16*PageNum;
Addr=0xA0;
EE_IIC_WriteMultByteToSlave(Addr,PageAddr,length,data) ;
}else if(PageNum>=16 && PageNum<=31)
{
Addr=0xA0+((((PageNum*16)>>8)&0x03)<<1);
PageAddr=(PageNum*16)&0xff;
EE_IIC_WriteMultByteToSlave(Addr,PageAddr,length,data) ;
}else if(PageNum>=32 && PageNum<=47)
{
Addr=0xA0+((((PageNum*16)>>8)&0x03)<<1);
PageAddr=(PageNum*16)&0xff;
EE_IIC_WriteMultByteToSlave(Addr,PageAddr,length,data) ;
}else if(PageNum>=48 && PageNum<=63)
{
Addr=0xA0+((((PageNum*16)>>8)&0x03)<<1);
PageAddr=(PageNum*16)&0xff;
EE_IIC_WriteMultByteToSlave(Addr,PageAddr,length,data) ;
}*/
//cht- eeprom进行写的时候,MP进行接地处理
EEPROMMP(0);
// vTaskDelay( 2/portTICK_RATE_MS );
EE_IIC_Delay(20);
DisableInterrupts;
Addr=0xA0+((((PageNum*16)>>8)&0x07)<<1);
PageAddr=(PageNum*16)&0xff;
EE_IIC_WriteMultByteToSlave(Addr,PageAddr,length,data) ;
EnableInterrupts;
for(i=0;i<6;i++)
{
for(j=0;j<500;j++)
{
EE_IIC_Delay(1);
}
// vTaskDelay( 1/portTICK_RATE_MS );
}
EEPROMMP(1);//cht-恢复高电平,可以读
return 1;
}
uint8_t EEPROM_PageRead(uint8 PageNum, uint8_t length, uint8_t* data)
{
uint8 Addr,PageAddr;
//每页16个字节,24c08一共64页,页地址是页号*16
/*if(PageNum<16)//0~15
{
PageAddr=16*PageNum;
Addr=0xA0;
EE_IIC_ReadMultByteFromSlave(Addr,PageAddr,length,data) ;
}else if(PageNum>=16 && PageNum<=31)
{
// PageAddr=(16*PageNum)%256;
Addr=0xA0+((((PageNum*16)>>8)&0x03)<<1);
PageAddr=(PageNum*16)&0xff;
EE_IIC_ReadMultByteFromSlave(Addr,PageAddr,length,data) ;
}else if(PageNum>=32 && PageNum<=47)
{
Addr=0xA0+((((PageNum*16)>>8)&0x03)<<1);
PageAddr=(PageNum*16)&0xff;
EE_IIC_ReadMultByteFromSlave(Addr,PageAddr,length,data) ;
}else if(PageNum>=48 && PageNum<=63)
{
Addr=0xA0+((((PageNum*16)>>8)&0x03)<<1);
PageAddr=(PageNum*16)&0xff;
EE_IIC_ReadMultByteFromSlave(Addr,PageAddr,length,data) ;
}*/
Addr=0xA0+((((PageNum*16)>>8)&0x07)<<1);
PageAddr=(PageNum*16)&0xff;
EE_IIC_ReadMultByteFromSlave(Addr,PageAddr,length,data) ;
return 1;
}
#define _24C08 4
#define PAGE_SIZE 16
#define PAGE_SIZE_REM 0x0F
#define MEM_SIZE 0x03ff
#define MAX_ADDR ( (MEM_SIZE+1)*DEV_24CXX_NUM-1 )
//芯片常量
#define DEV_24CXX_MAIN_ADDR 0xA0
#define DEV_24CXX_READ 0x01
#define DEV_24CXX_WRITE 0x00
#define IIC_SEND_ACK 0x10
#define IIC_SEND_NOACK 0x11
#define IIC_STATE_NOACK 0x20
#define IIC_STATE_ACK 0x21
uint16 HardAddr;
void _24CXXX_AddrProcess(unsigned int Addr)
{
HardAddr = 0x00;
}
void _24CXXX_WriteByte(unsigned int Addr,unsigned char Data)
{
_24CXXX_AddrProcess(Addr);
Addr &= MEM_SIZE;
EE_IIC_Start();
EE_IIC_SendByte(DEV_24CXX_MAIN_ADDR|(HardAddr<<1)|DEV_24CXX_WRITE);
EE_IIC_SendByte( ((unsigned char)(Addr>>8)) );
EE_IIC_SendByte( ((unsigned char)Addr) );
EE_IIC_SendByte( Data );
EE_IIC_Stop();
}
unsigned char _24CXXX_ReadByte(unsigned int Addr)
{
unsigned char temp;
_24CXXX_AddrProcess(Addr);
Addr &= MEM_SIZE;
EE_IIC_Start();
EE_IIC_SendByte(DEV_24CXX_MAIN_ADDR|(HardAddr<<1)|DEV_24CXX_WRITE);
EE_IIC_SendByte( ((unsigned char)(Addr>>8)) );
EE_IIC_SendByte( ((unsigned char)Addr) );
EE_IIC_Start();
EE_IIC_SendByte(DEV_24CXX_MAIN_ADDR|(HardAddr<<1)|DEV_24CXX_READ);
temp = EE_IIC_ReadByte(IIC_SEND_NOACK);
EE_IIC_Stop();
return temp;
}
void _24CXXX_WriteBytes(unsigned int StartAddr,unsigned char *Data,int DataNum)
{
unsigned char Num;
unsigned int i;
unsigned int y;
unsigned int yy;
_24CXXX_AddrProcess(StartAddr);
yy = StartAddr & MEM_SIZE;
y = yy&PAGE_SIZE_REM; //=StartAddr/PAGE_SIZE
y = PAGE_SIZE - y; //要写的第一页中有多少个数据
if( y <= PAGE_SIZE ) //写第一页(可能不到一页,以使页对齐)
{
EE_IIC_Start();
EE_IIC_SendByte( DEV_24CXX_MAIN_ADDR|(HardAddr<<1)|DEV_24CXX_WRITE );
EE_IIC_SendByte( ((unsigned char)(yy>>8)) );
EE_IIC_SendByte( ((unsigned char)yy) );
for(i=0;i<y;i++)
{
if( i>=DataNum )
{
break;
}
EE_IIC_SendByte( *(Data++) );
}
}
IIC_Stop();
//DelayMs(10);
vTaskDelay( 20/portTICK_RATE_MS );
DataNum -= y; //还有多少数据未写
i = 0;
Num = 0; //用于记录已经写了多少页
while( DataNum>0 ) //数据未写完
{
if( i == 0 )
{
yy = StartAddr+y+Num*PAGE_SIZE;
_24CXXX_AddrProcess(yy);
yy = yy & MEM_SIZE;
EE_IIC_Start();
EE_IIC_SendByte( DEV_24CXX_MAIN_ADDR|(HardAddr<<1)|DEV_24CXX_WRITE );
EE_IIC_SendByte( ((unsigned char)(yy>>8)) );
EE_IIC_SendByte( ((unsigned char)(yy)) );
}
EE_IIC_SendByte( *(Data++) );
i++;
if(i>=PAGE_SIZE ) //如果一页写完
{
i = 0;
Num++;
IIC_Stop();
vTaskDelay( 20/portTICK_RATE_MS );
}
DataNum--;
}
if(i!=0x0000) //如果写结束时,并未到达页的最后,则需要补上停止位
{
EE_IIC_Stop();
vTaskDelay( 20/portTICK_RATE_MS );
}
}
void _24CXXX_ReadBytes(unsigned int StartAddr,unsigned char *Data,int DataNum)
{
unsigned char Num;
unsigned int i;
unsigned int y;
unsigned int yy;
_24CXXX_AddrProcess(StartAddr);
yy = StartAddr & MEM_SIZE;
y = yy&PAGE_SIZE_REM; //=StartAddr/PAGE_SIZE
y = PAGE_SIZE - y; //要写的第一页中有多少个数据
if( y <= PAGE_SIZE ) //写第一页(可能不到一页,以使页对齐)
{
EE_IIC_Start();
EE_IIC_SendByte( DEV_24CXX_MAIN_ADDR|(HardAddr<<1)|DEV_24CXX_WRITE );
EE_IIC_SendByte( ((unsigned char)(StartAddr>>8)) );
EE_IIC_SendByte( ((unsigned char)StartAddr) );
EE_IIC_Start();
EE_IIC_SendByte(DEV_24CXX_MAIN_ADDR|(HardAddr<<1)|DEV_24CXX_READ);
for(i=0;i<y;i++)
{
if( i>=DataNum )
{
break;
}
if( (i==DataNum-1)||(i==y-1) ) //如果是该页的最后一个数据,则NOACK
*Data = EE_IIC_ReadByte(IIC_SEND_NOACK);
else
*Data = EE_IIC_ReadByte(IIC_SEND_ACK);
Data++;
}
}
EE_IIC_Stop();
//DelayUs(20);
DataNum -= y; //还有多少数据未写
i = 0;
Num = 0; //用于记录已经写了多少页
while( DataNum>0 ) //数据未写完
{
if( i == 0 )
{
yy = StartAddr+y+Num*PAGE_SIZE;
_24CXXX_AddrProcess(yy);
yy = yy & MEM_SIZE;
EE_IIC_Start();
EE_IIC_SendByte( DEV_24CXX_MAIN_ADDR|(HardAddr<<1)|DEV_24CXX_WRITE );
EE_IIC_SendByte( (unsigned char)(yy>>8) );
EE_IIC_SendByte( (unsigned char)(yy) );
EE_IIC_Start();
EE_IIC_SendByte(DEV_24CXX_MAIN_ADDR|(HardAddr<<1)|DEV_24CXX_READ);
}
if( (i==PAGE_SIZE-1)||(DataNum==1) ) //如果是该页或要读的最后一个数据,则NOACK
*Data = EE_IIC_ReadByte(IIC_SEND_NOACK);
else
*Data = EE_IIC_ReadByte(IIC_SEND_ACK);
Data++;
i++;
if(i>=PAGE_SIZE ) //如果一页写完
{
i = 0;
Num++;
EE_IIC_Stop();
//DelayUs(20);
}
DataNum--;
}
if(i!=0x0000) //如果写结束时,并未到达页的最后,则需要补上停止位
{
EE_IIC_Stop();
}
}
区分硬件IIC与软件IIC
一般使用软件模拟IIC,,可移植性高
一:模拟IIC与硬件IIC定义?
模拟I2C一般是用GPIO管脚,用软件控制管脚状态以模拟I2C通信波形。
硬件I2C对应芯片上的I2C外设,有相应I2C驱动电路,其所使用的I2C管脚也是专用。
二:优缺点
1.硬件I2C的效率要远高于软件的,而软件I2C由于不受管脚限制,接口比较灵活。
2.模拟I2C 是通过GPIO,软件模拟寄存器的工作方式,而硬件(固件)I2C是直接调用内部寄存器进行配置。如果要从具体硬件上来看,可以去看下芯片手册。因为固件I2C的端口是固定的,所以会有所区别。
三:如何区分它们
可以看底层配置,比如IO口配置,如果配置了IO口的功能(IIC功能)那就是固件IIC,否则就是模拟
可以看IIC写函数,看里面有木有调用现成的函数或者给某个寄存器赋值,如果有,则肯定是固件IIC功能,没有的话肯定是数据一个bit一个bit模拟发生送的,肯定用到了循环,则为模拟。
根据代码量判断,模拟的代码量肯定比固件的要大。