STM32页读页写AT24CXX(HAL库 模拟IIC)

参考文章:

这里附上一篇看到写得很好的大佬的文章:
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

cpp 复制代码
/**	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

cpp 复制代码
/**	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
相关推荐
musir110 分钟前
寄存器控制LED灯亮
单片机
JaneZJW17 分钟前
Proteus仿真——《51单片机AD和DA转换器的设计》
单片机·嵌入式硬件·51单片机·proteus
NEWEVA__zzera223 小时前
利用光耦来隔离485芯片与串口引脚,实现自动收发485电路
单片机·嵌入式硬件
m0_748240543 小时前
STM32第十一课:STM32-基于标准库的42步进电机的简单IO控制(附电机教程,看到即赚到)
stm32·单片机·嵌入式硬件
温柔的男孩像海洋丶3 小时前
vscode的keil assistant 中搜索不到全局变量
ide·vscode·单片机
沐欣工作室_lvyiyi3 小时前
基于单片机的多功能智能小车(论文+源码)
stm32·单片机·嵌入式硬件·毕业设计·单片机毕业设计
鸿喵小仙女4 小时前
C# WPF读写STM32/GD32单片机Flash数据
stm32·单片机·c#·wpf
lucy153027510794 小时前
MCU 功耗基准测试
科技·单片机·嵌入式硬件·智能家居·信号处理·工控主板
m0_748240915 小时前
OpenMV与STM32通信全面指南
stm32·单片机·嵌入式硬件
Cchengzu7 小时前
阿里巴巴2017实习生笔试题(二)
stm32·单片机·嵌入式硬件