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)
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


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/531787.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

WebGIS实现各地区COVID-19数据一览

1.项目地址 GISpjd/WebGIS-Show-Covid19 (github.com)&#xff0c;具体每个文件的职能可以参考README文档。 2.前言 预览 >> 所用技术栈&#xff1a; 项目需求本身不是过于复杂&#xff0c;所以没有在相应前端框架下完成&#xff0c;但转入框架也是比较容易的 &#…

thinkphp6入门(22)-- 如何下载文件

假设在public/uploads文件夹下有一个文件test.xlsx 在前端页面添加下载链接&#xff0c;用户点击该链接即可下载对应的文件。 <a href"xxxxxxx/downloadFile">下载文件</a> 2. 在后端控制器方法中&#xff0c;我们需要获取要下载的文件路径&#xff0…

看linux内核启动流程需要的arm汇编学习笔记(二)

文章目录 一、ldr1.地址偏移模式2.变基模式3.标签3.1 访问宏定义3.2 访问一个字符串3.3 访问一个data 二、ldp和stp1.双字节加载2.双字节存储3.双字节存储的后变基模式 三、位操作1. 移位2. 按位操作3. 位段插入4.位段提取5.零计数指令 四、跳转指令1. cmp比较两个数2. cmn负向…

面试官为什么喜欢考察Vue底层原理

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

系统更新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…

自动驾驶硬件-GNSS

自动驾驶硬件-GNSS 高精度全局定位系统本质上可以看做一个级联的定位系统&#xff0c;先通过GNSS系统提供一个可能的位置范围&#xff0c;再利用激光雷达(Lidar)系统、视觉定位系统等方法进行局部环境的搜索匹配&#xff0c;从而实现厘米级的定位精度。由于需要由GNSS为高精度…

shell脚本2

变量 变量是在程序中保存用户数据的一段内存存储空间&#xff0c;变量名是内存空间的首地址 字母、数字、下划线组成&#xff0c;不能以数字开头 原则&#xff1a;直接使用&#xff0c;不需要变量声明 格式&#xff1a;变量名 变量的值 环境变量 关闭窗口即会失效 若要永久生…

【Ubuntu】远程连接乌班图的方式-命令行界面、图形界面

​​​​​​系统环境&#xff1a;ubuntu-22.04.2-amd64.iso 连接工具&#xff1a;MobaXterm、windows自带远程桌面mstsc.exe 重置root密码&#xff1a;Ubuntu默认root密码是随机的&#xff0c;需要使用命令sudo passwd 进行重置。 一、命令行界面-SSH连接 1.1 SSH服务安装 …

数据的属性与相似性

目录 一、数据集的结构&#xff08;一&#xff09;二维表&#xff08;二&#xff09;数据矩阵 二、属性的类型&#xff08;一&#xff09;连续属性&#xff08;二&#xff09;离散属性&#xff08;三&#xff09;分类属性&#xff08;四&#xff09;二元属性&#xff08;五&…

CentOS 镜像下载

CentOS 镜像下载&#xff1a;https://www.centos.org/download/ 选择合适的架构&#xff0c;博主选择x86_64&#xff0c;表示CentOS7 64位系统x86架构&#xff0c;如下&#xff1a; 或者直接访问以下网站下载 清华大学开源软件镜像站&#xff1a;https://mirrors.tuna.tsin…

国产低代码工具,轻松搞定数据迁移

在日常的业务系统升级或者数据维护过程中&#xff0c;数据迁移是各个企业用户不得不面临的问题&#xff0c;尤其是数据迁移过程中要保障数据完整性、统一性和及时性&#xff0c;同时也需要注意源数据中的数据质量问题&#xff0c;比如缺失、无效、错误等问题&#xff0c;需要在…

安全大脑与盲人摸象

21世纪是数字科技和数字经济爆发的时代&#xff0c;互联网正从网状结构向类脑模型进行进化&#xff0c;出现了结构和覆盖范围庞大&#xff0c;能够适应不同技术环境、经济场景&#xff0c;跨地域、跨行业的类脑复杂巨型系统。如腾讯、Facebook等社交网络具备的神经网络特征&…

实验1 eNSP安装与使用

实验1 eNSP安装与使用 一、 原理描述二、 实验目的三、 实验内容四、 实验步骤1.下载并安装eNSP2.eNSP软件界面3.搭建并运行网络拓扑4. Wireshark 捕获分组并分析 一、 原理描述 eNSP&#xff08;Enterprise Network Simulation Platform&#xff09;是由华为提供的免费网络模…

JDK1.8的安装及环境变量的配置

下载路径&#xff1a; Java Downloads | Oracle 选择对应的操作系统进行下载 1&#xff1a;在D盘新建一个名称为Java的文件夹 [如果你下载的不是这个版本的请自行修改文件夹名称&#xff0c;如版本jdk1.8.0则文件夹名为jdk1.8.0] 2:复制红色框中的名称并在刚刚新建Java文件夹…

【攻防世界】wife_wife

原型链污染 源码 app.post(/register, (req, res) > {let user JSON.parse(req.body)if (!user.username || !user.password) {return res.json({ msg: empty username or password, err: true })}if (users.filter(u > u.username user.username).length) {return …

新平台上线需要注意哪些?

最近有很多被黑客攻击的老板问我前期平台上线安全防护方面需要注意哪些&#xff1f;下面就给大家讲一下。1、如果前期不打算上高防产品&#xff0c;数据一定要做好备份&#xff0c;否则一旦数据被篡改或者被加密&#xff0c;恢复都是比较困难的&#xff0c;甚至都没有办法恢复。…

【简单讲解下WebView的使用与后退键处理】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

使用QtChart绘制一个折线图

记录一下&#xff0c;以备以后查阅 效果图&#xff1a; #include "mychart.h" #include <QLineSeries> #include <QChart> #include <QChartView> #include <QBoxLayout> #include <QtMath>QT_CHARTS_USE_NAMESPACE MyChart::MyChart…

嵌入式:第一天(c语言入门)

目录 一、C语言是什么 二、基础语法 2.1 第一个C语言程序 2.2 注释 2.3 数据类型 数据类型介绍&#xff1a; 变量&#xff1a; 变量的语法&#xff1a; 使用特点&#xff1a; 命名规则和规范&#xff1a; 命令规范&#xff1a; char类型&#xff1a; Boolean类型&…

2024-04-08

作业要求&#xff1a; 1> 思维导图 2>使用手动连接&#xff0c;将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在自定义的槽函数中调用关闭函数 将登录按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在槽函数中判断ui界面上输入的账号是否…