1. I²C通信协议深度解析:从物理层到事务时序

I²C(Inter-Integrated Circuit)总线是一种由Philips(现NXP)开发的同步、半双工、多主多从串行通信协议,广泛应用于嵌入式系统中微控制器与外围器件(如EEPROM、RTC、传感器、音频编解码器等)之间的低速数据交换。其核心价值在于仅需两根信号线即可构建完整的通信网络,极大简化了PCB布线复杂度,并天然支持多设备挂载。在FPGA开发中,I²C常作为软核处理器(如MicroBlaze或Nios II)与外部存储器、配置芯片交互的标准接口;而在本实验所用的新起点V2开发板上,AT24C64 EEPROM正是通过I²C总线与FPGA内部逻辑进行数据读写。

理解I²C协议不能停留在“两根线”的表层认知,必须深入其电气特性、状态机定义与精确时序约束。任何驱动程序的可靠性,都直接取决于对这些底层规则的严格遵循。

1.1 物理层与电气特性

I²C总线由两条开漏(Open-Drain)或开集(Open-Collector)信号线构成:
- SCL(Serial Clock Line) :时钟信号线,由主机(Master)单向驱动。所有从机(Slave)均从此线上采样时钟边沿以同步数据采样。
- SDA(Serial Data Line) :双向数据线,既可由主机驱动发送数据,也可由从机驱动发送应答(ACK)或数据。该线的双向性是I²C协议的关键特征,也是其时序复杂性的根源。

由于SCL和SDA均为开漏结构,它们在空闲状态下必须通过外部上拉电阻(通常为1kΩ–10kΩ)被拉至高电平。这意味着:
- 任何节点只能将线“拉低”(输出低电平),而无法主动“推高”(输出高电平);
- 线上电平由所有连接节点的输出状态“线与”(Wired-AND)决定:只要有一个节点拉低,整条线即为低电平;
- 上拉电阻值的选择直接影响总线的上升时间(t r ),进而限制最高通信速率。过大的电阻导致上升过慢,易受噪声干扰;过小的电阻则增大功耗并可能超出驱动能力。

I²C定义了三种标准工作模式,其速率上限由从机器件的电气特性和总线电容共同决定:
- 标准模式(Standard Mode) :最高100 kbit/s;
- 快速模式(Fast Mode) :最高400 kbit/s;
- 高速模式(High-Speed Mode) :最高3.4 Mbit/s。

AT24C64数据手册明确指出,其在V CC = 5.0V时最大支持400 kbit/s,在V CC = 2.5V时则降至100 kbit/s。新起点V2开发板采用3.3V供电,因此实际工程中推荐使用250 kbit/s作为安全通信速率——这既远低于器件极限,又留有充足的裕量应对PCB走线电容和环境噪声。

1.2 通信状态机:起始、停止与重复起始

I²C协议通过SDA线在SCL高电平期间的跳变来定义关键的通信状态,这是整个协议的“心跳”与“语法”。所有I²C事务(Transaction)均由一个起始条件(START)开始,并以一个停止条件(STOP)结束。这些条件的产生与检测完全依赖于对SCL/SDA电平组合的精确控制。

  • 起始条件(START) :当SCL为高电平时,SDA由高电平向低电平跳变。此信号标志着一次新的I²C通信事务的开始,所有从机将此视为地址帧即将到来的提示。
  • 停止条件(STOP) :当SCL为高电平时,SDA由低电平向高电平跳变。此信号标志着当前事务的彻底结束,总线进入空闲状态,所有设备释放SDA线。
  • 重复起始条件(Repeated START) :在一次STOP之前,再次发出START信号。它允许主机在不释放总线控制权的情况下,无缝切换到另一个从机地址或改变读/写方向,是实现“读-修改-写”等复合操作的基础。

关键设计原则 :SDA线的状态变更(除START/STOP外) 必须且只能 发生在SCL为低电平期间。这是I²C协议的铁律。在SCL为高电平期间,SDA必须保持稳定,否则将被误判为非法的START或STOP信号,导致通信崩溃。这一规则直接决定了硬件设计(如上拉电阻选型)和软件驱动(如GPIO翻转时序)的实现方式。

1.3 数据传输与应答机制(ACK/NACK)

I²C的数据传输以字节(8位)为单位,每个字节后紧跟一个应答位(ACK Bit)。整个过程严格同步于SCL时钟:

  • 主机在SCL为低电平期间,将SDA置为所需数据位(MSB先行);
  • 主机在SCL上升沿采样SDA,完成一位数据的发送;
  • 经过8个时钟周期后,主机释放SDA线(将其设为输入或高阻态),进入第9个时钟周期(ACK周期);
  • 在此周期内, 从机 负责驱动SDA线:若从机成功接收并准备就绪,则在SCL高电平期间将SDA拉低(ACK = 0);若从机忙、地址不匹配或发生错误,则保持SDA为高电平(NACK = 1)。

应答机制是I²C协议健壮性的核心保障。它实现了:
- 地址确认 :主机发送7位从机地址+1位R/W后,从机必须ACK,否则主机可立即终止通信;
- 数据确认 :每次发送一个字节数据后,从机必须ACK,表明数据已被正确接收并可接受下一个字节;
- 流控基础 :从机可通过发送NACK来拒绝接收更多数据(例如EEPROM写入过程中内部擦写未完成)。

对于AT24C64而言,其ACK行为具有明确语义:在地址帧后ACK,表示该地址属于它;在数据帧后ACK,表示数据已存入其内部缓冲区,可接受下一次写入。

2. AT24C64器件特性与寻址机制

AT24C64是一款基于I²C接口的64Kbit(8192 × 8)串行EEPROM,其内部存储结构、寻址方式和操作模式是编写可靠驱动程序的前提。脱离器件特性谈协议,无异于纸上谈兵。

2.1 存储组织与地址空间

AT24C64的8192字节存储空间被划分为256页(Page),每页32字节。这种分页结构是其“页写”(Page Write)操作得以高效实现的物理基础。地址空间为13位(0x0000–0x1FFF),这意味着访问任意一个字节都需要提供完整的13位地址。

然而,I²C协议本身并不直接支持13位地址传输。AT24C64通过巧妙的设计,将13位地址拆分为两部分,并利用I²C的“字节流”特性进行传递:
- 高5位地址(A12–A8) :编码在7位从机地址(Slave Address)的最低三位(A2–A0)中;
- 低8位地址(A7–A0) :作为独立的字节,在地址帧之后连续发送。

这种设计使得AT24C64的从机地址不再是固定的,而是可配置的,从而允许多个同型号器件共挂于同一I²C总线。

2.2 从机地址(Slave Address)详解

AT24C64的7位从机地址格式为 1010 A2 A1 A0 R/W ,其中:
- 1010 是固定前缀,标识该器件为AT24系列EEPROM;
- A2 A1 A0 是三个可配置的地址引脚(A2, A1, A0),其电平状态(高/低)直接决定地址的低三位;
- R/W 是读/写位, 0 表示写操作, 1 表示读操作。

新起点V2开发板将AT24C64的A2、A1、A0全部接地(GND),因此其地址位为 000 。代入公式,得到其7位从机地址为 1010 000 ,即 0x50 (十六进制)。这是一个非常常见的默认地址,在绝大多数开发板上都能见到。

引脚状态 A2 A1 A0 7位从机地址 (二进制) 7位从机地址 (十六进制)
新起点V2 0 0 0 1010000 0x50
全部接VCC 1 1 1 1010111 0x57

重要警示 :多个AT24C64器件挂载在同一总线上时, A2/A1/A0 的组合必须互不相同。若地址重复,主机发出的地址帧将同时被多个从机响应,导致SDA线上出现“线与”冲突(一个拉低,一个释放),通信必然失败。这是初学者最常见的硬件连接错误之一。

2.3 写操作模式:字节写与页写

AT24C64支持两种写入模式,其本质区别在于主机在发送完第一个数据字节后,是立即发出STOP,还是继续发送后续数据。

  • 字节写(Byte Write)
  • 主机发送START → 7位地址 + W(0) → ACK → 2字节地址(高5位+低8位)→ ACK → 1字节数据 → ACK → STOP。
  • 此模式下,每次写入仅能更新一个字节,且写入后EEPROM需执行内部写周期(Write Cycle),典型时间为10ms。
  • 致命缺陷 :若主机在10ms内再次发起写操作,EEPROM尚未完成内部擦写,将返回NACK,导致写入失败。因此,字节写模式效率极低,仅适用于偶尔更新单个寄存器的场景。

  • 页写(Page Write)

  • 主机发送START → 7位地址 + W(0) → ACK → 2字节地址 → ACK → 连续发送最多32字节数据(每字节后均有ACK)→ STOP。
  • 页写的核心优势在于“地址自动递增”。当主机发送第一个数据字节后,EEPROM内部地址计数器自动加1;发送第二个字节,地址再加1,依此类推。
  • 关键边界 :地址递增仅在当前页内有效。一页为32字节(地址0–31),当地址从 0x001F (31)递增至 0x0020 (32)时,地址高位会回绕,重新指向本页起始地址 0x0000 。这意味着,若试图向地址 0x001E 写入3个字节,数据将依次写入 0x001E 0x001F 0x0000 ,覆盖原有数据。

页写模式将写入效率提升了32倍,是批量数据存储的首选。但其使用前提是:待写入的数据必须位于同一物理页内,或开发者能主动管理地址回绕。

3. I²C时序参数与硬件约束

I²C协议的可靠性建立在对一系列严格时序参数的遵守之上。这些参数并非凭空设定,而是由物理层的电气特性(如总线电容、驱动能力)和逻辑层的采样需求共同决定。任何驱动程序若忽略这些约束,都将面临间歇性通信失败的风险。

3.1 核心时序参数解析

AT24C64数据手册(如DS24464)中定义了关键的时序参数,它们构成了驱动程序时序生成的“黄金法则”。以下是针对400 kbit/s快速模式下的核心参数(单位:ns):

参数符号 名称 最小值 最大值 工程意义
t LOW SCL低电平时间 1300 保证从机有足够时间处理数据;过短会导致从机无法采样。
t HIGH SCL高电平时间 600 保证从机有足够时间准备下一次采样;过短会导致数据不稳定。
t SU;STA START建立时间 600 SDA在SCL为高前必须稳定为低的时间;不足将导致START无效。
t HD;STA START保持时间 600 START后SCL变低前,SDA必须保持低的时间;不足将导致START被忽略。
t SU;DAT 数据建立时间 100 SDA在SCL上升沿前必须稳定的最小时间;是数据可靠性的基石。
t HD;DAT 数据保持时间 0 SCL下降沿后,SDA必须保持稳定的最小时间;确保从机采样后数据不被篡改。
t r SDA上升时间 300 由上拉电阻和总线电容决定;过长易受噪声干扰,是速率瓶颈。
t f SDA下降时间 300 由驱动能力决定;过长同样影响速率。

工程实践要点
- 手册中的“最大值”(如t r , t f )是硬件设计的硬性约束,必须通过合理选择上拉电阻和优化PCB布局来满足。
- “最小值”(如t LOW , t HIGH )是软件驱动的硬性约束,必须在代码中通过精确延时(如循环或定时器)来保证。
- 对于FPGA实现,这些时序参数直接转化为状态机中的计数器阈值。例如,在400 kbit/s下,SCL周期为2500ns,若t LOW 要求≥1300ns,则低电平计数器至少需计数1300/时钟周期。

3.2 时序图深度解读:以字节写为例

下图展示了AT24C64字节写操作的完整时序流程。理解此图是掌握I²C编程的灵魂。

SCL:  ___     ___     ___     ___     ___     ___     ___     ___     ___
      | |     | |     | |     | |     | |     | |     | |     | |     | |
      | |_____| |_____| |_____| |_____| |_____| |_____| |_____| |_____| |
      ↑       ↑       ↑       ↑       ↑       ↑       ↑       ↑       ↑
      0       1       2       3       4       5       6       7       8 (ACK)

SDA:  _______         _       _       _       _       _       _       _       _
      |       \       / \     / \     / \     / \     / \     / \     / \     /
      |        \_____/   \___/   \___/   \___/   \___/   \___/   \___/   \___/
      ↑         ↑         ↑       ↑       ↑       ↑       ↑       ↑       ↑
    START     SLA+W     A12-A8  A7-A0   DATA    ACK     ...     ...    STOP
  1. START :SCL为高时,SDA由高变低。
  2. SLA+W :7位地址+写位(0),共8位,MSB先行。主机在SCL低电平时置位,在SCL高电平时采样。
  3. A12-A8 & A7-A0 :两个字节的内存地址。注意,第一个字节包含高5位地址(A12–A8),第二个字节包含低8位地址(A7–A0)。两者共同构成13位地址。
  4. DATA :待写入的8位数据。
  5. ACK :主机释放SDA,从机拉低SDA作为应答。主机在SCL高电平时采样SDA,若为低则继续,若为高则报错。
  6. STOP :SCL为高时,SDA由低变高。

最易忽视的细节 :在发送 SLA+W A12-A8 A7-A0 DATA 这四个字节时,主机全程控制SDA线;唯独在第九个时钟周期(ACK周期),主机必须将SDA设为高阻态(输入),将总线控制权让渡给从机。这是软件实现中最常出错的地方——忘记释放SDA,导致从机无法拉低,通信永远卡死。

4. FPGA上的I²C软核驱动设计实践

在FPGA平台上实现I²C驱动,核心挑战在于如何将严格的时序要求映射为可综合、可验证的硬件逻辑。与MCU的软件延时不同,FPGA必须通过状态机与时钟分频精确控制每一个信号的跳变时刻。

4.1 状态机架构设计

一个健壮的I²C控制器应采用分层状态机(Hierarchical State Machine)设计,清晰分离协议层(Protocol Layer)与物理层(Physical Layer):

  • 顶层状态机(Master FSM) :管理整个事务生命周期,状态包括 IDLE , START_GEN , ADDR_SEND , ADDR_ACK_WAIT , DATA_SEND , DATA_ACK_WAIT , STOP_GEN , ERROR
  • 底层时序引擎(Timing Engine) :一个独立的、运行在高频时钟(如100MHz)下的模块,负责生成符合手册要求的SCL波形和SDA采样/驱动时序。它根据顶层状态机的指令,精确控制SCL的高低电平持续时间,并在SCL上升沿/下降沿的指定时刻对SDA进行采样或驱动。

这种分离设计的优势在于:协议逻辑清晰易懂,时序逻辑可独立验证,且便于支持不同速率(只需修改分频系数)。

4.2 关键模块实现要点

  • SCL时钟生成 :使用一个可配置的计数器,对系统时钟进行分频。例如,要生成400kHz SCL,若系统时钟为100MHz,则分频系数为100,000,000 / 400,000 = 250。计数器在0–124计数为低电平(t LOW ≥1300ns),在125–249计数为高电平(t HIGH ≥600ns)。
  • SDA双向控制 :使用三态缓冲器(Tri-state Buffer)。当 SDA_OE (Output Enable)为高时, SDA_OUT 驱动SDA线;为低时,SDA线由外部上拉电阻拉高, SDA_IN 可读取线上电平。在ACK周期,必须将 SDA_OE 置为低。
  • START/STOP检测 :在SCL为高电平时,持续监测SDA电平变化。若检测到SDA由高变低,则触发START事件;由低变高,则触发STOP事件。此功能用于从机模式,但在主机模式下也用于总线仲裁检测。

4.3 调试与验证经验

在FPGA上调试I²C,示波器是不可或缺的工具。我曾在一个项目中遇到一个诡异问题:EEPROM写入成功率只有50%。用逻辑分析仪抓取波形后发现,SCL的t HIGH 仅为400ns,远低于手册要求的600ns。原因是分频计数器的高电平计数阈值设置错误。这个案例深刻说明: 理论计算必须经过实测验证

另一个常见陷阱是“时钟域交叉”(Clock Domain Crossing)。如果I²C控制器的时钟域与主CPU(如MicroBlaze)的时钟域不同,那么命令寄存器(Command Register)和状态寄存器(Status Register)的读写必须加入两级触发器(Two-stage synchronizer)进行跨时钟域同步,否则会出现亚稳态(Metastability),导致命令丢失或状态误读。

5. AT24C64读写操作的完整流程

掌握了协议与器件特性后,最终目标是实现可靠的读写功能。下面以新起点V2开发板上的AT24C64(地址0x50)为例,详细展开字节写、页写和随机读的完整流程。

5.1 字节写(Byte Write)流程

假设要将数据 0xAA 写入地址 0x0123 (十进制291):

  1. 生成START :SCL=1, SDA由1→0。
  2. 发送从机地址 0x50 10100000 ),R/W位为0,即 0xA0 。发送8位,等待ACK。
  3. 发送高5位地址 :地址 0x0123 的二进制为 0000000100100011 ,高5位为 00000 (即 0x00 )。发送 0x00 ,等待ACK。
  4. 发送低8位地址 :低8位为 0100100011 的低8位,即 0x23 。发送 0x23 ,等待ACK。
  5. 发送数据 :发送 0xAA ,等待ACK。
  6. 生成STOP :SCL=1, SDA由0→1。

此时,AT24C64进入内部写周期,约10ms后方可进行下一次操作。

5.2 页写(Page Write)流程

假设要将32字节数据 {0x00, 0x01, ..., 0x1F} 写入地址 0x0100 开始的一页:

  1. 生成START
  2. 发送从机地址 0xA0 ,等待ACK。
  3. 发送地址 :高5位 0x01 00001 ),低8位 0x00 ,等待ACK。
  4. 连续发送32字节数据 :从 0x00 0x1F ,每发送一字节后均等待ACK。
  5. 生成STOP

在此过程中,AT24C64内部地址计数器从 0x0100 自动递增至 0x011F 。若发送33字节,第33字节将写入 0x0100 ,覆盖首字节。

5.3 随机读(Random Read)流程

随机读需先“定位”,再“读取”:

  1. 生成START
  2. 发送从机地址(写模式) 0xA0 ,等待ACK。此步骤仅用于发送地址,不写入数据。
  3. 发送目标地址 :高5位 0x01 ,低8位 0x23 ,等待ACK。
  4. 生成REPEATED START :SCL=1, SDA由1→0。
  5. 发送从机地址(读模式) 0x51 10100001 ),R/W位为1,即 0xA1 ,等待ACK。
  6. 读取数据 :主机在SCL高电平时采样SDA,共8次。在第8位数据采样后,主机不发送ACK,而是发送NACK(SDA保持高),然后生成STOP。

此流程的关键在于 REPEATED START ,它避免了总线释放,确保地址定位与数据读取的原子性。

在实际项目中,我曾因省略 REPEATED START 而踩坑:在发送完地址后直接发 0xA1 ,结果EEPROM将 0xA1 误认为是下一个地址字节,导致读取位置偏移。这个教训让我深刻体会到,I²C的每一个比特,都承载着不可替代的协议语义。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐