目录
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 0−511)刚好 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()