关于此文一些名词术语不太理解的,可以去看我这篇博文
→ 《STM32F10x_模拟I2C读写EEPROM(1)》
读写E2函数(带备份区+校验和判断),可以去看我这篇博文
→ 《STM32F10x_模拟I2C读写EEPROM(3)(读写E2备份区 + 校验位 + 完整代码 + 应用实例)》
E2的中文资料可以到我博客资源里下载,没有积分下载的,可以评论Ding我o( ̄▽ ̄)ブ
// I2C引脚
#define PORT_I2C_SCL GPIOx
#define PORT_I2C_SDA GPIOx
#define PIN_I2C_SCL GPIO_Pin_x
#define PIN_I2C_SDA GPIO_Pin_x
// 控制 SDA / SCL 高低电平
#define I2C_SCL_LOW (PORT_I2C_SCL->BRR = PIN_I2C_SCL)
#define I2C_SCL_HIGH (PORT_I2C_SCL->BSRR = PIN_I2C_SCL)
#define I2C_SDA_LOW (PORT_I2C_SDA->BRR = PIN_I2C_SDA)
#define I2C_SDA_HIGH (PORT_I2C_SDA->BSRR = PIN_I2C_SDA)
// 读 SDA 电平状态
#define I2C_SDA_READ (PORT_I2C_SDA->IDR & PIN_I2C_SDA)
// 应答位信息
#define I2C_ACK 0 //应答
#define I2C_NOACK 1 //非应答
/*
1、"地址长度"根据芯片型号不同略有不同
8位: AT24C01、AT24C02
16位: AT24C04、AT24C08、AT24C16、AT24C32、AT24C64、AT24C128、AT24C256、AT24C512
2、"页长度"根据芯片型号不同略有不同
8字节: AT24C01、AT24C02
16字节: AT24C04、AT24C08、AT24C16
32字节: AT24C32、AT24C64
64字节: AT24C128、AT24C256
128字节: AT24C512
*/
// 此文的E2型号 - AT24C512
#define EEPROM_WORD_ADDR_SIZE 16 //地址长度
#define EEPROM_PAGE_SIZE 128 //页长度
#define EEPROM_DEV_ADDR 0xA0 //地址(设备地址:与A2、A1、A0有关)
#define EEPROM_WR 0x00 //写
#define EEPROM_RD 0x01 //读
/************************************************
函数名称 : I2C_Delay
功 能 : I2C延时(非标准延时,请根据MCU速度 调节大小)
参 数 : 无
返 回 值 : 无
*************************************************/
static void I2C_Delay(void)
{
uint16_t cnt = 100;
while(cnt--);
}
/************************************************
函数名称 : I2C_Start
功 能 : I2C开始
参 数 : 无
返 回 值 : 无
*************************************************/
void I2C_Start(void)
{
I2C_SCL_HIGH; //SCL高
I2C_Delay();
I2C_SDA_HIGH; //SDA高
I2C_Delay();
I2C_SDA_LOW; //SDA低
I2C_Delay();
I2C_SCL_LOW; //SCL低
I2C_Delay();
}
/************************************************
函数名称 : I2C_Stop
功 能 : I2C停止
参 数 : 无
返 回 值 : 无
*************************************************/
void I2C_Stop(void)
{
I2C_SDA_LOW; //SDA低
I2C_Delay();
I2C_SCL_HIGH; //SCL高
I2C_Delay();
I2C_SDA_HIGH; //SDA高
I2C_Delay();
}
/************************************************
函数名称 : I2C_SDA_SetInput
功 能 : I2C_SDA设置为输入
参 数 : 无
返 回 值 : 无
*************************************************/
void I2C_SDA_SetInput(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = PIN_I2C_SDA;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // I2C_SDA设置为 浮空输入
GPIO_Init(PORT_I2C_SDA, &GPIO_InitStructure);
}
/************************************************
函数名称 : I2C_SDA_SetOutput
功 能 : I2C_SDA设置为输出
参 数 : 无
返 回 值 : 无
*************************************************/
void I2C_SDA_SetOutput(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = PIN_I2C_SDA;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; // I2C_SDA设置为 开漏输出
GPIO_Init(PORT_I2C_SDA, &GPIO_InitStructure);
}
/************************************************
函数名称 : I2C_GetAck
功 能 : I2C主机读取应答(或非应答)位
参 数 : 无
返 回 值 : I2C_ACK ----- 应答
I2C_NOACK --- 非应答
*************************************************/
uint8_t I2C_GetAck(void)
{
uint8_t ack;
I2C_SCL_LOW; //SCL低(此时从机可变化SDA电平产生应答位信息)
I2C_Delay();
I2C_SDA_SetInput(); //SDA配置为输入模式(切换SDA方向)
I2C_SCL_HIGH; //SCL高(SCL上升沿时,从机发应答位信息到SDA线上)
I2C_Delay();
if(I2C_SDA_READ) //读取从机应答位信息
ack = I2C_NOACK; //非应答
else
ack = I2C_ACK; //应答
I2C_SCL_LOW; //SCL低(防止误操作起始 / 结束信号)
I2C_Delay();
I2C_SDA_SetOutput(); //SDA配置为输出模式(切换SDA方向)
return ack; //返回应答位
}
主机(MCU)读数据的时候,主机产生应答,从机(E2)读取应答位进行检测;
/************************************************
函数名称 : I2C_PutAck
功 能 : I2C主机产生应答(或非应答)位
参 数 : I2C_ACK ----- 应答
I2C_NOACK --- 非应答
返 回 值 : 无
*************************************************/
void I2C_PutAck(uint8_t Ack)
{
I2C_SCL_LOW; //SCL低(此时主机可变化SDA电平产生应答位信息)
I2C_Delay();
if(I2C_ACK == Ack)
I2C_SDA_LOW; //主机产生 → 应答
else
I2C_SDA_HIGH; //主机产生 → 非应答
I2C_Delay();
I2C_SCL_HIGH; //SCL高 (SCL上升沿时,主机发应答位信息到SDA线上)
I2C_Delay();
I2C_SCL_LOW; //SCL低(防止误操作起始 / 结束信号)
I2C_Delay();
}
主机每写完一个字节后,读取从机返回的应答位:
/************************************************
函数名称 : I2C_WriteByte
功 能 : I2C写一字节
参 数 : Data -------- 数据
返 回 值 : I2C_ACK ----- 应答
I2C_NOACK --- 非应答
*************************************************/
uint8_t I2C_WriteByte(uint8_t Data)
{
uint8_t cnt;
// 发送一个字节(8位)
for(cnt = 0; cnt < 8; cnt++)
{
I2C_SCL_LOW; //SCL低(SCL为低电平时,主机变化SDA有效,产生SDA数据)
I2C_Delay();
if(Data & 0x80)
I2C_SDA_HIGH; //SDA高
else
I2C_SDA_LOW; //SDA低
Data <<= 1;
I2C_Delay();
I2C_SCL_HIGH; //SCL高(SCL上升沿,主机发送数据出去)
I2C_Delay();
}
I2C_SCL_LOW; //SCL低(防止误操作起始 / 结束信号)
I2C_Delay();
return I2C_GetAck(); //主机读取应答位
}
/************************************************
函数名称 : I2C_ReadByte
功 能 : I2C读一字节
参 数 : ack --------- 产生应答(或者非应答)位
返 回 值 : data -------- 读取的一字节数据
*************************************************/
uint8_t I2C_ReadByte(uint8_t ack)
{
uint8_t cnt;
uint8_t data;
I2C_SCL_LOW; //SCL低(此时从机可变化SDA电平产生数据)
I2C_Delay();
I2C_SDA_SetInput(); //SDA配置为输入模式(切换SDA方向)
for(cnt = 0; cnt < 8; cnt++)
{
I2C_SCL_HIGH; //SCL高(SCL上升沿时,从机发数据到SDA线上)
I2C_Delay();
data <<= 1;
if(I2C_SDA_READ) //SDA为高(数据有效)
data |= 0x01;
I2C_SCL_LOW; //SCL低(防止误操作起始 / 结束信号)
I2C_Delay();
}
I2C_SDA_SetOutput(); //SDA配置为输出模式(切换SDA方向)
I2C_PutAck(ack); //产生应答(或者非应答)位
return data; //返回数据
}
一次页写操作写入的数据字节数最大值为E2的页大小;
不同型号的E2对应的页大小可能不同(下面简称页大小的值为 P ,举例:AT24C512 → P = 128);
E2 页写时序说明:
注意:如果进行页写操作时,n > p,地址计数器将自动翻转,先前写入的数据会被覆盖,所以要注意每次页写的字节数不能大于E2的页大小P,否则会影响数据的正确性。
/************************************************
函数名称 : EEPROM_WritePage
功 能 : EEPROM写页
参 数 : Addr ------ 地址
pData ----- 数据
Length -----长度(<=EEPROM_PAGE_SIZE)
返 回 值 : I2C_ACK ----应答
I2C_NOACK - 非应答
*************************************************/
uint8_t EEPROM_WritePage(uint16_t Addr, uint8_t *Data, uint16_t Length)
{
uint8_t ack;
uint8_t i;
/* 1.开始信号 */
I2C_Start();
/* 2.设备地址/写 */
ack = I2C_WriteByte(EEPROM_DEV_ADDR | EEPROM_WR);
if(I2C_NOACK == ack)
{
I2C_Stop();
return I2C_NOACK;
}
/* 3.数据地址 */
#if(8 == EEPROM_WORD_ADDR_SIZE)
ack = I2C_WriteByte((uint8_t)(Addr&0x00FF)); //数据地址(8位)
if(I2C_NOACK == ack)
{
I2C_Stop();
return I2C_NOACK;
}
#else
ack = I2C_WriteByte((uint8_t)(Addr>>8)); //数据地址(16位)
if(I2C_NOACK == ack)
{
I2C_Stop();
return I2C_NOACK;
}
ack = I2C_WriteByte((uint8_t)(Addr&0x00FF));
if(I2C_NOACK == ack)
{
I2C_Stop();
return I2C_NOACK;
}
#endif
/* 4.写一字节数据(循环) */
for(i = 0; i < Length; i++)
{
ack = I2C_WriteByte(*(Data + i));
if(I2C_NOACK == ack)
{
I2C_Stop();
return I2C_NOACK;
}
}
/* 5.停止信号 */
I2C_Stop();
return I2C_ACK;
}
/************************************************
函数名称 : EEPROM_ReadSequential
功 能 : EEPROM读一字节
参 数 : Addr ------ 地址
Data ------ 数据
Length -----长度
返 回 值 : I2C_ACK --- 应答
I2C_NOACK - 非应答
*************************************************/
uint8_t EEPROM_ReadSequential(uint16_t Addr, uint8_t *Data, uint16_t Length)
{
uint8_t ack;
uint8_t i;
/* 1.开始信号 */
I2C_Start();
/* 2.设备地址/写 */
ack = I2C_WriteByte(EEPROM_DEV_ADDR | EEPROM_WR);
if(I2C_NOACK == ack)
{
I2C_Stop();
return I2C_NOACK;
}
/* 3.数据地址 */
#if (8 == EEPROM_WORD_ADDR_SIZE)
ack = I2C_WriteByte((uint8_t)(Addr&0x00FF)); //数据地址(8位)
if(I2C_NOACK == ack)
{
I2C_Stop();
return I2C_NOACK;
}
#else
ack = I2C_WriteByte((uint8_t)(Addr>>8)); //数据地址(16位)
if(I2C_NOACK == ack)
{
I2C_Stop();
return I2C_NOACK;
}
ack = I2C_WriteByte((uint8_t)(Addr&0x00FF));
if(I2C_NOACK == ack)
{
I2C_Stop();
return I2C_NOACK;
}
#endif
/* 4.重新开始 */
I2C_Start();
/* 5.设备地址/读 */
ack = I2C_WriteByte(EEPROM_DEV_ADDR | EEPROM_RD);
if(I2C_NOACK == ack)
{
I2C_Stop();
return I2C_NOACK;
}
/* 6.读一字节数据 */
for(i = 0; i < Length - 1; i++)
{
*(Data + i) = I2C_ReadByte(I2C_ACK); //只读取1字节(产生应答)
}
*(Data + i) = I2C_ReadByte(I2C_NOACK); //只读取1字节(产生非应答)
/* 7.停止信号 */
I2C_Stop();
return I2C_ACK;
}