STM32实战:基于STM32F103的I2C通信(AT24Cxx EEPROM读写)

文章目录

    • 一、前言
      • [1.1 技术背景](#1.1 技术背景)
      • [1.2 本文目标](#1.2 本文目标)
      • [1.3 技术栈](#1.3 技术栈)
    • 二、环境准备
      • [2.1 硬件准备](#2.1 硬件准备)
      • [2.2 AT24Cxx存储结构](#2.2 AT24Cxx存储结构)
    • 三、I2C通信协议
      • [3.1 I2C基本原理](#3.1 I2C基本原理)
      • [3.2 I2C通信流程](#3.2 I2C通信流程)
    • 四、驱动程序实现
      • [4.1 I2C外设初始化](#4.1 I2C外设初始化)
      • [4.2 主程序](#4.2 主程序)
    • 五、测试与验证
      • [5.1 硬件测试](#5.1 硬件测试)
      • [5.2 软件测试](#5.2 软件测试)
    • 六、故障排查
    • 七、总结
      • [7.1 核心知识点](#7.1 核心知识点)
      • [7.2 应用场景](#7.2 应用场景)

一、前言

1.1 技术背景

在嵌入式系统中,经常需要保存一些掉电不丢失的配置参数、校准数据或用户设置。EEPROM(Electrically Erasable Programmable Read-Only Memory)是理想的非易失性存储方案,相比Flash具有以下优势:

  • 字节级擦写:无需整页/扇区擦除,可直接改写单个字节
  • 长寿命:擦写次数可达100万次
  • 低功耗:待机电流仅1-2μA
  • 高可靠性:数据保存期长达100年
  • 简单易用:I2C接口,只需2根线

AT24C系列是Atmel(现Microchip)公司的I2C接口EEPROM,容量从128字节到512Kbit不等,广泛应用于:

  • 系统参数存储
  • 用户配置保存
  • 校准数据存储
  • 运行日志记录

1.2 本文目标

通过本教程,你将掌握:

  • I2C通信协议原理和时序
  • STM32F103的I2C外设配置
  • AT24C02/04/08/16/32/64/128系列EEPROM操作
  • 分页写入和随机读取
  • 写保护和地址寻址

适合读者:

  • 需要存储配置参数的嵌入式开发者
  • 学习I2C通信协议的初学者
  • 需要实现数据持久化的项目开发者

1.3 技术栈

组件 型号/版本 说明
主控芯片 STM32F103C8T6 ARM Cortex-M3
EEPROM AT24C02/04/08/16 2Kbit~16Kbit I2C EEPROM
开发环境 Keil MDK 5 嵌入式开发IDE
通信协议 I2C 串行总线接口

二、环境准备

2.1 硬件准备

核心硬件清单:

  • STM32F103C8T6最小系统板 × 1
  • AT24C02/04/08/16模块 × 1
  • USB转TTL模块 × 1
  • 上拉电阻4.7KΩ × 2(部分模块已集成)
  • 杜邦线若干

硬件连接图:

复制代码
STM32F103C8T6          AT24C02
┌─────────────┐        ┌─────────────┐
│             │        │             │
│  PB6(SCL) ──┼────────┼────> SCL    │
│  PB7(SDA) ──┼────────┼────> SDA    │
│  3.3V     ──┼────────┼────  VCC    │
│  GND      ──┼────────┼────  GND    │
│             │        │             │
└─────────────┘        └─────────────┘

上拉电阻连接(如模块未集成):
3.3V ──[4.7KΩ]── SCL
3.3V ──[4.7KΩ]── SDA

AT24C02引脚定义:

引脚 名称 功能
1 A0 地址选择位0
2 A1 地址选择位1
3 A2 地址选择位2
4 GND
5 SDA 串行数据
6 SCL 串行时钟
7 WP 写保护(高电平有效)
8 VCC 电源(2.5-5.5V)

地址引脚配置:

  • A0、A1、A2接地或接VCC,用于设置设备地址
  • 最多可连接8个AT24Cxx器件在同一I2C总线上

2.2 AT24Cxx存储结构

容量对照表:

型号 容量 字节数 页大小 页数 地址字节
AT24C01 1Kbit 128 8 16 1
AT24C02 2Kbit 256 8 32 1
AT24C04 4Kbit 512 16 32 1
AT24C08 8Kbit 1024 16 64 1
AT24C16 16Kbit 2048 16 128 1
AT24C32 32Kbit 4096 32 128 2
AT24C64 64Kbit 8192 32 256 2
AT24C128 128Kbit 16384 64 256 2

重要特性:

  • 最小写入单位:页
  • 页内地址自动递增
  • 跨页写入需要分多次操作
  • 写入周期:最大5ms

三、I2C通信协议

3.1 I2C基本原理

I2C(Inter-Integrated Circuit)是一种同步串行通信协议,特点:

  • 两线制:SDA(数据线)、SCL(时钟线)
  • 多主多从:支持一主多从或多主模式
  • 地址寻址:7位或10位设备地址
  • 应答机制:每字节传输后接收方应答
  • 开漏输出:需外接上拉电阻

I2C时序:

复制代码
起始条件(S)和停止条件(P):

    SCL ──┐     ┌─────┐     ┌─────┐     ┌──
          └─────┘     └─────┘     └─────┘
    SDA ──┐  ┌────────────────────────────────
          └──┘
          ↑
        起始条件:SCL高时,SDA从高变低

    SCL ──┐     ┌─────┐     ┌─────┐     ┌──
          └─────┘     └─────┘     └─────┘
    SDA ────────────────────────────┐  ┌───
                                    └──┘
                                     ↑
                                   停止条件:SCL高时,SDA从低变高

数据传输时序:

    SCL ──┐  ┌──┐  ┌──┐  ┌──┐  ┌──┐  ┌──┐  ┌──┐  ┌──┐  ┌──┐  ┌──
          └──┘  └──┘  └──┘  └──┘  └──┘  └──┘  └──┘  └──┘  └──┘
              ↑     ↑     ↑     ↑     ↑     ↑     ↑     ↑
            采样  采样  采样  采样  采样  采样  采样  采样
            (上升沿)

    SDA ───┐     ┌─────┐     ┌───────────┐     ┌─────┐
           └─────┘     └─────┘           └─────┘     └────
           D7    D6    D5    D4    D3    D2    D1    D0    ACK

3.2 I2C通信流程

写操作(单字节):

复制代码
主机发送:
┌─────┬─────────┬─────────┬─────────┬─────────┬─────┐
│  S  │ 设备地址 │   ACK   │ 内存地址 │   ACK   │ 数据 │
│     │  (写)   │  (从机) │         │  (从机) │     │
└─────┴─────────┴─────────┴─────────┴─────────┴─────┘

设备地址格式(7位):
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
│ A6  │ A5  │ A4  │ A3  │ A2  │ A1  │ A0  │ R/W │
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
│        固定部分       │  地址引脚 │  0=写  │
│    (1010 for EEPROM)  │  (A2A1A0)│  1=读  │

读操作(随机读取):

复制代码
主机发送:
┌─────┬─────────┬─────────┬─────────┬─────┐
│  S  │ 设备地址 │   ACK   │ 内存地址 │  ACK │
│     │  (写)   │  (从机) │         │      │
└─────┴─────────┴─────────┴─────────┴─────┘

重复起始:
┌─────┬─────────┬─────────┬─────────┬─────┐
│ Sr  │ 设备地址 │   ACK   │   数据   │ ACK │
│     │  (读)   │  (从机) │  (从机)  │(主机)│
└─────┴─────────┴─────────┴─────────┴─────┘

四、驱动程序实现

4.1 I2C外设初始化

📄 创建文件:i2c_eeprom.c

c 复制代码
/**
 * @file i2c_eeprom.c
 * @brief AT24Cxx I2C EEPROM驱动程序
 * 
 * 功能:
 * - I2C1初始化(标准模式100KHz)
 * - AT24Cxx基本操作(字节读写、页写入)
 * - 多型号支持(AT24C02~AT24C128)
 * - 写保护和等待处理
 */

#include "i2c_eeprom.h"
#include "stm32f10x.h"
#include <string.h>

/* EEPROM设备地址基址(A2A1A0=000) */
#define EEPROM_BASE_ADDR    0xA0

/* 超时时间 */
#define I2C_TIMEOUT_MS      100
#define EEPROM_WRITE_DELAY_MS   6   // 写入周期最大5ms

/* 当前EEPROM型号参数 */
static uint16_t eeprom_page_size = 8;       // 页大小
static uint16_t eeprom_total_size = 256;    // 总字节数
static uint8_t eeprom_addr_bytes = 1;       // 地址字节数

/**
 * @brief 初始化I2C1
 * 
 * 配置参数:
 * - 时钟频率:100KHz(标准模式)
 * - 占空比:2:1
 * - 应答使能:开启
 * - 7位地址模式
 */
void I2C_EEPROM_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    I2C_InitTypeDef I2C_InitStruct;
    
    /* 1. 使能时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
    
    /* 2. 配置GPIO */
    // PB6 - SCL (复用开漏)
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStruct);
    
    // PB7 - SDA (复用开漏)
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
    GPIO_Init(GPIOB, &GPIO_InitStruct);
    
    /* 3. 配置I2C */
    I2C_InitStruct.I2C_ClockSpeed = 100000;     // 100KHz
    I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
    I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
    I2C_InitStruct.I2C_OwnAddress1 = 0x00;      // 主机模式
    I2C_InitStruct.I2C_Ack = I2C_Ack_Enable;
    I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    I2C_Init(I2C1, &I2C_InitStruct);
    
    /* 4. 使能I2C */
    I2C_Cmd(I2C1, ENABLE);
}

/**
 * @brief 设置EEPROM型号
 * @param type EEPROM型号
 */
void EEPROM_SetType(EEPROM_Type type)
{
    switch (type) {
        case EEPROM_AT24C01:
            eeprom_page_size = 8;
            eeprom_total_size = 128;
            eeprom_addr_bytes = 1;
            break;
        case EEPROM_AT24C02:
            eeprom_page_size = 8;
            eeprom_total_size = 256;
            eeprom_addr_bytes = 1;
            break;
        case EEPROM_AT24C04:
            eeprom_page_size = 16;
            eeprom_total_size = 512;
            eeprom_addr_bytes = 1;
            break;
        case EEPROM_AT24C08:
            eeprom_page_size = 16;
            eeprom_total_size = 1024;
            eeprom_addr_bytes = 1;
            break;
        case EEPROM_AT24C16:
            eeprom_page_size = 16;
            eeprom_total_size = 2048;
            eeprom_addr_bytes = 1;
            break;
        case EEPROM_AT24C32:
            eeprom_page_size = 32;
            eeprom_total_size = 4096;
            eeprom_addr_bytes = 2;
            break;
        case EEPROM_AT24C64:
            eeprom_page_size = 32;
            eeprom_total_size = 8192;
            eeprom_addr_bytes = 2;
            break;
        case EEPROM_AT24C128:
            eeprom_page_size = 64;
            eeprom_total_size = 16384;
            eeprom_addr_bytes = 2;
            break;
        default:
            eeprom_page_size = 8;
            eeprom_total_size = 256;
            eeprom_addr_bytes = 1;
            break;
    }
}

/**
 * @brief 等待I2C事件
 * @param event 等待的事件
 * @param timeout 超时时间
 * @return 0=成功,1=超时
 */
static uint8_t I2C_WaitEvent(uint32_t event, uint32_t timeout)
{
    uint32_t start = GetSysTick();
    while (!I2C_CheckEvent(I2C1, event)) {
        if ((GetSysTick() - start) > timeout) {
            return 1;
        }
    }
    return 0;
}

/**
 * @brief 发送起始条件
 * @return 0=成功,1=失败
 */
static uint8_t I2C_Start(void)
{
    I2C_GenerateSTART(I2C1, ENABLE);
    if (I2C_WaitEvent(I2C_EVENT_MASTER_MODE_SELECT, I2C_TIMEOUT_MS)) {
        return 1;
    }
    return 0;
}

/**
 * @brief 发送停止条件
 */
static void I2C_Stop(void)
{
    I2C_GenerateSTOP(I2C1, ENABLE);
}

/**
 * @brief 发送设备地址
 * @param addr 设备地址
 * @param direction 传输方向(I2C_Direction_Transmitter/Receiver)
 * @return 0=成功,1=失败
 */
static uint8_t I2C_SendAddress(uint8_t addr, uint8_t direction)
{
    I2C_Send7bitAddress(I2C1, addr, direction);
    
    if (direction == I2C_Direction_Transmitter) {
        if (I2C_WaitEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED, I2C_TIMEOUT_MS)) {
            return 1;
        }
    } else {
        if (I2C_WaitEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED, I2C_TIMEOUT_MS)) {
            return 1;
        }
    }
    return 0;
}

/**
 * @brief 发送数据
 * @param data 数据
 * @return 0=成功,1=失败
 */
static uint8_t I2C_SendData(uint8_t data)
{
    I2C_SendData(I2C1, data);
    if (I2C_WaitEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED, I2C_TIMEOUT_MS)) {
        return 1;
    }
    return 0;
}

/**
 * @brief 接收数据
 * @param ack 是否发送ACK(1=ACK,0=NACK)
 * @return 接收到的数据
 */
static uint8_t I2C_ReceiveData(uint8_t ack)
{
    if (ack) {
        I2C_AcknowledgeConfig(I2C1, ENABLE);
    } else {
        I2C_AcknowledgeConfig(I2C1, DISABLE);
    }
    
    while (I2C_GetFlagStatus(I2C1, I2C_FLAG_RXNE) == RESET);
    return I2C_ReceiveData(I2C1);
}

/**
 * @brief 等待EEPROM写入完成
 * @param device_addr 设备地址
 * @return 0=成功,1=超时
 */
static uint8_t EEPROM_WaitWriteComplete(uint8_t device_addr)
{
    uint32_t start = GetSysTick();
    
    while ((GetSysTick() - start) < EEPROM_WRITE_DELAY_MS) {
        // 尝试发送起始条件+设备地址
        if (I2C_Start() == 0) {
            I2C_Send7bitAddress(I2C1, device_addr, I2C_Direction_Transmitter);
            
            // 等待ACK或超时
            uint32_t ack_start = GetSysTick();
            while ((GetSysTick() - ack_start) < 10) {
                if (I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) {
                    I2C_Stop();
                    return 0;  // 写入完成
                }
                if (I2C_GetFlagStatus(I2C1, I2C_FLAG_AF)) {
                    I2C_ClearFlag(I2C1, I2C_FLAG_AF);
                    break;  // 未响应,继续等待
                }
            }
            I2C_Stop();
        }
    }
    
    return 1;  // 超时
}

/**
 * @brief 计算设备地址
 * @param mem_addr 内存地址
 * @return 设备地址(含块选择位)
 */
static uint8_t EEPROM_GetDeviceAddr(uint16_t mem_addr)
{
    uint8_t addr = EEPROM_BASE_ADDR;
    
    // 对于AT24C04/08/16,使用地址位作为块选择
    if (eeprom_total_size > 256 && eeprom_total_size <= 2048) {
        // AT24C04: A8位用于块选择
        // AT24C08: A8,A9位用于块选择
        // AT24C16: A8,A9,A10位用于块选择
        addr |= ((mem_addr >> 8) & 0x07) << 1;
    }
    
    return addr;
}

/**
 * @brief 写入一个字节
 * @param addr 内存地址
 * @param data 数据
 * @return 0=成功,1=失败
 */
uint8_t EEPROM_WriteByte(uint16_t addr, uint8_t data)
{
    uint8_t device_addr = EEPROM_GetDeviceAddr(addr);
    
    // 起始条件
    if (I2C_Start()) return 1;
    
    // 发送设备地址(写)
    if (I2C_SendAddress(device_addr, I2C_Direction_Transmitter)) {
        I2C_Stop();
        return 1;
    }
    
    // 发送内存地址
    if (eeprom_addr_bytes == 2) {
        if (I2C_SendData((addr >> 8) & 0xFF)) {
            I2C_Stop();
            return 1;
        }
    }
    if (I2C_SendData(addr & 0xFF)) {
        I2C_Stop();
        return 1;
    }
    
    // 发送数据
    if (I2C_SendData(data)) {
        I2C_Stop();
        return 1;
    }
    
    // 停止条件
    I2C_Stop();
    
    // 等待写入完成
    return EEPROM_WaitWriteComplete(device_addr);
}

/**
 * @brief 读取一个字节
 * @param addr 内存地址
 * @return 读取的数据
 */
uint8_t EEPROM_ReadByte(uint16_t addr)
{
    uint8_t device_addr = EEPROM_GetDeviceAddr(addr);
    uint8_t data;
    
    // 第一步:发送内存地址(写模式)
    if (I2C_Start()) return 0xFF;
    if (I2C_SendAddress(device_addr, I2C_Direction_Transmitter)) {
        I2C_Stop();
        return 0xFF;
    }
    
    // 发送内存地址
    if (eeprom_addr_bytes == 2) {
        if (I2C_SendData((addr >> 8) & 0xFF)) {
            I2C_Stop();
            return 0xFF;
        }
    }
    if (I2C_SendData(addr & 0xFF)) {
        I2C_Stop();
        return 0xFF;
    }
    
    // 第二步:重复起始,读数据
    if (I2C_Start()) {
        I2C_Stop();
        return 0xFF;
    }
    if (I2C_SendAddress(device_addr, I2C_Direction_Receiver)) {
        I2C_Stop();
        return 0xFF;
    }
    
    // 读取数据(NACK)
    data = I2C_ReceiveData(0);
    
    // 停止条件
    I2C_Stop();
    
    return data;
}

/**
 * @brief 页写入
 * @param addr 起始地址(必须页对齐)
 * @param data 数据指针
 * @param len 数据长度(不能超过页大小)
 * @return 0=成功,1=失败
 */
uint8_t EEPROM_PageWrite(uint16_t addr, const uint8_t *data, uint8_t len)
{
    uint8_t device_addr = EEPROM_GetDeviceAddr(addr);
    
    // 检查参数
    if (len == 0 || len > eeprom_page_size) {
        return 1;
    }
    
    // 起始条件
    if (I2C_Start()) return 1;
    
    // 发送设备地址(写)
    if (I2C_SendAddress(device_addr, I2C_Direction_Transmitter)) {
        I2C_Stop();
        return 1;
    }
    
    // 发送内存地址
    if (eeprom_addr_bytes == 2) {
        if (I2C_SendData((addr >> 8) & 0xFF)) {
            I2C_Stop();
            return 1;
        }
    }
    if (I2C_SendData(addr & 0xFF)) {
        I2C_Stop();
        return 1;
    }
    
    // 发送数据
    for (uint8_t i = 0; i < len; i++) {
        if (I2C_SendData(data[i])) {
            I2C_Stop();
            return 1;
        }
    }
    
    // 停止条件
    I2C_Stop();
    
    // 等待写入完成
    return EEPROM_WaitWriteComplete(device_addr);
}

/**
 * @brief 连续写入(自动分页)
 * @param addr 起始地址
 * @param data 数据指针
 * @param len 数据长度
 * @return 0=成功,1=失败
 */
uint8_t EEPROM_WriteData(uint16_t addr, const uint8_t *data, uint16_t len)
{
    uint16_t page_addr;
    uint16_t page_offset;
    uint16_t write_len;
    
    while (len > 0) {
        // 计算当前页地址和偏移
        page_addr = addr & ~(eeprom_page_size - 1);
        page_offset = addr & (eeprom_page_size - 1);
        
        // 计算本次可写入的长度
        write_len = eeprom_page_size - page_offset;
        if (write_len > len) {
            write_len = len;
        }
        
        // 页写入
        if (EEPROM_PageWrite(page_addr, data, write_len) != 0) {
            return 1;
        }
        
        addr += write_len;
        data += write_len;
        len -= write_len;
    }
    
    return 0;
}

/**
 * @brief 连续读取
 * @param addr 起始地址
 * @param buffer 数据缓冲区
 * @param len 读取长度
 */
void EEPROM_ReadData(uint16_t addr, uint8_t *buffer, uint16_t len)
{
    uint8_t device_addr = EEPROM_GetDeviceAddr(addr);
    
    if (len == 0) return;
    
    // 第一步:发送内存地址(写模式)
    I2C_Start();
    I2C_SendAddress(device_addr, I2C_Direction_Transmitter);
    
    // 发送内存地址
    if (eeprom_addr_bytes == 2) {
        I2C_SendData((addr >> 8) & 0xFF);
    }
    I2C_SendData(addr & 0xFF);
    
    // 第二步:重复起始,读数据
    I2C_Start();
    I2C_SendAddress(device_addr, I2C_Direction_Receiver);
    
    // 读取数据
    for (uint16_t i = 0; i < len; i++) {
        if (i < len - 1) {
            buffer[i] = I2C_ReceiveData(1);  // 发送ACK
        } else {
            buffer[i] = I2C_ReceiveData(0);  // 最后一个字节发送NACK
        }
    }
    
    // 停止条件
    I2C_Stop();
}

📄 创建文件:i2c_eeprom.h

c 复制代码
/**
 * @file i2c_eeprom.h
 * @brief AT24Cxx EEPROM驱动头文件
 */

#ifndef __I2C_EEPROM_H
#define __I2C_EEPROM_H

#include <stdint.h>

/* EEPROM型号枚举 */
typedef enum {
    EEPROM_AT24C01 = 0,
    EEPROM_AT24C02,
    EEPROM_AT24C04,
    EEPROM_AT24C08,
    EEPROM_AT24C16,
    EEPROM_AT24C32,
    EEPROM_AT24C64,
    EEPROM_AT24C128
} EEPROM_Type;

/* 函数声明 */
void I2C_EEPROM_Init(void);
void EEPROM_SetType(EEPROM_Type type);
uint8_t EEPROM_WriteByte(uint16_t addr, uint8_t data);
uint8_t EEPROM_ReadByte(uint16_t addr);
uint8_t EEPROM_PageWrite(uint16_t addr, const uint8_t *data, uint8_t len);
uint8_t EEPROM_WriteData(uint16_t addr, const uint8_t *data, uint16_t len);
void EEPROM_ReadData(uint16_t addr, uint8_t *buffer, uint16_t len);

#endif /* __I2C_EEPROM_H */

4.2 主程序

📄 创建文件:main.c

c 复制代码
/**
 * @file main.c
 * @brief I2C EEPROM测试主程序
 */

#include "stm32f10x.h"
#include "i2c_eeprom.h"
#include "usart1.h"
#include <stdio.h>
#include <string.h>

static volatile uint32_t sys_tick = 0;

void SysTick_Handler(void)
{
    sys_tick++;
}

uint32_t GetSysTick(void)
{
    return sys_tick;
}

void Delay_ms(uint32_t ms)
{
    uint32_t start = GetSysTick();
    while ((GetSysTick() - start) < ms);
}

void Test_WriteRead(void)
{
    uint8_t write_data[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
    uint8_t read_data[8];
    
    printf("\r\n=== EEPROM读写测试 ===\r\n");
    
    // 写入数据
    printf("写入数据: ");
    for (int i = 0; i < 8; i++) {
        printf("%02X ", write_data[i]);
    }
    printf("\r\n");
    
    if (EEPROM_WriteData(0, write_data, 8) == 0) {
        printf("写入成功\r\n");
    } else {
        printf("写入失败\r\n");
        return;
    }
    
    Delay_ms(10);
    
    // 读取数据
    EEPROM_ReadData(0, read_data, 8);
    
    printf("读取数据: ");
    for (int i = 0; i < 8; i++) {
        printf("%02X ", read_data[i]);
    }
    printf("\r\n");
    
    // 校验
    if (memcmp(write_data, read_data, 8) == 0) {
        printf("校验通过!\r\n");
    } else {
        printf("校验失败!\r\n");
    }
}

void ShowMenu(void)
{
    printf("\r\n");
    printf("========== I2C EEPROM 测试菜单 ==========\r\n");
    printf("1. 读写测试\r\n");
    printf("2. 页写入测试\r\n");
    printf("3. 连续写入测试\r\n");
    printf("0. 清屏\r\n");
    printf("=========================================\r\n");
    printf("请输入选项: ");
}

int main(void)
{
    char cmd;
    
    SystemInit();
    SysTick_Config(SystemCoreClock / 1000);
    USART1_Init();
    I2C_EEPROM_Init();
    EEPROM_SetType(EEPROM_AT24C02);
    
    printf("\r\n");
    printf("====================================\r\n");
    printf("    AT24C02 I2C EEPROM 测试程序\r\n");
    printf("====================================\r\n");
    
    ShowMenu();
    
    while (1) {
        if (USART1_GetRxDataLength() > 0) {
            if (USART1_ReadData((uint8_t *)&cmd, 1) > 0) {
                switch (cmd) {
                    case '1':
                        Test_WriteRead();
                        break;
                    case '2':
                        printf("页写入测试...\r\n");
                        break;
                    case '3':
                        printf("连续写入测试...\r\n");
                        break;
                    case '0':
                        for (int i = 0; i < 50; i++) printf("\r\n");
                        break;
                    default:
                        printf("无效选项\r\n");
                        break;
                }
                ShowMenu();
            }
        }
    }
}

五、测试与验证

5.1 硬件测试

  • 测量SDA和SCL电压:空闲时高电平(3.3V)
  • 检查上拉电阻:4.7KΩ
  • 使用示波器观察I2C波形

5.2 软件测试

测试项目:

  1. 单字节读写
  2. 页写入(8字节)
  3. 跨页写入
  4. 连续读写
  5. 写入寿命测试

六、故障排查

问题 原因 解决方案
无ACK 地址错误 检查A0A1A2引脚配置
数据错误 时序问题 降低I2C速度
写入失败 写保护 检查WP引脚
读取全FF 未写入 先执行写入操作

七、总结

7.1 核心知识点

  1. I2C通信协议和时序
  2. AT24Cxx存储结构和寻址
  3. 分页写入和跨页处理
  4. STM32 I2C外设配置

7.2 应用场景

  • 系统参数存储
  • 用户配置保存
  • 校准数据存储
  • 运行日志记录
相关推荐
徐某人..1 小时前
基于i.MX6ULL平台的智能网关系统开发
arm开发·c++·单片机·qt·物联网·学习·arm
星恒讯工业路由器3 小时前
MCU+WiFi与CPU+WiFi模块区别
单片机·嵌入式硬件
LCMICRO-133108477463 小时前
长芯微LD7940完全P2P替代AD7940,是一款14位、逐次逼近型模数转换器(ADC)
单片机·嵌入式硬件·fpga开发·硬件工程·dsp开发·模数转换器adc
进击的小头5 小时前
20_第20篇:嵌入式外设驱动开发基础:寄存器级开发与库函数开发对比实战
arm开发·驱动开发·单片机
guygg886 小时前
基于STM32的智能小区管理系统设计
stm32·单片机·嵌入式硬件
Deitymoon6 小时前
STM32——震动传感器控制led
stm32·单片机·嵌入式硬件
bubiyoushang8887 小时前
51单片机MPU6050 DMP驱动实现
单片机·嵌入式硬件·51单片机
BT-BOX8 小时前
STM32的温湿度防盗安防报警器仿真_LCD1602显示
stm32·安防·烟雾·防盗·lcd1602显示·dht11温湿度·火焰
Deitymoon8 小时前
STM32——继电器
stm32·单片机·嵌入式硬件