EEPROM读写驱动程序

本文详细介绍了EEPROM(电可擦除只读存储器)的IIC接口操作,包括EEPROM_TypeDef结构定义、应答查询机制、初始化步骤、单字节和页写入,以及读取数据的函数实现。涵盖了常见型号如24C04、24C32等的寻址策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


前言

EEPROM有多种型号,操作方式略有不同。
在这里插入图片描述

AT24C256页信息

在这里插入图片描述
从图中可以看到24C04占用了A0寻址地址作为数据寻址地址,24C08和24C16也占用了器件地址作为数据寻址地址,而24C32、24C64、24C128、24C256则使用2个字节为数据寻址地址。


一、EEPROM_TypeDef

抽象出EERPOM的结构体,包括IIC接口、页大小、和启动传输的方式等。

typedef enum
{
	AT24C02 = 0,
	AT24C04,
	AT24C08,
	AT24C16,
	AT24C32 = 4,
	AT24C64,
	AT24C128,
	AT24C256 = 7,
} EEPROM_Type;

typedef struct
{
	IIC_TypeDef IIC;//IIC接口
	EEPROM_Type Type;//EEPROM类型
	uint16_t PageSize;//页大小
	uint16_t PageCount;//页数
	uint16_t TotalCapacity;//总容量
	//因为EEPROM器件型号不同启动传输的操作也有所不同,所以使用的启动方式也有所不同,如AT24C04在启动一次传输时需要占用一个bit的器件地址做寻址地址
	uint8_t (*EEPROM_StartTransmission)(void* self, uint16_t address);
} EEPROM_TypeDef;

一、应答查询

很多EEPROM的驱动程序都是写入之后添加一个10ms的延时等待在下一次查询,但EEPROM的数据手册上都是推荐使用应答查询的机制,在使用过程中也发现写入的字节少时等待的时间也会减少,使用延时的方法会造成一定的性能损耗。
在这里插入图片描述

/** 
 * @brief 判断EEPROM是否在忙状态,包含一个开始和发送设备地址的过程
 * @param EEPROM_TypeDef *pEEPROM
 * @param uint8_t devAdd 设备地址
 * @retval 0:不忙可以继续操作,1:忙禁止操作
 */
static uint8_t EEPROM_IsBusy(EEPROM_TypeDef *const pEEPROM, uint8_t devAdd)
{
	for (uint8_t i = 0; i < EEPROM_BUSY_CNT; i++)
	{
		IIC_Start(&pEEPROM->IIC);
		if(IIC_SendAndSack(&pEEPROM->IIC, devAdd) == 0)
		{
			return 0;
		}
		if(i < EEPROM_BUSY_CNT - 1)
		{
			IIC_Stop(&pEEPROM->IIC);
			Delay_ms(1);
		}
	}
	return 1;
}

二、初始化

在初始化时设置根据器件型号设置一些参数,页大小,寻址操作函数和总容量。

/** 
 * @brief 寻址处理函数,支持AT24C02,AT24C04,AT24C08,AT24C16
 * @param void* self
 * @param uint16_t address
 * @retval: 
 */
uint8_t AT24C0X_StartTransmission(void* self, uint16_t address)
{
	EEPROM_TypeDef* pEEPROM = self;
	uint8_t add = pEEPROM->IIC.ADD | ((address>>8) & ~(0xff << (uint8_t)pEEPROM->Type));
	if(EEPROM_IsBusy(pEEPROM, add))
		return 1;
	if (IIC_SendAndSack(&pEEPROM->IIC, address & 0xFF))
		return 2;
	return 0;
}

/** 
 * @brief 寻址处理函数,支持AT24C32,AT24C64,AT24C128,AT24C256
 * @param void* self
 * @param uint16_t address
 * @retval: 
 */
uint8_t AT24CXX_StartTransmission(void* self, uint16_t address)
{
	IIC_TypeDef* IIC = self;
	if (EEPROM_IsBusy(self, IIC->ADD))
		return 1;
	if (IIC_SendAndSack(IIC, address >> 8))
		return 2;
	if (IIC_SendAndSack(IIC, address & 0xFF))
		return 3;
	return 0;
}

/**
 * @brief 初始化EEPROM
 * @param EEPROM_TypeDef *pEEPROM
 * @param EEPROM_Type type EEPROM类型
 * @retval 0:成功,其他失败
 */
uint8_t EEPROM_Init(EEPROM_TypeDef *const pEEPROM, EEPROM_Type type)
{
	IIC_Init(&pEEPROM->IIC);
	switch(type)
	{
		case AT24C02:
		case AT24C04:
		case AT24C08:
		case AT24C16:
			pEEPROM->Type = type;
			if (type == AT24C02) pEEPROM->PageSize = 8;
			else pEEPROM->PageSize = 16;
			pEEPROM->EEPROM_StartTransmission = &AT24C0X_StartTransmission;
		break;
		case AT24C32:
		case AT24C64:
		case AT24C128:
		case AT24C256:
			pEEPROM->Type = type;
			if (type == AT24C256) pEEPROM->PageSize = 64;
			else pEEPROM->PageSize = 32;
			pEEPROM->EEPROM_StartTransmission = &AT24CXX_StartTransmission;
		break;
		default:
			return 2;
	}
	pEEPROM->TotalCapacity = 256;
	for (uint8_t i = 0; i < (uint8_t)type; i++)
	{
		pEEPROM->TotalCapacity *= 2;
	}
	pEEPROM->PageCount = pEEPROM->TotalCapacity / pEEPROM->PageSize;
	return EEPROM_Check(pEEPROM);
}

三、写数据

1.写一个字节

在这里插入图片描述

/**
 * @brief 写入一个字节
 * @param EEPROM_TypeDef *pEEPROM
 * @param uint16_t address 写入地址
 * @param uint8_t dat 数据
 * @retval 0:成功,其他失败
 */
uint8_t EEPROM_WriteByte(EEPROM_TypeDef *const pEEPROM, uint16_t address, uint8_t dat)
{
	uint8_t result = 0;

	if(pEEPROM->EEPROM_StartTransmission(pEEPROM, address))
	{
		result = 1;
		goto err;
	}
	// 发送数据
	if (IIC_SendAndSack(&pEEPROM->IIC, dat))
	{
		result = 2;
	}
err:
	IIC_Stop(&pEEPROM->IIC);
	return result;
}

2.页写

从图中可以看到页写为在写入一个字节后继续发送数据,并且需要进行页对齐,这里是使用递归来实现的。
在这里插入图片描述

/** 
 * @brief: 
 * @param EEPROM_TypeDef *pEEPROM
 * @param uint16_t address
 * @param uint8_t *memory
 * @param uint16_t size
 * @retval: 
 */
uint8_t EEPROM_Write(EEPROM_TypeDef *const pEEPROM, uint16_t address, uint8_t *memory, uint16_t size)
{
	uint8_t result = 0;

	//开始一次IIC传输
	if(pEEPROM->EEPROM_StartTransmission(pEEPROM, address))
	{
		result = 1;
		goto err;
	}
	// 写入数据
	for (uint16_t i = 0; i < size; i++)
	{
		if (IIC_SendAndSack(&pEEPROM->IIC, memory[i]))
		{
			result = 2 + i;
			break;
		}
		// 结束
		if (i + 1 == size)
			break;
		//如果下一个地址是新页的开头结束本次传输
		if ((address + i + 1) % pEEPROM->PageSize == 0)
		{
			IIC_Stop(&pEEPROM->IIC);//结束本页传输
			//递归操作剩下的数据
			return EEPROM_Write(pEEPROM, address + i + 1, memory + i + 1, size - i - 1);
		}
	}

err:
	IIC_Stop(&pEEPROM->IIC);
	return result;
}

四、读数据

1.读一个字节

从图中可以看出在发送完操作地址之后会再次发送一个开始时序和一个器件地址然后才开始读取数据。
在这里插入图片描述

/** 
 * @brief 读取一个字节
 * @param EEPROM_TypeDef *pEEPROM
 * @param uint16_t address
 * @param uint8_t* dat
 * @retval 0:成功,其他失败
 */
uint8_t EEPROM_ReadByte(EEPROM_TypeDef *const pEEPROM, uint16_t address, uint8_t* dat)
{
	uint8_t result = 0;

	// 发送写设备地址
	if(pEEPROM->EEPROM_StartTransmission(pEEPROM, address))
	{
		result = 1;
		goto err;
	}
	// 重启IIC
	IIC_Start(&pEEPROM->IIC);
	// 发送读设备地址
	if (IIC_SendReadAddress(&pEEPROM->IIC))
	{
		result = 2;
		goto err;
	}
	*dat = IIC_ReadAndAck(&pEEPROM->IIC, 0);

err:
	IIC_Stop(&pEEPROM->IIC);
	return result;
}

2.读多个数据

读多个数据较为简单,不需要像写数据一样进行页对齐。
在这里插入图片描述

/** 
 * @brief 读取数据
 * @param EEPROM_TypeDef *pEEPROM
 * @param uint16_t address
 * @param uint8_t *memory
 * @param uint16_t size
 * @retval 0:成功,其他失败
 */
uint8_t EEPROM_Read(EEPROM_TypeDef *const pEEPROM, uint16_t address, uint8_t *memory, uint16_t size)
{
	uint8_t result = 0;

	// 发送写设备地址
	if(pEEPROM->EEPROM_StartTransmission(pEEPROM, address))
	{
		result = 1;
		goto err;
	}
	// 重启IIC
	IIC_Start(&pEEPROM->IIC);
	// 发送读设备地址
	if (IIC_SendReadAddress(&pEEPROM->IIC))
	{
		result = 2;
		goto err;
	}

	IIC_Read(&pEEPROM->IIC, memory, size )

err:
	IIC_Stop(&pEEPROM->IIC);
	return result;
}

程序地址

软件IIC驱动地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值