文章目录
一、前言
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
/**
* @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
/**
* @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
/**
* @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 软件测试
测试项目:
- 单字节读写
- 页写入(8字节)
- 跨页写入
- 连续读写
- 写入寿命测试
六、故障排查
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 无ACK | 地址错误 | 检查A0A1A2引脚配置 |
| 数据错误 | 时序问题 | 降低I2C速度 |
| 写入失败 | 写保护 | 检查WP引脚 |
| 读取全FF | 未写入 | 先执行写入操作 |
七、总结
7.1 核心知识点
- I2C通信协议和时序
- AT24Cxx存储结构和寻址
- 分页写入和跨页处理
- STM32 I2C外设配置
7.2 应用场景
- 系统参数存储
- 用户配置保存
- 校准数据存储
- 运行日志记录
2155

被折叠的 条评论
为什么被折叠?



