使用HAL库模拟IIC实现STM32对AT24CXX的页读页写

参考文章:

这里附上一篇看到写得很好的大佬的文章:
STM32F407单片机通用24CXXX读写程序(KEIL),兼容24C系列存储器(24C01到24C512),支持存储器任意地址跨页连续读写多个页

 AT24C32/64官方手册:AT24C32/64

一、AT24CXX容量表

二、AT24CXX寻址方式

三、AT24CXX时序图

1.字节写

2.页写

3.当前地址读

4.顺序读

 5.随机读

四、代码


一、AT24CXX容量表

型号 容量(bit) 容量(byte) 页数 每页字节数(byte)
AT24C01 1K 128 16 8
AT24C02 2K 256 32 8
AT24C04 4K 512 32 16
AT24C08 8K 1024 64 16
AT24C16 16K 2048 128 16
AT24C32 32K 4096 128 32
AT24C64 64K 8192 256 32
AT24C128 128K 16384 256 64
AT24C256 256K 32768 512 64
AT24C512 512K 65536 512 128

二、AT24CXX寻址方式

型号 WORD ADDRESS(bit) 型号 WORD ADDRESS(bit)
AT24C01 7 AT24C32 12
AT24C02 8 AT24C64 13
AT24C04 9 AT24C128 14
AT24C08 10 AT24C256 15
AT24C16 11 AT24C512 16

三、AT24CXX时序图

1.字节写

2.页写

         AT24CXX内部是有分页的,根据型号不同,页数不同,每页字节数不同。连续写入数据的时候,内部指针会+1,当内部指针移动到当前页末的时候,就会自动移动到当前页头部,再往里写数据的时候就会覆盖掉之前的数据。

        如果想要连续写多页数据,那就需要去判断是否需要翻页,如果地址是在另一页,就需要重新发送字节写的时序。

3.当前地址读

4.顺序读

 5.随机读

        顺序读是从当前地址开始读,那么随机读搭配顺序读即可以读取任意地址。随机读就是先发送写命令,让EEPROM将指针移动到要读取的位置,然后主机发送起始条件,发送从机地址(读写位为读),即开始顺序读。

四、代码

这里附上的代码是基于STM32 HAL库,模拟IIC读写EEPROM,对AT24CXX系列通用。

bsp_at24cxx.c

/**	BSP_AT24CXX.C	EEPROM AT24CXX/FM24CXX驱动
 * 
 * @author	Dai Zu<zhangruilin@163.com>
 * @date	2024/4/10
 * 
*/

/* BSP头文件 */
#include "BSP_AT24CXX.h"

/* 宏定义 */
#define AT24CXX_ADDR	0xA0		// 从机地址
// #define AT24C01		{128, 8, AT24CXX_ADDR}
// #define AT24C02		{256, 8, AT24CXX_ADDR}
// #define AT24C04		{512, 16, AT24CXX_ADDR}
// #define AT24C08		{1024, 16, AT24CXX_ADDR}
// #define AT24C16		{2048, 16, AT24CXX_ADDR}
// #define AT24C32		{4096, 32, AT24CXX_ADDR}
#define AT24C64	    {8192, 32, AT24CXX_ADDR}
// #define AT24C128	{16384, 64, AT24CXX_ADDR}
// #define AT24C256	{32768, 64, AT24CXX_ADDR}
// #define AT24C512	{65536, 128, AT24CXX_ADDR}

/* EEPROM结构体 */
struct AT24CXX_TYPE
{
	uint32_t size;		// 容量,单位(字节)
	uint8_t	pageSize;	// 每页字节数
	uint8_t addr;		// 从机地址
};

struct AT24CXX_TYPE EEPROM_TYPE= AT24C64;





#define IIC_Soft	1	// 软件IIC
#if IIC_Soft
/* 使用STM32Hal库,以下是IIC接口,移植IIC时只需要实现以下接口即可 */
#define	IIC_SCL_PORT	EEP_SCL_GPIO_Port
#define IIC_SDA_PORT	EEP_SDA_GPIO_Port
#define IIC_SCL_PIN		EEP_SCL_Pin
#define IIC_SDA_PIN		EEP_SDA_Pin

#define IIC_SCL_SET		HAL_GPIO_WritePin(IIC_SCL_PORT, IIC_SCL_PIN, GPIO_PIN_SET)
#define IIC_SCL_RESET	HAL_GPIO_WritePin(IIC_SCL_PORT, IIC_SCL_PIN, GPIO_PIN_RESET)
#define IIC_SDA_SET		HAL_GPIO_WritePin(IIC_SDA_PORT, IIC_SDA_PIN, GPIO_PIN_SET)
#define IIC_SDA_RESET	HAL_GPIO_WritePin(IIC_SDA_PORT, IIC_SDA_PIN, GPIO_PIN_RESET)

#define READ_SDA		HAL_GPIO_ReadPin(IIC_SDA_PORT, IIC_SDA_PIN)

void IIC_SDA_Dir(uint8_t dir);

/* IO方向 */
enum IIC_SDA_DIR
{
	IIC_SDA_OUTPUT = 0,
	IIC_SDA_INPUT
};

enum IIC_ACK
{
	ACK = 0,
	NACK = 1
};

/**
 * @brief	初始化IIC相关的外设
*/
void IIC_MSP_Init(void)
{
	
}

/**
 * @brief	初始化IIC
*/
void IIC_Init(void)
{					     
 	IIC_MSP_Init();
}

/**
 * @brief		设置SDA的方向
 * @param	dir	IIC_SDA_DIR
 * IIC_SDA_OUTPUT or IIC_SDA_INPUT
*/
void IIC_SDA_Dir(uint8_t dir)
{
	if (dir == IIC_SDA_INPUT)
	{
		IIC_SDA_SET;
	}
}

void Delay_us(uint32_t us)
{
	__IO uint32_t Delay = us * 48 / 8;//(SystemCoreClock / 8U / 1000000U)
    //见stm32f1xx_hal_rcc.c -- static void RCC_Delay(uint32_t mdelay)
  	do
  	{
  	  __NOP();
  	}
  	while (Delay --);
}

/**
 * @brief	产生IIC起始信号
 * SCL高电平期间,SDA产生下降沿
*/
void IIC_Start(void)
{
	IIC_SDA_Dir(IIC_SDA_OUTPUT);
	IIC_SDA_SET;	  	  
	IIC_SCL_SET;
	Delay_us(4);
 	IIC_SDA_RESET;
	Delay_us(4);
	IIC_SCL_RESET; 
}

/**
 * @brief	产生IIC停止信号
 * SCL高电平期间,SDA产生上升沿
*/
void IIC_Stop(void)
{
	IIC_SDA_Dir(IIC_SDA_OUTPUT);
	IIC_SCL_RESET;
	IIC_SDA_RESET;
 	Delay_us(4); 
	IIC_SCL_SET;
 	Delay_us(4); 
	IIC_SDA_SET;						   	
}

/**
 * @brief	等待应答信号
 * @return	ACK or NACK
*/
uint8_t IIC_Wait_Ack(void)
{
	uint8_t ucErrTime=0;
	IIC_SDA_Dir(IIC_SDA_INPUT);
	IIC_SDA_SET;Delay_us(1);	   
	IIC_SCL_SET;Delay_us(1);	 
	while(READ_SDA)
	{
		ucErrTime++;
		if(ucErrTime>250)
		{
			IIC_Stop();
			return NACK;
		}
	}
	IIC_SCL_RESET;	   
	return ACK;  
} 

/**
 * @brief	产生ACK应答
*/
void IIC_Ack(void)
{
	IIC_SCL_RESET;
	IIC_SDA_Dir(IIC_SDA_OUTPUT);
	IIC_SDA_RESET;
	Delay_us(2);
	IIC_SCL_SET;
	Delay_us(2);
	IIC_SCL_RESET;
}

/**
 * @brief	不应答 
*/	    
void IIC_NAck(void)
{
	IIC_SCL_RESET;
	IIC_SDA_Dir(IIC_SDA_OUTPUT);
	IIC_SDA_SET;
	Delay_us(2);
	IIC_SCL_SET;
	Delay_us(2);
	IIC_SCL_RESET;
}

/**
 * @brief	IIC发送一个字节
*/	  
void IIC_Send_Byte(uint8_t txd)
{                        
    uint8_t t;   
	IIC_SDA_Dir(IIC_SDA_OUTPUT);
    IIC_SCL_RESET;
    for(t=0;t<8;t++)
    {
		if ((txd&0x80)>>7)
		{
			IIC_SDA_SET;
		}
		else
		{
			IIC_SDA_RESET;
		}
        txd<<=1; 	  
		Delay_us(2);
		IIC_SCL_SET;
		Delay_us(2); 
		IIC_SCL_RESET;	
		Delay_us(2);
    }	 
}

/**
 * @brief	读一个字节
 * @param	ack	是否发送应答
 * 0-发送应答,1-不发送应答
*/  
uint8_t IIC_Read_Byte(unsigned char ack)
{
	unsigned char i,receive=0;
	IIC_SDA_Dir(IIC_SDA_INPUT); 
    for(i=0;i<8;i++ )
	{
        IIC_SCL_RESET; 
        Delay_us(2);
		IIC_SCL_SET;
        receive<<=1;
        if(READ_SDA)receive++;   
		Delay_us(1); 
    }
    if (ack)
	{
        IIC_NAck();
	}
    else
	{
        IIC_Ack();
	}
    return receive;
}

/**
 * @brief	初始化
*/
void AT24CXX_Init(void)
{
	IIC_Init();
}

/**
 * @brief				从指定地址读出一个数据
 * @param	ReadAddr	数据地址
 * @retval				读取到的数据
*/
uint8_t AT24CXX_ReadOneByte(uint16_t ReadAddr)
{				  
	uint8_t temp=0;		  	    																 
    IIC_Start();  
	if(EEPROM_TYPE.size>2048)
	{
		IIC_Send_Byte(EEPROM_TYPE.addr);	   //发送写命令
		IIC_Wait_Ack();
		IIC_Send_Byte(ReadAddr>>8);//发送高地址	    
	}else 
	{
		IIC_Send_Byte(EEPROM_TYPE.addr+((ReadAddr/256)<<1));   //发送器件地址,写数据
	}   
	IIC_Wait_Ack(); 
    IIC_Send_Byte(ReadAddr%256);   				//发送低地址
	IIC_Wait_Ack();	    
	IIC_Start();  	 	   
	IIC_Send_Byte(EEPROM_TYPE.addr+1);		   
	IIC_Wait_Ack();	 
    temp=IIC_Read_Byte(1);		   
    IIC_Stop();    
	return temp;
}

/**
 * @brief				向指定地址写入一个字节
 * @param	WriteAddr	数据地址
 * @param	DataToWrite	要写入的数据
*/
void AT24CXX_WriteOneByte(uint16_t WriteAddr,uint8_t DataToWrite)
{				   	  	    																 
    IIC_Start();
	if(EEPROM_TYPE.size>2048)
	{
		IIC_Send_Byte(EEPROM_TYPE.addr);	   //发送写命令
		IIC_Wait_Ack();
		IIC_Send_Byte(WriteAddr>>8);//发送高地址
	}else
	{
		IIC_Send_Byte(EEPROM_TYPE.addr+((WriteAddr/256)<<1));   //发送器件地址,写数据
	}
	IIC_Wait_Ack();
    IIC_Send_Byte(WriteAddr%256);				//发送低地址
	IIC_Wait_Ack();
	IIC_Send_Byte(DataToWrite);
	IIC_Wait_Ack();
    IIC_Stop();
	HAL_Delay(10);
}

/**
 * @brief				向指定地址写入16位或32位数据
 * @param	WriteAddr	数据地址
 * @param	DataToWrite	要写入的数据
 * @param	Len			2字节或4字节
*/
void AT24CXX_WriteLenByte(uint16_t WriteAddr,uint32_t DataToWrite,uint8_t Len)
{  	
	uint8_t t;
	for(t=0;t<Len;t++)
	{
		AT24CXX_WriteOneByte(WriteAddr+t,(DataToWrite>>(8*t))&0xff);
	}												    
} 

/**
 * @brief				从指定地址读取16位或32位的数据
 * @param	WriteAddr	数据地址
 * @param	Len			2字节或4字节
 * @retval				读出的数据
*/
uint32_t AT24CXX_ReadLenByte(uint16_t ReadAddr,uint8_t Len)
{  	
	uint8_t t;
	uint32_t temp=0;
	for(t=0;t<Len;t++)
	{
		temp<<=8;
		temp+=AT24CXX_ReadOneByte(ReadAddr+Len-t-1); 	 				   
	}
	return temp;												    
}

/**
 * @brief	检查EEPROM是否正常
 * @retval	1-检测失败	0-成功
*/
uint8_t AT24CXX_Check(void)
{
	uint8_t temp;
	temp=AT24CXX_ReadOneByte(255);
	if(temp==0X55)return 0;		   
	else//排除第一次初始化的情况
	{
		AT24CXX_WriteOneByte(255,0X55);
	    temp=AT24CXX_ReadOneByte(255);	  
		if(temp==0X55)return 0;
	}
	return 1;											  
}

/**
 * @brief			连续读
 * @param	addr	数据地址
 * @param	data	存储位置
 * @param	length	读取长度
 * 
*/
void AT24CXX_SequentialRead(uint16_t addr,uint8_t *data,uint16_t length)
{
	if (length == 0)
	{
		return;
	}
	
    IIC_Start();
	if(EEPROM_TYPE.size>2048)
	{
		IIC_Send_Byte(EEPROM_TYPE.addr);	   //发送写命令
		IIC_Wait_Ack();
		IIC_Send_Byte(addr>>8);//发送高地址
	}else
	{
		IIC_Send_Byte(EEPROM_TYPE.addr+((addr/256)<<1));   //发送器件地址,写数据
	}
	IIC_Wait_Ack();
    IIC_Send_Byte(addr%256);   				//发送低地址
	IIC_Wait_Ack();
	IIC_Start();
	IIC_Send_Byte(EEPROM_TYPE.addr+1);
	IIC_Wait_Ack();

    *data++=IIC_Read_Byte(0);
	while(--length)
	{
		*data++=IIC_Read_Byte(0);
	}
	IIC_Stop();
}

/**
 * @brief			页写
 * @param	addr	数据地址
 * @param	data 	数据指针
 * @param	length 	要写入数据的个数
*/
void AT24CXX_PageWrite(uint16_t addr,uint8_t *data,uint16_t length)
{
	if (length==0 || addr>=EEPROM_TYPE.size)
	{
		return;
	}

	IIC_Start();
	if(EEPROM_TYPE.size>2048)
	{
		IIC_Send_Byte(EEPROM_TYPE.addr);		// 发送写命令
		IIC_Wait_Ack();
		IIC_Send_Byte(addr>>8);					// 发送高地址
	}else
	{
		IIC_Send_Byte(EEPROM_TYPE.addr+((addr/256)<<1));   //发送器件地址,写数据
	}
	IIC_Wait_Ack();
    IIC_Send_Byte(addr%256);					// 发送低地址
	IIC_Wait_Ack();

	for (uint16_t i = 0; i < length; i++)
	{
		IIC_Send_Byte(data[i]);
		IIC_Wait_Ack();
		addr++;

		if (addr >= EEPROM_TYPE.size)			// 内存已满
		{
			break;
		}
		
		if ((addr)%EEPROM_TYPE.pageSize == 0)	// 满页
		{
			IIC_Stop();
			HAL_Delay(10);
			IIC_Start();
			if(EEPROM_TYPE.size>2048)
			{
				IIC_Send_Byte(EEPROM_TYPE.addr);		// 发送写命令
				IIC_Wait_Ack();
				IIC_Send_Byte(addr>>8);					// 发送高地址
			}else
			{
				IIC_Send_Byte(EEPROM_TYPE.addr+((addr/256)<<1));   //发送器件地址,写数据
			}
			IIC_Wait_Ack();
    		IIC_Send_Byte(addr%256);					// 发送低地址
			IIC_Wait_Ack();
		}
		
	}

	IIC_Stop();
	HAL_Delay(10);
}
#elif

#endif

BSP_AT24CXX.h

/**	BSP_AT24CXX.h	EEPROM AT24CXX/FM24CXX驱动
 * 
 * @author	Dai Zu<zhangruilin@163.com>
 * @date	2024/4/10
 * 
*/

#ifndef __BSP_AT24Cxx_H
#define __BSP_AT24Cxx_H

#include "main.h"

uint8_t AT24CXX_ReadOneByte(uint16_t ReadAddr);
void AT24CXX_WriteOneByte(uint16_t WriteAddr,uint8_t DataToWrite);
void AT24CXX_WriteLenByte(uint16_t WriteAddr,uint32_t DataToWrite,uint8_t Len);
uint32_t AT24CXX_ReadLenByte(uint16_t ReadAddr,uint8_t Len);
void AT24CXX_SequentialRead(uint16_t addr,uint8_t *data,uint16_t length);
void AT24CXX_PageWrite(uint16_t addr,uint8_t *data,uint16_t length);

uint8_t AT24CXX_Check(void);
void AT24CXX_Init(void);

#endif


作者:天师电通电容爆破工程师

物联沃分享整理
物联沃-IOTWORD物联网 » 使用HAL库模拟IIC实现STM32对AT24CXX的页读页写

发表评论