1 概述

1.1 实验目的

        本实验旨在通过 STM32 微控制器的硬件 I²C 接口,对 AT24C02 外部 EEPROM 存储芯片 进行读写操作。实验演示了芯片地址配置、单字节/多字节读写、跨页写入(Page Write)功能。并提供完整的驱动代码,帮助读者深入理解 STM32 硬件 I²C 通信协议 的工作机制,并掌握基于 I²C 总线的低速存储设备 的数据存储与访问方法。

        通过本实验,读者不仅可以掌握 I²C 接口的基本使用技巧,还能够熟悉 AT24C02 的命令结构、存储特性及应用方法。这对于日后在嵌入式系统开发中涉及 小容量数据存储(如配置参数、掉电保存数据、传感器校准值等) 提供了技术积累和实践基础,具有良好的工程指导意义。

1.2 IIC协议

        I2C(Inter-Integrated Circuit)是一种同步、半双工、多主多从的串行通信协议。它通过两根信号线(SCL时钟线、SDA数据线)实现设备间通信,广泛应用于传感器、存储器、显示器等低速外设与主控制器(如MCU)的连接。接口引脚

引脚名称 全称 功能说明 方向 特点
SCL Serial Clock 串行时钟信号,由主设备(Master)产生,用于同步数据传输 主输出从输入 开漏/开集电极输出,需要上拉电阻
SDA Serial Data 串行数据信号,双向传输数据(主、从设备之间共用) 双向 开漏/开集电极输出,需要上拉电阻

1.3 AT24C02介绍

1.3.1 芯片引脚

引脚号 名称 功能说明
1 A0 器件地址选择引脚(Address Input 0),用于设定 I²C 从机地址的低位。可接 GND 或 VCC。
2 A1 器件地址选择引脚(Address Input 1),作用同 A0。
3 A2 器件地址选择引脚(Address Input 2),作用同 A0。
4 GND 电源地。
5 SDA 串行数据线(Serial Data)。I²C 双向数据线,开漏输出,需要上拉电阻。
6 SCL 串行时钟线(Serial Clock)。由 I²C 主机产生,开漏输出,需要上拉电阻。
7 WP 写保护(Write Protect)。高电平时禁止写操作(只读),低电平时可读可写。
8 VCC 电源正极(常用 2.7V–5.5V)。

1.3.2 芯片地址

        AT24C 系列 EEPROM 的 I²C 从机地址由固定的高 4 位(1010)和由引脚 A2、A1、A0 决定的 3 位地址位组成,最后一位是读写控制位(R/W)。A0~A2 引脚可接高电平(VCC)或低电平(GND)来改变芯片地址,从而在同一总线上使用多个 EEPROM 设备。

  • 写操作地址:R/W = 0

  • 读操作地址:R/W = 1

因此,芯片最多可以有8种地址,枚举如下

A2 A1 A0 7 位地址(十六进制) 写地址(8 位) 读地址(8 位)
0 0 0 0x50 0xA0 0xA1
0 0 1 0x51 0xA2 0xA3
0 1 0 0x52 0xA4 0xA5
0 1 1 0x53 0xA6 0xA7
1 0 0 0x54 0xA8 0xA9
1 0 1 0x55 0xAA 0xAB
1 1 0 0x56 0xAC 0xAD
1 1 1 0x57 0xAE 0xAF

1.3.3 其他型号

型号 寻址范围 页大小(Byte) 页数量 总容量(Byte) 总容量(Kbit)
AT24C01 0x00 – 0x7F 8 16 128 1 Kbit
AT24C02 0x00 – 0xFF 8 32 256 2 Kbit
AT24C04 0x000 – 0x1FF 16 32 512 4 Kbit
AT24C08 0x000 – 0x3FF 16 64 1024 8 Kbit
AT24C16 0x000 – 0x7FF 16 128 2048 16 Kbit
AT24C32 0x0000 – 0x0FFF 32 128 4096 32 Kbit
AT24C64 0x0000 – 0x1FFF 32 256 8192 64 Kbit
AT24C128 0x0000 – 0x3FFF 64 256 16384 128 Kbit
AT24C256 0x0000 – 0x7FFF 64 512 32768 256 Kbit
AT24C512 0x0000 – 0xFFFF 128 512 65536 512 Kbit

注:在阅读芯片资料时,AT24C02的页大小是8字节,但在实际测试过程中,我发现芯片页大小其实是16字节。其实是芯片版本不同导致的,我芯片具体型号是Atmel327 24C02BN 其中 “24C02BN” 里的 B 版本,就是 16 Byte 页大小的版本。而 N 一般表示封装(SOIC、PDIP、TSSOP 等)及无铅环保版本,不影响存储结构。

版本标识 页大小 总容量 备注
AT24C02A / AT24C02C 8 Byte 256 Byte 经典版本,很多资料引用这个
AT24C02B / AT24C02BN 16 Byte 256 Byte 后期改进版,页写效率更高

1.3.4 与flash区别

对比项 AT24C02(I²C EEPROM) W25Q32(SPI NOR Flash)
总容量 256 Byte 4 MByte
接口 I²C(2 线) SPI(4 线)
最小写入单元 1 Byte(页写一次最多 8 Byte) 1 Byte(页编程一次最多 256 Byte)
最小擦除单元 无需擦除,自动覆盖 4KB 扇区
擦写机制 自动擦写,直接覆盖 擦除→写入(1→0,0→1 需擦除)
写入延迟 固定 5ms 左右/页写 擦除慢(50~150ms),写快(<1ms)
寿命 100 万次写/字节 10 万次擦/扇区
断电数据保持 典型 100 年 典型 20 年
使用场景 小容量配置存储 大容量固件、资源存储

        AT24C02芯片是EEPROM类型芯片,写入不用擦除,相对方便,字节在对应地址中写入数据即可,如果原来有就覆盖。

2. STM32CubeMX 配置

2.1 SYS配置

不管你用的是STLink 还是JLink,都属于并行调试下载,所以在配置SYS时,均以下图为准。

2.2 RCC配置

2.3 USART配置

用于串口打印日志,调试用

2.4 IIC配置

        由于芯片连接的是stm32通用引脚PB10和PB11,这两个引脚的复用接口是stm32第二个IIC的接口,所以此处配置要选IIC2和硬件连线相匹配。

2.5 project配置

3. Keil MDK配置

3.1 debug配置

 根据自己的下载器选择debug现象,作者用的JLink

避免每次下载完程序后,还要按复位键才能跑最新程序

3.2 target配置

为了使用串口进行调试重定向打印调试信息,在target设置卡中引入LIB包

3.3 新增文件夹及文件

3.4 配置工程目录

3.5 配置工程文件

4. VSCode代码

4.1 驱动文件

#include "at24c02.h"
#include "string.h"
/**
特性	    M24C02 (2Kbit)	        M24C32 (32Kbit)
容量	    256字节 (0x00-0xFF)	    4096字节 (0x000-0xFFF)
地址位宽	8位地址	                16位地址(需2字节)
页大小	    16字节/页	            32字节/页
页数量      16页                    128页
设备地址	固定(A2-A0=0)	        固定(A2-A0=0)

24C32
第 0 页:0x0000 ~ 0x001F(32 字节)
第 1 页:0x0020 ~ 0x003F(32 字节)
第 2 页:0x0040 ~ 0x005F(32 字节)
...
第 127 页:0x0F80 ~ 0x0FFF(32 字节)
总字节数:128 页 × 32 字节/页 = 4,096 字节(与 AT24C32 容量一致)。

24C02
第 0 页:0x00 ~ 0x0F(16 字节)
第 1 页:0x10 ~ 0x1F(16 字节)
第 2 页:0x20 ~ 0x2F(16 字节)
...
第 15 页:0xF0 ~ 0xFF(16 字节)
总字节数:16 页 × 16 字节/页 = 256 字节(与 AT24C02 容量一致)。
 */



//向EEPROM写入一个字节
void AT24C02_WriteByte(uint8_t innerAddr, uint8_t byte){
    HAL_I2C_Mem_Write(&hi2c2, W_ADDR, innerAddr, I2C_MEMADD_SIZE_8BIT , &byte , 1 , 1000);
    // 延迟等待写入周期结束
    HAL_Delay(5);
}
//读取EEPROM一个字节
uint8_t AT24C02_ReadByte(uint8_t innerAddr){
    uint8_t byte;
    HAL_I2C_Mem_Read(&hi2c2, R_ADDR, innerAddr, I2C_MEMADD_SIZE_8BIT , &byte , 1 ,1000);
    return byte;
}
//连续写入多个字节(页写入)
void AT24C02_WriteBytes(uint8_t innerAddr, uint8_t *bytes, uint8_t size){
    HAL_I2C_Mem_Write(&hi2c2, W_ADDR, innerAddr, I2C_MEMADD_SIZE_8BIT , bytes, size , 1000);
    // 延迟等待写入周期结束
    HAL_Delay(5);
}
//连续读出多个字节(页读出 等价于 跨页读出)
void AT24C02_ReadBytes(uint8_t innerAddr, uint8_t *buffer, uint8_t size){
     HAL_I2C_Mem_Read(&hi2c2, R_ADDR, innerAddr, I2C_MEMADD_SIZE_8BIT , buffer , size ,1000);
}
//跨页写入
void AT24C02_WriteBytesPro(uint8_t innerAddr, uint8_t *data, uint8_t size) {
    while (size > 0) {
        uint8_t page_offset = innerAddr % EEPROM_PAGE_SIZE;  // AT24C02 页大小通常是 8 或 16
        uint8_t page_space = EEPROM_PAGE_SIZE - page_offset;
        uint8_t bytes_to_write = (size < page_space) ? size : page_space;

        // 使用 HAL_I2C_Mem_Write 自动处理地址
        HAL_I2C_Mem_Write(
            &hi2c2, W_ADDR, innerAddr, I2C_MEMADD_SIZE_8BIT,
            data, bytes_to_write, HAL_MAX_DELAY
        );
        HAL_Delay(5);  // AT24C02 写入需要 5ms

        // 更新指针和剩余长度
        innerAddr += bytes_to_write;  // 确保这里正确递增
        data += bytes_to_write;
        size -= bytes_to_write;
    }
}

#ifndef __AT24C02_H__
#define __AT24C02_H__

#include "i2c.h"

#define W_ADDR 0xA0
#define R_ADDR 0xA1
// 每页写入限制大小16字节
#define EEPROM_PAGE_SIZE 16

//向EEPROM写入一个字节
void AT24C02_WriteByte(uint8_t innerAddr, uint8_t byte);
//读取EEPROM一个字节
uint8_t AT24C02_ReadByte(uint8_t innerAddr);
//连续写入多个字节(页写入)
void AT24C02_WriteBytes(uint8_t innerAddr, uint8_t * bytes, uint8_t size);
//连续读出多个字节(可跨页读出)
void AT24C02_ReadBytes(uint8_t innerAddr, uint8_t * buffer, uint8_t size);

//跨页写入函数
void AT24C02_WriteBytesPro(uint8_t innerAddr, uint8_t *data, uint8_t size);
#endif

4.2 usart重定向代码

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    usart.c
  * @brief   This file provides code for the configuration
  *          of the USART instances.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "usart.h"

/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

UART_HandleTypeDef huart1;

/* USART1 init function */

void MX_USART1_UART_Init(void)
{

  /* USER CODE BEGIN USART1_Init 0 */

  /* USER CODE END USART1_Init 0 */

  /* USER CODE BEGIN USART1_Init 1 */

  /* USER CODE END USART1_Init 1 */
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART1_Init 2 */

  /* USER CODE END USART1_Init 2 */

}

void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(uartHandle->Instance==USART1)
  {
  /* USER CODE BEGIN USART1_MspInit 0 */

  /* USER CODE END USART1_MspInit 0 */
    /* USART1 clock enable */
    __HAL_RCC_USART1_CLK_ENABLE();

    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**USART1 GPIO Configuration
    PA9     ------> USART1_TX
    PA10     ------> USART1_RX
    */
    GPIO_InitStruct.Pin = GPIO_PIN_9;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* USART1 interrupt Init */
    HAL_NVIC_SetPriority(USART1_IRQn, 4, 0);
    HAL_NVIC_EnableIRQ(USART1_IRQn);
  /* USER CODE BEGIN USART1_MspInit 1 */

  /* USER CODE END USART1_MspInit 1 */
  }
}

void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
{

  if(uartHandle->Instance==USART1)
  {
  /* USER CODE BEGIN USART1_MspDeInit 0 */

  /* USER CODE END USART1_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_USART1_CLK_DISABLE();

    /**USART1 GPIO Configuration
    PA9     ------> USART1_TX
    PA10     ------> USART1_RX
    */
    HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10);

    /* USART1 interrupt Deinit */
    HAL_NVIC_DisableIRQ(USART1_IRQn);
  /* USER CODE BEGIN USART1_MspDeInit 1 */

  /* USER CODE END USART1_MspDeInit 1 */
  }
}

/* USER CODE BEGIN 1 */
int fputc(int ch, FILE * file){ //打印重定向
  HAL_UART_Transmit(&huart1,(uint8_t*)&ch,1,1000);
  return ch;
}
/* USER CODE END 1 */
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    usart.h
  * @brief   This file contains all the function prototypes for
  *          the usart.c file
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __USART_H__
#define __USART_H__

#ifdef __cplusplus
extern "C" {
#endif

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* USER CODE BEGIN Includes */
#include "stdio.h" 
/* USER CODE END Includes */

extern UART_HandleTypeDef huart1;

/* USER CODE BEGIN Private defines */

/* USER CODE END Private defines */

void MX_USART1_UART_Init(void);

/* USER CODE BEGIN Prototypes */

/* USER CODE END Prototypes */

#ifdef __cplusplus
}
#endif

#endif /* __USART_H__ */

4.3 主函数

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "i2c.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "at24c02.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  MX_I2C2_Init();
  /* USER CODE BEGIN 2 */
  printf("hello my at24c02\t\n");
  
  //读写单个字节
  AT24C02_WriteByte(0x00,'A');
  uint8_t byte1 = AT24C02_ReadByte(0x00);
  printf("byte1= %c\t" , byte1);

  // 读写多个字节
  AT24C02_WriteBytes(0x00,"123456",6);
  uint8_t buffer[100] = {0};
  AT24C02_ReadBytes(0x00,buffer,6);
  printf("buffer= %s\n" , buffer);

  // 跨页读写
  uint8_t bufferPro[256] = {0};
  AT24C02_WriteBytesPro(0x00,"Hello darkness, my old friend,I've come to talk with you again,Because a vision softly creeping, Left its seeds while I was sleeping, And the vision that was planted in my brain Still remains Within the sound of silence.",255);
  AT24C02_ReadBytes(0x00,bufferPro,255);
  printf("bufferPro= %s\n" , bufferPro);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    
    HAL_Delay(3000);
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

5. 结论

        本次实验 成功验证了 AT24C02 EEPROM 的基本读写功能,包括单字节、多字节连续读写和跨页读写。通过STM32硬件I2C 协议与芯片通信,确保了数据存储的准确性和可靠性。实验结果符合预期,为后续 数据掉电存储、传感器数据记录 等应用奠定了基础。注意事项:

  • I2C 地址参数:AT24C02 必须使用 I2C_MEMADD_SIZE_8BIT
  • 页边界处理:跨页写入需分页处理,避免数据丢失,芯片24C02带后缀B版本页大小为16字节。
  • 写入延迟:EEPROM 写入后需等待 5ms 确保数据固化。
  • 芯片地址:注意查看芯片地址引脚接线,判断地址。

        如果读者采用AT24C32芯片,那么上述驱动文件是不可共用的,因为AT24C32的地址是两个字节,且页存储为32字节,共128页,总存储32Kbit,是AT24C02的16倍,存储空间结构完全不同。如果读者感兴趣,可以评论区留言,作者提供AT24C32相关的驱动文件。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐