1. ESP32驱动AT24C02 EEPROM全解析
最近在做一个物联网数据采集项目,需要ESP32存储一些配置参数和临时数据。虽然ESP32自带的Flash可以满足基本需求,但频繁擦写会影响Flash寿命。这时候我想到了经典的AT24C02 EEPROM芯片,它支持10万次擦写,正好解决我的痛点。下面就把我的实现过程完整分享给大家。
先说说为什么选择AT24C02:
- 价格便宜,市场价约0.5元/片
- 采用I2C接口,接线简单
- 2KB容量足够存储设备配置和运行日志
- 支持5ms快速写入
- 数据可保存100年不丢失
2. 硬件连接与电路设计
2.1 元器件清单
- ESP32开发板(我用的是ESP32-WROOM-32D)
- AT24C02芯片(8引脚SOIC封装)
- 4.7kΩ电阻×2(I2C上拉用)
- 0.1μF电容×1(电源滤波)
- 面包板及杜邦线若干
2.2 电路连接示意图
code复制ESP32 AT24C02
3.3V ------> VCC
GND ------> GND
GPIO41 -----> SDA
GPIO42 -----> SCL
注意:
- SDA和SCL必须接4.7kΩ上拉电阻到3.3V
- WP引脚接地以禁用写保护
- A0-A2全部接地,设置器件地址为0x50
实际布线时,建议将上拉电阻尽量靠近AT24C02放置,这样可以获得更好的信号质量。我在第一次测试时把电阻放在ESP32端,结果在400kHz速率下经常出现通信失败。
3. ESP-IDF开发环境配置
3.1 基础环境搭建
我使用的是VSCode + ESP-IDF插件方案,比纯命令行更方便:
- 安装VSCode 1.8.0+
- 搜索安装Espressif IDF插件
- 按F1选择"ESP-IDF: Configure ESP-IDF extension"
- 选择"EXPRESS"安装方式自动完成工具链部署
验证安装成功:
bash复制idf.py --version
# 应显示ESP-IDF版本如v5.1.2
3.2 项目创建
bash复制idf.py create-project at24c02_demo
cd at24c02_demo
code .
项目结构说明:
code复制├── CMakeLists.txt
├── main
│ ├── CMakeLists.txt
│ └── main.c
└── sdkconfig
4. I2C驱动实现详解
4.1 I2C总线初始化
创建components/my_iic目录,新建myiic.h头文件:
c复制#ifndef __MYIIC_H
#define __MYIIC_H
#include "driver/gpio.h"
#include "driver/i2c_master.h"
// 硬件配置
#define IIC_PORT_NUM I2C_NUM_0
#define IIC_FREQ_HZ 400000
#define IIC_SDA_PIN GPIO_NUM_41
#define IIC_SCL_PIN GPIO_NUM_42
// 错误处理宏
#define IIC_CHECK(a, msg) \
if(!(a)) { \
ESP_LOGE(TAG, "%s(%d): %s", __FUNCTION__, __LINE__, msg); \
return ESP_FAIL; \
}
esp_err_t i2c_master_init(void);
#endif
对应的myiic.c实现:
c复制#include "myiic.h"
static const char *TAG = "I2C";
static i2c_master_bus_handle_t bus_handle;
esp_err_t i2c_master_init(void)
{
i2c_master_bus_config_t bus_cfg = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.i2c_port = IIC_PORT_NUM,
.scl_io_num = IIC_SCL_PIN,
.sda_io_num = IIC_SDA_PIN,
.glitch_ignore_cnt = 7,
.flags.enable_internal_pullup = true,
};
esp_err_t ret = i2c_new_master_bus(&bus_cfg, &bus_handle);
IIC_CHECK(ret == ESP_OK, "I2C bus init failed");
ESP_LOGI(TAG, "I2C initialized successfully");
return ESP_OK;
}
关键点说明:
glitch_ignore_cnt设置为7可以过滤掉信号线上的毛刺- 启用内部上拉可以省去外部电阻(但实测稳定性不如外部4.7kΩ电阻)
- 400kHz是AT24C02支持的最高速率
4.2 AT24C02驱动实现
at24c02.h头文件定义:
c复制#ifndef __AT24C02_H
#define __AT24C02_H
#include "myiic.h"
#define AT24C02_ADDR 0x50
#define AT24C02_SIZE 256
#define AT24C02_PAGE_SIZE 8
// 写入延迟(ms)
#define AT24C02_WRITE_DELAY 5
esp_err_t at24c02_init(void);
esp_err_t at24c02_write_byte(uint16_t addr, uint8_t data);
esp_err_t at24c02_read_byte(uint16_t addr, uint8_t *data);
esp_err_t at24c02_write_page(uint16_t addr, uint8_t *data, uint8_t len);
esp_err_t at24c02_read_seq(uint16_t addr, uint8_t *data, uint16_t len);
esp_err_t at24c02_erase_chip(void);
#endif
at24c02.c核心实现:
c复制#include "at24c02.h"
#include "esp_timer.h"
static i2c_master_dev_handle_t dev_handle;
esp_err_t at24c02_init(void)
{
i2c_device_config_t dev_cfg = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = AT24C02_ADDR,
.scl_speed_hz = IIC_FREQ_HZ,
};
esp_err_t ret = i2c_master_bus_add_device(bus_handle, &dev_cfg, &dev_handle);
if(ret != ESP_OK) {
ESP_LOGE(TAG, "Add device failed");
return ret;
}
// 测试通信
uint8_t dummy;
ret = at24c02_read_byte(0, &dummy);
if(ret != ESP_OK) {
ESP_LOGE(TAG, "AT24C02 not responding");
return ret;
}
ESP_LOGI(TAG, "AT24C02 initialized");
return ESP_OK;
}
esp_err_t at24c02_write_byte(uint16_t addr, uint8_t data)
{
uint8_t write_buf[2] = {addr & 0xFF, data};
esp_err_t ret = i2c_master_transmit(dev_handle, write_buf, sizeof(write_buf), -1);
if(ret != ESP_OK) {
ESP_LOGE(TAG, "Write byte failed");
return ret;
}
// 必须等待写入完成
vTaskDelay(pdMS_TO_TICKS(AT24C02_WRITE_DELAY));
return ESP_OK;
}
几个关键注意事项:
- 每次写入后必须延时5ms以上,否则下次操作会失败
- 页写入时不能跨页,地址必须8字节对齐
- 读取操作不需要延时,可以连续快速读取
5. 应用层实现与测试
5.1 main.c主程序
c复制#include "at24c02.h"
#include "esp_log.h"
void app_main(void)
{
// 初始化I2C总线
ESP_ERROR_CHECK(i2c_master_init());
// 初始化AT24C02
if(at24c02_init() != ESP_OK) {
ESP_LOGE("MAIN", "AT24C02 init failed!");
return;
}
// 测试数据
uint8_t test_data[32];
for(int i=0; i<sizeof(test_data); i++) {
test_data[i] = i;
}
// 写入测试
ESP_ERROR_CHECK(at24c02_write_page(0, test_data, sizeof(test_data)));
// 读取验证
uint8_t read_data[32];
ESP_ERROR_CHECK(at24c02_read_seq(0, read_data, sizeof(read_data)));
// 打印结果
for(int i=0; i<sizeof(read_data); i++) {
if(read_data[i] != test_data[i]) {
ESP_LOGE("MAIN", "Verify failed at %d: %02x vs %02x",
i, read_data[i], test_data[i]);
return;
}
}
ESP_LOGI("MAIN", "AT24C02 test passed!");
}
5.2 常见问题排查
-
I2C通信失败
- 检查接线是否正确,特别是SDA/SCL不要接反
- 用示波器查看信号波形是否正常
- 尝试降低I2C时钟频率到100kHz
-
写入后读取数据不对
- 确保每次写入后有足够延时
- 检查WP引脚是否接地(未接地会导致写入失败)
- 确认器件地址设置正确(A0-A2引脚电平)
-
页写入数据错乱
- 确保写入不跨页(地址低3位为0时是新页开始)
- 单次写入不超过8字节
- 连续写入时需要检查ACK响应
6. 性能优化技巧
经过实际测试,我总结出几个提升AT24C02使用效率的方法:
- 批量写入优化
c复制// 智能写入函数,自动处理页边界
esp_err_t at24c02_smart_write(uint16_t addr, uint8_t *data, uint16_t len)
{
while(len > 0) {
uint8_t chunk = AT24C02_PAGE_SIZE - (addr % AT24C02_PAGE_SIZE);
chunk = (len < chunk) ? len : chunk;
esp_err_t ret = at24c02_write_page(addr, data, chunk);
if(ret != ESP_OK) return ret;
addr += chunk;
data += chunk;
len -= chunk;
}
return ESP_OK;
}
- 写入延迟优化
c复制// 使用esp_timer实现精确延时
void at24c02_write_delay(void)
{
uint64_t start = esp_timer_get_time();
while(esp_timer_get_time() - start < AT24C02_WRITE_DELAY * 1000) {
vTaskDelay(pdMS_TO_TICKS(1));
}
}
- 数据校验机制
c复制// 带CRC校验的写入
esp_err_t at24c02_write_with_crc(uint16_t addr, uint8_t *data, uint16_t len)
{
uint8_t crc = 0;
for(int i=0; i<len; i++) crc ^= data[i];
ESP_ERROR_CHECK(at24c02_smart_write(addr, data, len));
ESP_ERROR_CHECK(at24c02_write_byte(addr + len, crc));
return ESP_OK;
}
7. 实际项目应用案例
在我的环境监测项目中,AT24C02用于存储以下数据:
- 设备配置参数(采样间隔、上报频率等)
- WiFi连接凭证(SSID和密码)
- 传感器校准数据
- 运行日志(循环存储)
实现的关键代码片段:
c复制#define CONFIG_ADDR 0x00 // 配置参数区
#define WIFI_ADDR 0x40 // WiFi凭证区
#define CALIB_ADDR 0x80 // 校准数据区
#define LOG_ADDR 0xC0 // 日志区
typedef struct {
uint16_t sample_interval;
uint8_t report_hour;
float temp_offset;
} device_config_t;
void save_device_config(device_config_t *config)
{
uint8_t buf[sizeof(device_config_t)];
memcpy(buf, config, sizeof(device_config_t));
at24c02_write_with_crc(CONFIG_ADDR, buf, sizeof(buf));
}
bool load_device_config(device_config_t *config)
{
uint8_t buf[sizeof(device_config_t) + 1]; // +1 for CRC
at24c02_read_seq(CONFIG_ADDR, buf, sizeof(buf));
uint8_t crc = 0;
for(int i=0; i<sizeof(device_config_t); i++) crc ^= buf[i];
if(crc == buf[sizeof(device_config_t)]) {
memcpy(config, buf, sizeof(device_config_t));
return true;
}
return false;
}
这个方案已经稳定运行6个月,经历了-20℃到60℃的环境温度考验,数据始终保持完好。AT24C02的可靠性完全满足工业级应用需求。