STM32实战:详解AT24CXX EEPROM使用方法(上)
EEPROM
定义
电可擦可写EEPROM(Electrically Erasable Programmable Read-Only Memory)芯片是一种特殊的存储芯片,其特点在于其数据可以在通电状态下被擦除和重新编程,而且数据在断电后不会丢失。
总结
- 可编程性:EEPROM允许修改存储在其中的数据。
- 可擦除性:EEPROM中的数据可以被清空,从而可以将存储器用于新的应用场景。
- 随机访问:EEPROM支持随机访问,可以直接访问要读写的地址,而无需像磁带或磁盘那样顺序读取。
- 低功耗:在待机模式时,EEPROM几乎没有任何能耗,但在活动模式下仍能正常工作。
AT24CXX
AT24CXX是一系列电可擦可写EEPROM芯片。
传输协议
这些芯片支持I2C总线数据传送协议,并具有较宽的工作电压范围、写保护功能以及长使用寿命等特点。以下是对AT24CXX的详细讲解:
基本特性
- 宽电压范围:AT24CXX系列芯片的工作电压范围为1.8V至5.5V,适用于不同MCU平台的设计要求。
- 写保护功能:通过WP(写保护)引脚,可以实现写保护功能。当WP为高电平时,芯片只能进行读操作,防止数据被意外改写。
- 长使用寿命:AT24CXX的擦写次数可达到100万次,数据保存时间长达100年。(所以你买的很值)
引脚与设备地址
A0 A1 A2
IIC
IIC协议已经是老生常谈了,所以还不会的宝子可以上网查查噢。
STM32代码实战
我们采用的是AT24C02,其实是因为最便宜。
但是AT24CXX同系列有什么区别,我来给你举例。
- AT24C01: 有128个地址空间(从0到127)的EEPROM,因为2^7 = 128,但地址通常是从0开始计数的,所以实际的最大地址是127。
- AT24C02: 有256个地址空间的EEPROM(从0到255)。
- AT24C04: 有512个地址空间的EEPROM(从0到511)。
- AT24C08: 个有1024个地址空间的EEPROM(从0到1023)。
- 以此类推,对于更大的EEPROM容量,宏定义也相应地增加。
但是我们在定义地址空间时一定要记住我们的地址是从0开始的!所以我们的地址空间有128个,在定义最大地址时表示为127。
初始化
void C04_IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE );
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10|GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_10|GPIO_Pin_11);
}
我们这里初始化串口其实就是初始化IIC协议所需要的SCL以及SDA,所以接下来的IIC高低电平的控制函数构建不要太简单。
SCL SDA控制函数
我们的IIC在工作时,{开始工作——停止工作——等待应答——接收/不接受应答}{发送字节+读取字节}所以我们根据这些会写出7个不同的函数这里我就不详细讲原理了。
为什么SDA需要在输入输出间切换
OLED在操作IIC中的SDA时往往不需要改变SDA的输入输出模式,因为OLED屏幕通常不会主动向主机发送数据,除非是为了响应特定的命令或查询。而对于我们的EEPROM需要读更需要写(微控制器向主机发送自己内存中的信息)因此SDA输入,SDA输出变换是必须存在的!!!!!!
因为在切换工作模式时需要频繁修改定义所以我们将其设置为一个宏放在.h文件中。
宏定义
#define C04_SDA_IN() {GPIOB->CRH&=0XFFFF0FFF;GPIOB->CRH|=(u32)8<<12;} //上拉/下拉输入模式
#define C04_SDA_OUT() {GPIOB->CRH&=0XFFFF0FFF;GPIOB->CRH|=(u32)3<<12;} //通用推挽输出模式
到这里很多人已经晕了,那这串代码表示什么呢?让我来给你讲讲吧。(以第一个输入模式为例)

GPIOB->CRH&=0XFFFF0FFF
:(F——>1111,0——>0000)这是一个位清除操作。它将GPIOB的CRH寄存器的高4位(对应于PB12到PB15)清零。这是通过设置与操作(AND)来完成的。我们这里是修改SDA的GPIOB_Pin_11所以我们将对应的(1111)修改为(0000)。可以解释为选取对应的引脚。GPIOB->CRH|=(u32)8<<12
:这是一个位设置操作。它将一个值(在这里是8,即二进制1000
)左移12位,然后与CRH寄存器的当前值进行或操作(OR)。可以理解为更改引脚的工作模式。(自己查STM32手册,这里的1000是上拉|下拉输入模式,0011为推挽输出模式)#define C04_IIC_SCL PBout(10) //SCL
#define C04_IIC_SDA PBout(11) //SDA
我们这里 通过IO口控制高低电平,所以我们还需要引入一个“sys.c|h”函数
void C04_IIC_Start(void)
{
C04_SDA_OUT(); //sda线输出
C04_IIC_SDA=1;
C04_IIC_SCL=1;
Delay_us(4);
C04_IIC_SDA=0;//START:when CLK is high,DATA change form high to low
Delay_us(4);
C04_IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
}
//产生IIC停止信号
void C04_IIC_Stop(void)
{
C04_SDA_OUT();//sda线输出
C04_IIC_SCL=0;
C04_IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
Delay_us(4);
C04_IIC_SCL=1;
C04_IIC_SDA=1;//发送I2C总线结束信号
Delay_us(4);
}
//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
u8 C04_IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
C04_SDA_IN(); //SDA设置为输入
C04_IIC_SDA=1;Delay_us(1);
C04_IIC_SCL=1;Delay_us(1);
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
C04_IIC_Stop();
return 1;
}
}
C04_IIC_SCL=0;//时钟输出0
return 0;
}
//产生ACK应答
void C04_IIC_Ack(void)
{
C04_IIC_SCL=0;
C04_SDA_OUT();
C04_IIC_SDA=0;
Delay_us(2);
C04_IIC_SCL=1;
Delay_us(2);
C04_IIC_SCL=0;
}
//不产生ACK应答
void C04_IIC_NAck(void)
{
C04_IIC_SCL=0;
C04_SDA_OUT();
C04_IIC_SDA=1;
Delay_us(2);
C04_IIC_SCL=1;
Delay_us(2);
C04_IIC_SCL=0;
}
为什么IIC这么写,为什么要添加延时。请直接移步到我的以前文章。
STM32——OLED外设库的讲解(1/2)-CSDN博客
读取字节
//在AT24CXX指定地址读出一个数据
//ReadAddr:开始读数的地址
//返回值 :读到的数据
u8 AT24CXX_ReadOneByte(u16 ReadAddr)
{
u8 temp=0;
C04_IIC_Start();
if(EE_TYPE>AT24C16)
{
C04_IIC_Send_Byte(0XA0); //发送写命令
C04_IIC_Wait_Ack();
C04_IIC_Send_Byte(ReadAddr>>8);//发送高地址
C04_IIC_Wait_Ack();
}else C04_IIC_Send_Byte(0XA0+((ReadAddr/256)<<1)); //发送器件地址0XA0,写数据
C04_IIC_Wait_Ack();
C04_IIC_Send_Byte(ReadAddr%256); //发送低地址
C04_IIC_Wait_Ack();
C04_IIC_Start();
C04_IIC_Send_Byte(0XA1); //进入接收模式
C04_IIC_Wait_Ack();
temp=C04_IIC_Read_Byte(0);
C04_IIC_Stop();//产生一个停止条件
return temp;
}
读取指的是主机从EEPROM上读取一个字节。
u16 ReadAddr
: 表示要读取数据的地址。temp:
用于存储从EEPROM中读取的数据。1.打开IIC。
2.if(EE_TYPE>AT24C16)
EE_TYPE指的是我们使用的EEPROM类型,如果EEPROM的类型大于AT24C16,则执行大容量的EEPROM的读取操作;否则,执行小容量的EEPROM的读取操作。
3.IIC发送一个字节
我来详细点讲把 !C04_IIC_Send_Byte
函数被用来通过I2C总线发送一个字节的数据。在读取AT24CXX系列EEPROM数据时,如果EEPROM的容量超过256字节(即需要超过一个字节的地址来寻址),则需要发送两个地址字节:一个高地址字节和一个低地址字节。如果EEPROM的容量较小,例如AT24C01(128字节)或AT24C02(256字节),则只需要一个地址字节。
4. C04_IIC_Send_Byte(0XA1); //进入接收模式
等待应答确认
C04_IIC_Send_Byte(0XA0); //进入发送模式
等待应答确认
5.C04_IIC_Send_Byte(ReadAddr>>8);
发送地址的高字节。
由于我们可能正在使用一个大于2KB的EEPROM,我们需要两个字节的地址(一个高字节和一个低字节)。通过将我们所需要读取的内容ReadAddr
右移8位,我们得到地址的高字节。
6. else C04_IIC_Send_Byte(0XA0+((ReadAddr/256)<<1));
对于小容量的EEPROM(例如,256字节或更小),我们通常只需要发送一个地址字节。这里的表达式0XA0+((ReadAddr/256)<<1)
并不直接发送地址字节。由于ReadAddr
是一个16位的地址,而我们的EEPROM只有256字节,所以ReadAddr/256
总是等于0(对于任何有效的地址)。这意味着我们实际上只是在发送0XA0
或0XA2
(取决于位移操作的结果,但在这里它总是0)。
这个else部分应该简化为直接发送器件地址(对于读操作,通常是0xA1
)和地址字节。
7.发送低地址位。
C04_IIC_Send_Byte(ReadAddr%256); 发送低地址 ,
ReadAddr%256
,我们得到地址的低字节。8.C04_IIC_Start(); C04_IIC_Send_Byte(0XA1); //进入接收模式
temp=C04_IIC_Read_Byte(0);
C04_IIC_Stop();
发送读命令0XA1,
读取一个字节的数据并存储在temp
变量中。
发送字节
//在AT24CXX指定地址写入一个数据
//WriteAddr :写入数据的目的地址
//DataToWrite:要写入的数据
void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite)
{
C04_IIC_Start();
if(EE_TYPE>AT24C16)
{
C04_IIC_Send_Byte(0XA0); //发送写命令
C04_IIC_Wait_Ack();
C04_IIC_Send_Byte(WriteAddr>>8);//发送高地址
}else
{
C04_IIC_Send_Byte(0XA0+((WriteAddr/256)<<1)); //发送器件地址0XA0,写数据
}
C04_IIC_Wait_Ack();
C04_IIC_Send_Byte(WriteAddr%256); //发送低地址
C04_IIC_Wait_Ack();
C04_IIC_Send_Byte(DataToWrite); //发送字节
C04_IIC_Wait_Ack();
C04_IIC_Stop();//产生一个停止条件
Delay_ms(10);
}
就是和读取字节换一下部分代码的位置,无非就是多一个延迟!这个延时加不加看自己单片机的运行情况!
总结一下吧:(我以最复杂的EEPROM情况来讲)先判断EEPROM大小,如果为大内存,那我们先选择EEPROM读取或者发送命令,完成动作后等待应答,应答成功后,发送或接受高八位,等待应答,再发送或接收低八位数据,再次等待应答,等待我们的微控制器或者主机发送或读取我们刚刚读取或发送的数据后,又一次开始的等待应答,最后停止EEPROM的IIC传输。
今天先学一半,不要贪杯哦!
明天接着来哈!咱们继续讲!
作者:STM大善人