当前位置: 首页 > news >正文

STM32CubeMX + HAL 库:用硬件IIC接口实现AT24C02 EEPROM芯片的读写操作

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)的连接。接口引脚

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

1.3 AT24C02介绍

1.3.1 芯片引脚

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

A2A1A07 位地址(十六进制)写地址(8 位)读地址(8 位)
0000x500xA00xA1
0010x510xA20xA3
0100x520xA40xA5
0110x530xA60xA7
1000x540xA80xA9
1010x550xAA0xAB
1100x560xAC0xAD
1110x570xAE0xAF

1.3.3 其他型号

型号寻址范围页大小(Byte)页数量总容量(Byte)总容量(Kbit)
AT24C010x00 – 0x7F8161281 Kbit
AT24C020x00 – 0xFF8322562 Kbit
AT24C040x000 – 0x1FF16325124 Kbit
AT24C080x000 – 0x3FF166410248 Kbit
AT24C160x000 – 0x7FF16128204816 Kbit
AT24C320x0000 – 0x0FFF32128409632 Kbit
AT24C640x0000 – 0x1FFF32256819264 Kbit
AT24C1280x0000 – 0x3FFF6425616384128 Kbit
AT24C2560x0000 – 0x7FFF6451232768256 Kbit
AT24C5120x0000 – 0xFFFF12851265536512 Kbit

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

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

1.3.4 与flash区别

对比项AT24C02(I²C EEPROM)W25Q32(SPI NOR Flash)
总容量256 Byte4 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 或 16uint8_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 ConfigurationPA9     ------> USART1_TXPA10     ------> 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 ConfigurationPA9     ------> USART1_TXPA10     ------> 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相关的驱动文件。

http://www.lryc.cn/news/617384.html

相关文章:

  • 【算法训练营Day23】贪心算法part1
  • InfluxDB 在物联网设备数据采集与分析中的应用(二)
  • Apache Ignite超时管理核心组件解析
  • 元数据管理与数据治理平台:Apache Atlas 基本搜索 Basic Search
  • 强化学习常用数据集
  • linux 秒 安装谷歌浏览器 区分ubuntu和centos 给python爬取网站使用
  • 提升行车安全的关键技术:BSD(盲点监测)与DSM(驾驶员监测)是如何工作的?
  • 剧本杀小程序系统开发:推动行业数字化转型新动力
  • 【VS Code - Qt】如何基于Docker Linux配置Windows10下的VS Code,开发调试ARM 版的Qt应用程序?
  • AI模型服务接入WAF防火墙
  • 为什么Open WebUI可以不联网问问题,而直接使用Ollama可能需要联网
  • 虚幻GAS底层原理解剖十 (网络)
  • Linux操作系统从入门到实战(二十)进程优先级
  • 汉森(1982)提出的广义矩估计法
  • ResponseBodyAdvice是什么?
  • Agent用户体验设计:人机交互的最佳实践
  • Cobalt Strike的简单搭建与使用
  • ARM基础概念 day51
  • 环境配置-拉取NVIDIA Docker镜像时出现401Unauthorized错误
  • Redis 01 数据结构
  • 第7节 大模型性能评估与优化方法指南
  • Web 开发前端与后端 API 的交互
  • 101. 孤岛的总面积
  • 区块链技术原理(5)-网络
  • 《深度解构:React与Redux构建复杂表单的底层逻辑与实践》
  • (附源码)基于Spring Boot的4S店信息管理系统 的设计与实现
  • 虚拟财产刑事辩护:跨地域性与匿名性带来的挑战
  • 【C/C++】(struct test*)0->b 讲解
  • 从零开始的ReAct Agent尝试
  • HTTPS应用层协议-中间攻击人