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

news/2024/6/3 18:27:33 标签: stm32, 嵌入式硬件, 单片机

参考文章:

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

 AT24C32/64官方手册:AT24C32/64

一、AT24CXX容量表

二、AT24CXX寻址方式

三、AT24CXX时序图

1.字节写

2.页写

3.当前地址读

4.顺序读

 5.随机读

四、代码


一、AT24CXX容量表

型号容量(bit)容量(byte)页数每页字节数(byte)
AT24C011K128168
AT24C022K256328
AT24C044K5123216
AT24C088K10246416
AT24C1616K204812816
AT24C3232K409612832
AT24C6464K819225632
AT24C128128K1638425664
AT24C256256K3276851264
AT24C512512K65536512128

二、AT24CXX寻址方式

型号WORD ADDRESS(bit)型号WORD ADDRESS(bit)
AT24C017AT24C3212
AT24C028AT24C6413
AT24C049AT24C12814
AT24C0810AT24C25615
AT24C1611AT24C51216

三、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



http://www.niftyadmin.cn/n/5480699.html

相关文章

系统更新Javahome之后,eclipse ide没有同步更新的解决方案

1、确认eclipse idea当前使用jdk 路径 &#xff1a; 2、确认Ide路径为旧的之后&#xff0c;去到eclipse的应用启动路径&#xff0c;编辑【eclipse.ini】, 在【-vmargs】之前设置vm路径&#xff08;换行为必须的&#xff09;&#xff1a; -vm C:\Program Files\Java\jdk1.8.0_1…

gemini1.5 API调用

https://ai.google.dev/pricing?hlzh-cn 查询可用的model https://generativelanguage.googleapis.com/v1beta/models?keyxxx 使用postman调用 https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro-latest:generateContent?keyxxx https://ai.google…

微信小程序页面交互综合练习 (重点:解决“setData of undefined”报错问题)

一、写一个注册表单&#xff0c;点击“注册”按钮将用户输入的数据带到服务器&#xff0c;并且能在控制台显示参数。 &#xff08;1&#xff09;首先&#xff0c;我需要在vscode里面创建一个简易的node.js服务器 //第一步:引入http模块 var http require(http); //第二步:创建…

QT使用单例模式创建全局引用类

单例模式介绍 单例模式是一种设计模式,用于确保类只能创建一个实例,并提供一种全局访问该实例的方式。在单例模式中,类的构造函数被私有化,这样就不能通过常规方式创建对象实例。相反,类提供一个静态方法或静态变量来获取其唯一的实例。 单例模式通常用于管理全局状态或…

【Go】go mod初始化

遇到问题 创建go项目时使用 go mod init 命令提示如下错误&#xff1a; go: cannot determine module path for source directory /Users/easton/go/basic-go (outside GOPATH, module path must be specified) 解决方案一 这是因为go mod init 初始化项目时&#xff0c;需要…

Substance 3D2024版 下载地址及安装教程

Substance 3D是Adobe公司推出的一套全面的3D设计和创作工具集合&#xff0c;用于创建高质量的3D资产、纹理和材质。 Substance 3D包括多个功能强大的软件和服务&#xff0c;如Substance 3D Painter、Substance 3D Designer和Substance 3D Sampler等。这些工具提供了广泛的功能…

Python爬虫之Scrapy框架基础

Scrapy爬虫框架介绍 文档 英文文档中文文档 什么是scrapy 基于twisted搭建的异步爬虫框架. scrapy爬虫框架根据组件化设计理念和丰富的中间件, 使其成为了一个兼具高性能和高扩展的框架 scrapy提供的主要功能 具有优先级功能的调度器去重功能失败后的重试机制并发限制ip使用次…

Objective-C学习笔记(ARC,分类,延展)4.10

1.自动释放池autoreleasepool&#xff1a;存入到自动释放池的对象&#xff0c;在自动释放池销毁时&#xff0c;会自动调用池内所有对象的release方法。调用autorelease方法将对象放入自动释放池。 Person *p1 [ [ [ Person alloc ] init ] autorelease]; 2.在类方法里写一个…