AT24C02是一个2K位串行CMOS E2PROM, 内部含有256个8位字节存储单元,该器件通过IIC总线接口进行操作, AT24C02把存储空间分为 32 页,每页可存储8个字节的数据,具有硬件数据写保护功能,100万次擦写,数据保持100年。
当你的产品有存储一些关键且易变的参数时,内置的Flash频繁在程序运行时擦写会有一些风险,而片外挂接一些Flash数据量小又没有必要,所以片外挂一个E2PROM是一种常见的解决办法,本例介绍在STM32平台上利用其提供的I2C控制器来实现对E2PROM的读写操作。
文章目录
概念说明
实现原理
嵌入式程序
概念说明
- 页:页(Page)、扇区(Sector)、块(Block)是存储器空间的单位,定义来源于早期的软盘、硬盘等存储器发展而来,单位大小:页>扇区>块。不同厂家的、不同类型存储器的页大小不同,在AT24C02中一页等于8字节数据。
- IIC:IIC(Inter-Integrated Circuit)是一种通用的总线协议,总线上只有一个Master,通信全由Master发起。对于硬件设计人员来说,只需要2个管脚(时钟SCL与数据SDA),极少的连接线和面积,就可以实现芯片间的通讯,对于软件开发者来说,可以使用同一个I2C驱动库,来实现实现不同器件的驱动,大大减少了软件的开发时间。I2C协议包含四种信号:起始、停止、应答和非应答信号。
- 起始:I2C协议规定,SCL处于高电平时,SDA由高到低变化,这种信号是起始信号。表示要开始传输数据。
- 停止:I2C协议规定,SCL处于高电平,SDA由低到高变化,这种信号是停止信号。
- ACK与NACK:应答信号出现在1个字节传输完成之后,即第9个SCL时钟周期内,此时Master需要释放SDA总线,把总线控制权交给Slave,由于上拉电阻的作用,此时总线为高电平,如果从机正确的收到了主机发来的数据,会把SDA拉低,表示应答响应(ACK)。如果没有拉低,则无应答(NACK),下图是I2C总线电平时序示意图:
实现原理
嵌入式程序跑在STM32平台,封装正确的初始化I2C控制器以及读写接口供上层按地址读写EEPROM即可。原理示意图如下:
SCL和SDK两条线都接了10K的上拉电阻,MCU需要把这两个引脚设置为开漏输出(低电平,高阻态)而不是推挽输出,这样才能实现Master与Slave的ACK握手机制。
嵌入式程序
嵌入式代码构成也比较简单:
- I2C控制器初始化,包括IO口配置
- E2PROM写函数
- E2PROM读函数
首先是初始化部分代码:
//i2c IO口初始化
static void I2C_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* 使能与 I2C1 有关的时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
/* 配置SCL SDA引脚速率输出方式 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 开漏输出
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
//i2c 控制器配置
static void I2C_Mode_Configu(void)
{
I2C_InitTypeDef I2C_InitStructure;
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; /* I2C 配置 */
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; /* 高电平数据稳定,低电平数据变化 SCL 时钟线的占空比 */
I2C_InitStructure.I2C_OwnAddress1 = I2C1_OWN_ADDRESS7;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; /* I2C 的寻址模式 */
I2C_InitStructure.I2C_ClockSpeed = I2C_Speed; /* 通信速率 */
I2C_Init(I2C1, &I2C_InitStructure); /* I2C1 初始化 */
I2C_Cmd(I2C1, ENABLE); /* 使能 I2C1 */
}
//AT24C02 驱动初始化
void I2C_EE_Init(void)
{
I2C_GPIO_Config();
I2C_Mode_Config();
/* 根据头文件 i2c_ee. 14 h 中的定义来选择 EEPROM 要写入的地址 */
#ifdef EEPROM_Block0_ADDRESS /* 选择 EEPROM Block0 来写入 */
EEPROM_ADDRESS = EEPROM_Block0_ADDRESS;
#endif
#ifdef EEPROM_Block1_ADDRESS /* 选择 EEPROM Block1 来写入 */
EEPROM_ADDRESS = EEPROM_Block1_ADDRESS;
#endif
#ifdef EEPROM_Block2_ADDRESS /* 选择 EEPROM Block2 来写入 */
EEPROM_ADDRESS = EEPROM_Block2_ADDRESS;
#endif
#ifdef EEPROM_Block3_ADDRESS /* 选择 EEPROM Block3 来写入 */
EEPROM_ADDRESS = EEPROM_Block3_ADDRESS;
#endif
}
然后是写EEPROM实现代码:
//AT24C02页写操作
void I2C_EE_PageWrite(u8* pBuffer, u8 WriteAddr, u8 NumByteToWrite)
{
while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));
I2C_GenerateSTART(I2C1, ENABLE); /* Send START condition */
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); /* Test on EV5 and clear it */
I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter); /* Send EEPROM address for write */
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); /* Test on EV6 and clear it */
I2C_SendData(I2C1, WriteAddr); /* Send the EEPROM's internal address to write to */
while (! I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); /* Test on EV8 and clear it */
while (NumByteToWrite--) /* While there is data to be written */
{
I2C_SendData(I2C1, *pBuffer); /* Send the current byte */
pBuffer++; /* Point to the next byte to be written */
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED) ); /* Test on EV8 and clear it */
}
I2C_GenerateSTOP(I2C1, ENABLE); /* Send STOP condition */
}
//AT24C02等待页写完毕
void I2C_EE_WaitEepromStandbyState(void)
{
vu16 SR1_Tmp = 0;
do
{
I2C_GenerateSTART(I2C1, ENABLE); /* Send START condition */
SR1_Tmp = I2C_ReadRegister(I2C1, I2C_Register_SR1); /* Read I2C1 SR1 register */
I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter); /* Send EEPROM address for write */
}
while (!(I2C_ReadRegister(I2C1, I2C_Register_SR1) & 0x0002));
I2C_ClearFlag(I2C1, I2C_FLAG_AF); /* Clear AF flag */
I2C_GenerateSTOP(I2C1, ENABLE); /* STOP condition */
}
//指定起始地址写入数据
void I2C_EE_BufferWrite(u8* pBuffer, u8 WriteAddr, u16 NumByteToWrite)
{
u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;
Addr = WriteAddr % I2C_PageSize;
count = I2C_PageSize - Addr;
NumOfPage = NumByteToWrite / I2C_PageSize;
NumOfSingle = NumByteToWrite % I2C_PageSize;
/* If WriteAddr is I2C_PageSize aligned */
if (Addr == 0)
{
/* If NumByteToWrite < I2C_PageSize */
if (NumOfPage == 0)
{
I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
I2C_EE_WaitEepromStandbyState();
}
/* If NumByteToWrite > I2C_PageSize */
else
{
while (NumOfPage--)
{
I2C_EE_PageWrite(pBuffer, WriteAddr, I2C_PageSize);
I2C_EE_WaitEepromStandbyState();
WriteAddr += I2C_PageSize;
pBuffer += I2C_PageSize;
}
if (NumOfSingle != 0)
{
I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
I2C_EE_WaitEepromStandbyState();
}
}
}
/* If WriteAddr is not I2C_PageSize aligned */
else
{
/* If NumByteToWrite < I2C_PageSize */
if (NumOfPage == 0)
{
I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
I2C_EE_WaitEepromStandbyState();
}
/* If NumByteToWrite > I2C_PageSize */
else
{
NumByteToWrite -= count;
NumOfPage = NumByteToWrite / I2C_PageSize;
NumOfSingle = NumByteToWrite % I2C_PageSize;
if (count != 0)
{
I2C_EE_PageWrite(pBuffer, WriteAddr, count);
I2C_EE_WaitEepromStandbyState();
WriteAddr += count;
pBuffer += count;
}
while (NumOfPage--)
{
I2C_EE_PageWrite(pBuffer, WriteAddr, I2C_PageSize);
I2C_EE_WaitEepromStandbyState();
WriteAddr += I2C_PageSize;
pBuffer += I2C_PageSize;
}
if (NumOfSingle != 0)
{
I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
I2C_EE_WaitEepromStandbyState();
}
}
}
}
最后是读EEPROM的实现:
//EEprom读取操作
void I2C_EE_BufferRead(u8* pBuffer, u8 ReadAddr, u16 NumByteToRead)
{
//*((u8 *)0x4001080c) |=0x80;
while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); // Added by Najoua
/* Send START condition */
I2C_GenerateSTART(I2C1, ENABLE);
//*((u8 *)0x4001080c) &=~0x80;
/* Test on EV5 and clear it */
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
/* Send EEPROM address for write */
I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Transmitter);
/* Test on EV6 and clear it */
while (!I2C_CheckEvent(I2C1,
I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
/* Clear EV6 by setting again the PE bit */
I2C_Cmd(I2C1, ENABLE);
/* Send the EEPROM's internal address to write to */
I2C_SendData(I2C1, ReadAddr);
/* Test on EV8 and clear it */
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
/* Send STRAT condition a second time */
I2C_GenerateSTART(I2C1, ENABLE);
/* Test on EV5 and clear it */
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
/* Send EEPROM address for read */
I2C_Send7bitAddress(I2C1, EEPROM_ADDRESS, I2C_Direction_Receiver);
/* Test on EV6 and clear it */
while (!I2C_CheckEvent(I2C1,
I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
/* While there is data to be read */
while (NumByteToRead)
{
if (NumByteToRead == 1)
{
/* Disable Acknowledgement */
I2C_AcknowledgeConfig(I2C1, DISABLE);
/* Send STOP Condition */
I2C_GenerateSTOP(I2C1, ENABLE);
}
/* Test on EV7 and clear it */
if (I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED))
{
/* Read a byte from the EEPROM */
*pBuffer = I2C_ReceiveData(I2C1);
/* Point to the next location where the byte read will be
saved */
pBuffer++;
/* Decrement the read bytes counter */
NumByteToRead--;
}
}
/* Enable Acknowledgement to be ready for another reception */
I2C_AcknowledgeConfig(I2C1, ENABLE);
}
十六宿舍 原创作品,转载必须标注原文链接。
©2023 Yang Li. All rights reserved.
欢迎关注 『十六宿舍』,大家喜欢的话,给个👍,更多关于嵌入式相关技术的内容持续更新中。