《I2C阻塞通信实战与HAL源码》
一、AT24C02 EEPROM 基础详解
1. EEPROM 技术定位
EEPROM(Electrically Erasable Programmable Read-Only Memory,电可擦除可编程只读存储器),是一种支持字节级修改、断电数据不丢失的非易失性存储器。
- 核心优势:支持按字节擦写,修改粒度远小于大容量Flash,适合存储需要频繁小量更新、又需长期保存的配置数据、校准参数、设备日志。
- 与Flash的区别:Flash按扇区/页擦写,适合大容量程序存储;EEPROM按字节读写,适合小容量参数存储。
- 应用场景:存储MAC地址、传感器校准值、设备配置参数、断电记忆参数。
2. 型号命名体系与厂商生态
命名规则(以AT24C02为例)
字段 含义 说明 AT 厂商前缀 原Atmel(爱特梅尔,2016年被Microchip微芯科技收购),行业通用代名词 24 接口系列 代表I2C接口协议;同系列25为SPI接口,93为Microwire接口 C 工艺标识 低功耗工艺,支持宽电压1.7V~5.5V,适配3.3V STM32系统 02 容量标识 单位为bit,代表2Kbit总容量 厂商与国产替代
- 原厂:Microchip(原Atmel)、ST(M24系列)。
- 国产替代:国内厂商如英锐芯(IDCHIP)等生产兼容型号,通常去掉AT前缀,直接命名为24C02,软硬件完全兼容,成本更低。文档中灵犀开发板使用的就是国产24C02芯片。
3. AT24C02 核心参数与存储组织
参数项 具体规格 总容量 2K bit = 256 字节(2048bit ÷ 8) 存储组织 32页 × 8字节/页,共256字节 接口协议 I2C,兼容标准模式/快速模式 最高时钟 5V下1MHz;2.5V下400kHz 内部写周期 最大5ms(字节/页写入后,内部编程时间) 页写能力 单页最多8字节,页内地址自动递增 读取方式 随机读、顺序读,地址自动递增 擦写寿命 约100万次 数据保持 100年 封装 SOP-8、DIP-8、SOT23-5 存储地址映射
可以理解为一个**
uint8_t array[256]的一维数组****,也可理解为uint8_t array[32][8]的二维页结构:**
- 页号031,页内偏移07。
- 地址范围:0x00 ~ 0xFF。
- 顺序读取时地址指针自动递增,到0xFF后停止,不会回卷。
4. 引脚功能与硬件配置
引脚定义(SOP-8封装)
引脚号 引脚名 功能 1 A0 地址配置位0 2 A1 地址配置位1 3 A2 地址配置位2 4 GND 地 5 SDA I2C串行数据 6 SCL I2C串行时钟 7 WP 写保护 8 VCC 电源 设备地址配置
- 7位从机地址格式:
0 1 0 1 A2 A1 A0(二进制),即高4位固定为0101,低3位由A2/A1/A0引脚决定。- 灵犀板A0/A1/A2全部接地,因此7位地址为
0101000,即0x50。- 地址引脚作用:同一总线上可通过不同引脚配置挂载多片24C02,避免地址冲突。
写保护WP引脚
- WP接GND(低电平):允许正常读写操作。
- WP接VCC(高电平):整个存储器硬件写保护,禁止所有写入操作,仅可读取。
- 用途:防止误写入、保护出厂配置参数。
5. 读写特性与核心限制
读操作特性
- 支持随机地址单字节读、连续多字节顺序读。
- 读取时内部地址指针自动递增,可连续读取直到地址末尾。
- 读取无等待,数据实时输出,无需内部编程时间。
写操作特性
- 支持字节写、页写两种模式。
- 页写限制:
- 单次写入最大长度为1页(8字节)。
- 写入起始地址必须是页起始地址(地址低3位为0,即8的整数倍)。
- 超出页边界时,地址会回卷到本页开头,覆盖页首已有数据,不会自动翻到下一页。
- 示例:从0x06开始写入4字节,会写入0x06、0x07,然后回卷到0x00、0x01,覆盖原0x00、0x01的数据。
- 内部写周期:写入指令结束后,芯片需要最长5ms的内部编程时间,将数据写入非易失存储单元;期间芯片不响应任何I2C命令。
二、系统硬件设计与实战目标
1. 硬件架构与器件选型
- 主控:STM32F103ZET6(Cortex-M3内核)。
- 从机:AT24C02 EEPROM,作为外部配置存储设备。
- I2C通道:选择MCU的I2C2外设作为主机。
- 通信级别:阻塞式轮询模式,无中断、无DMA。
2. I2C总线连接细节
- 引脚映射:I2C2_SCL → PB10,I2C2_SDA → PB11。
- 上拉电阻:SCL、SDA分别串联10kΩ上拉电阻接3.3V。
- 共地要求:MCU与EEPROM共地,保证电平参考一致。
- 地址设置:EEPROM的A0/A1/A2全部接地,固定设备地址0x50。
3. 辅助外设
- USART1串口
- 作用:实现单片机与上位机(串口助手)的交互式通信,用于输入配置参数、输出运行日志。
- 硬件:灵犀板板载USB转TTL芯片,无需外接模块。
- 状态LED
- 4颗LED(PF8~PF11),分别指示通信状态、运行效果。
- 正常通信、异常状态分别对应不同LED指示。
4. 实战功能目标
- 上位机通过串口与单片机交互,选择新建配置或读取已有配置。
- 配置参数包括:LED编号、闪烁次数、闪烁间隔、启动延迟时间。
- 配置参数通过I2C阻塞写入AT24C02,断电永久保存。
- 单片机重启后自动从EEPROM读取配置参数。
- 按配置执行点灯逻辑,串口输出对应日志(如"LEDx被点亮")。
三、STM32CubeMX 工程配置详解
1. 系统基础配置
调试接口
- 配置为Serial Wire(SWD串行调试模式)。
- 注意:错误禁用调试接口会导致烧录后无法连接调试器。
时钟配置
- 时钟源:选择外部晶振(Crystal/Ceramic Resonator),即HSE。
- 晶振参数:开发板载8MHz外部HSE晶振,系统主频配置为72MHz。
- 选型原因:
- 内部RC时钟(HSI)受温度影响大,温漂会导致I2C波特率偏差,通信不稳定。
- 外部晶振精度高、温漂小,适合I2C、USART等对时钟精度要求高的外设。
2. I2C2 外设核心配置
配置项 配置值 原理说明 模式 I2C快速模式 AT24C02支持400kHz快速模式,在可靠前提下选择最高速率 时钟速度 400000Hz(400kHz) 快速模式标准速率 占空比 Duty cycle Tlow/Thigh = 2 低电平/高电平时间比为2:1,符合快速模式协议时序要求 地址模式 7位地址模式 与AT24C02的7位地址匹配 自身地址 0 主机模式下从机地址无效,设为0即可 双地址模式 禁用 不需要第二个从机地址 广播呼叫 禁用 不响应通用广播地址 时钟拉伸禁用 禁用 允许从机时钟拉伸,符合标准I2C协议 引脚自动配置
- 使能I2C2后,CubeMX自动将PB10、PB11配置为Alternate Function Open Drain(复用开漏输出)。
- 模式解释:
- 复用功能:引脚控制权从普通GPIO转交给I2C外设。
- 开漏输出:引脚内部只有NMOS下拉管,只能主动拉低,高电平靠外部上拉电阻,符合I2C总线规范。
3. 占空比的协议级深度原理
定义
I2C领域的占空比 = SCL低电平持续时间 / SCL高电平持续时间**,区别于常规的"有效电平/总周期"定义。**
为什么快速模式需要非1:1占空比
根据NXP(原飞利浦半导体,I2C协议制定方)官方规范《UM10204 I2C-bus规范与用户手册》:
- 标准模式(100kHz):低电平最小4.7μs,高电平最小4.0μs,接近1:1,可默认配置。
- 快速模式(400kHz):低电平最小1.3μs,高电平最小0.6μs,低电平要求更长。
2:1占空比的必要性
- 协议时序要求:快速模式下低电平最低要求高于高电平,1:1分配会导致低电平时间不满足规范。
- SDA电平变化需要时间:SDA只能在SCL低电平期间变化,开漏输出+总线电容导致电平跳变需要时间,更长的低电平保证数据稳定建立。
- 上升沿速度慢:开漏输出高电平靠上拉电阻充电,上升沿慢;低电平是NMOS主动下拉,速度快。整体低电平需要预留更多时间。
4. 其他外设配置
- USART1:异步模式,波特率115200,8位数据位、奇校验、1位停止位。
- LED:PF8~PF11共4颗,配置为开漏输出模式,低电平点亮。
四、应用层代码架构与实现
1. 代码分层设计
层级 文件 功能 应用层 App.h / App.c 业务逻辑、用户交互、点灯控制 板级支持包 BSP_AT24C02.h/c EEPROM驱动封装,隔离HAL接口 BSP_LED.h/c LED驱动 BSP_Usart_Redir.h/c 串口printf重定向 HAL驱动层 stm32f1xx_hal_i2c.c ST官方I2C驱动库
2. 配置结构体与宏定义
配置结构体 config_t
cpptypedef struct _config_t { uint8_t magic; // 魔数,用于校验配置有效性 uint8_t version; // 配置版本号 uint8_t led_id; // 选择的LED编号 uint8_t repeat_cnt; // 闪烁次数 uint16_t delay_ms; // 闪烁间隔(ms) uint16_t start_time; // 启动延时(ms) }config_t;
- 魔数**
APP_MAGIC 0x35、版本号APP_VERSION 0x01:用于判断EEPROM中存储的配置是否有效、是否为对应版本,避免上电读取乱码。**地址与操作宏
#define CONFIG_ADDRESS 0x00 // 配置存储在EEPROM的0地址 #define SAVE_CONFIG(addr, len) BSP_AT24C02_Write(CONFIG_ADDRESS, addr, len) #define LOAD_CONFIG(addr, len) BSP_AT24C02_Read(CONFIG_ADDRESS, addr, len)
3. 主业务流程
App_Init 初始化交互
- 提示用户选择:1.重新配置参数 2.使用已有配置。
- 选择1:依次输入LED编号、闪烁次数、间隔、启动时间,显示配置清单,写入EEPROM保存。
- 选择2:从EEPROM读取配置到全局变量。
- 输入错误则提示重输。
App_Run 运行业务
- 校验魔数和版本号,有效则执行业务。
- 延时配置的启动时间。
- 按配置执行LED闪烁,串口输出对应日志。
4. BSP层 AT24C02 驱动封装
设备结构体 eeprom_t
typedef struct { I2C_HandleTypeDef *hi2c; // I2C句柄指针 uint16_t dev_address; // 从机地址(左移1位后) uint8_t page_size; // 页大小(8字节) bool is_init; // 初始化标记 }eeprom_t;
- 注意:传入HAL库的设备地址必须左移1位(7位地址+读写位),因此初始化时**
dev_address = DEV_ADDRESS << 1。**核心函数
- BSP_AT24C02_Init
- 绑定I2C句柄,设置设备地址、页大小。
- 调用
HAL_I2C_IsDeviceReady检测设备是否在线,确认硬件连接正常。- BSP_AT24C02_Read
- 任意地址、任意长度读取。
- 内部直接调用
HAL_I2C_Mem_Read,自动处理时序。- 参数校验:地址+长度不超过总容量256字节。
- BSP_AT24C02_Write(自动跨页写入)
- 支持任意地址、任意长度写入,自动拆分页边界。
- 实现逻辑:
- 计算当前地址的页内偏移,得到当前页剩余空间。
- 剩余数据 ≤ 页剩余空间:一次性写入当前页。
- 剩余数据 > 页剩余空间:先写满当前页,再写下一页。
- 每次页写入后,调用等待就绪函数,等待内部写周期完成。
- 解决了页写回卷问题,无需用户手动分页。
- BSP_AT24C02_Ready(内部)
- 多次调用
HAL_I2C_IsDeviceReady轮询,等待EEPROM内部写周期完成。- 替代简单延时,提高效率。
五、HAL库I2C阻塞模式 源码深度解析
这是文档的核心部分,从寄存器级逐行拆解HAL库的实现逻辑。
1. 核心数据结构
(1)I2C_TypeDef 寄存器结构体
- 本质:I2C外设寄存器的内存映射,结构体成员顺序与硬件寄存器地址一一对应。
- 包含:CR1、CR2、OAR1、OAR2、DR、SR1、SR2、CCR、TRISE共9个寄存器。
- 基地址:I2C2_BASE = 0x40005800,指针直接指向硬件寄存器,可通过结构体直接操作硬件。
(2)I2C_HandleTypeDef 句柄结构体
HAL库的核心控制块,包含:
Instance:指向I2C寄存器基地址。Init:初始化参数结构体。- 缓冲区指针、传输计数器、传输选项。
State:通信状态机,标记当前外设状态(就绪、发送忙、接收忙、错误等)。Mode:通信模式(无、主机、从机、内存模式)。- 错误码、DMA句柄、锁对象等。
2. 初始化流程源码拆解
(1)HAL_I2C_MspInit 回调
- 硬件底层初始化:
- 开启GPIOB时钟。
- 配置PB10/PB11为复用开漏输出、高速。
- 开启APB1总线上的I2C2时钟。
- 对应RCC知识点:I2C2挂载在APB1总线,必须先开启外设时钟才能操作寄存器。
(2)寄存器配置
通过
MODIFY_REG宏依次配置CR2、TRISE、CCR、CR1、OAR等寄存器。MODIFY_REG 宏深度解析
#define MODIFY_REG(REG, CLEARMASK, SETMASK) \ WRITE_REG(REG, (READ_REG(REG) & (~CLEARMASK)) | SETMASK)
- 功能:安全修改寄存器的指定位,不影响其他位。
- 执行步骤:
- 读取寄存器当前值。
- 和清除掩码的反码按位与,将目标位清零。
- 和设置掩码按位或,将目标位设置为新值。
- 写回寄存器。
- 设计思想:"先清后设",避免其他位被误修改,是HAL操作寄存器的通用范式。
(3)最后使能外设
设置CR1寄存器的
PE位,开启I2C外设。
3. HAL_I2C_IsDeviceReady 函数全解析
功能
检测目标I2C设备是否在线且可应答;用于设备扫描、EEPROM写入后轮询等待内部写周期。
三重就绪判断逻辑
- 外设自身状态检查
- 判断句柄
State是否为HAL_I2C_STATE_READY。- 作用:防止重入,避免多线程/中断中重复操作。
- 总线空闲检查
- 等待SR2寄存器的
BUSY标志清零。- 作用:确保总线没有被其他通信占用。
- 设备应答检测
- 置位CR1的
START位,发起起始信号。- 等待SB(起始完成)标志。
- 写入设备写地址到DR寄存器。
- 循环等待ADDR(地址匹配/发送完成)标志或AF(应答失败)标志。
- 检测到ADDR:设备在线,应答成功。
- 检测到AF或超时:本次检测失败。
重试机制
- 支持指定重试次数
Trials,失败后自动重试。- 多次尝试只要有一次成功,就返回HAL_OK。
收尾流程
- 发送停止信号。
- 等待BUSY标志清零,总线回到空闲。
- 恢复句柄状态为READY,解锁。
4. HAL_I2C_Mem_Write 函数全解析
功能
实现完整的存储型外设写入时序:
起始 → 设备写地址 → 内部存储器地址 → 数据 → 停止。
原生适配EEPROM、寄存器型外设,无需手动拼接时序。
完整执行流程
- 前置检查:参数校验、状态检查、等待总线空闲。
- 加锁:
__HAL_LOCK防止重入。- 状态设置:设置为
BUSY_TX发送忙状态,模式为MEM内存模式。- 发起内存写请求(I2C_RequestMemoryWrite)
- 置位START,发起起始信号。
- 等待SB标志,起始完成。
- 写入设备写地址,等待ADDR标志。
- 写入内部存储器地址(8位地址写1字节,16位写2字节)。
- 循环发送数据
- 等待
TXE(发送数据寄存器空)标志。- 向DR寄存器写入1字节数据,指针后移、计数递减。
- 双缓冲优化:检测到
BTF(字节传输完成)且还有数据时,提前写入下一字节,让DR和移位寄存器流水线工作,提升效率。- 等待传输完成:等待BTF标志,确认最后一个字节发送完毕。
- 错误处理:检测到AF(应答失败),立即发送停止信号,返回错误。
- 收尾:置位STOP发送停止信号,恢复状态为READY,解锁。
5. HAL_I2C_Mem_Read 函数全解析
功能
实现完整的随机读时序:
起始 → 设备写地址 → 内部地址 → 重复起始 → 设备读地址 → 接收数据 → NACK → 停止。 这是I2C最复杂的常用函数,核心难点是最后一个字节回复NACK的时序处理。
核心难点
I2C硬件收到字节后会自动回复ACK,且动作有时序延迟。
如果等收到最后一个字节再关闭ACK,时序上来不及,必须提前配置。
完整执行流程
- 前置准备:参数校验、状态检查、加锁、开启ACK自动应答。
- 发起内存读请求(I2C_RequestMemoryRead)
- 发起起始,发送设备写地址。
- 发送内部存储器地址。
- 发送重复起始信号(ReSTART),不释放总线,切换读写方向。
- 发送设备读地址,等待应答。
- 分长度提前配置NACK(HAL最核心设计) 根据剩余接收字节数,提前配置应答和停止,确保最后一字节回复NACK:
- 0字节:直接发送停止信号。
- 1字节:提前关闭ACK、提前设置STOP位;收完数据后硬件自动回复NACK并产生停止。
- 2字节:设置
POS位 + 关闭ACK;实现第1字节回复ACK、第2字节回复NACK。- 3字节及以上:默认开启ACK;剩余最后3字节时,再逐步配置关闭ACK。
POS位 深度解析
- 定义:应答位置控制位,控制ACK/NACK作用于当前移位寄存器的字节,还是下一个字节。
- 作用:专门解决2字节接收的时序问题。
- 配置:POS=1 + ACK=0 → 当前字节回复ACK,下一个字节回复NACK。
- 原因:单靠关闭ACK,无法在1个字节内切换应答状态;POS位实现了"跨字节预约NACK"。
- 循环接收数据
- 等待
RXNE(接收数据寄存器非空)或BTF标志。- 读取DR寄存器数据,存入用户缓冲区,指针后移、计数递减。
- 双缓冲机制:BTF置位表示DR和移位寄存器都有数据,可连续读取,提升吞吐量。
- 收尾:确认停止完成,清除标志位,恢复状态为READY,解锁。
6. 通用机制
标志位编码技巧
HAL库所有标志位采用统一编码:
- 高16位:标记所属寄存器(0x0001=SR1,0x0010=SR2)。
- 低16位:标记寄存器内的位偏移。
- 通过
__HAL_I2C_GET_FLAG宏统一判断,自动选择对应寄存器,屏蔽差异。状态机机制
- 句柄
State字段标记外设当前状态(就绪、发送忙、接收忙、超时、错误)。- 操作前检查状态,避免重入、冲突。
- 操作完成后恢复就绪状态。
超时机制
- 所有阻塞等待都配有超时检测,基于SysTick计时。
- 超时后返回错误,避免程序卡死。
锁机制
__HAL_LOCK/__HAL_UNLOCK保护临界区。- 防止中断、多线程中同时操作同一个I2C外设。
六、核心工程结论与常见坑点
- **函数选型:**操作EEPROM等带内部地址的存储外设,优先使用
HAL_I2C_Mem_Read/Write,自动处理内部地址和重复起始,比Master_Transmit/Receive更便捷。- EEPROM写入禁忌
- 写完不能立刻再写,必须延时5ms或轮询
IsDeviceReady等待内部写周期完成。- 页写不能跨页,否则地址回卷覆盖数据;工程中必须做自动分页处理。
- 硬件规范
- I2C引脚必须配置开漏输出,配合外部上拉电阻,绝对不能用推挽。
- 快速模式必须配置对应占空比,否则时序不满足规范,通信不稳定。
- **地址注意:**调用HAL库函数时,设备地址必须是左移1位后的8位格式(包含读写位)。
- **读取NACK:**最后一个字节必须回复NACK通知从机停止发送;HAL库已提前处理,自定义实现时需注意时序提前量。
- **波特率精度:**尽量使用外部晶振,避免内部RC的温漂导致波特率偏差,出现乱码、应答失败。




