目录
一、初识AT24C02
1. 介绍
AT24C02是 低工作电压的 2Kb 串行电可擦除 只读存储器,可存储 256个字节 数据,内部有一个 16字节页写缓冲器。AT24C02工作电压 1.8~5.5V,采用二线制 IIC数据传输协议,支持硬件写保护,能擦写 100万次,数据可保存 100年。
通过器件地址输入端 A0、A1和 A2可以实现将 最多 8个 AT24C02器件 连接到 IIC总线上。
补:EEPROM 是一种 掉电后数据不丢失的储存器,常用来存储一些配置信息,在系统重新上电时就可以加载。
详细描述可以参考:
2. 引脚功能
补:
① A2、A1、A0:器件地址输入引脚。AT24C02 在一个总线上最多可寻址 8个器件,A2、A1 和 A0 必须连接。
② SDA:可实现 双向串行数据传输。该引脚为 开漏输出,可与其他 多个开漏输出器件 或开集电极器件线 或连接。
③ SCL:在 SCL输入时钟信号的 上升沿将数据送入 EEPROM器件,并在时钟的 下降沿将数据读出。
④ VCC、GND:2~6V 电源输入。
二、AT24C02组成
1. 存储结构
AT24C02的存储容量为 256 Byte,由 32页 组成且每页8 Byte。
2. AT24C02通讯地址
主器件通过 发送一个 起始信号 启动发送过程,然后 发送它所要 寻址的 从器件的地址。高 4位 固定为 1010,接下来的 3位 A2、A1、A0为器件 的地址位(由器件 1~3引脚外接电平决定),用来 寻址哪个器件 被主器件访问,8位 地址的 最低位作为读写控制位( 1表示对 从器件进行读操作,0 表示对从器件 进行写操作 )。
在主器件 发送起始信号 和从器件地址字节后,AT24C02监视 总线并当 其地址与发送的从地址相符时 响应一个 应答信号(通过SDA线),再根据 读写控制位 R / W 的状态进行 读或写操作。
(1)不可编程部分:1010。
(2)可编程部分:由硬件管脚 A0 / 1 / 2决定。
(3)数据传输方向:读数据‘1’还是写数据‘0’。(写操作地址:0xA0 读操作地址:0xA1)
3. AT24C02寻址方式
AT24C02有两种寻址方式:芯片寻址、片内子地址寻址。
(1)芯片寻址
AT24C02芯片地址固定为1010,它是 IIC总线器件的 特征编码,其地址控制字的格式为 1010 A2 A1 A0 R/W。A2 A1 A0 引脚接高、低电平后得到 确定的 3位编码,与1010 形成的 7位编码,即为 该器件的地址码。由于 A2 A1 A0共有 8种组合,故系统最多可外接 8片 AT24C02,R / W是 对芯片的 读 / 写控制位。
(2)片内子地址寻址
确定AT24C02芯片的 7位地址码 后,片内的存储空间 可用 1字节的 地址码寻址,寻址范围为00H~FFH,可对片内的 256个单元进行 读 / 写操作。
三、IlC通信部分规则
1. 启动信号和停止信号 只能由主机发起。
2. IIC通信中 SCL 永远由主机控制,SDA 由发送方控制(发送方可以是主机也可以是从机,接收方同理)。
3. 接收方在收到 8 bit 数据 后必须 发送一个 应答信号给 发送方。
4. 8 bit 数据位是 高位先发,因为是 串行通信,所以 接收方也是高位先收。
5. IIC 在收发 8 bit 数据过程中:
① 发送方只能在 SCL的 低电平期间 改变 SDA电平 以发送数据。
② 接收方只能在 SCL的 高电平期间 读取 SDA电平 以获取数据。
四、AT24C02时序
1. 总线空闲
数据线 和 时钟线同时为 高电平。
2. 启动数据传输
时钟(SCL)为高电平时,SDA 从高电平变为低电平表示 起始条件产生。起始条件 必须 先于所有的命令 产生。
3. 停止数据传输
时钟 (SCL)为高电平时,SDA 从低电平变为 高电平表示 停止条件产生。所有操作都必须 以停止条件结束。
4. 数据有效
数据线的状态 表明数据 何时有效。在起始条件之后,数据线在 时钟处于高电平期间 保持稳定。必须在 时钟信号为 低电平期间 改变数据线。一个数据位对应 一个时钟 脉冲。
数据的 每次传输以起始条件开始,以 停止条件结束。在 起始条件和停止条件之间传输的数据字节数目 由主器件决定。
5. 确认时序
五、AT24C02读写时序
1. 写操作
AT24C02有两种写入方式:字节写入方式 和 页写入方式。字节写模式 就是一个地址一个数据进行写入。页写模式 就是连续写入数据。
只需要写一个地址,连续写入数据时 地址会自增,但存在 页的限制,超出一页时,超出数据覆盖原 先写入的数据。但 读会自动翻页。
(1)字节写入方式
时序简单描述:
字节写操 作以来自于主器件的 起始位开始,4 位 控制码 紧随其后。接下来的 3 位是存储块寻址位(不带地址输入引脚的器件)或 止选位(带地址输入引脚的器件)。然后 主发送器将 R / W 位(该位为逻辑低电平)发送到总线。从器件在 第九个 时钟周期产生一个 确认位。
单片机(主器件)先发送启动信号和 1 字节的控制字,从器件 发出应答信号后,单片机再发送 1 字节 的存储单元子地址(AT24C02芯片内部 单元的地址码),单片机收到 AT24C02应答后,再发送 8 位 数据 和 1 位终止信号。
流程:
① 主器件 发送起始命令 和 从器件地址信息(R / W 位置0),等待 从器件应答。
② 主器件 发送 一个地址字节( 数据要写入的 AT24C02 地址单元 ),等待 从器件应答。
③ 主器件 发送待 写数据字节,等待 从器件应答。
④ 主器件 发送停止信号后,AT24C02 开始 内部数据的 擦写,在内部擦写过程中,AT24C02 不再应答 主器件的 任何请求。
(2)页写入方式
单片机先发送启动信号和 1 字节 的控制字,再发送1字节的 存储器起始单元地址,上述几字节都得到 AT24C02的 应答后,就可以发送最多 1页的数据,即一次写入最多 16个字节的数据至 AT24C02。这些数据字节临时存储在片 内页缓冲器中。在主器件发送 停止条件之后,这些数据将被 写入存储器。
数据页写操作的 启动和字节 写一样,不同在于 传送了1 字节数据 后并不产生 停止信号。主器件可以发送 15个额外的字节,每发送一个字节数据后,AT24C02 产生一个 应答位并将字节 地址低 4 位 加 1( 1111 加 1 变成 0000 ),高 4 位 保持不变。同时数据 顺序存放在由 已指定的起始地址开始的 相继单元中。
如果在 发送停止信号 之前 主器件 发送超过 16个 字节数据,先前 写入的 数据将被 顺序覆盖,AT24C02 只保留 最后传输的 16个字节 数据。
主器件发送停止信号后,AT24C02 启动内部写周期,将数据写到数据区。所有接收的数据在一个写周期内 写入,写一个字节与同时写 16个 字节的 时间相同,整个写周期大约需要 10ms。
2. 读操作
AT24C02的读操作也有三种方式:立即读方式、指定地址读方式和指定地址连续读方式。
(1)立即读方式
AT24C02的 地址计数器内容为 最后操作字节 的地址加 1。也就是说,如果上次 读 / 写 的操作地址为 N,则立 即读的地址 从地址 N+1 开始。如果 N=255,则地址计数器 将翻转到 0 且继续输出数据。
流程:
① 主器件 发送起始命令 和 从器件地址信息( R / W 位置 1 )。
② AT24C02 发送 应答信号,应答 后发送一个 8位 字节数据。
③ 主器件 发送 停止信号,结束 读操作。
(2)指定地址读方式
单片机 发送启动信号后,先 发送含有芯片 地址的 写操作控制字,AT24C02 应答后,单片机再发送 1 字节 的指定单元 的地址,AT24C02 应答后再发送 1个 含有芯片 地址的 读操作 控制字,此时如果 AT24C02 做出应答,被访问 单元的数据就 会按 SCL信号同步 出现在SDA线上,供单片机 读取。
流程:
① 主器件 发送起始命令 和 从器件地址信息( R / W 位置 0 ),等待 从器件应答。
② 主器件 发送一个 地址字节( 要读的数据 地址单元 ),等待 从器件应答。
③主器件 重发起始 信号,执行一次 立即 地址读操作。
(3)连续读方式
连续读操作可通过 立即读 或 指定地址读操作启动,在 AT24C02 发送完一个 8 位 字节数据后,主器件 产生一个 应答信号来 响应,告知 AT24C02 主器件要求 更多的数据,只有 AT24C02检测到应答信号后,其 内部的 地址寄存器 就自动加 1 指向 下一个单元,并顺序 将指向 单元的数据送到 SDA 线上。对应 每个主机产生的 应答信号,AT24C02 将发送 一个数据字节,当 主器件不发送应答信号而 发送停止位 时结束此操作。
从 AT24C02 输出的数据按顺序由 N 到 N+1 输出。读操作时,地址计数器在 AT24C02 整个地址内 增加,这样 整个寄存器区域 可在一个读操作内全部读出。当前读取的 字节地址为255时,地址计数器 将翻转到 0 并继续 输出 数据字节。
补充:
在 IIC总线 上每次传送的 数据字节数不限,但 每一个字节必须为 8 位,而且 每个传送的字节 后面必须 跟一个 应答信号。每次 都是先传 最高位,通常 从器件在 接收到 每个字节后都会 做出响应,即释放 SCL线 返回高电平,准备 接收下一个 数据字节,主器件 可继续 传送。
如果 从器件 正在处理一个 实时事件而 不能接收数据 时,可以 使时钟线 SCL 保持 低电平,从器件 必须使 SDA 保持高电平,此时 主器件产生 1 个 结束信号,使传 送异常结束,迫使主器件 处于 等待状态。当 从器件处理 完毕时将 释放 SCL 线,主器件 继续 完成传送。
六、参考代码
注:代码可能有些许问题,望大佬们指点一二。
1. at24c02.h
/*
*********************************************************************************************************
*
* 模块名称 : at24c02驱动文件
* 文件名称 : at24c02.h
*
*********************************************************************************************************
*/
#ifndef __AT24C02_H
#define __AT24C02_H
#include "main.h"
#define AT24CXX_MAX_SIZE 256
#define AT24CXX_PAGE_SIZE 8
#define AT24CXX_PAGE_TOTAL (AT24CXX_MAX_SIZE/AT24CXX_PAGE_SIZE)
#define AT24C02_SlaveAddress 0xa0 // 从机地址
#define IIC_READ_SCL HAL_GPIO_ReadPin(AT24C02_SCL_GPIO_Port, AT24C02_SCL_Pin)
#define IIC_READ_SDA HAL_GPIO_ReadPin(AT24C02_SDA_GPIO_Port, AT24C02_SDA_Pin)
#define AT24C02_SCL(x) ((x==1) ? (AT24C02_SCL_GPIO_Port->BSRR=AT24C02_SCL_Pin) : (AT24C02_SCL_GPIO_Port->BRR=AT24C02_SCL_Pin))
#define AT24C02_SDA(x) ((x==1) ? (AT24C02_SDA_GPIO_Port->BSRR=AT24C02_SDA_Pin) : (AT24C02_SDA_GPIO_Port->BRR=AT24C02_SDA_Pin))
HAL_StatusTypeDef AT24C02_IsConnected(void);
void AT24C02_IIC_SendByte(uint8_t byte);
uint8_t AT24C02_IIC_RecByte(void);
void AT24C02_Write_Data(uint8_t address, uint8_t byte);
uint8_t AT24C02_Read_Data(uint8_t address);
int AT24C02_write(uint8_t addr, uint8_t* dataPtr, uint16_t dataSize);
int AT24C02_read(uint8_t addr, uint8_t* dataPtr, uint16_t dataSize);
#endif
2. at24c02.c
/*
*********************************************************************************************************
*
* 模块名称 : at24c02驱动文件
* 文件名称 : at24c02.c
*
*********************************************************************************************************
*/
#include "at24c02.h"
#include "i2c.h"
void AT24C02_IIC_Delay(uint16_t us)
{
HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000000);
HAL_Delay(us - 1);
HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);
}
/**
* @brief IIC 总线连接检查
*
* @return HAL_StatusTypeDef
*/
HAL_StatusTypeDef AT24C02_IsConnected(void)
{
return HAL_I2C_IsDeviceReady(&hi2c1, AT24C02_SlaveAddress, 1, 50);
}
/**
* @brief IIC 总线启动信号
*
* @return void
*/
void AT24C02_IIC_Start(void)
{
AT24C02_SDA(1);
AT24C02_SCL(1);
AT24C02_IIC_Delay(20);
AT24C02_SDA(0);
AT24C02_IIC_Delay(5);
AT24C02_SCL(0);
}
/**
* @brief IIC 总线停止信号
*
* @return void
*/
void AT24C02_IIC_Stop(void)
{
AT24C02_SDA(0);
AT24C02_SCL(0);
AT24C02_IIC_Delay(5);
AT24C02_SCL(1);
AT24C02_IIC_Delay(5);
AT24C02_SDA(1);
}
/**
* @brief 发送应答信号或非应答信号
*
* @param ack 0:应答 / 1:非应答
*
* @return void
*/
void AT24C02_IIC_SendAck(uint16_t ack)
{
AT24C02_SCL(0);
AT24C02_SDA(ack);
AT24C02_IIC_Delay(5);
AT24C02_SCL(1);
AT24C02_IIC_Delay(5);
AT24C02_SCL(0);
AT24C02_SDA(1); // 接收器释放SDA
AT24C02_IIC_Delay(5);
}
/**
* @brief 接收应答信号或非应答信号
*
* @return uint16_t 0:应答 / 1:非应答
*/
uint16_t AT24C02_IIC_WaitAck(void)
{
uint16_t ack;
AT24C02_SDA(1); // 发送器释放SDA
AT24C02_SCL(1);
AT24C02_IIC_Delay(5);
ack = IIC_READ_SDA;
AT24C02_SCL(0);
AT24C02_IIC_Delay(5);
return ack;
}
/**
* @brief 发送一个字节
*
* @param byte 1 字节数据
*
* @return void
*/
void AT24C02_IIC_SendByte(uint8_t byte)
{
AT24C02_SCL(0);
for(int i = 0; i < 8; i++)
{
AT24C02_IIC_Delay(2);
AT24C02_SDA( ((byte>>(7-i)) & 0x01) ); // 获取最高位
AT24C02_IIC_Delay(3);
AT24C02_SCL(1);
AT24C02_IIC_Delay(5);
AT24C02_SCL(0);
}
AT24C02_IIC_WaitAck(); // 等待应答信号
}
/**
* @brief 接收一个字节
*
* @return uint8_t
*/
uint8_t AT24C02_IIC_RecByte(void)
{
uint8_t data;
AT24C02_SDA(1);
for(int i = 0; i < 8; i++)
{
AT24C02_SCL(1);
AT24C02_IIC_Delay(5);
data <<= 1;
if(IIC_READ_SDA)
data |= 1;
AT24C02_SCL(0);
AT24C02_IIC_Delay(5);
}
AT24C02_IIC_SendAck(0); // 发送应答信号
return data;
}
/**
* @brief 向 AT24C02的地址address中 写入一个字节数据
*
* @param byte 1 字节数据
*
* @return void
*/
void AT24C02_Write_Data(uint8_t address, uint8_t byte)
{
AT24C02_IIC_Start();
AT24C02_IIC_SendByte(AT24C02_SlaveAddress);
AT24C02_IIC_WaitAck(); // 等待应答信号
AT24C02_IIC_SendByte(address);
AT24C02_IIC_WaitAck();
AT24C02_IIC_SendByte(byte);
AT24C02_IIC_WaitAck();
AT24C02_IIC_Stop();
}
/**
* @brief 向 AT24C02的地址address中 读出一个字节数据
*
* @param byte 1 字节数据
*
* @return uint8_t
*/
uint8_t AT24C02_Read_Data(uint8_t address)
{
uint8_t data;
AT24C02_IIC_Start();
AT24C02_IIC_SendByte(AT24C02_SlaveAddress);
AT24C02_IIC_WaitAck(); // 等待应答信号
AT24C02_IIC_SendByte(address);
AT24C02_IIC_WaitAck();
AT24C02_IIC_Start();
AT24C02_IIC_SendByte(AT24C02_SlaveAddress + 1);
AT24C02_IIC_WaitAck();
data = AT24C02_IIC_RecByte();
AT24C02_IIC_WaitAck();
AT24C02_IIC_Stop();
return data;
}
/**
* @brief 向 AT24C02的地址address中 存储数据
*
* @param addr 存储器地址
* @param dataPtr 待存储数据
* @param dataSize 存储数据长度
*
* @return uint8_t
*/
int AT24C02_write(uint8_t addr, uint8_t* dataPtr, uint16_t dataSize)
{
if (0 == dataSize) { return -1; }
int res = HAL_OK;
int selectPage_idx = addr % AT24CXX_PAGE_SIZE;
int selectPage_rest = AT24CXX_PAGE_SIZE - selectPage_idx;
if (dataSize <= selectPage_rest) {
res = HAL_I2C_Mem_Write(&hi2c1,
AT24C02_SlaveAddress,
addr,
I2C_MEMADD_SIZE_8BIT,
dataPtr,
dataSize,
0xFF);
if (HAL_OK != res) { return -1; }
HAL_Delay(10);
} else {
/*! 1 write selectPage rest*/
res = HAL_I2C_Mem_Write(&hi2c1,
AT24C02_SlaveAddress,
addr,
I2C_MEMADD_SIZE_8BIT,
dataPtr,
selectPage_rest,
0xFF);
if (HAL_OK != res) { return -1; }
addr += selectPage_rest;
dataSize -= selectPage_rest;
dataPtr += selectPage_rest;
AT24C02_IIC_Delay(5);
/*! 2 write nextPage full */
int fullPage = dataSize/AT24CXX_PAGE_SIZE;
for (int iPage = 0; iPage < fullPage; ++iPage) {
res = HAL_I2C_Mem_Write(&hi2c1,
AT24C02_SlaveAddress,
addr,
I2C_MEMADD_SIZE_8BIT,
dataPtr,
AT24CXX_PAGE_SIZE,
0xFF);
if (HAL_OK != res) { return -1; }
HAL_Delay(5);
addr += AT24CXX_PAGE_SIZE;
dataSize -= AT24CXX_PAGE_SIZE;
dataPtr += AT24CXX_PAGE_SIZE;
}
/*! 3 write rest */
if (0 != dataSize) {
res = HAL_I2C_Mem_Write(&hi2c1,
AT24C02_SlaveAddress + 1,
addr,
I2C_MEMADD_SIZE_8BIT,
dataPtr,
dataSize,
0xFF);
if (HAL_OK != res) { return -1; }
HAL_Delay(5);
}
}
return 0;
}
/**
* @brief 向 AT24C02的地址address中 读出数据
*
* @param addr 存储器地址
* @param dataPtr 待接收数据
* @param dataSize 存储数据长度
*
* @return uint8_t
*/
int AT24C02_read(uint8_t addr, uint8_t* dataPtr, uint16_t dataSize)
{
int res = HAL_I2C_Mem_Read(&hi2c1,
AT24C02_SlaveAddress + 1,
addr,
I2C_MEMADD_SIZE_8BIT,
dataPtr,
dataSize,
0xFF);
if (HAL_OK != res) { return -1; }
return 0;
}
3. 工程地址
3_EEPROM_AT24C02 · CXDNW/STM32F103C8T6练习项目 - 码云 - 开源中国 (gitee.com)
IIC学习可参考这篇文章:
嵌入式—— IIC