STM32F407单片机通用24CXXX读写程序(KEIL),兼容24C系列存储器(24C01到24C512),支持存储器任意地址跨页连续读写多个页

2023-11-14

一、AT24CXXX容量

  AT24C01,AT24C02,AT24C04,AT24C08,AT24C16,AT24C32,AT24C64,AT24C128,AT24C256…不同的xxx代表不同的容量。

AT24CXXX bit容量 Byte容量
AT24C01 1Kbit 128Byte
AT24C02 2Kbit 256Byte
AT24C04 4Kbit 512Byte
AT24C08 8Kbit 1024Byte
AT24C16 16Kbit 2048Byte
AT24C32 32Kbit 4096Byte
AT24C64 64Kbit 8192Byte
AT24C128 128Kbit 16384Byte
AT24C256 256Kbit 32768Byte
AT24C512 512Kbit 65536Byte

二、AT24CXXX页与页内单元

  总容量(Byte容量) = 页数 × 页内字节单元数。

AT24CXXX Byte容量 页数 页内字节单元数
AT24C01 128Byte 16页 8Byte
AT24C02 256Byte 32页 8Byte
AT24C04 512Byte 32页 16Byte
AT24C08 1024Byte 64页 16Byte
AT24C16 2048Byte 128页 16Byte
AT24C32 4096Byte 128页 32Byte
AT24C64 8192Byte 256页 32Byte
AT24C128 16384Byte 256页 64Byte
AT24C256 32768Byte 512页 64Byte
AT24C512 65536Byte 512页 128Byte

三、AT24CXXXX寻址方式(不是IIC地址,是存储器内部寻址)

  对AT24CXXX进行读写操作时,都得先访问存储地址、比如AT24C01写一个字节的IIC时序:
在这里插入图片描述
  先发送设备地址,收到应答后再发送需要写数据的地址(WORD ADDRESS)。AT24C01容量为128Byte则WORD ADDRESS只需要7bit就可以覆盖128Byte的数据地址。通俗的讲就是128Byte就占用了128个地址,一个7bit的数据范围为(0-127)刚好128,所以128Byte的字节地址需要一个7bit的数据来表示。
AT24CXXX 字节地址如下(*表示无效位):

AT24CXXX 容量(Byte) WORD ADDRESS(占用bit数) WORD ADDRESS
AT24C01 128Byte 7bit 在这里插入图片描述
AT24C02 256Byte 8bit 在这里插入图片描述
AT24C04 512Byte 9bit 在这里插入图片描述
AT24C08 1024Byte 10bit 在这里插入图片描述
AT24C16 2048Byte 11bit 在这里插入图片描述
AT24C32 4096Byte 12bit 在这里插入图片描述
AT24C64 8192Byte 13bit 在这里插入图片描述
AT24C128 16384Byte 14bit 在这里插入图片描述
AT24C256 32768Byte 15bit 在这里插入图片描述
AT24C512 65536Byte 16bit 在这里插入图片描述

四、AT24CXXX页地址与页内单元地址

  比如AT24C256有512页每页64个字节,15bit的地址数据对其寻址,低6bit(D5-D0)为页内字节单元地址,高9bit(D14-D6)为页地址。
在这里插入图片描述
如第16页开始写,则WORD ADDRESS = 0x0400(0000 0100 0000 0000)
0:地址无效位
000 0100 00:9位页地址
00 0000:6位页内字节单元地址
下表如AT24C01
16页:需要4bit寻址(2^4=16)
8Byte:需要3bit寻址(2^3=8)

AT24CXXX Byte容量 页数 页内字节单元数 页地址 页内偏移地址
AT24C01 128Byte 16页 8Byte 在这里插入图片描述
AT24C02 256Byte 32页 8Byte 在这里插入图片描述
AT24C04 512Byte 32页 16Byte 在这里插入图片描述
AT24C08 1024Byte 64页 16Byte 在这里插入图片描述
AT24C16 2048Byte 128页 16Byte 在这里插入图片描述
AT24C32 4096Byte 128页 32Byte 在这里插入图片描述
AT24C64 8192Byte 256页 32Byte 在这里插入图片描述
AT24C128 16384Byte 256页 64Byte 在这里插入图片描述
AT24C256 32768Byte 512页 64Byte 在这里插入图片描述
AT24C512 65536Byte 512页 128Byte 在这里插入图片描述

查看手册
AT24C01字节寻址需一个7bit地址:
在这里插入图片描述
AT24C128字节寻址需一个14bit地址:
在这里插入图片描述
以此类推,其实就是上面总结的那张表。

五、AT24CXXX IIC地址

  IIC通信需要先向从设备发送设备地址,AT24CXXX芯片上有A2、A1、A0引脚,通过这三个引脚我们就可以自定义AT24CXXX芯片的通信地址。
在这里插入图片描述
地址构成如下(手册上都会有写),比如A2、A1、A0接地,则IIC写地址为1010 0000(0xA0),读地址为1010 0001(0xA1),有关IIC地址详情请看IIC协议详解
在这里插入图片描述

六、AT24CXXX 数据的读写

AT24C256为例

1、字节写

在这里插入图片描述

2、按页写

在这里插入图片描述
★★★注意:
  往AT24CXXX中写数据时,每写一个Byte的数据页内地址+1,当前页写满后会重新覆盖掉这一页前面的数据,而不会自动跳转到下一页,但是读会自动翻页。
具体看手册:
在这里插入图片描述

3、如何翻页写

  按页写其实就是执行一次下面的时序,也就是发送一次从机设备和字节地址最大就可以写入64字节的数据,如果要连写多页,就重新按照以下时序发送从机地址和字节地址等。
在这里插入图片描述

4、读

有以下模式,和写差不多
在这里插入图片描述

 

在这里插入图片描述

七、源程序

 1、i2c_gpio.h

#ifndef _BSP_I2C_GPIO_H
#define _BSP_I2C_GPIO_H

#include "stm32f4xx.h"


#define I2C_WR	0		// 写控制bit
#define I2C_RD	1		// 读控制bit



void BSP_AT24CXX_InitI2C(void);
void i2c_Start(void);
void i2c_Stop(void);
void i2c_SendByte(uint8_t _ucByte);
uint8_t i2c_ReadByte(void);
uint8_t i2c_WaitAck(void);
void i2c_Ack(void);
void i2c_NAck(void);
uint8_t i2c_CheckDevice(uint8_t _Address);

#endif

2、i2c_ee.h

#ifndef __I2C_EE_H
#define	__I2C_EE_H

#include "stm32f4xx.h"

/* 
 * AT24C02 2kb = 2048bit = 2048/8 B = 256 B
 * 32 pages of 8 bytes each
 *
 * Device Address
 * 1 0 1 0 A2 A1 A0 R/W
 * 1 0 1 0 0  0  0  0 = 0xA0
 * 1 0 1 0 0  0  0  1 = 0xA1 
 */

/* AT24C01/02每页有8个字节 
 * AT24C04/08A/16A每页有16个字节 、
 */
	


#define AT24C512



#ifdef AT24C01
	#define EE_MODEL_NAME		"AT24C01"
	#define EE_DEV_ADDR			0xA0		/* 设备地址 */
	#define EE_PAGE_SIZE		8			/* 页面大小(字节) */
	#define EE_SIZE				128			/* 总容量(字节) */
	#define EE_ADDR_BYTES		1			/* 地址字节个数 */
	#define EE_ADDR_A8			0			/* 地址字节的高8bit不在首字节 */
#endif



#ifdef AT24C02
	#define EE_MODEL_NAME		"AT24C02"
	#define EE_DEV_ADDR			0xA0		/* 设备地址 */
	#define EE_PAGE_SIZE		8			/* 页面大小(字节) */
	#define EE_SIZE				256			/* 总容量(字节) */
	#define EE_ADDR_BYTES		1			/* 地址字节个数 */
	#define EE_ADDR_A8			0			/* 地址字节的高8bit不在首字节 */
#endif



#ifdef AT24C04
	#define EE_MODEL_NAME		"AT24C04"
	#define EE_DEV_ADDR			0xA0		/* 设备地址 */
	#define EE_PAGE_SIZE		8			/* 页面大小(字节) */
	#define EE_SIZE				512			/* 总容量(字节) */
	#define EE_ADDR_BYTES		1			/* 地址字节个数 */
	#define EE_ADDR_A8			1			/* 地址字节的高8bit在首字节 */
#endif



#ifdef AT24C08
	#define EE_MODEL_NAME		"AT24C08"
	#define EE_DEV_ADDR			0xA0		/* 设备地址 */
	#define EE_PAGE_SIZE		16			/* 页面大小(字节) */
	#define EE_SIZE				(16*64)		/* 总容量(字节) */
	#define EE_ADDR_BYTES		2			/* 地址字节个数 */
	#define EE_ADDR_A8			1			/* 地址字节的高8bit在首字节 */
#endif




#ifdef AT24C16
	#define EE_MODEL_NAME		"AT24C16"
	#define EE_DEV_ADDR			0xA0		/* 设备地址 */
	#define EE_PAGE_SIZE		16			/* 页面大小(字节) */
	#define EE_SIZE				(128*16)	/* 总容量(字节) */
	#define EE_ADDR_BYTES		2			/* 地址字节个数 */
	#define EE_ADDR_A8			1			/* 地址字节的高8bit在首字节 */
#endif



#ifdef AT24C32
	#define EE_MODEL_NAME		"AT24C32"
	#define EE_DEV_ADDR			0xA0		/* 设备地址 */
	#define EE_PAGE_SIZE		32			/* 页面大小(字节) */
	#define EE_SIZE				(128*32)	/* 总容量(字节) */
	#define EE_ADDR_BYTES		2			/* 地址字节个数 */
	#define EE_ADDR_A8			1			/* 地址字节的高8bit在首字节 */
#endif


#ifdef AT24C64
	#define EE_MODEL_NAME		"AT24C64"
	#define EE_DEV_ADDR			0xA0		/* 设备地址 */
	#define EE_PAGE_SIZE		32			/* 页面大小(字节) */
	#define EE_SIZE				(256*32)	/* 总容量(字节) */
	#define EE_ADDR_BYTES		2			/* 地址字节个数 */
	#define EE_ADDR_A8			1			/* 地址字节的高8bit在首字节 */
#endif



#ifdef AT24C128
	#define EE_MODEL_NAME		"AT24C128"
	#define EE_DEV_ADDR			0xA0		/* 设备地址 */
	#define EE_PAGE_SIZE		64			/* 页面大小(字节) */
	#define EE_SIZE				(256*64)	/* 总容量(字节) */
	#define EE_ADDR_BYTES		2			/* 地址字节个数 */
	#define EE_ADDR_A8			0			/* 地址字节的高8bit不在首字节 */
#endif



#ifdef AT24C256
	#define EE_MODEL_NAME		"AT24C256"
	#define EE_DEV_ADDR			0xA0		/* 设备地址 */
	#define EE_PAGE_SIZE		64			/* 页面大小(字节) */
	#define EE_SIZE				(512*64)	/* 总容量(字节) */
	#define EE_ADDR_BYTES		2			/* 地址字节个数 */
	#define EE_ADDR_A8			0			/* 地址字节的高8bit不在首字节 */
#endif



#ifdef AT24C512
	#define EE_MODEL_NAME		"AT24C512"
	#define EE_DEV_ADDR			0xA0		/* 设备地址 */
	#define EE_PAGE_SIZE		128			/* 页面大小(字节) */
	#define EE_SIZE				(512*128)	/* 总容量(字节) */
	#define EE_ADDR_BYTES		2			/* 地址字节个数 */
	#define EE_ADDR_A8			0			/* 地址字节的高8bit不在首字节 */
#endif



#endif /* __I2C_EE_H */


3、i2c_gpio.c

*
*********************************************************************************************************
*
*	模块名称 : I2C总线驱动模块
*	文件名称 : bsp_i2c_gpio.c
*	版    本 : V1.0
*	说    明 : 用gpio模拟i2c总线, 适用于STM32F4系列CPU。该模块不包括应用层命令帧,仅包括I2C总线基本操作函数。
*
*	修改记录 :
*		版本号  日期        作者     说明
*		V1.0    2013-02-01 armfly  正式发布
*
*	Copyright (C), 2013-2014, 安富莱电子 www.armfly.com
*
*********************************************************************************************************
*/

/*
	应用说明:
	在访问I2C设备前,请先调用 i2c_CheckDevice() 检测I2C设备是否正常,该函数会配置GPIO
*/



#include "stm32f4xx.h"
#include "i2c_gpio.h"




#define RCC_AT24CXX_I2C_PORT 			RCC_AHB1Periph_GPIOB		// GPIO端口时钟
#define GPIO_AT24CXX_I2C_PORT			GPIOB						// GPIO端口
#define GPIO_AT24CXX_I2C_SCL_Pin		GPIO_Pin_6					// 连接到SCL时钟线的GPIO
#define GPIO_AT24CXX_I2C_SDA_Pin		GPIO_Pin_7					// 连接到SDA数据线的GPIO



#define I2C_SCL_1()  	GPIO_SetBits(GPIO_AT24CXX_I2C_PORT, GPIO_AT24CXX_I2C_SCL_Pin)			// SCL = 1
#define I2C_SCL_0()  	GPIO_ResetBits(GPIO_AT24CXX_I2C_PORT, GPIO_AT24CXX_I2C_SCL_Pin)			// SCL = 0
#define I2C_SDA_1()  	GPIO_SetBits(GPIO_AT24CXX_I2C_PORT, GPIO_AT24CXX_I2C_SDA_Pin)			// SDA = 1
#define I2C_SDA_0()  	GPIO_ResetBits(GPIO_AT24CXX_I2C_PORT, GPIO_AT24CXX_I2C_SDA_Pin)			// SDA = 0
#define I2C_SDA_READ()  GPIO_ReadInputDataBit(GPIO_AT24CXX_I2C_PORT, GPIO_AT24CXX_I2C_SDA_Pin)				// 读SDA口线状态
#define I2C_SCL_READ()  GPIO_ReadInputDataBit(GPIO_AT24CXX_I2C_PORT, GPIO_AT24CXX_I2C_SCL_Pin)				// 读SCL口线状态



/*
*********************************************************************************************************
*	函 数 名: bsp_InitI2C
*	功能说明: 配置I2C总线的GPIO,采用模拟IO的方式实现
*	形    参:  无
*	返 回 值: 无
*********************************************************************************************************
*/
void BSP_AT24CXX_InitI2C(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;

	
	RCC_AHB1PeriphClockCmd(RCC_AT24CXX_I2C_PORT , ENABLE);	/* 打开GPIO时钟 */

	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;		/* 设为输出口 */
	GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;		/* 设为开漏模式 */
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;	/* 上下拉电阻不使能 */
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz;	/* IO口最大速度 */
	GPIO_InitStructure.GPIO_Pin = GPIO_AT24CXX_I2C_SCL_Pin | GPIO_AT24CXX_I2C_SDA_Pin;
	GPIO_Init(GPIO_AT24CXX_I2C_PORT, &GPIO_InitStructure);

	/* 给一个停止信号, 复位I2C总线上的所有设备到待机模式 */
	i2c_Stop();
}



/*
*********************************************************************************************************
*	函 数 名: i2c_Delay
*	功能说明: I2C总线位延迟,最快400KHz
*	形    参:  无
*	返 回 值: 无
*********************************************************************************************************
*/
static void i2c_Delay(void)
{
	uint8_t i;

	/* 
		CPU主频168MHz时,在内部Flash运行, MDK工程不优化。用台式示波器观测波形。
		循环次数为5时,SCL频率 = 1.78MHz (读耗时: 92ms, 读写正常,但是用示波器探头碰上就读写失败。时序接近临界)
		循环次数为10时,SCL频率 = 1.1MHz (读耗时: 138ms, 读速度: 118724B/s)
		循环次数为30时,SCL频率 = 440KHz, SCL高电平时间1.0us,SCL低电平时间1.2us

		上拉电阻选择2.2K欧时,SCL上升沿时间约0.5us,如果选4.7K欧,则上升沿约1us

		实际应用选择400KHz左右的速率即可
	*/
	for (i = 0; i < 30; i++)
	{
		__NOP();
		__NOP();
	}
}



/*
*********************************************************************************************************
*	函 数 名: i2c_Start
*	功能说明: CPU发起I2C总线启动信号
*	形    参:  无
*	返 回 值: 无
*********************************************************************************************************
*/
void i2c_Start(void)
{
	/* 当SCL高电平时,SDA出现一个下跳沿表示I2C总线启动信号 */
	I2C_SDA_1();
	I2C_SCL_1();
	i2c_Delay();
	I2C_SDA_0();
	i2c_Delay();
	I2C_SCL_0();
	i2c_Delay();
}



/*
*********************************************************************************************************
*	函 数 名: i2c_Start
*	功能说明: CPU发起I2C总线停止信号
*	形    参:  无
*	返 回 值: 无
*********************************************************************************************************
*/
void i2c_Stop(void)
{
	/* 当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号 */
	I2C_SDA_0();
	I2C_SCL_1();
	i2c_Delay();
	I2C_SDA_1();
	i2c_Delay();
}



/*
*********************************************************************************************************
*	函 数 名: i2c_SendByte
*	功能说明: CPU向I2C总线设备发送8bit数据
*	形    参:  _ucByte : 等待发送的字节
*	返 回 值: 无
*********************************************************************************************************
*/
void i2c_SendByte(uint8_t _ucByte)
{
	uint8_t i;

	/* 先发送字节的高位bit7 */
	for (i = 0; i < 8; i++)
	{
		if (_ucByte & 0x80)
		{
			I2C_SDA_1();
		}
		else
		{
			I2C_SDA_0();
		}
		i2c_Delay();
		I2C_SCL_1();
		i2c_Delay();
		I2C_SCL_0();
		if (i == 7)
		{
			 I2C_SDA_1(); // 释放总线
		}
		_ucByte <<= 1;	/* 左移一个bit */
		i2c_Delay();
	}
}



/*
*********************************************************************************************************
*	函 数 名: i2c_ReadByte
*	功能说明: CPU从I2C总线设备读取8bit数据
*	形    参:  无
*	返 回 值: 读到的数据
*********************************************************************************************************
*/
uint8_t i2c_ReadByte(void)
{
	uint8_t i;
	uint8_t value;

	/* 读到第1个bit为数据的bit7 */
	value = 0;
	for (i = 0; i < 8; i++)
	{
		value <<= 1;
		I2C_SCL_1();
		i2c_Delay();
		if (I2C_SDA_READ())
		{
			value++;
		}
		I2C_SCL_0();
		i2c_Delay();
	}
	return value;
}



/*
*********************************************************************************************************
*	函 数 名: i2c_WaitAck
*	功能说明: CPU产生一个时钟,并读取器件的ACK应答信号
*	形    参:  无
*	返 回 值: 返回0表示正确应答,1表示无器件响应
*********************************************************************************************************
*/
uint8_t i2c_WaitAck(void)
{
	uint8_t re;

	I2C_SDA_1();	/* CPU释放SDA总线 */
	i2c_Delay();
	I2C_SCL_1();	/* CPU驱动SCL = 1, 此时器件会返回ACK应答 */
	i2c_Delay();
	if (I2C_SDA_READ())	/* CPU读取SDA口线状态 */
	{
		re = 1;
	}
	else
	{
		re = 0;
	}
	I2C_SCL_0();
	i2c_Delay();
	return re;
}



/*
*********************************************************************************************************
*	函 数 名: i2c_Ack
*	功能说明: CPU产生一个ACK信号
*	形    参:  无
*	返 回 值: 无
*********************************************************************************************************
*/
void i2c_Ack(void)
{
	I2C_SDA_0();	/* CPU驱动SDA = 0 */
	i2c_Delay();
	I2C_SCL_1();	/* CPU产生1个时钟 */
	i2c_Delay();
	I2C_SCL_0();
	i2c_Delay();
	I2C_SDA_1();	/* CPU释放SDA总线 */
}



/*
*********************************************************************************************************
*	函 数 名: i2c_NAck
*	功能说明: CPU产生1个NACK信号
*	形    参:  无
*	返 回 值: 无
*********************************************************************************************************
*/
void i2c_NAck(void)
{
	I2C_SDA_1();	/* CPU驱动SDA = 1 */
	i2c_Delay();
	I2C_SCL_1();	/* CPU产生1个时钟 */
	i2c_Delay();
	I2C_SCL_0();
	i2c_Delay();
}



/*
*********************************************************************************************************
*	函 数 名: i2c_CheckDevice
*	功能说明: 检测I2C总线设备,CPU向发送设备地址,然后读取设备应答来判断该设备是否存在
*	形    参:  _Address:设备的I2C总线地址
*	返 回 值: 返回值 0 表示正确, 返回1表示未探测到
*********************************************************************************************************
*/
uint8_t i2c_CheckDevice(uint8_t _Address)
{
	uint8_t ucAck;

	if (I2C_SDA_READ() && I2C_SCL_READ())
	{
		i2c_Start();		/* 发送启动信号 */

		/* 发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传 */
		i2c_SendByte(_Address | I2C_WR);
		ucAck = i2c_WaitAck();	/* 检测设备的ACK应答 */

		i2c_Stop();			/* 发送停止信号 */

		return ucAck;
	}
	return 1;	/* I2C总线异常 */
}

4、i2c_ee.c

/*
*********************************************************************************************************
*
*	模块名称 : 串行EEPROM 24xx驱动模块
*	文件名称 : bsp_eeprom_24xx.c
*	版    本 : V1.0
*	说    明 : 实现24xx系列EEPROM的读写操作。写操作采用页写模式提高写入效率。
*
*	修改记录 :
*		版本号  日期        作者     说明
*		V1.0    2013-02-01 armfly  正式发布
*
*	Copyright (C), 2013-2014, 安富莱电子 www.armfly.com
*
*********************************************************************************************************
*/

/*
	应用说明:访问串行EEPROM前,请先调用一次 bsp_InitI2C()函数配置好I2C相关的GPIO.
*/



#include "i2c_gpio.h"
#include "i2c_ee.h"




/*
*********************************************************************************************************
*	函 数 名: ee_CheckOk
*	功能说明: 判断串行EERPOM是否正常
*	形    参:  无
*	返 回 值: 1 表示正常, 0 表示不正常
*********************************************************************************************************
*/
uint8_t ee_CheckOk(void)
{
	if (i2c_CheckDevice(EE_DEV_ADDR) == 0)
	{
		return 1;
	}
	else
	{
		/* 失败后,切记发送I2C总线停止信号 */
		i2c_Stop();
		return 0;
	}
}


/*
*********************************************************************************************************
*	函 数 名: ee_ReadBytes
*	功能说明: 从串行EEPROM指定地址处开始读取若干数据
*	形    参:  _usAddress : 起始地址
*			 _usSize : 数据长度,单位为字节
*			 _pReadBuf : 存放读到的数据的缓冲区指针
*	返 回 值: 0 表示失败,1表示成功
*********************************************************************************************************
*/
uint8_t ee_ReadBytes(uint8_t *_pReadBuf, uint16_t _usAddress, uint16_t _usSize)
{
	uint16_t i;

	/* 采用串行EEPROM随即读取指令序列,连续读取若干字节 */

	/* 第1步:发起I2C总线启动信号 */
	i2c_Start();

	/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
	#if EE_ADDR_A8 == 1
		i2c_SendByte(EE_DEV_ADDR | I2C_WR | ((_usAddress >> 7) & 0x0E));	/* 此处是写指令 */
	#else
		i2c_SendByte(EE_DEV_ADDR | I2C_WR);	/* 此处是写指令 */
	#endif

	/* 第3步:发送ACK */
	if (i2c_WaitAck() != 0)
	{
		goto cmd_fail;	/* EEPROM器件无应答 */
	}

	/* 第4步:发送字节地址,24C02只有256字节,因此1个字节就够了,如果是24C04以上,那么此处需要连发多个地址 */
	if (EE_ADDR_BYTES == 1)
	{
		i2c_SendByte((uint8_t)_usAddress);
		if (i2c_WaitAck() != 0)
		{
			goto cmd_fail;	/* EEPROM器件无应答 */
		}
	}
	else
	{
		i2c_SendByte(_usAddress >> 8);
		if (i2c_WaitAck() != 0)
		{
			goto cmd_fail;	/* EEPROM器件无应答 */
		}

		i2c_SendByte(_usAddress);
		if (i2c_WaitAck() != 0)
		{
			goto cmd_fail;	/* EEPROM器件无应答 */
		}
	}

	/* 第6步:重新启动I2C总线。下面开始读取数据 */
	i2c_Start();

	/* 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
	#if EE_ADDR_A8 == 1
		i2c_SendByte(EE_DEV_ADDR | I2C_RD | ((_usAddress >> 7) & 0x0E));	/* 此处是写指令 */
	#else		
		i2c_SendByte(EE_DEV_ADDR | I2C_RD);	/* 此处是写指令 */
	#endif	

	/* 第8步:发送ACK */
	if (i2c_WaitAck() != 0)
	{
		goto cmd_fail;	/* EEPROM器件无应答 */
	}

	/* 第9步:循环读取数据 */
	for (i = 0; i < _usSize; i++)
	{
		_pReadBuf[i] = i2c_ReadByte();	/* 读1个字节 */

		/* 每读完1个字节后,需要发送Ack, 最后一个字节不需要Ack,发Nack */
		if (i != _usSize - 1)
		{
			i2c_Ack();	/* 中间字节读完后,CPU产生ACK信号(驱动SDA = 0) */
		}
		else
		{
			i2c_NAck();	/* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */
		}
	}
	/* 发送I2C总线停止信号 */
	i2c_Stop();
	return 1;	/* 执行成功 */

cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
	/* 发送I2C总线停止信号 */
	i2c_Stop();
	return 0;
}

/*
*********************************************************************************************************
*	函 数 名: ee_WriteBytes
*	功能说明: 向串行EEPROM指定地址写入若干数据,采用页写操作提高写入效率
*	形    参:  _usAddress : 起始地址
*			 _usSize : 数据长度,单位为字节
*			 _pWriteBuf : 存放读到的数据的缓冲区指针
*	返 回 值: 0 表示失败,1表示成功
*********************************************************************************************************
*/
uint8_t ee_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize)
{
	uint16_t i,m;
	uint16_t usAddr;

	/*
		写串行EEPROM不像读操作可以连续读取很多字节,每次写操作只能在同一个page。
		对于24xx02,page size = 8
		简单的处理方法为:按字节写操作模式,每写1个字节,都发送地址
		为了提高连续写的效率: 本函数采用page wirte操作。
	*/

	usAddr = _usAddress;
	for (i = 0; i < _usSize; i++)
	{
		/* 当发送第1个字节或是页面首地址时,需要重新发起启动信号和地址 */
		if ((i == 0) || (usAddr & (EE_PAGE_SIZE - 1)) == 0)
		{
			/* 第0步:发停止信号,启动内部写操作 */
			i2c_Stop();

			/* 通过检查器件应答的方式,判断内部写操作是否完成, 一般小于 10ms
				CLK频率为200KHz时,查询次数为30次左右
			*/
			for (m = 0; m < 1000; m++)
			{
				/* 第1步:发起I2C总线启动信号 */
				i2c_Start();

				/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
				
				#if EE_ADDR_A8 == 1
					i2c_SendByte(EE_DEV_ADDR | I2C_WR | ((_usAddress >> 7) & 0x0E));	/* 此处是写指令 */
				#else				
					i2c_SendByte(EE_DEV_ADDR | I2C_WR);
				#endif

				/* 第3步:发送一个时钟,判断器件是否正确应答 */
				if (i2c_WaitAck() == 0)
				{
					break;
				}
			}
			if (m  == 1000)
			{
				goto cmd_fail;	/* EEPROM器件写超时 */
			}

			/* 第4步:发送字节地址,24C02只有256字节,因此1个字节就够了,如果是24C04以上,那么此处需要连发多个地址 */
			if (EE_ADDR_BYTES == 1)
			{
				i2c_SendByte((uint8_t)usAddr);
				if (i2c_WaitAck() != 0)
				{
					goto cmd_fail;	/* EEPROM器件无应答 */
				}
			}
			else
			{
				i2c_SendByte(usAddr >> 8);
				if (i2c_WaitAck() != 0)
				{
					goto cmd_fail;	/* EEPROM器件无应答 */
				}

				i2c_SendByte(usAddr);
				if (i2c_WaitAck() != 0)
				{
					goto cmd_fail;	/* EEPROM器件无应答 */
				}
			}
		}

		/* 第6步:开始写入数据 */
		i2c_SendByte(_pWriteBuf[i]);

		/* 第7步:发送ACK */
		if (i2c_WaitAck() != 0)
		{
			goto cmd_fail;	/* EEPROM器件无应答 */
		}

		usAddr++;	/* 地址增1 */
	}

	/* 命令执行成功,发送I2C总线停止信号 */
	i2c_Stop();

	/* 通过检查器件应答的方式,判断内部写操作是否完成, 一般小于 10ms
		CLK频率为200KHz时,查询次数为30次左右
	*/
	for (m = 0; m < 1000; m++)
	{
		/* 第1步:发起I2C总线启动信号 */
		i2c_Start();

		/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */	
		#if EE_ADDR_A8 == 1
			i2c_SendByte(EE_DEV_ADDR | I2C_WR | ((_usAddress >> 7) & 0x0E));	/* 此处是写指令 */
		#else		
			i2c_SendByte(EE_DEV_ADDR | I2C_WR);	/* 此处是写指令 */
		#endif

		/* 第3步:发送一个时钟,判断器件是否正确应答 */
		if (i2c_WaitAck() == 0)
		{
			break;
		}
	}
	if (m  == 1000)
	{
		goto cmd_fail;	/* EEPROM器件写超时 */
	}

	/* 命令执行成功,发送I2C总线停止信号 */
	i2c_Stop();	

	return 1;

cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
	/* 发送I2C总线停止信号 */
	i2c_Stop();
	return 0;
}




 

八、测试

下面以AT24C512为例,进行测试,测试以下功能:

        1、任意地址连续跨页读多页数据

        2、任意地址连续跨页写多页数据

注意:如果需要测试AT24C512,需要在i2c_ee.h中定义宏定义 AT24C512,告诉单片机目前的芯片是AT24C512芯片。

#define AT24C512

注意:如果需要测试AT24C128,需要在i2c_ee.h中定义宏定义 AT24C128,告诉单片机目前的芯片是AT24C128芯片.

#define AT24C128

为便于观察数据读写的每一个字节都是否正确,初始化数组时,将test_array1[0---127] 初始化数值 = 1---128

                                                  test_array1[128---255] 初始化数值 = 1---128

                                                  test_array1[256---383] 初始化数值 = 1---128

下面的测试程序先将存储在数组test_array1中的连续3页数据写到起始地址为80的芯片中。然后将起始地址为80的芯片中的数据读到数组test_array2.

 

main.c

    #include "i2c_gpio.h"
    #include "i2c_ee.h"

    uint8_t test_array1[3*EE_PAGE_SIZE];   //注:AT24C512时,EE_PAGE_SIZE=128
    uint8_t test_array2[3*EE_PAGE_SIZE];   //    AT24C512时,一个页面有128个字节 



    void DEBUG_test_AT24C512(void)
    {
		uint16_t i;
		uint16_t j;
		
		for (i=0;i<3*EE_PAGE_SIZE;i++)
		{
			if (i>=256)
				j=i-256;            //test_array1[256---383] 单元初始化数值 = 1---128
			else if (i>=128)
			   j=i-128;            //test_array1[128---255] 单元初始化数值 = 1---128
			else
				j=i;              //test_array1[0---127] 单元初始化数值 = 1---128
			test_array1[i]=j+1;
		}

		memset(test_array2,0x00,3*EE_PAGE_SIZE);  
		
	    if (ee_CheckOk() == 1)   //如果检测到I2C器件存在
	    {
		    ee_WriteBytes(test_array1,80,3*EE_PAGE_SIZE);  //从I2C的地址80处开始写3页字节(测试跨页连续写)
		    ee_ReadBytes(test_array2,80,3*EE_PAGE_SIZE); //从I2C的地址80处开始读3页字节(测试跨页连续读)

        }
    }


int main(void)
{
    
    BSP_AT24CXX_InitI2C();

    DEBUG_test_AT24C512();
    
   while (1)
   {
   }
}

九、测试结果

 

 

可以观察到先将3页数据(共计128*3=384个字节) 写到AT24C512起始地址80处,然后再次读出,数据完全正确。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

STM32F407单片机通用24CXXX读写程序(KEIL),兼容24C系列存储器(24C01到24C512),支持存储器任意地址跨页连续读写多个页 的相关文章

  • 基于stm32f407通过USB配置CH340

    1 在设备枚举的case ENUM SET CONFIGURATION 设置配置 状态后 xff0c 配置CH340的波特率 xff0c 然后跳过CBW和CSW协议 xff1b 直接采用批量传输的方式进行数据传输
  • STM32F407-跑马灯

    硬件准备 xff08 STM32F407ZGT6 xff09 1 初始准备 1 1打开Template模板 xff0c 在工程目录下新建HARDWARE文件夹 1 2 新建在HARDWARE路径中新建led c led h两个文件 xff0
  • STM32F407-串口通信基本原理

    1 处理器与外部设备通信的两种方式 xff1a 并行通信 传输原理 xff1a 数据各个位同时传输 优点 xff1a 速度快 缺点 xff1a 占用引脚资源多 串行通信 传输原理 xff1a 数据按位顺序传输 优点 xff1a 占用引脚资源
  • STM32F407-串口数据传送

    一 串口基础 1 常用的串口相关寄存器 USART SR状态寄存器USART DR数据寄存器USART BRR波特率寄存器 2 串口操作相关库函数 xff08 省略入口参数 xff09 void USART Init 串口初始化 xff1a
  • STM32F407-ADC(模数转换)

    一 硬件 STM32F407开发板 xff0c 杜邦线 通过通道获取板载电压的模拟输入信号转变为数字信号 xff0c 并通过转换变成电压 STM32F407有3个ADC xff0c 每个ADC有16个通道 xff0c 下表为ADC通道对应的
  • STM32F407控制42,57两个步进电机用传感器限制位置

    功夫不负有心人 xff0c 终于把这个做出来了 xff0c 本项目为控制42 57两个步进电机 xff0c 带动齿轮 xff0c 进行上下左右转动 xff0c 四个限位金属传感器限制位置 传感器配置过程 步进电机配置过程 记录一下一个问题
  • stm32f407 FreeRTOS+LVGL移植

    参考资料 xff1a 正点原子 littleVGL 开发指南 正点原子 STM32F407 FreeRTOS 开发指南 硬件平台 xff1a stm32f407开发板 xff08 或最小系统 xff09 4 3寸TFTLCD 以正点原子的例
  • 基于STM32F407时钟配置学习

    STM32F4x系列时钟树如下 xff1a 1 系统时钟SYSCLK 在STM32F407中 xff0c 除了一些特定的时钟 xff08 例如 xff0c USB OTG FS时钟 xff0c I2S时钟 xff09 外 xff0c 系统所
  • stm32f407 RTC不更新问题排查

    1 问题 在做stm32f407rtc实验时 xff0c 代码是用cubemx生成的 xff0c 通过串口打印出时间值 xff0c 1s打印一次 但是结果与料想中的不一致 发现打印出来的值一直不更新 按下复位键 xff0c 后时间会更新一次
  • STM32F407 Flash操作笔记

    简述 STM32F4XX的闪存擦除方式分为两种 xff1a 扇区擦除 xff08 最小单元16K xff09 和整片擦除 在实际应用中 xff0c 为满足重要信息的存储 xff0c 需将信息存入FLASH中 xff0c 针对以上两种擦除方式
  • STM32F407 串口编程USART1,USART2,USART3,UART4

    串口设置的一般步骤可以总结为如下几个步骤 xff1a 1 串口时钟使能 xff0c GPIO时钟使能 2 设置引脚复用器映射 xff1a 调用GPIO PinAFConfig函数 3 GPIO初始化设置 xff1a 要设置模式为复用功能 4
  • 从STM32F407到AT32F407(一)

    雅特力公司的MCU有着性能超群 xff0c 价格优越的巨大优势 xff0c 缺点是相关资料少一些 xff0c 我们可以充分利用ST的现有资源来开发它 我用雅特力的STM32F437开发板 xff0c 使用原子 stm32f407的开发板自带
  • STM32F103 UART4串口使用DMA接收不定长数据和DMA中断发送

    一 前言 使用DMA通信的好处是 不占用单片机资源 不像普通串口中断 发送一个字节触发一次中断 发送100个字节触发100次中断 接收一个字节触发一次中断 接收200个字节触发200次中断 数据接收完毕触发一次DMA中断 发送数据完毕触发一
  • STM32F407+ESP8266连接机智云过程详解

    工程创建 代码调试过程参见 STM32F407 ESP8266 程序源码下载 STM32F407 ESP8266连接机智云程序源码
  • I2C通信基本原理及其实现

    I2C是一种总线式结构 它只需要SCL时钟信号线与SDA数据线 两根线就能将连接与总线上的设备实现数据通信 由于它的简便的构造设计 于是成为一种较为常用的通信方式 由于I2C采用的是主从式通信方式 所以 通信的过程完全由主设备仲裁 在通信之
  • Micropython应用篇四---F407VE Black开发板IIC OLED1306

    Micropython应用篇四 F407VE Black开发板IIC OLED1306显示 最近一段时间做Keil例程比Micropython多很多 无论如何 Micropython Arduino作为嵌入式入门篇也发过一些文章 包括公司的
  • SD卡 FATFS CSV 文件中的 逗号和换行

    RFC 4180 Common Format and MIME Type for Comma Separated Values CSV Files 要点有 1 CSV的换行符号要使用CRLF 即 回车符 换行符 的形式 2 文字可以使用双引
  • STM32F407单片机通用24CXXX读写程序(KEIL),兼容24C系列存储器(24C01到24C512),支持存储器任意地址跨页连续读写多个页

    一 AT24CXXX容量 AT24C01 AT24C02 AT24C04 AT24C08 AT24C16 AT24C32 AT24C64 AT24C128 AT24C256 不同的xxx代表不同的容量 AT24CXXX bit容量 Byte
  • FATFS实现数据追加功能(原文不覆盖)

    在对FATFS的应用中我们经常需要把采集的数据存入的文件中 用作保存 也许我们的系统是一个长期的运行过程 但是我们的数据可能不是持续采集的 所以我们这样写代码 注册一个工作区域 f mount 0 fs 打开创建一个新文件 res f op
  • 单片机串口实现字符串命令解析---使用函数指针(类似哈希表)

    通常情况下串口通信用的大多数都是用十六进制数据来传输指令 比如最常见的modbus的通信 如读保持寄存器指令 01 03 00 00 00 01 84 0A 这种十六进制的指令在这里就不讨论了 想要详细了解可以看往期的文章 串口相关文章链接

随机推荐

  • vcglib 说明(转载)

    先来看看 VCGlib 能做什么 最基本的 它提供 Mesh triangular mesh tetrahedralmesh 三角网格或四面体网格 数据结构的定义 该数据结构支持对 Mesh数据的快速访问 拓扑信息 空间查询等 以及高效执行
  • Linux编译器-gcc 的使用以及 make/Makefile的用法

    文章目录 一 gcc 编译器 1 gcc 命令格式 gcc选项 2 完成过程 2 1预处理 2 2 编译 生成汇编 2 3 汇编 生成机器可识别代码 2 4 链接 生成可执行文件 二 make Makefile 1 简单介绍 2 示例代码
  • Scala高阶函数

    匿名函数 而在大量的spark中大都用的是匿名函数 不为函数命名 然后将其复制个一个变量 如 匿名函数格式 Val 变量名 参数 类型 gt 函数体 高阶函数 函数参数 1 将函数做参数传给另一个函数 如 首先我们定义了一个函数BigDat
  • 学习记录——matlab批量读取与存储

    要求文件名按照一定规律排列 如 代码 clc close all clear 设置目标文件夹的路径 folder C Users 26748 Desktop two saveFolder C Users 26748 Desktop two0
  • 手撕机器学习算法--一步步推导-------NFL(没有免费午餐定理)

    文章目录 前言 一 NFL是什么 二 表现形式 三 介绍 四 手动推导 前言 其实机器学习也好 深度学习也罢 在我看来 代码编程终究是不重要的 因为现成的库 其数学原理 其公式推导才是我们需要理解的地方 一 NFL是什么 没有免费的午餐定理
  • BLAS+BLACS+LAPACK+SCALAPACK安装

    最快的安装是用下面的scalapack installer 它将自动联网安装SCALAPACK以及所需要的BLAS BLACS LAPACK 下面是简短说明 INTRODUCTION The ScaLAPACK installer is a
  • 人脸识别打卡项目(4)

    目录 服务器打卡函数实现 签到验证检测 百度人脸识别复用 总结 服务器打卡函数实现 打卡函数的主要工作流程如图所示 当启动开始签到后 调用打卡签到响应函数 启动人脸采集设 备 然后与百度人脸库注册的人脸进行对比 如果用户存在 返回用户姓名
  • MATLAB之绘图基础

    第7部分 MATLAB的绘图基础 1 二维图形绘制 1 plot 函数 格式 plot x plot x y 图形绘制函数plot x 的格式说明 x内容 说明 实向量y 以y元素下标序号i为横坐标 元素y为纵坐标 绘制 I y 的有序集合
  • paddle在Edgeboard与安卓上部署

    Edgboard PaddleLite进行推理 支持Paddle模型的推理部署 不需要模型转化过程 支持c 和hpyton的接口 提供ZU3 ZU5 ZU9 推荐使用Paddlx进行模型训练 其训练出的模型会有API进行模型导出 图像前处理
  • NIO、AIO、BIO的区别(通俗理解)

    BIO 同步阻塞 服务端需要对客户端的每个请求处理完成后 才会继续接受客户端的请求 客户端也会等待服务端处理完请求后才会发送请求 通常会使用多线程去处理 因为BIO每个连接一个单独的线程 NIO 同步非阻塞 NIO使用单线程或者只使用少量的
  • 2021百度之星初赛三---网格路径(dfs+剪枝/dp方程+LGV定理)

    任意门 题意 思路 每一步只能向下走或者向右走 然后所构成的路径不能重叠 所以就尽可能的往下面走 然后给第二条更大的空间 解法1 dfs 剪枝 include
  • 微软常用运行库(持续更新中)

    一 什么是运行库 运行库就是支持大部分程序软件运行的基础 由于很多Windows系统的常用软件都是采用 Microsoft Visual Studio 编写的 如图1 1所示 所以这类软件的运行需要依赖微软Visual C 运行库 比如像
  • [VarifocalNet] VarifocalNet: An IoU-aware Dense Object Detector (CVPR. 2021oral)

    文章目录 1 Motivation 2 Contribution 3 Method 3 1 IACS IoU Aware Classification Score 3 2 Varifocal Loss 3 3 Star Shaped Box
  • 报错,props 不可重写,需要复制给data重新定义

    报错 Vue warn Avoid mutating a prop directly since the value will be overwritten whenever the parent component re renders
  • egg学习笔记

    安装egg 我们推荐直接使用脚手架 只需几条简单指令 即可快速生成项目 npm gt 6 1 0 mkdir egg example cd egg example npm init egg type simple npm i 启动项目 np
  • Java 检测类是否可加载

    检查类是否存在的一种常见方法是执行 Class forName my Class 您可以使用捕获 ClassNotFoundException 的 try catch 来包装它并决定要做什么 如果需要 您可以在具有 main 的包装类中执行
  • Android : 通过pid获取app包名

    方法一 这个方法用在app主线程的activity或service里 因为要有context获取am private String getAppName int pid String pkgName ActivityManager am A
  • [暑假]操作系统概述笔记[学习方法篇]

    不要对编程抱有任何侥幸心理 jyy老师 jyy yyds 把时间用到更正确的地方 因为时间是有限的 这门课的另一个意义 告诉你可以去变得更强 真正的强 在痛苦中摸爬滚打才能学好 听课看书都不重要 独立完成编程作业即可理解操作系统 不要借助O
  • ruoyi微服务版本如何自动生成Vue组合式前端代码?

    ruoyi微服务版本如何自动生成Vue组合式前端代码 技术介绍 ruoyi框架 微服务版本 问题描述 因为ruoyi微服务版本默认生成的前端代码是选项式的 但是习惯了组合式版本的vue代码 所以在这里需要进行修改一下 问题解决 1 大家主要
  • STM32F407单片机通用24CXXX读写程序(KEIL),兼容24C系列存储器(24C01到24C512),支持存储器任意地址跨页连续读写多个页

    一 AT24CXXX容量 AT24C01 AT24C02 AT24C04 AT24C08 AT24C16 AT24C32 AT24C64 AT24C128 AT24C256 不同的xxx代表不同的容量 AT24CXXX bit容量 Byte