【STM32】 I2C阻塞通信实战与HAL源码(教程篇)


《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. 单次写入最大长度为1页(8字节)。
    2. 写入起始地址必须是页起始地址(地址低3位为0,即8的整数倍)。
    3. 超出页边界时,地址会回卷到本页开头,覆盖页首已有数据,不会自动翻到下一页。
  • 示例:从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. 辅助外设

  1. USART1串口
    • 作用:实现单片机与上位机(串口助手)的交互式通信,用于输入配置参数、输出运行日志。
    • 硬件:灵犀板板载USB转TTL芯片,无需外接模块。
  2. 状态LED
    • 4颗LED(PF8~PF11),分别指示通信状态、运行效果。
    • 正常通信、异常状态分别对应不同LED指示。

4. 实战功能目标

  1. 上位机通过串口与单片机交互,选择新建配置或读取已有配置。
  2. 配置参数包括:LED编号、闪烁次数、闪烁间隔、启动延迟时间。
  3. 配置参数通过I2C阻塞写入AT24C02,断电永久保存。
  4. 单片机重启后自动从EEPROM读取配置参数。
  5. 按配置执行点灯逻辑,串口输出对应日志(如"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:1分配会导致低电平时间不满足规范。
  2. SDA电平变化需要时间:SDA只能在SCL低电平期间变化,开漏输出+总线电容导致电平跳变需要时间,更长的低电平保证数据稳定建立。
  3. 上升沿速度慢:开漏输出高电平靠上拉电阻充电,上升沿慢;低电平是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
cpp 复制代码
typedef 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. 提示用户选择:1.重新配置参数 2.使用已有配置。
  2. 选择1:依次输入LED编号、闪烁次数、间隔、启动时间,显示配置清单,写入EEPROM保存。
  3. 选择2:从EEPROM读取配置到全局变量。
  4. 输入错误则提示重输。
App_Run 运行业务
  1. 校验魔数和版本号,有效则执行业务。
  2. 延时配置的启动时间。
  3. 按配置执行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。**
核心函数
  1. BSP_AT24C02_Init
    • 绑定I2C句柄,设置设备地址、页大小。
    • 调用HAL_I2C_IsDeviceReady检测设备是否在线,确认硬件连接正常。
  2. BSP_AT24C02_Read
    • 任意地址、任意长度读取。
    • 内部直接调用HAL_I2C_Mem_Read,自动处理时序。
    • 参数校验:地址+长度不超过总容量256字节。
  3. BSP_AT24C02_Write(自动跨页写入)
    • 支持任意地址、任意长度写入,自动拆分页边界。
    • 实现逻辑:
      1. 计算当前地址的页内偏移,得到当前页剩余空间。
      2. 剩余数据 ≤ 页剩余空间:一次性写入当前页。
      3. 剩余数据 > 页剩余空间:先写满当前页,再写下一页。
      4. 每次页写入后,调用等待就绪函数,等待内部写周期完成。
    • 解决了页写回卷问题,无需用户手动分页。
  4. 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 回调
  • 硬件底层初始化:
    1. 开启GPIOB时钟。
    2. 配置PB10/PB11为复用开漏输出、高速。
    3. 开启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)
  • 功能:安全修改寄存器的指定位,不影响其他位。
  • 执行步骤:
    1. 读取寄存器当前值。
    2. 和清除掩码的反码按位与,将目标位清零。
    3. 和设置掩码按位或,将目标位设置为新值。
    4. 写回寄存器。
  • 设计思想:"先清后设",避免其他位被误修改,是HAL操作寄存器的通用范式。
(3)最后使能外设

设置CR1寄存器的PE位,开启I2C外设。


3. HAL_I2C_IsDeviceReady 函数全解析

功能

检测目标I2C设备是否在线且可应答;用于设备扫描、EEPROM写入后轮询等待内部写周期。

三重就绪判断逻辑
  1. 外设自身状态检查
    • 判断句柄State是否为HAL_I2C_STATE_READY
    • 作用:防止重入,避免多线程/中断中重复操作。
  2. 总线空闲检查
    • 等待SR2寄存器的BUSY标志清零。
    • 作用:确保总线没有被其他通信占用。
  3. 设备应答检测
    • 置位CR1的START位,发起起始信号。
    • 等待SB(起始完成)标志。
    • 写入设备写地址到DR寄存器。
    • 循环等待ADDR(地址匹配/发送完成)标志或AF(应答失败)标志。
    • 检测到ADDR:设备在线,应答成功。
    • 检测到AF或超时:本次检测失败。
重试机制
  • 支持指定重试次数Trials,失败后自动重试。
  • 多次尝试只要有一次成功,就返回HAL_OK。
收尾流程
  • 发送停止信号。
  • 等待BUSY标志清零,总线回到空闲。
  • 恢复句柄状态为READY,解锁。

4. HAL_I2C_Mem_Write 函数全解析

功能

实现完整的存储型外设写入时序:

起始 → 设备写地址 → 内部存储器地址 → 数据 → 停止。

原生适配EEPROM、寄存器型外设,无需手动拼接时序。

完整执行流程
  1. 前置检查:参数校验、状态检查、等待总线空闲。
  2. 加锁:__HAL_LOCK防止重入。
  3. 状态设置:设置为BUSY_TX发送忙状态,模式为MEM内存模式。
  4. 发起内存写请求(I2C_RequestMemoryWrite)
    • 置位START,发起起始信号。
    • 等待SB标志,起始完成。
    • 写入设备写地址,等待ADDR标志。
    • 写入内部存储器地址(8位地址写1字节,16位写2字节)。
  5. 循环发送数据
    • 等待TXE(发送数据寄存器空)标志。
    • 向DR寄存器写入1字节数据,指针后移、计数递减。
    • 双缓冲优化:检测到BTF(字节传输完成)且还有数据时,提前写入下一字节,让DR和移位寄存器流水线工作,提升效率。
  6. 等待传输完成:等待BTF标志,确认最后一个字节发送完毕。
  7. 错误处理:检测到AF(应答失败),立即发送停止信号,返回错误。
  8. 收尾:置位STOP发送停止信号,恢复状态为READY,解锁。

5. HAL_I2C_Mem_Read 函数全解析

功能

实现完整的随机读时序:

起始 → 设备写地址 → 内部地址 → 重复起始 → 设备读地址 → 接收数据 → NACK → 停止。 这是I2C最复杂的常用函数,核心难点是最后一个字节回复NACK的时序处理。

核心难点

I2C硬件收到字节后会自动回复ACK,且动作有时序延迟。

如果等收到最后一个字节再关闭ACK,时序上来不及,必须提前配置。

完整执行流程
  1. 前置准备:参数校验、状态检查、加锁、开启ACK自动应答。
  2. 发起内存读请求(I2C_RequestMemoryRead)
    • 发起起始,发送设备写地址。
    • 发送内部存储器地址。
    • 发送重复起始信号(ReSTART),不释放总线,切换读写方向。
    • 发送设备读地址,等待应答。
  3. 分长度提前配置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"。
  1. 循环接收数据
    • 等待RXNE(接收数据寄存器非空)或BTF标志。
    • 读取DR寄存器数据,存入用户缓冲区,指针后移、计数递减。
    • 双缓冲机制:BTF置位表示DR和移位寄存器都有数据,可连续读取,提升吞吐量。
  2. 收尾:确认停止完成,清除标志位,恢复状态为READY,解锁。

6. 通用机制

标志位编码技巧

HAL库所有标志位采用统一编码:

  • 高16位:标记所属寄存器(0x0001=SR1,0x0010=SR2)。
  • 低16位:标记寄存器内的位偏移。
  • 通过__HAL_I2C_GET_FLAG宏统一判断,自动选择对应寄存器,屏蔽差异。
状态机机制
  • 句柄State字段标记外设当前状态(就绪、发送忙、接收忙、超时、错误)。
  • 操作前检查状态,避免重入、冲突。
  • 操作完成后恢复就绪状态。
超时机制
  • 所有阻塞等待都配有超时检测,基于SysTick计时。
  • 超时后返回错误,避免程序卡死。
锁机制
  • __HAL_LOCK/__HAL_UNLOCK保护临界区。
  • 防止中断、多线程中同时操作同一个I2C外设。

六、核心工程结论与常见坑点

  1. **函数选型:**操作EEPROM等带内部地址的存储外设,优先使用HAL_I2C_Mem_Read/Write,自动处理内部地址和重复起始,比Master_Transmit/Receive更便捷。
  2. EEPROM写入禁忌
    • 写完不能立刻再写,必须延时5ms或轮询IsDeviceReady等待内部写周期完成。
    • 页写不能跨页,否则地址回卷覆盖数据;工程中必须做自动分页处理。
  3. 硬件规范
    • I2C引脚必须配置开漏输出,配合外部上拉电阻,绝对不能用推挽。
    • 快速模式必须配置对应占空比,否则时序不满足规范,通信不稳定。
  4. **地址注意:**调用HAL库函数时,设备地址必须是左移1位后的8位格式(包含读写位)。
  5. **读取NACK:**最后一个字节必须回复NACK通知从机停止发送;HAL库已提前处理,自定义实现时需注意时序提前量。
  6. **波特率精度:**尽量使用外部晶振,避免内部RC的温漂导致波特率偏差,出现乱码、应答失败。

相关推荐
Brilliantwxx3 小时前
【STM32】 I2C_EEPROM_test 工程(实战篇)
开发语言·stm32·单片机·嵌入式硬件·ecmascript
youcans_4 小时前
【嵌入式软件AI编程】09. 使用VS Code调试STM32程序
stm32·mcu·ai编程·嵌入式软件·claude code
开发笔记-阿牛4 小时前
做工业报警器语音提示,CK6159A 为什么更合适?
人工智能·stm32·单片机·嵌入式硬件·音频
恶魔泡泡糖5 小时前
stm32F103C8T6标准库串口接收轮询方式1
stm32·单片机·嵌入式硬件
糖糖单片机设计7 小时前
基于STM32的远程宠物自动投喂系统
人工智能·stm32·51单片机·语音识别·智能硬件
单片机仿真设计9 小时前
【proteus仿真】基于 STM32 单片机农业灾害监测系统(仿真图+程序)
stm32·单片机·嵌入式硬件·proteus
zhangzhangkeji10 小时前
stm32,3-3 :GPIO 输入
stm32
Zw-awa10 小时前
嵌入式里面互相嵌套的结构体
stm32
佳児素花痴╮12 小时前
STM32(中断)
stm32·单片机·嵌入式硬件