GD32F4学习之路第五章----IIC读写AT24C02
IIC(Inter-Integrated Circuit,也写作I²C或I2C)是一种串行通信总线,由飞利浦公司在1980年代开发,用于连接微控制器和各种外围设备。双线制:仅使用两条线(SCL和SDA)实现双向通信主从架构:一个或多个主设备控制总线,从设备响应主设备的命令寻址机制:每个从设备都有唯一的地址,支持多设备共享总线低速率:标准模式为100kbit/s,快速模式为400kbit/s,高速模
目录
1. IIC协议概述
1.1 IIC协议定义
IIC(Inter-Integrated Circuit,也写作I²C或I2C)是一种串行通信总线,由飞利浦公司在1980年代开发,用于连接微控制器和各种外围设备。IIC协议具有以下特点:
- 双线制:仅使用两条线(SCL和SDA)实现双向通信
- 主从架构:一个或多个主设备控制总线,从设备响应主设备的命令
- 寻址机制:每个从设备都有唯一的地址,支持多设备共享总线
- 低速率:标准模式为100kbit/s,快速模式为400kbit/s,高速模式可达3.4Mbit/s
- 开放集电极:允许多个设备共享总线而不会发生冲突
1.2 物理特性
IIC总线由两条信号线组成:
- SCL(Serial Clock Line):时钟线,由主设备产生
- SDA(Serial Data Line):数据线,可由主设备或从设备控制
这两条线都需要上拉电阻(通常为4.7kΩ)连接到电源,形成开漏(或开集电极)输出结构。这种结构允许多个设备共享总线,并实现"线与"逻辑。
1.3 通信时序
IIC协议定义了几种特殊的总线条件和基本时序:
起始条件(START)
当SCL为高电平时,SDA从高电平切换到低电平,表示通信开始。
____
SDA \___
_______
SCL
停止条件(STOP)
当SCL为高电平时,SDA从低电平切换到高电平,表示通信结束。
____
SDA ____/
_______
SCL
数据传输
数据按字节(8位)传输,高位先传。每个字节后跟一个应答位(ACK/NACK)。
- 数据在SCL低电平期间准备
- 数据在SCL高电平期间有效并被采样
- 每个字节传输后,接收方发送一个应答位(ACK=0表示应答,NACK=1表示不应答)
_ _ _ _ _ _ _ _ _
SCL _| |_| |_| |_| |_| |_| |_| |_| |_| |_
___ ___ ___ ___ ___
SDA |___| |___| |___| |___| |___
D7 D6 D5 D4 D3 D2 D1 D0 ACK
完整通信流程
一个典型的IIC通信序列包括:
- 主设备发送START条件
- 主设备发送从设备地址(7位)和读/写位(1位,0=写,1=读)
- 从设备发送ACK
- 数据传输(一个或多个字节,每个字节后跟ACK/NACK)
- 主设备发送STOP条件
1.4 寻址方式
IIC设备使用7位或10位地址。对于7位寻址(最常用):
- 地址占用7位(可寻址128个设备)
- 第8位表示读/写操作(0=写,1=读)
2. AT24CXX EEPROM概述
2.1 产品简介
AT24CXX是Atmel(现为Microchip)生产的一系列IIC接口EEPROM(电可擦除可编程只读存储器)。该系列包括多种型号,如AT24C01、AT24C02、AT24C04、AT24C08、AT24C16、AT24C32、AT24C64等,容量从1Kbit到512Kbit不等。
主要特点:
- IIC兼容接口
- 低功耗
- 页写缓冲
- 高可靠性(100,000次写入周期,数据保存超过40年)
- 宽工作电压范围(通常为1.8V至5.5V)
2.2 工作原理
AT24CXX EEPROM通过IIC总线与微控制器通信,使用电可擦除可编程技术存储数据。其工作原理包括:
- 读操作:主控制器发送设备地址和存储器地址,然后读取数据
- 写操作:主控制器发送设备地址、存储器地址和要写入的数据
- 页写操作:可以一次写入多个字节(一页,通常为8或16字节)
- 写周期:写入操作后,EEPROM需要一段时间(通常为5ms)完成内部写入周期
2.3 存储组织
不同型号的AT24CXX有不同的存储容量和组织方式:
| 型号 | 容量 | 组织方式 | 页大小 | 地址字节数 |
|---|---|---|---|---|
| AT24C01 | 1Kbit | 128×8 | 8字节 | 1 |
| AT24C02 | 2Kbit | 256×8 | 8字节 | 1 |
| AT24C04 | 4Kbit | 512×8 | 16字节 | 1 |
| AT24C08 | 8Kbit | 1024×8 | 16字节 | 1 |
| AT24C16 | 16Kbit | 2048×8 | 16字节 | 1 |
| AT24C32 | 32Kbit | 4096×8 | 32字节 | 2 |
| AT24C64 | 64Kbit | 8192×8 | 32字节 | 2 |
2.4 寻址模式
AT24CXX的寻址方式根据容量不同而有所差异:
-
AT24C01/C02(小容量):
- 器件地址:1010 + A2 + A1 + A0 + R/W
- 使用1字节内部地址(0-127或0-255)
-
AT24C04/C08/C16(中容量):
- 器件地址:1010 + A2 + (A1) + (A0) + R/W
- 其中A1、A0可能被高位地址位替代
- AT24C04:1010 + A2 + A1 + a8 + R/W
- AT24C08:1010 + A2 + a9 + a8 + R/W
- AT24C16:1010 + a10 + a9 + a8 + R/W
-
AT24C32/C64及以上(大容量):
- 器件地址:1010 + A2 + A1 + A0 + R/W
- 使用2字节内部地址
其中:
- A0-A2:硬件地址引脚
- a8-a10:存储器寻址位
- R/W:读/写控制位(0=写,1=读)
3. 软件实现
3.1 IIC驱动实现
IIC驱动通过软件模拟方式实现,主要包括以下功能:
初始化
void iic_init(void)
{
rcu_periph_clock_enable(RCU_GPIOB); /* 使能GPIOB时钟 */
/* 配置SCL引脚:推挽输出模式,上拉,高速 */
gpio_mode_set(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, GPIO_PIN_8);
gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_8);
/* 配置SDA引脚:开漏输出模式,上拉,高速
* 开漏输出可以实现双向IO功能,上拉电阻(=1)时也可以读取外部信号的高低电平 */
gpio_mode_set(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, GPIO_PIN_9);
gpio_output_options_set(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
iic_stop(); /* 发送停止信号,复位所有设备 */
}
起始信号
void iic_start(void)
{
IIC_SDA(1); /* 确保SDA初始为高 */
IIC_SCL(1); /* 确保SCL初始为高 */
iic_delay();
IIC_SDA(0); /* START信号:当SCL为高时,SDA由高变低,表示起始信号 */
iic_delay();
IIC_SCL(0); /* 钳制I2C总线,准备发送或接收数据 */
iic_delay();
}
停止信号
void iic_stop(void)
{
IIC_SDA(0); /* STOP信号:当SCL为高时,SDA由低变高,表示停止信号 */
iic_delay();
IIC_SCL(1);
iic_delay();
IIC_SDA(1); /* 释放I2C总线,结束信号 */
iic_delay();
}
发送字节
void iic_send_byte(uint8_t data)
{
uint8_t t;
for (t = 0; t < 8; t++)
{
IIC_SDA((data & 0x80) >> 7); /* 高位先发送 */
iic_delay();
IIC_SCL(1); /* 产生SCL时钟 */
iic_delay();
IIC_SCL(0); /* 时钟输出0 */
data <<= 1; /* 左移一位,准备下一次发送 */
}
IIC_SDA(1); /* 发送完成,主机释放SDA线 */
}
接收字节
uint8_t iic_read_byte(uint8_t ack)
{
uint8_t i;
uint8_t receive = 0;
for (i = 0; i < 8; i++) /* 接收1个字节数据 */
{
receive <<= 1; /* 左移一位,接收到的数据位要放到最低位 */
IIC_SCL(1);
iic_delay();
if (IIC_READ_SDA)
{
receive++;
}
IIC_SCL(0);
iic_delay();
}
if (!ack)
{
iic_nack(); /* 发送nACK */
}
else
{
iic_ack(); /* 发送ACK */
}
return receive; /* 返回接收到的数据 */
}
应答和非应答
void iic_ack(void)
{
IIC_SDA(0); /* SCL 0 -> 1 时 SDA = 0,表示应答 */
iic_delay();
IIC_SCL(1); /* 产生一个时钟 */
iic_delay();
IIC_SCL(0);
iic_delay();
IIC_SDA(1); /* 主机释放SDA线 */
iic_delay();
}
void iic_nack(void)
{
IIC_SDA(1); /* SCL 0 -> 1 时 SDA = 1,表示不应答 */
iic_delay();
IIC_SCL(1); /* 产生一个时钟 */
iic_delay();
IIC_SCL(0);
iic_delay();
}
等待应答
uint8_t iic_wait_ack(void)
{
uint8_t waitTime = 0;
uint8_t rAck = 0;
IIC_SDA(1); /* 主机释放SDA线(此时外部器件可以拉低SDA线) */
iic_delay();
IIC_SCL(1); /* SCL=1,此时从机可以发送ACK */
iic_delay();
while (IIC_READ_SDA) /* 等待应答 */
{
waitTime++;
if (waitTime > 250) /* 超时未收到应答信号 */
{
iic_stop();
rAck = 1;
break;
}
}
IIC_SCL(0); /* SCL=0,结束ACK检测 */
iic_delay();
return rAck;
}
3.2 AT24CXX驱动实现
AT24CXX驱动基于IIC驱动,实现了EEPROM的读写操作:
初始化
void at24cxx_init(void)
{
iic_init(); /* 初始化IIC接口 */
}
读取单字节
uint8_t at24cxx_read_one_byte(uint16_t addr)
{
uint8_t temp = 0;
iic_start(); /* 发送起始信号 */
/* 根据不同的24CXX型号,发送设备地址和寻址方式 */
if (EE_TYPE > AT24C16) /* 24C16以上型号,需要发送2字节地址 */
{
iic_send_byte(0XA0); /* 发送写命令,IIC规定最低位为0表示写入 */
iic_wait_ack(); /* 等待从机应答 */
iic_send_byte(addr >> 8); /* 发送高字节地址 */
}
else
{
iic_send_byte(0XA0 + ((addr >> 8) << 1)); /* 器件地址 0XA0 + 高位地址 */
}
iic_wait_ack(); /* 等待从机应答 */
iic_send_byte(addr % 256); /* 发送低字节地址 */
iic_wait_ack(); /* 等待从机应答,此时地址设置完成 */
iic_start(); /* 重新发送起始信号,准备读取数据 */
iic_send_byte(0XA1); /* 进入读取模式,IIC规定最低位为1表示读取 */
iic_wait_ack(); /* 等待从机应答 */
temp = iic_read_byte(0); /* 读取一个字节数据,发送NACK */
iic_stop(); /* 发送停止信号 */
return temp;
}
写入单字节
void at24cxx_write_one_byte(uint16_t addr, uint8_t data)
{
iic_start(); /* 发送起始信号 */
if (EE_TYPE > AT24C16) /* 24C16以上型号,需要发送2字节地址 */
{
iic_send_byte(0XA0); /* 发送写命令,IIC规定最低位为0表示写入 */
iic_wait_ack(); /* 等待从机应答 */
iic_send_byte(addr >> 8); /* 发送高字节地址 */
}
else
{
iic_send_byte(0XA0 + ((addr >> 8) << 1)); /* 器件地址 0XA0 + 高位地址 */
}
iic_wait_ack(); /* 等待从机应答 */
iic_send_byte(addr % 256); /* 发送低字节地址 */
iic_wait_ack(); /* 等待从机应答,此时地址设置完成 */
/* 写数据时不需要重新发送起始信号,直接发送数据即可 */
iic_send_byte(data); /* 发送要写入的数据 */
iic_wait_ack(); /* 等待从机应答 */
iic_stop(); /* 发送停止信号 */
delay_ms(10); /* EEPROM写操作需要时间,等待10ms以上才能进行下一次写入 */
}
读取多字节
void at24cxx_read(uint16_t addr, uint8_t *pbuf, uint16_t datalen)
{
while (datalen--)
{
*pbuf++ = at24cxx_read_one_byte(addr++); /* 循环读取每个字节 */
}
}
写入多字节
void at24cxx_write(uint16_t addr, uint8_t *pbuf, uint16_t datalen)
{
while (datalen--)
{
at24cxx_write_one_byte(addr, *pbuf); /* 写入一个字节数据 */
addr++; /* 地址递增 */
pbuf++; /* 数据指针递增 */
}
}
设备检测
uint8_t at24cxx_check(void)
{
uint8_t temp;
uint16_t addr = EE_TYPE; /* 使用最大地址进行测试 */
/* 首先读取当前值,避免不必要的写操作 */
temp = at24cxx_read_one_byte(addr);
if (temp == 0X55) /* 如果当前值已经是0x55,说明器件正常 */
{
return 0;
}
else /* 当前值不是0x55,需要进行写入测试 */
{
at24cxx_write_one_byte(addr, 0X55); /* 写入测试数据 */
temp = at24cxx_read_one_byte(addr); /* 读取写入的数据 */
if (temp == 0X55) return 0; /* 读取值正确,设备正常 */
}
return 1; /* 读取值错误,设备异常 */
}
4. 应用示例
4.1 基本读写操作
以下是AT24CXX EEPROM基本读写操作的示例代码:
/* 初始化 */
at24cxx_init();
/* 检查设备是否正常 */
if (at24cxx_check() == 0)
{
printf("AT24CXX检测正常!\r\n");
/* 写入测试数据 */
uint8_t write_buf[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
at24cxx_write(0, write_buf, 10);
/* 读取测试数据 */
uint8_t read_buf[10] = {0};
at24cxx_read(0, read_buf, 10);
/* 验证数据 */
uint8_t i;
for (i = 0; i < 10; i++)
{
printf("读取数据[%d]: %d\r\n", i, read_buf[i]);
}
}
else
{
printf("AT24CXX检测失败!\r\n");
}
4.2 数据存储应用
AT24CXX EEPROM常用于存储系统配置、校准数据、日志信息等。以下是一个存储系统配置的示例:
/* 定义系统配置结构体 */
typedef struct
{
uint8_t device_id;
uint16_t sample_rate;
uint8_t channel_num;
uint8_t filter_mode;
uint16_t alarm_threshold;
uint8_t checksum; /* 校验和,用于验证数据完整性 */
} SystemConfig_t;
/* 计算校验和 */
uint8_t calculate_checksum(SystemConfig_t *config)
{
uint8_t *ptr = (uint8_t *)config;
uint8_t sum = 0;
uint16_t i;
/* 计算除校验和外所有字节的和 */
for (i = 0; i < sizeof(SystemConfig_t) - 1; i++)
{
sum += ptr[i];
}
return sum;
}
/* 保存系统配置 */
void save_system_config(SystemConfig_t *config)
{
/* 计算校验和 */
config->checksum = calculate_checksum(config);
/* 写入EEPROM */
at24cxx_write(0, (uint8_t *)config, sizeof(SystemConfig_t));
}
/* 加载系统配置 */
uint8_t load_system_config(SystemConfig_t *config)
{
/* 从EEPROM读取配置 */
at24cxx_read(0, (uint8_t *)config, sizeof(SystemConfig_t));
/* 验证校验和 */
uint8_t checksum = calculate_checksum(config);
if (checksum == config->checksum)
{
return 0; /* 校验成功 */
}
else
{
return 1; /* 校验失败 */
}
}
/* 使用示例 */
void config_example(void)
{
SystemConfig_t config;
/* 初始化AT24CXX */
at24cxx_init();
/* 尝试加载配置 */
if (load_system_config(&config) == 0)
{
printf("加载配置成功!\r\n");
printf("设备ID: %d\r\n", config.device_id);
printf("采样率: %d\r\n", config.sample_rate);
}
else
{
printf("配置无效或首次使用,使用默认配置\r\n");
/* 设置默认配置 */
config.device_id = 1;
config.sample_rate = 1000;
config.channel_num = 4;
config.filter_mode = 2;
config.alarm_threshold = 500;
/* 保存默认配置 */
save_system_config(&config);
}
}
在OTA(空中升级)应用中,AT24CXX也可用于存储固件版本信息、升级状态等关键数据:
/* 读取OTA标志 */
void Read_OTA_Flag(void)
{
memset(&ota_info, 0, OTA_INFO_SIZE);
at24cxx_read(0, (uint8_t *)&ota_info, OTA_INFO_SIZE);
}
/* 写入OTA标志 */
void Write_OTA_Flag(void)
{
uint8_t *wptr = (uint8_t *)&ota_info;
at24cxx_write(0, wptr, OTA_INFO_SIZE);
}
通过这种方式,系统可以在断电或重启后恢复之前的状态,确保OTA过程的可靠性和连续性。
更多推荐



所有评论(0)