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


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

一、AT24CXXX 系列存储器介绍

1、基本信息

下表是 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

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)
{
   
   
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
	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()
评论 42
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值