一、AT24C02简介
AT24C02 是一个 2K bit 的串行 EEPROM 存储器,内部含有 256 个字节。在 24C02 里面还有一个 8 字节的页写缓冲器。该设备的通信方式 I2C,通过其 SCL 和 SDA 与其他设备通信,芯片的引脚图如下图所示。
上图中有一个 WP,这个是写保护引脚,接高电平只读,接地允许读和写。每一个设备都有自己的设备地址,AT24C02 也不例外,但是 AT24C02 的设备地址是包括不可编程部分和可编程部分,可编程部分是根据上图的硬件引脚 A0、A1 和 A2 所决定。设备地址最后一位用于设置数据的传输方向,即读操作/写操作,0 是写操作,1 是读操作,具体格式如下图 所示:
二、AT24C02写时序
AT24C02 支持 字节写模式 和 页写模式。字节写模式 就是一个地址一个数据写入。页写模式 就是连续写入数据。只需要写一个地址,连续写入数据时地址会自增,但存在页的限制,超过一页时,超出数据覆盖原先写入数据。
2.1、字节写模式
主机在 I2C 总线发送第 1 个字节的数据为 AT24C02的设备地址 0xA0,用于寻找总线上找到 AT24C02,在获得 AT24C02 的应答信号之后,继续发送第 2 个字节数据,该字节数据是 AT24C02 的内存地址,再等到 AT24C02 的应答信号,主机继续发送第 3 字节数据,这里的数据即是写入在第 2 字节内存地址的数据。主机完成写操作后,可以发出停止信号,终止数据传输。
#define AT24C02_ADDRESS 0xA0
/**
* @brief AT24C02写一个字节函数
*
* @param address 待写入数据的内存地址
* @param data 待写入的数据
*/
void AT24C02_WriteOneByte(uint8_t address, uint8_t data)
{
I2C_Start(); // 1、产生起始信号
I2C_SendOneByte(AT24C02_ADDRESS); // 2、发送写操作地址
I2C_WaitAck(); // 3、等待应答信号
I2C_SendOneByte(address); // 4、发送内存地址
I2C_WaitAck(); // 5、等待应答信号
I2C_SendOneByte(data); // 6、发送数据
I2C_WaitAck(); // 7、等待应答信号
I2C_Stop(); // 8、产生停止信号
Delay_ms(10); // 9、等待AT24C02写入完成
}
2.2、页写模式
在单字节写时序时,每次写入数据时都需要先写入设备的内存地址才能实现,在页写时序中,只需要告诉 AT24C02 第一个内存地址 1,后面数据会按照顺序写入到内存地址 2,内存地址 3等,大大节省了通信时间,提高了时效性。因为 AT24C02 每次只能 8bit 数据,所以它的页大小也就是 1 字节。页写时序的操作方式跟上面的单字节写时序差不多。
/**
* @brief AT24C02在一页中写入多个字节
*
* @param address 待写入数据的内存地址
* @param pBuffer 待写入的数据
* @param length 待写入数据的个数
*
* @note AT24C02的页写模式存在页的限制,超过一页(8字节)时,超出数据覆盖原先写入数据
*/
void AT24C02_WriteOnePage(uint8_t address, uint8_t *pBuffer, uint8_t length)
{
I2C_Start(); // 1、产生起始信号
I2C_SendOneByte(AT24C02_ADDRESS); // 2、发送写操作地址
I2C_WaitAck(); // 3、等待应答信号
I2C_SendOneByte(address); // 4、发送内存地址
I2C_WaitAck(); // 5、等待应答信号
for (uint8_t i = 0; i < length; i++)
{
I2C_SendOneByte(pBuffer[i]); // 6、发送数据
I2C_WaitAck(); // 7、等待应答信号
}
I2C_Stop(); // 8、产生停止信号
Delay_ms(10); // 9、等待AT24C02写入完成
}
三、AT24C02读时序
AT24C02 支持 当前地址读模式,随机地址读模式 和 顺序读模式。AT24C02 的读操作会自动翻页。当前地址读模式 是基于上一次读/写操作的最后位置继续读出数据。随机地址读模式 是指定地址读出数据。顺序读模式 是连续读出数据。
3.1、当前地址读模式
内部地址计数器保存着上次访问时最后一个地址加 1 的值。只要芯片有电,该地址就一直保存。当读到最后页的最后字节,地址会回转到 0;当写到某页尾的最后一个字节,地址会回转到该页的首字节。接收器件地址(读/写选择位为 "1")、EEPROM 应答 ACK 后,当前地址的数据就随时钟送出。主器件无需应答 "0",但需发送停止条件。
3.2、随机地址读模式
AT24C02 读取数据的过程是一个复合的时序,其中包含写时序和读时序。先看第一个通信过程,这里是写时序,起始信号产生后,主机发送 AT24C02 设备地址 0xA0,获取从机应答信号后,接着发送需要读取的内存地址;在读时序中,起始信号产生后,主机发送 AT24C02 设备地址 0xA1,获取从机应答信号后,接着从机返回刚刚在写时序中内存地址的数据,以字节为单位传输在总线上,假如主机获取数据后返回的是应答信号,那么从机会一直传输数据,当主机发出的是非应答信号并以停止信号发出为结束,从机就会结束传输。
/**
* @brief AT24C02读一个字节函数
*
* @param address 待读取数据的内存地址
* @return uint8_t 读取的数据
*/
uint8_t AT24C02_ReadOneByte(uint8_t address)
{
uint8_t receive = 0;
I2C_Start(); // 1、产生起始信号
I2C_SendOneByte(AT24C02_ADDRESS); // 2、发送写操作地址
I2C_WaitAck(); // 3、等待应答信号
I2C_SendOneByte(address); // 4、发送内存地址
I2C_WaitAck(); // 5、等待应答信号
I2C_Start(); // 6、再次产生起始信号
I2C_SendOneByte(AT24C02_ADDRESS | 1); // 7、发送读操作地址
I2C_WaitAck(); // 8、等待应答信号
receive = I2C_ReadOneByte(1); // 9、读取数据,并发送非应答信号
I2C_Stop(); // 10、产生停止信号
return receive; // 11、返回读取的数据
}
3.3、顺序读模式
顺序读可以通过 “当前地址读” 或 “随机读” 启动。主器件接收到一个数据后,应答 ACK。只要 EEPROM 接收到 ACK,将自动增加字地址并继续随时钟发送后面的数据。若达到存储器地址末尾,地址自动回转到 0,仍可继续顺序读取数据。主器件发送非应答信号,即可结束顺序读操。
/**
* @brief AT24C02顺序读取多个字节数据函数
*
* @param address 起始地址
* @param pBuffer 保存读取的数据的缓冲区
* @param length 读取字节的个数
*/
void AT24C02_ReadBytes(uint8_t address, uint8_t *pBuffer, uint16_t length)
{
I2C_Start(); // 1、产生起始信号
I2C_SendOneByte(AT24C02_ADDRESS); // 2、发送写操作地址
I2C_WaitAck(); // 3、等待应答信号
I2C_SendOneByte(address); // 4、发送内存地址
I2C_WaitAck(); // 5、等待应答信号
I2C_Start(); // 6、再次产生起始信号
I2C_SendOneByte(AT24C02_ADDRESS | 1); // 7、发送读操作地址
I2C_WaitAck(); // 8、等待应答信号
for (uint8_t i = 0; i < length; i++)
{
if (i < length - 1)
{
pBuffer[i] = I2C_ReadOneByte(0); // 9、读取数据,并发送应答信号
}
else
{
pBuffer[i] = I2C_ReadOneByte(1); // 10、读取最后一个数据,并发送非应答信号
}
}
I2C_Stop(); // 11、产生停止信号
}
四、源码实现
4.1、原理图
从原理图上,我们可以看出 AT24C02 的 A0、A1 和 A2 引脚都接地,因此 AT24C02 的设备地址为 1010000
。
4.2、程序源码
I2C1 初始化函数内容如下:
I2C_HandleTypeDef g_i2c1_handle;
/**
* @brief I2C1初始化
*
* @param speed SCL的时钟频率,此值要低于400000
*/
void I2C1_Init(uint32_t speed)
{
g_i2c1_handle.Instance = I2C1; // 使用的I2C
g_i2c1_handle.Init.ClockSpeed = speed; // SCL时钟频率
g_i2c1_handle.Init.DutyCycle = I2C_DUTYCYCLE_2; // 时钟占空比
g_i2c1_handle.Init.OwnAddress1 = 0; // STM32自身设备地址1
g_i2c1_handle.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; // 地址模式
g_i2c1_handle.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; // 双地址模式
g_i2c1_handle.Init.OwnAddress2 = 0; // STM32自身设备地址2
g_i2c1_handle.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; // 通用广播地址
g_i2c1_handle.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; // 禁止时钟延长模式
HAL_I2C_Init(&g_i2c1_handle);
}
I2C 底层初始化函数内容如下:
/**
* @brief I2C底层初始化函数
*
* @param hi2c I2C句柄
*/
void HAL_I2C_MspInit(I2C_HandleTypeDef *hi2c)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(hi2c->Instance == I2C1)
{
__HAL_RCC_I2C1_CLK_ENABLE(); // 使能I2C1时钟
__HAL_RCC_GPIOB_CLK_ENABLE(); // 使能I2C1对应的GPIO时钟
GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_9; // SCL引脚和SDA引脚
GPIO_InitStruct.Mode = GPIO_MODE_AF_OD; // 复用开漏输出
GPIO_InitStruct.Pull = GPIO_PULLUP; // 使用上拉电阻
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 输出速度
GPIO_InitStruct.Alternate = GPIO_AF4_I2C1; // 复用功能
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
}
AT24C02 一次写入多个字节函数内容如下:
/**
* @brief AT24C02在一页中写入多个字节
*
* @param hi2c I2C句柄
* @param address 待写入数据的内存地址
* @param pData 待写入的数据
* @param length 待写入数据的个数
*/
void AT24C02_WriteData(I2C_HandleTypeDef *hi2c, uint16_t address, uint8_t *pData, uint16_t length)
{
HAL_I2C_Mem_Write(hi2c, AT24C02_ADDRESS, address, I2C_MEMADD_SIZE_8BIT, pData, length, 1000);
HAL_Delay(5);
}
AT24C02 一次读取多个字节函数内容如下:
/**
* @brief AT24C02顺序读取多个字节数据函数
*
* @param hi2c I2C句柄
* @param address 起始地址
* @param pData 保存读取的数据的缓冲区
* @param length 读取字节的个数
*/
void AT24C02_ReadData(I2C_HandleTypeDef *hi2c, uint16_t address, uint8_t *pData, uint16_t length)
{
HAL_I2C_Mem_Read(hi2c, AT24C02_ADDRESS, address, I2C_MEMADD_SIZE_8BIT, pData, length, 1000);
}
有关时钟配置函数请在 STM32 的时钟系统 篇章查看。
有关 USART1 的配置请在 串口通信 篇章查看。
main() 函数内容如下:
int main(void)
{
uint8_t data[10] = {0};
HAL_Init();
System_Clock_Init(8, 336, 2, 7);
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2); // 设置中断优先级分组
USART1_Init(115200);
I2C1_Init(100000);
AT24C02_WriteData(&g_i2c1_handle, 8, (uint8_t *)"123456789", 10);
AT24C02_ReadData(&g_i2c1_handle, 8, data, 10);
printf("%s\r\n", data);
while (1)
{
}
return 0;
}
标签:字节,应答,param,地址,AT24C02,I2C,EEPROM,14
From: https://www.cnblogs.com/kurome/p/18076305