1. EEPROM是什么
EEPROM是指带电可擦可编程只读存储器。是一种掉电后数据不丢失的存储芯片
2.AT24C512是什么
价格在1块多左右
重要的参数
速率在400K-1M
写入比读出功耗电流大
写周期是5毫秒
写入的寿命是100万次
-
AT24C512是一个串行传输(I2C总线)EEPROM存储器,每片内部有64K字节存储空间,
每个存储单元有自己的(16位=2字节)地址,
每片地址从0000H到FFFFH。
这64KB存储空间又分成512个页,
每页128个字节。(这个芯片代号512就是这么来的)
512128 = 65536Byte = 64Kbyte
512是512个页,每页128字节
然后512128 = 64K
所以AT24512是多大的内存空间呢
是64K的内存空间
-
AT24Cxx是一系列类似的存储芯片)。分页存储在读取数据时不必理会,但在写入时要理会,否则会覆盖页内数据。另外与其他EEPROM不同的是写入操作不需要先擦除扇区(页),片内的管理单元代用户完成了这个动作。不用擦除命令
-
AT24Cxx是IIC(I2C)协议,每传输完一个字节数据要有一个应答信号然后才能继续。主控设备(MCU)向从设备(AT24C512)发送一字节数据后,从设备做出应答确认(拉低数据线),反之从设备向主设备发送一字节数据后,主设备要做出类似应答确认(拉低数据线),主动拉低数据线相当于输出”0” ,这个应答确认资料上写成 ACKNOWLEDGE,简写ACK。
-
芯片有8个引脚,最重要的SCL和SDA,分别连接到单片机的两个I/O引脚上做时钟和数据,时钟线是单向的由主控设备MCU提供,数据线是双向的。单片机上这两个引脚应该是普通双向I/O,尤其SDA不能设置为其他模式,否则可能收不到应答和数据。VCC和GND不必说,但不能忽视芯片工作电压与系统电压是否匹配
-
WP引脚的功能是写保护,接到VCC就只能读不能写,
-
接到GND可读可写,如果悬空内部接地
-
A0和A1可以接到VCC或GND,用来确定芯片物理地址。所以一个系统可以连接4片AT24C512。它们的地址分别是(二进制)00,01,10,11.如果悬空在内部也连接到地
-
即使系统内只有一片AT24C512,根据你的连接它的物理地址你应该清楚,因为后面的操作需要这个地址写命令。(比如A1,A0都接地或悬空它的地址是00;如果A1接地或悬空A0接电源它的地址是01,余此类推)
-
NO是空引脚。芯片的功耗很低,写入数据时电流3mA,读出时更小,空闲状态几个uA。可以用单片机的I/O引脚为芯片供电,相当于片选
-
AT24C512的工作频率在5V时最高为1MHZ,2.7V时为400KHZ,就是说输入到SCL的时钟频率不能超过这个数值,否则读写失败
-
VCC=5V或3.3V时,SCL脉冲宽度上下边各不小于1us,保守点可以再宽些
-
直译”设备地址”,我把它叫“寻址命令”,
-
仅一个字节,前5位固定是 10100,
-
A1,A0就是上面提到的芯片物理地址,00
-
所以设备的物理地址是1010000 = 0x50
-
最后一位R/W如果填1,表示对选中芯片进行读操作,
-
填0表示对选中芯片进行写操作。
-
比如要对物理地址为00的芯片进行读操作,就发送命令:10100001,= 0XA1
-
要对该芯片进行写操作就发送命令:10100000, = 0XA0
写数据的(时序)过程是:
1开始命令;
2发送芯片寻址命令(写);
3发送2字节地址;
4发送1字节数据;
如果继续发转回4;
5停止命令。
读数据的(时序)过程是:
1开始命令;
2发送芯片寻址命令(写);
3发送2字节地址;
4开始命令;
5发送芯片寻址命令(读);
6读1字节数据;
继续读转回6 ;
7停止命令。
- 向AT24C512写入数据不能跨页,就是说上面例程如果写入129个数据,将把第129数据写到页的开始0000单元。如果跨页需要修改地址后再写
- 读数据没有页限制,可以从任何地址开始读若干字节数据,每收到一个字节数据需要应答,但最后一个字节不发应答
- 和同系列其他芯片一样,器件地址为8位,前5位固定,最低位为读/写标志,第二、三位为总线地址。电路中A0、A1引脚接地,故读、写的器件地址分别为0xA1,0xA0
写操作结束后,MCU会向EEPROM发送一个停止位,在发出下一个起始位之前,EEPORM芯片会进入写周期(internally-timed write cycle),这段时间内不能进行任何输入,所以在两个写操作或写操作与读操作之间需要有一个保证wirte cycle的延时,一般5~10ms即可
**
https://blog.csdn.net/weixin_43772810/article/details/122149151
链接1
https://blog.csdn.net/lin5103151/article/details/103551569
链接2
https://blog.csdn.net/wanglong3713/article/details/124909042
链接3
https://www.zhihu.com/tardis/zm/art/399830985?source_id=1005
链接4
https://www.cnblogs.com/leo0621/p/8251897.html
链接5
发送起始信号–>
发送器件地址(包含写入命令)–>
收到应答–>
发送需要写入数据的地址的高字节(8bit)–>
收到应答–>
发送需要写入数据的地址的低字节(8bit)–>
收到应答–>
发送需要写入的数据–>
收到应答–>
发送停止信号
- 参考一写入一字节数据
/*******************************************************************************
* 函数名:x24Cxx_WriteByte
* 功 能:写一个字节
* 参 数:u16Addr要写入的地址
u8Data要写入的数据
* 返回值:无
* 说 明:无
*******************************************************************************/
void x24Cxx_WriteByte(uint16_t u16Addr, uint8_t u8Data)
{
x24Cxx_WriteEnable();//使能写入
IIC_Start();//起始信号
IIC_WriteByte(DEV_ADDR | WRITE_CMD);//器件寻址+读/写选择
IIC_WaitAck();//等待应答
IIC_WriteByte((uint8_t)((u16Addr >> 8) & 0xFF));
IIC_WaitAck();//等待应答
IIC_WriteByte((uint8_t)(u16Addr & 0xFF));
IIC_WaitAck();//等待应答
IIC_WriteByte(u8Data);
IIC_WaitAck();//等待应答
IIC_Stop();//停止信号
x24Cxx_WriteDisble();//禁止写入
}
- 参考二写入一页数据
/*******************************************************************************
* 函数名:x24Cxx_WritePage
* 功 能:页写
* 参 数:u16Addr要写入的首地址;
u8Len写入数据字节数,最大为PAGE_SIZE
pData要写入的数据首地址
* 返回值:无
* 说 明:最多写入1页,防止翻卷,如果地址跨页则去掉跨页的部分
*******************************************************************************/
void x24Cxx_WritePage(uint16_t u16Addr, uint8_t u8Len, uint8_t *pData)
{
uint8_t i;
x24Cxx_WriteEnable();//使能写入
IIC_Start();//起始信号
IIC_WriteByte(DEV_ADDR | WRITE_CMD);//器件寻址+读/写选择
IIC_WaitAck();//等待应答
IIC_WriteByte((uint8_t)((u16Addr >> 8) & 0xFF));
IIC_WaitAck();//等待应答
IIC_WriteByte((uint8_t)(u16Addr & 0xFF));
IIC_WaitAck();//等待应答
if (u8Len > PAGE_SIZE)//长度大于页的长度
{
u8Len = PAGE_SIZE;
}
if ((u16Addr + (uint16_t)u8Len) > CAPACITY_SIZE)//超过容量
{
u8Len = (uint8_t)(CAPACITY_SIZE - u16Addr);
}
if (((u16Addr % PAGE_SIZE) + (uint16_t)u8Len) > PAGE_SIZE)//判断是否跨页
{
u8Len -= (uint8_t)((u16Addr + (uint16_t)u8Len) % PAGE_SIZE);//跨页,截掉跨页的部分
}
for (i = 0; i < u8Len; i++)
{
IIC_WriteByte(*(pData + i));
IIC_WaitAck();//等待应答
}
IIC_Stop();//停止信号
x24Cxx_WriteDisble();//禁止写入
}
- 参考三读一字节数据
发送起始信号–>发送器件地址(包含写入命令)–>
收到应答–>
发送需要读取数据的地址的高字节(8bit)–>
收到应答–>
发送需要读取数据的地址的低字节(8bit)–>
收到应答–>
发送起始信号–>
发送器件地址(包含读取命令)–>
收到应答–>
读取数据–>
不应答–>
发送停止信号
/*******************************************************************************
* 函数名:x24Cxx_ReadByte
* 功 能:读一个字节
* 参 数:u16Addr要读取的地址
* 返回值:u8Data读出的数据
* 说 明:无
*******************************************************************************/
uint8_t x24Cxx_ReadByte(uint16_t u16Addr)
{
uint8_t u8Data = 0;
IIC_Start();//起始信号
IIC_WriteByte(DEV_ADDR | WRITE_CMD);//器件寻址+读/写选择
IIC_WaitAck();//等待应答
IIC_WriteByte((uint8_t)((u16Addr >> 8) & 0xFF));
IIC_WaitAck();//等待应答
IIC_WriteByte((uint8_t)(u16Addr & 0xFF));
IIC_WaitAck();//等待应答
IIC_Start();//起始信号
IIC_WriteByte(DEV_ADDR | READ_CMD);//器件寻址+读
IIC_WaitAck();//等待应答
u8Data = IIC_ReadByte();
IIC_NoAck();
IIC_Stop();//停止信号
return u8Data;
}
- 参考四读一页数据
/*******************************************************************************
* 函数名:x24Cxx_ReadPage
* 功 能:页读
* 参 数:u16Addr要读取的首地址;
u8Len读取数据字节数,最大为PAGE_SIZE
pBuff读取数据存入的缓存
* 返回值:无
* 说 明:最多读1页,防止翻卷,如果地址跨页则去掉跨页的部分
*******************************************************************************/
void x24Cxx_ReadPage(uint16_t u16Addr, uint8_t u8Len, uint8_t *pBuff)
{
uint8_t i;
IIC_Start();//起始信号
IIC_WriteByte(DEV_ADDR | WRITE_CMD);//器件寻址+读/写选择
IIC_WaitAck();//等待应答
IIC_WriteByte((uint8_t)((u16Addr >> 8) & 0xFF));
IIC_WaitAck();//等待应答
IIC_WriteByte((uint8_t)(u16Addr & 0xFF));
IIC_WaitAck();//等待应答
IIC_Start();//起始信号
IIC_WriteByte(DEV_ADDR | READ_CMD);//器件寻址+读
IIC_WaitAck();//等待应答
if (u8Len > PAGE_SIZE)//长度大于页的长度
{
u8Len = PAGE_SIZE;
}
if ((u16Addr + (uint16_t)u8Len) > CAPACITY_SIZE)//超过容量
{
u8Len = (uint8_t)(CAPACITY_SIZE - u16Addr);
}
if (((u16Addr % PAGE_SIZE) + (uint16_t)u8Len) > PAGE_SIZE)//判断是否跨页
{
u8Len -= (uint8_t)((u16Addr + (uint16_t)u8Len) % PAGE_SIZE);//跨页,截掉跨页的部分
}
for (i = 0; i < (u8Len - 1); i++)
{
*(pBuff + i) = IIC_ReadByte();
IIC_Ack();//主机应答
}
*(pBuff + u8Len - 1) = IIC_ReadByte();
IIC_NoAck();//最后一个不应答
IIC_Stop();//停止信号
}
抓取时序图+个人整理
-
多啰嗦一句
-
了解时序最有效直接的方式是看时序图
-
快捷有效
-
写入一字节数据
-
读取一字节数据
-
写入多字节数据
-
读取多字节数据
- 参考代码:写入一字节
/*
* 写一字节数据_16位内存地址
*/
void user_write_byte_16bit_extend(iic_control_t *io_ctl, uint8_t dev_addr,uint16_t mem_addr, uint8_t mem_data)
{
//开始
user_iic_start_extend(io_ctl);
//写地址
user_iic_write_address_extend(io_ctl, dev_addr, IIC_WRITE);
//内部寄存器地址
user_iic_write_data_extend(io_ctl, mem_addr>>8);
user_iic_write_data_extend(io_ctl, mem_addr&0xFF);
//内部寄存器数据
user_iic_write_data_extend(io_ctl, mem_data);
//停止
user_iic_stop_extend(io_ctl);
}
- 参考代码写入多字节
/*
* 写多字节数据_16位内存地址
* IIC_EEPROM_at24c512
* 页写
*/
void user_write_byte_16bit_page_extend(iic_control_t *io_ctl, uint8_t dev_addr,uint16_t mem_addr, uint8_t *mem_data, uint8_t len)
{
//开始
user_iic_start_extend(io_ctl);
//写地址
user_iic_write_address_extend(io_ctl, dev_addr, IIC_WRITE);
//内部寄存器地址
user_iic_write_data_extend(io_ctl, mem_addr>>8);
user_iic_write_data_extend(io_ctl, mem_addr&0xFF);
if(len >= AT24C512_PAGE_SIZE)
len = AT24C512_PAGE_SIZE;
if(mem_addr + len > AT24C512_ROM_SIZE)
len = AT24C512_ROM_SIZE - mem_addr;
if((mem_addr%AT24C512_PAGE_SIZE) + len > AT24C512_PAGE_SIZE)
len = len - ((mem_addr+len)%AT24C512_PAGE_SIZE) ;
for(uint8_t t=0; t<len; t++)
{
//内部寄存器数据
user_iic_write_data_extend(io_ctl, *(mem_data+t));
}
//停止
user_iic_stop_extend(io_ctl);
}
- 参考代码读取一字节
/*
* 读一字节数据_16位地址
*/
void user_read_byte_16bit_extend(iic_control_t *io_ctl, uint8_t dev_addr,uint16_t mem_addr, uint8_t *mem_data)
{
uint8_t rec_data =0;
//开始
user_iic_start_extend(io_ctl);
//写地址
user_iic_write_address_extend(io_ctl, dev_addr, IIC_WRITE);
//内部寄存器地址
user_iic_write_data_extend(io_ctl, mem_addr>>8);
user_iic_write_data_extend(io_ctl, mem_addr&0xFF);
//开始
user_iic_start_extend(io_ctl);
//读地址
user_iic_write_address_extend(io_ctl, dev_addr, IIC_READ);
//读数据
rec_data = user_iic_read_data_extend(io_ctl, IIC_NO_ACK);
//停止
user_iic_stop_extend(io_ctl);
*mem_data = rec_data;
}
- 参考代码读取多字节
/*
* 读多字节数据_16位内存地址
* IIC_EEPROM_at24c512
* 页读
*/
void user_read_byte_16bit_page_extend(iic_control_t *io_ctl, uint8_t dev_addr,uint16_t mem_addr, uint8_t *mem_data, uint8_t len)
{
//开始
user_iic_start_extend(io_ctl);
//写地址
user_iic_write_address_extend(io_ctl, dev_addr, IIC_WRITE);
//内部寄存器地址
user_iic_write_data_extend(io_ctl, mem_addr>>8);
user_iic_write_data_extend(io_ctl, mem_addr&0xFF);
//开始
user_iic_start_extend(io_ctl);
//读地址
user_iic_write_address_extend(io_ctl, dev_addr, IIC_READ);
if(len >= AT24C512_PAGE_SIZE)
len = AT24C512_PAGE_SIZE;
if(mem_addr + len > AT24C512_ROM_SIZE)
len = AT24C512_ROM_SIZE - mem_addr;
if((mem_addr%AT24C512_PAGE_SIZE) + len > AT24C512_PAGE_SIZE)
len = len - ((mem_addr+len)%AT24C512_PAGE_SIZE) ;
for(uint8_t t=0; t<len; t++)
{
//内部寄存器数据
*(mem_data+t) = user_iic_read_data_extend(io_ctl, IIC_ACK);
}
//停止
user_iic_stop_extend(io_ctl);
}