STM32 通过软件模拟 I2C 驱动 24Cxx 系列存储器

news2024/11/14 3:51:58

目录

  • 一、AT24CXXX 系列存储器介绍
    • 1、基本信息
    • 2、寻址方式
    • 3、页地址与页内单元地址
    • 4、I2C 地址
    • 5、AT24CXX 的数据读写
      • 5.1 写操作
        • 5.1.1 按字节写
        • 5.1.2 按页写
      • 5.2 读操作
        • 5.2.1 当前地址读取
        • 5.2.2 随机地址读取
        • 5.2.3 顺序读取
  • 二、代码实现
    • 1、ctl_i2c
    • 2、at24c
    • 3、测试程序


I2C 相关知识可以参考 IIC 通信协议详解

一、AT24CXXX 系列存储器介绍

1、基本信息

下表是 AT24CXXX 的容量

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

AT24CXXXbit容量Byte容量
AT24C011Kbit128Byte
AT24C022Kbit256Byte
AT24C044Kbit512Byte
AT24C088Kbit1024Byte
AT24C1616Kbit2048Byte
AT24C3232Kbit4096Byte
AT24C6464Kbit8192Byte
AT24C128128Kbit16384Byte
AT24C256256Kbit32768Byte
AT24C512512Kbit65536Byte

下表是 AT24CXXX 的页内单元数

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

AT24CXXXByte容量页数页内字节单元数
AT24C01128Byte16页8Byte
AT24C02256Byte32页8Byte
AT24C04512Byte32页16Byte
AT24C081024Byte64页16Byte
AT24C162048Byte128页16Byte
AT24C324096Byte128页32Byte
AT24C648192Byte256页32Byte
AT24C12816384Byte256页64Byte
AT24C25632768Byte512页64Byte
AT24C51265536Byte512页128Byte

2、寻址方式

不是 I2C 地址,是存储器内部寻址

AT24CXXX 进行读写操作时,都得先访问存储地址、比如 AT24C04 写一个字节的 I2C 时序:

先发送设备地址,收到应答后再发送需要写数据的地址(WORD ADDRESS)。AT24C04 容量为 512Byte 则 WORD ADDRESS 只需要 9bit 就可以覆盖 512Byte 的数据地址。通俗的讲就是 512Byte 就占用了 512 个地址,一个 9bit 的数据范围为( 0 − 511 0-511 0511)刚好 512,所以 512Byte 的字节地址需要一个 9bit 的数据来表示。

3、页地址与页内单元地址

比如 AT24C04 有 32 页每页 16 个字节,9bit 的地址数据对其寻址,低 4bit(D3-D0)为页内字节单元地址,高 5bit(D8-D4)为页地址。

如从第 16 页开始写,则 WORD ADDRESS = 0x0100(0001 0000 0000),则:

  • 000:地址无效位
  • 1 0000:5 位页地址
  • 0000:4 位页内单元地址

4、I2C 地址

I2C 通信需要先向从设备发送设备地址,AT24CXXX 芯片上有 A2、A1、A0 引脚,通过这三个引脚我们就可以自定义 AT24CXXX 芯片的通信地址。

下面以 24C04 和 24C08 的官方手册为例,说明其 I2C 地址,其它型号的芯片自行查阅手册。

可以看到,前 4 位是固定的为 1010,而后的 A2、A1、P0 三个引脚以及读写标志位有我们自己设置。如果将 A2、A1、P0 接地,则 I2C 写地址为 1010 0000(0xA0),读地址为 1010 0001(0xA1)。

5、AT24CXX 的数据读写

5.1 写操作

5.1.1 按字节写

5.1.2 按页写


和按字节写类似,不过在往 AT24CXXX 中写数据时,每写一个 Byte 的数据页内地址 +1,当前页写满后会重新覆盖掉这一页前面的数据,而不会自动跳转到下一页,但是读会自动翻页。

那要如何实现翻页写呢?

按页写其实就是执行一次上面的时序,也就是发送一次从机设备和字节地址最大就可以写入 16 字节(AT24C04)的数据,如果要连写多页,就重新按照上面的时序发送从机地址和字节地址等。

5.2 读操作

写操作和读操作类似,不过 R/W 标志位要设置为 1。

5.2.1 当前地址读取

5.2.2 随机地址读取

5.2.3 顺序读取

二、代码实现

说明:

// 实现i2c相关设置和初始化
ctl_i2c.h
ctl_i2c.c

// 实现at24cx系列芯片的读写操作
at24c.h
at24c.c

1、ctl_i2c

下面是 ctl_i2c.h 文件,没什么可说的,实现了一些宏,以及相关函数的声明。

// ctl_i2c.h
#ifndef _BSP_I2C_GPIO_H
#define _BSP_I2C_GPIO_H
 
#include "stm32f4xx.h"
 
 
#define I2C_WR	0		// 写控制bit
#define I2C_RD	1		// 读控制bit
 
#define RCC_AT24CXX_I2C_PORT 			RCC_AHB1Periph_GPIOB		// GPIO端口时钟
#define GPIO_AT24CXX_I2C_PORT			GPIOB						// GPIO端口
#define GPIO_AT24CXX_I2C_SCL_Pin		GPIO_Pin_8					// 连接到SCL时钟线的GPIO
#define GPIO_AT24CXX_I2C_SDA_Pin		GPIO_Pin_9					// 连接到SDA数据线的GPIO
 
#define I2C_SCL_H()  	 GPIO_SetBits(GPIO_AT24CXX_I2C_PORT, GPIO_AT24CXX_I2C_SCL_Pin) 		      // SCL = 1
#define I2C_SCL_L()  	 GPIO_ResetBits(GPIO_AT24CXX_I2C_PORT, GPIO_AT24CXX_I2C_SCL_Pin) 		  // SCL = 0
#define I2C_SDA_H()  	 GPIO_SetBits(GPIO_AT24CXX_I2C_PORT, GPIO_AT24CXX_I2C_SDA_Pin) 		      // SDA = 1
#define I2C_SDA_L()  	 GPIO_ResetBits(GPIO_AT24CXX_I2C_PORT, GPIO_AT24CXX_I2C_SDA_Pin) 		  // SDA = 0
#define I2C_SDA_RD()     GPIO_ReadInputDataBit(GPIO_AT24CXX_I2C_PORT, GPIO_AT24CXX_I2C_SDA_Pin)   // 读SDA口线状态
#define I2C_SCL_RD()     GPIO_ReadInputDataBit(GPIO_AT24CXX_I2C_PORT, GPIO_AT24CXX_I2C_SCL_Pin)   // 读SCL口线状态


void ctl_at24cxx_i2c_init(void);
void ctl_i2c_start(void);
void ctl_i2c_stop(void);
void ctl_i2c_sendbyte(uint8_t byte);
void ctl_i2c_ack(void);
void ctl_i2c_nack(void);
uint8_t ctl_i2c_waitack(void);
uint8_t ctl_i2c_readbyte(void);
uint8_t ctl_i2c_checkdevice(uint8_t address);
 
#endif

接下来看 ctl_i2c.c 文件:

初始化 I2C 的 GPIO 端口:

/******************************************************************************
 * @brief  初始化I2C总线的GPIO
 * 
 * @return none
 * 
 * @note   采用模拟IO的方式实现
 * 
******************************************************************************/
void ctl_at24cxx_i2c_init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
 
	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总线上的所有设备到待机模式
	ctl_i2c_stop();
}

延时函数的实现:

/******************************************************************************
 * @brief  I2C总线位延迟,最快400KHz
 * 
 * @return none
 * 
******************************************************************************/
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 开始信号:当 SCL 线在高电平期间 SDA 线从高电平向低电平切换

/******************************************************************************
 * @brief  CPU发起I2C总线启动信号
 * 
 * @return none
 * 
 * @note   当SCL高电平时,SDA出现一个下跳沿表示I2C总线启动信号
 * 
******************************************************************************/
void ctl_i2c_start(void)
{
	// 当SCL高电平时,SDA出现一个下跳沿表示I2C总线启动信号
	I2C_SDA_H();
	I2C_SCL_H();
	i2c_delay();
	I2C_SDA_L();
	i2c_delay();
	I2C_SCL_L();
	i2c_delay();
}

I2C 停止信号:当 SCL 线在高电平期间 SDA 线由低电平向高电平切换

/******************************************************************************
 * @brief  CPU发起I2C总线停止信号
 * 
 * @return none
 * 
 * @note   当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号
 * 
******************************************************************************/
void ctl_i2c_stop(void)
{
	/* 当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号 */
	I2C_SDA_L();
	I2C_SCL_H();
	i2c_delay();
	I2C_SDA_H();
	i2c_delay();
}

下面是应答信号和非应答信号的函数实现:

在第 9 个时钟时,数据发送端会释放 SDA 的控制权,由数据接收端控制 SDA,给发送端传输应答或非应答信号

  • SDA 为高电平:表示非应答信号(NACK)
  • SDA为低电平:表示应答信号(ACK)
/******************************************************************************
 * @brief  CPU产生一个ACK信号
 * 
 * @return none
 * 
******************************************************************************/
void ctl_i2c_ack(void)
{
	I2C_SDA_L();	// SCL低电平期间,SDA 为低电平,表示应答信号
	i2c_delay();
	I2C_SCL_H();	// CPU产生1个时钟
	i2c_delay();
	I2C_SCL_L();
	i2c_delay();
	I2C_SDA_H();	// 应答完成释放SDA总线,否则接收到的数据全是0
}
 
/******************************************************************************
 * @brief  CPU产生1个NACK信号
 * 
 * @return none
 * 
******************************************************************************/
void ctl_i2c_nack(void)
{
	I2C_SDA_H();  // CPU驱动SDA = 1
	i2c_delay();
	I2C_SCL_H();  // SCL 高电平期间,SDA 为高电平,表示非应答信号
	i2c_delay();
	I2C_SCL_L();
	i2c_delay();
}

为什么数据发送端要释放 SDA 的控制权(将SDA总线置为高电平)

数据有效性:IIC 总线进行数据传送时,SCL 信号为高电平期间,SDA 上的数据必须保持稳定,只有在 SCL 上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化(准备下一位数据)。数据在 SCL 的上升沿到来之前就需准备好。并在下降沿到来之前必须稳定。

数据传输:在 SCL 串行时钟的配合下,在 SDA 上逐位地串行传送每一位数据。数据位的传输是边沿触发

/******************************************************************************
 * @brief      CPU向I2C总线设备发送8bit数据
 * 
 * @param[in]  byte    :     等待发送的1个字节数据
 * 
 * @return     none
 * 
 * @note       SDA 上的数据变化只能在 SCL 低电平期间发生
 * 
******************************************************************************/
void ctl_i2c_sendbyte(uint8_t byte)
{
	uint8_t i;
 
	/* 先发送字节的高位bit7 */
	for (i = 0; i < 8; i++)
	{
		if (byte & 0x80)
		{
			I2C_SDA_H();
		}
		else
		{
			I2C_SDA_L();
		}
		i2c_delay();
		I2C_SCL_H();  // SCL高电平有效,发送一位数据
		i2c_delay();
		I2C_SCL_L();  // SCL低电平,准备下一位数据

		// 若是最后一位数据,释放SDA总线,表示数据传输结束
		if (i == 7)
		{
			 I2C_SDA_H(); // 释放总线
		}

		// 数据左移,准备下一位数据(高位先到
		byte <<= 1;
		i2c_delay();
	}
} 
 
/******************************************************************************
 * @brief  CPU从I2C总线设备读取8bit数据
 * 
 * @return uint8_t 
 * 
******************************************************************************/
uint8_t ctl_i2c_readbyte(void)
{
	uint8_t i;
	uint8_t value = 0;
 
	/* 读到第1个bit为数据的bit7 */
	for (i = 0; i < 8; i++)
	{
		value <<= 1;

		I2C_SCL_H();  // 将SCL拉高,准备接收数据
		i2c_delay();

		// 判断EEPROM发送过来的是1还是0
		if (I2C_SDA_RD())
		{
			value++;
		}

		I2C_SCL_L();  // 让EEPROM准备下一位数据
		i2c_delay();
	}

	return value;
}

最后是等待从机 EEPROM 应答和检查设备是否已连接:

/******************************************************************************
 * @brief  CPU产生一个时钟,并读取器件的ACK应答信号
 * 
 * @return uint8_t 
 * 
******************************************************************************/
uint8_t ctl_i2c_waitack(void)
{
	uint8_t re;
 
	I2C_SDA_H();// 自动释放SDA总线,将控制权交给EEPROM
	i2c_delay();
	I2C_SCL_H();	/* CPU驱动SCL = 1, 此时器件会返回ACK应答 */
	i2c_delay();
	if (I2C_SDA_RD())	/* CPU读取SDA口线状态 */
	{
		re = 1;
	}
	else
	{
		re = 0;
	}
	I2C_SCL_L();
	i2c_delay();
	return re;
}

/******************************************************************************
 * @brief      检测I2C总线设备,CPU向发送设备地址,然后读取设备应答来判断该设备是否存在
 * 
 * @param[in]  address    :     设备地址
 * 
 * @return     uint8_t    :     0 表示成功检测到设备; 返回1表示未探测到
 * 
******************************************************************************/
uint8_t ctl_i2c_checkdevice(uint8_t _Address)
{
	uint8_t ucAck;
 
	if (I2C_SDA_RD() && I2C_SCL_RD())
	{
		ctl_i2c_start();  // 发送启动信号
 
		// 发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传
		ctl_i2c_sendbyte(_Address | I2C_WR);
		ucAck = ctl_i2c_waitack();	// 检测设备的ACK应答
 
		ctl_i2c_stop();  // 发送停止信号
 
		return ucAck;
	}
	return 1;  // I2C总线异常
}

2、at24c

at24.h 文件中针对 AT24CX 系列的容量和页内单元数设置了不同的宏,可以针对自己使用的型号设置选择不同的宏使用,这里以 AT24C04 为例:#define AT24C04

// at24.h
#ifndef __AT24C_H
#define	__AT24C_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 AT24C04
 
 
 
#ifdef AT24C01
	#define AT24CX_MODEL_NAME		"AT24C01"
	#define AT24CX_DEV_ADDR			0xA0		/* 设备地址 */
	#define AT24CX_PAGE_SIZE		8			/* 页面大小(字节) */
	#define AT24CX_SIZE				128			/* 总容量(字节) */
	#define AT24CX_ADDR_BYTES		1			/* 地址字节个数 */
	#define AT24CX_ADDR_A8			0			/* 地址字节的高8bit不在首字节 */
#endif
 
#ifdef AT24C02
	#define AT24CX_MODEL_NAME		"AT24C02"
	#define AT24CX_DEV_ADDR			0xA0		/* 设备地址 */
	#define AT24CX_PAGE_SIZE		8			/* 页面大小(字节) */
	#define AT24CX_SIZE				256			/* 总容量(字节) */
	#define AT24CX_ADDR_BYTES		1			/* 地址字节个数 */
	#define AT24CX_ADDR_A8			0			/* 地址字节的高8bit不在首字节 */
#endif
 
#ifdef AT24C04
	#define AT24CX_MODEL_NAME		"AT24C04"
	#define AT24CX_DEV_ADDR			0xA0		/* 设备地址 */
	#define AT24CX_PAGE_SIZE		8			/* 页面大小(字节) */
	#define AT24CX_SIZE				512			/* 总容量(字节) */
	#define AT24CX_ADDR_BYTES		1			/* 地址字节个数 */
	#define AT24CX_ADDR_A8			1			/* 地址字节的高8bit在首字节 */
#endif 
 
#ifdef AT24C08
	#define AT24CX_MODEL_NAME		"AT24C08"
	#define AT24CX_DEV_ADDR			0xA0		/* 设备地址 */
	#define AT24CX_PAGE_SIZE		16			/* 页面大小(字节) */
	#define AT24CX_SIZE				(16*64)		/* 总容量(字节) */
	#define AT24CX_ADDR_BYTES		2			/* 地址字节个数 */
	#define AT24CX_ADDR_A8			1			/* 地址字节的高8bit在首字节 */
#endif
 
#ifdef AT24C16
	#define AT24CX_MODEL_NAME		"AT24C16"
	#define AT24CX_DEV_ADDR			0xA0		/* 设备地址 */
	#define AT24CX_PAGE_SIZE		16			/* 页面大小(字节) */
	#define AT24CX_SIZE				(128*16)	/* 总容量(字节) */
	#define AT24CX_ADDR_BYTES		2			/* 地址字节个数 */
	#define AT24CX_ADDR_A8			1			/* 地址字节的高8bit在首字节 */
#endif
 
#ifdef AT24C32
	#define AT24CX_MODEL_NAME		"AT24C32"
	#define AT24CX_DEV_ADDR			0xA0		/* 设备地址 */
	#define AT24CX_PAGE_SIZE		32			/* 页面大小(字节) */
	#define AT24CX_SIZE				(128*32)	/* 总容量(字节) */
	#define AT24CX_ADDR_BYTES		2			/* 地址字节个数 */
	#define AT24CX_ADDR_A8			1			/* 地址字节的高8bit在首字节 */
#endif

#ifdef AT24C64
	#define AT24CX_MODEL_NAME		"AT24C64"
	#define AT24CX_DEV_ADDR			0xA0		/* 设备地址 */
	#define AT24CX_PAGE_SIZE		32			/* 页面大小(字节) */
	#define AT24CX_SIZE				(256*32)	/* 总容量(字节) */
	#define AT24CX_ADDR_BYTES		2			/* 地址字节个数 */
	#define AT24CX_ADDR_A8			1			/* 地址字节的高8bit在首字节 */
#endif
 
#ifdef AT24C128
	#define AT24CX_MODEL_NAME		"AT24C128"
	#define AT24CX_DEV_ADDR			0xA0		/* 设备地址 */
	#define AT24CX_PAGE_SIZE		64			/* 页面大小(字节) */
	#define AT24CX_SIZE				(256*64)	/* 总容量(字节) */
	#define AT24CX_ADDR_BYTES		2			/* 地址字节个数 */
	#define AT24CX_ADDR_A8			0			/* 地址字节的高8bit不在首字节 */
#endif
 
#ifdef AT24C256
	#define AT24CX_MODEL_NAME		"AT24C256"
	#define AT24CX_DEV_ADDR			0xA0		/* 设备地址 */
	#define AT24CX_PAGE_SIZE		64			/* 页面大小(字节) */
	#define AT24CX_SIZE				(512*64)	/* 总容量(字节) */
	#define AT24CX_ADDR_BYTES		2			/* 地址字节个数 */
	#define AT24CX_ADDR_A8			0			/* 地址字节的高8bit不在首字节 */
#endif 
 
#ifdef AT24C512
	#define AT24CX_MODEL_NAME		"AT24C512"
	#define AT24CX_DEV_ADDR			0xA0		/* 设备地址 */
	#define AT24CX_PAGE_SIZE		128			/* 页面大小(字节) */
	#define AT24CX_SIZE				(512*128)	/* 总容量(字节) */
	#define AT24CX_ADDR_BYTES		2			/* 地址字节个数 */
	#define AT24CX_ADDR_A8			0			/* 地址字节的高8bit不在首字节 */
#endif
 

uint8_t at24cx_checkok(void);
uint8_t at24cx_readbytes(uint8_t *readbuf, uint16_t address, uint16_t size);
uint8_t at24cx_writebytes(uint8_t *writebuf, uint16_t address, uint16_t size);
 
#endif /* __AT24CH */

下面是 at24c.c 函数的实现:

首先检查设备是否连接成功:

/******************************************************************************
 * @brief  判断串行EERPOM是否正常
 * 
 * @return uint8_t : 1 表示正常, 0 表示不正常
 * 
******************************************************************************/
uint8_t at24cx_checkok(void)
{
	if (ctl_i2c_checkdevice(AT24CX_DEV_ADDR) == 0)
	{
		return 1;
	}
	else
	{
		// 失败后,切记发送I2C总线停止信号
		ctl_i2c_stop();
		return 0;
	}
}

然后是读写函数:

/******************************************************************************
 * @brief      从串行EEPROM指定地址处开始读取若干数据
 * 
 * @param[in]  readuf    :    起始地址  
 * @param[in]  address   :    数据长度,单位为字节
 * @param[in]  size      :    存放读到的数据的缓冲区指针
 * 
 * @return     uint8_t   :    0 表示失败,1表示成功
 * 
******************************************************************************/
uint8_t at24cx_readbytes(uint8_t *readbuf, uint16_t address, uint16_t size)
{
	uint16_t i;
 
	/**
	 * 采用串行AT24CXPROM随即读取指令序列,连续读取若干字节
	 */ 
 
	// 第1步:发起I2C总线启动信号
	ctl_i2c_start();
 
	// 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读
#if AT24CX_ADDR_A8 == 1
	ctl_i2c_sendbyte(AT24CX_DEV_ADDR | I2C_WR | ((address >> 7) & 0x0E));	// 写指令
#else
	ctl_i2c_sendbyte(AT24CX_DEV_ADDR | I2C_WR);	 //写指令
#endif
 
	// 第3步:发送ACK
	if (ctl_i2c_waitack() != 0)
	{
		goto cmd_fail;	// AT24CXPROM器件无应答
	}
 
	// 第4步:发送字节地址,24C02只有256字节,因此1个字节就够了,如果是24C04以上,那么此处需要连发多个地址
	if (AT24CX_ADDR_BYTES == 1)
	{
		ctl_i2c_sendbyte((uint8_t)address);
		if (ctl_i2c_waitack() != 0)
		{
			goto cmd_fail;	// AT24CXPROM器件无应答
		}
	}
	else
	{
		ctl_i2c_sendbyte(address >> 8);
		if (ctl_i2c_waitack() != 0)
		{
			goto cmd_fail;	// AT24CXPROM器件无应答
		}
 
		ctl_i2c_sendbyte(address);
		if (ctl_i2c_waitack() != 0)
		{
			goto cmd_fail;	// AT24CXPROM器件无应答
		}
	}
 
	// 第5步:重新启动I2C总线。下面开始读取数据
	ctl_i2c_start();
 
	// 第6步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读
#if AT24CX_ADDR_A8 == 1
	ctl_i2c_sendbyte(AT24CX_DEV_ADDR | I2C_RD | ((address >> 7) & 0x0E));	// 写指令
#else		
	ctl_i2c_sendbyte(AT24CX_DEV_ADDR | I2C_RD);  // 此处是写指令
#endif	
 
	// 第7步:发送ACK 
	if (ctl_i2c_waitack() != 0)
	{
		goto cmd_fail;	// AT24CXPROM器件无应答 
	}
 
	// 第8步:循环读取数据 
	for (i = 0; i < size; i++)
	{
		readbuf[i] = ctl_i2c_readbyte();	// 读1个字节
 
		// 每读完1个字节后,需要发送Ack, 最后一个字节不需要Ack,发Nack
		if (i != size - 1)
		{
			ctl_i2c_ack();	// 中间字节读完后,CPU产生ACK信号(驱动SDA = 0)
		}
		else
		{
			ctl_i2c_nack();	// 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) 
		}
	}

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

/******************************************************************************
 * @brief      向串行EEPROM指定地址写入若干数据,采用页写操作提高写入效率
 * 
 * @param[in]  writeBuf   :   起始地址  
 * @param[in]  address    :   数据长度,单位为字节
 * @param[in]  size       :   存放读到的数据的缓冲区指针
 * 
 * @return     uint8_t    :   0 表示失败,1表示成功
 * 
******************************************************************************/
uint8_t at24cx_writebytes(uint8_t *writebuf, uint16_t address, uint16_t size)
{
	uint16_t i, m;
	uint16_t addr;
 
	/**
	 *	写串行AT24CXPROM不像读操作可以连续读取很多字节,每次写操作只能在同一个page。
	 *	对于24xx02,page size = 8
	 *	简单的处理方法为:按字节写操作模式,每写1个字节,都发送地址
	 *	为了提高连续写的效率: 本函数采用page wirte操作。
	 */
 
	addr = address;
	for (i = 0; i < size; i++)
	{
		// 当发送第1个字节或是页面首地址时,需要重新发起启动信号和地址
		if ((i == 0) || (addr & (AT24CX_PAGE_SIZE - 1)) == 0)
		{
			// 第0步:发停止信号,启动内部写操作
			ctl_i2c_stop();
 
			/**
			 *  通过检查器件应答的方式,判断内部写操作是否完成, 一般小于 10ms
			 *	CLK频率为200KHz时,查询次数为30次左右
			 */
			for (m = 0; m < 1000; m++)
			{
				// 第1步:发起I2C总线启动信号
				ctl_i2c_start();
 
				// 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读
			#if AT24CX_ADDR_A8 == 1
				ctl_i2c_sendbyte(AT24CX_DEV_ADDR | I2C_WR | ((address >> 7) & 0x0E));  // 此处是写指令
			#else				
				ctl_i2c_sendbyte(AT24CX_DEV_ADDR | I2C_WR);
			#endif
 
				// 第3步:发送一个时钟,判断器件是否正确应答
				if (ctl_i2c_waitack() == 0)
				{
					break;
				}
			}
			if (m  == 1000)
			{
				goto cmd_fail;	// AT24CXPROM器件写超时
			}
 
			// 第4步:发送字节地址,24C02只有256字节,因此1个字节就够了,如果是24C04以上,那么此处需要连发多个地址
			if (AT24CX_ADDR_BYTES == 1)
			{
				ctl_i2c_sendbyte((uint8_t)addr);
				if (ctl_i2c_waitack() != 0)
				{
					goto cmd_fail;	// AT24CXPROM器件无应答
				}
			}
			else
			{
				ctl_i2c_sendbyte(addr >> 8);
				if (ctl_i2c_waitack() != 0)
				{
					goto cmd_fail;	// AT24CXPROM器件无应答
				}
 
				ctl_i2c_sendbyte(addr);
				if (ctl_i2c_waitack() != 0)
				{
					goto cmd_fail;	// AT24CXPROM器件无应答
				}
			}
		}
 
		// 第5步:开始写入数据 
		ctl_i2c_sendbyte(writebuf[i]);
 
		// 第6步:发送ACK
		if (ctl_i2c_waitack() != 0)
		{
			goto cmd_fail;	// AT24CXPROM器件无应答
		}
 
		addr++;  // 地址增1
	}
 
	// 命令执行成功,发送I2C总线停止信号
	ctl_i2c_stop();
 
	/**
	 *  通过检查器件应答的方式,判断内部写操作是否完成, 一般小于 10ms
	 *	CLK频率为200KHz时,查询次数为30次左右
	 */
	for (m = 0; m < 1000; m++)
	{
		// 第1步:发起I2C总线启动信号
		ctl_i2c_start();
 
		// 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读
	#if AT24CX_ADDR_A8 == 1
		ctl_i2c_sendbyte(AT24CX_DEV_ADDR | I2C_WR | ((address >> 7) & 0x0E));  // 此处是写指令
	#else		
		ctl_i2c_sendbyte(AT24CX_DEV_ADDR | I2C_WR);	 // 此处是写指令
	#endif
 
		// 第3步:发送一个时钟,判断器件是否正确应答 
		if (ctl_i2c_waitack() == 0)
		{
			break;
		}
	}
	if (m  == 1000)
	{
		goto cmd_fail;	// AT24CXPROM器件写超时
	}
 
	// 命令执行成功,发送I2C总线停止信号
	ctl_i2c_stop();	
 
	return 1;
 
	// 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备
cmd_fail: 
	// 发送I2C总线停止信号
	ctl_i2c_stop();
	return 0;
}

3、测试程序

uint8_t test_array1[3 * AT24CX_PAGE_SIZE]; // 注:AT24C04时,AT24CX_PAGE_SIZE=8
uint8_t test_array2[3 * AT24CX_PAGE_SIZE]; //     AT24C04时,一个页面有24个字节


void at24c04_test_num(void)
{
	uint16_t i;
	uint16_t j;

	for (i = 0; i < 3 * AT24CX_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 * AT24CX_PAGE_SIZE);

	if (at24cx_checkok() == 1) // 如果检测到I2C器件存在
	{
		at24cx_writebytes(test_array1, 80, 3 * AT24CX_PAGE_SIZE); // 从I2C的地址80处开始写3页字节(测试跨页连续写)
		at24cx_readbytes(test_array2, 80, 3 * AT24CX_PAGE_SIZE);  // 从I2C的地址80处开始读3页字节(测试跨页连续读)
	}

	printf("test begin\r\n");
	
	for (i = 0; i < sizeof(test_array2); ++i)
	{
		printf("%d, ", test_array2[i]);
	}
}

结果如下:

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

c++难点核心笔记(一)

文章目录 前言C的应用领域 核心编程内存分区模型1.程序运行前2.程序运行后3.new操作符引用 函数1.概述和函数原型2.函数的定义和参数3.使用函数处理不同类型的数据4.微处理器如何处理函数调用函数的分文件编写 指针和引用什么是指针动态内存分配使用指针时常犯的编程错误指针编…

为你介绍五款超实用免费报表工具,一文说清优缺点

1. 山海鲸可视化 山海鲸可视化是一款完全免费的报表工具&#xff0c;不仅能够处理各式复杂报表&#xff0c;而且提供了非常丰富的组件和模板&#xff0c;软件操作方式为零代码的拖拽式操作&#xff0c;新手用户也能快速上手。同时&#xff0c;它附送一个免费的网站后台&#x…

JVM java主流的追踪式垃圾收集器

目录 前言 分代垃圾收集理论 标记清除算法 标记复制算法 标记整理法 前言 从对象消亡的角度出发, 垃圾回收器可以分为引用计数式垃圾收集和追踪式垃圾收集两大类, 但是java主流的一般是追踪式的垃圾收集器, 因此我们重点讲解. 分代垃圾收集理论 分代收集这种理…

腾讯云负载均衡ssl漏洞(CVE-201602183)解决

绿盟漏洞扫描腾讯云应用&#xff0c;提示有1个高危、1个中危。 看IP是应用服务器前端的负载均衡。 漏洞详细信息如下&#xff1a; 根据腾讯云文档&#xff0c;可以通过设置负载均衡加密算法设置&#xff0c;来缓解漏洞风险。 登录 负载均衡控制台&#xff0c;在左侧导航栏单击…

宸励投资专注高新技术投资,助推中小企业快速发展

宸励投资&#xff0c;作为一家新兴的互联网式新轻创型投行公司&#xff0c;专注在人工智能、专精特新及数字化美业三大板块领域&#xff0c;展现了其深厚的专业背景和卓越的引领能力。这家公司不仅在各自的领域内深耕细作&#xff0c;更通过其前瞻性的视角和独到的战略布局&…

Windows X86 远线程注入问题解惑

&#x1f3c6;本文收录于《CSDN问答解惑-专业版》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收…

ProtoBuf介绍及安装

文章目录 序列反序列化ProtoBuf特点安装ProtoBufwindowsUbuntuCentos 序列反序列化 在网络传输过程当中&#xff0c;可以理解为&#xff1a; 发送方接收方 它们彼此要通信&#xff0c;先要定好一个规则&#xff0c;也就是协议&#xff0c;双方都能认识的结构化数据&#xff…

Linux C——网络编程

本案例运行环境&#xff1a;Ubuntu 12.04.1 LTS 1、基本概念 网络的七层模型&#xff1a; 物理层 数据链路层 网络层 传输层 会话层 表示层 应用层 其中&#xff1a;1、2、3层主要面向通过网络端到端的数据流&#xff0c; 4、5、6、7层定义了程序的功能 …

静态链接和动态链接的Golang二进制文件

关注TechLead&#xff0c;复旦博士&#xff0c;分享云服务领域全维度开发技术。拥有10年互联网服务架构、AI产品研发经验、团队管理经验&#xff0c;复旦机器人智能实验室成员&#xff0c;国家级大学生赛事评审专家&#xff0c;发表多篇SCI核心期刊学术论文&#xff0c;阿里云认…

李沐 模型选择、过拟合和欠拟合相关代码【动手学深度学习v2】

多项式回归 生成数据集 给定x,我们将使用以下三阶多项式来生成训练和测试数据的标签: y=5+1.2x−3.4+5.6+ϵ where ϵ∼( ). 噪声项ϵ服从均值为0且标准差为0.1的正态分布。 在优化

GraphRAG与VectorRAG我都选:HybridRAG

从金融应用中产生的非结构化文本数据&#xff08;如财报电话会议记录&#xff09;提取和解释复杂信息&#xff0c;即使采用当前最佳实践使用检索增强生成&#xff08;RAG&#xff09;技术&#xff0c;对于大型语言模型&#xff08;LLMs&#xff09;来说仍存在重大挑战。这些挑战…

【游戏党必看】2024年最适合玩游戏的电脑系统推荐!

许多玩家都在问如果在电脑上玩游戏装什么系统好呢&#xff1f;以下系统之家小编给大家推荐两款专门为游戏玩家打造的操作系统&#xff0c;针对大型游戏进行了深度优化&#xff0c;显著提升了系统性能&#xff0c;确保游戏运行更为流畅无阻&#xff0c;能完美兼容各种类型的游戏…

三好夫人|最强“逼”婚神器,送完一次就领证

三好夫人&#xff5c;揭秘最强“逼”婚神器&#xff0c;让你的爱情甜蜜升级&#xff0c;速领见家长通行证&#xff01; 男人们请记住&#xff0c;如果一个女生给你送三好夫人&#xff0c;那么你赶快带她见家长把婚事定了。 在这个快节奏的时代&#xff0c;爱情似乎也被按下了快…

基于51单片机的电机控制和角度检测

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于51单片机&#xff0c;采用滑动变阻器连接ADC0832数模转换器模拟角度传感器&#xff0c;然后通过LCD1602显示数值&#xff0c;然后按键按下不动&#xff0c;电机正转&#xff0c;松开停止。第二…

显示和隐藏图片【JavaScript】

使用 JavaScript 来实现显示和隐藏图片。下面是一个简单的示例&#xff0c;展示如何通过按钮点击来切换图片的可见性。 实现效果: 代码&#xff1a; <!DOCTYPE html> <html lang"zh"><head><meta charset"UTF-8"><meta name&…

神奇的交互!Ethernet IP转Profinet网关与发那科机器人的数据交互

在当今的工业领域&#xff0c;随着自动化程度的不断提高&#xff0c;工业化升级已成为必然趋势。在这个过程中&#xff0c;对机器人的联网需求变得日益迫切。机器人作为工业生产中的重要组成部分&#xff0c;其高效运行和与其他设备的协同工作对于提高生产效率至关重要。然而&a…

EI-Bisynch协议

EI-Bisynch&#xff08;Extended Interface-Bisynchronous&#xff09;协议是一种早期用于设备通信的协议&#xff0c;主要用于工业控制系统中的串行通信。随着技术的发展&#xff0c;EI-Bisynch的使用已经大幅减少&#xff0c;逐渐被更现代化、灵活性更高的通信协议&#xff0…

【Linux】手把手教你制作一个简易shell——(进程创建fork进程替换wait与进程等待exec的应用)(自定义shell程序设计)

前言 大家好吖&#xff0c;欢迎来到 YY 滴Linux系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C Linux的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的…

华为昇腾智算中心-智算中心测试方案与标准

本方案是企业内训课程《华为昇腾智算中心深度技术研修》的一部分授课课件的样例。方案内容中详细阐述了华为昇腾环境下智算中心的测试方案和标准&#xff0c;以确保硬件和软件系统在实际部署和运行中的高效性和稳定性。主要内容包括集群硬件清单、节点拓扑配置以及环境配置。硬…

企业微信oauth2的code换用户身份一直40029解决方案

序&#xff1a; 雪狼的微信表情包&#xff0c;欢迎下载【程序员雪狼】微信表情 - 来自微信表情商店&#xff0c;扫二维码下载表情 正文&#xff1a; 雪狼在用oauth2返回的code要去请求getuserinfo3rd接口的时候&#xff0c;报错如下40029 一样&#xff0c;肝了一天&#xff0c…