I²C协议深度解析与AT24C64 FPGA驱动实践
I²C(Inter-Integrated Circuit)是一种广泛应用的同步、半双工串行通信协议,以仅需SCL和SDA两根线、支持多主多从和内置ACK应答机制为技术特征。其工作原理依赖开漏结构、上拉电阻、严格时序约束(如t_LOW、t_SU;STA)及START/STOP状态机控制,决定了硬件设计与驱动开发的底层逻辑。该协议在嵌入式系统中承担微控制器与EEPROM、传感器等外围器件的数据交互任务
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
- START :SCL为高时,SDA由高变低。
- SLA+W :7位地址+写位(0),共8位,MSB先行。主机在SCL低电平时置位,在SCL高电平时采样。
- A12-A8 & A7-A0 :两个字节的内存地址。注意,第一个字节包含高5位地址(A12–A8),第二个字节包含低8位地址(A7–A0)。两者共同构成13位地址。
- DATA :待写入的8位数据。
- ACK :主机释放SDA,从机拉低SDA作为应答。主机在SCL高电平时采样SDA,若为低则继续,若为高则报错。
- 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):
- 生成START :SCL=1, SDA由1→0。
- 发送从机地址 :
0x50(10100000),R/W位为0,即0xA0。发送8位,等待ACK。 - 发送高5位地址 :地址
0x0123的二进制为0000000100100011,高5位为00000(即0x00)。发送0x00,等待ACK。 - 发送低8位地址 :低8位为
0100100011的低8位,即0x23。发送0x23,等待ACK。 - 发送数据 :发送
0xAA,等待ACK。 - 生成STOP :SCL=1, SDA由0→1。
此时,AT24C64进入内部写周期,约10ms后方可进行下一次操作。
5.2 页写(Page Write)流程
假设要将32字节数据 {0x00, 0x01, ..., 0x1F} 写入地址 0x0100 开始的一页:
- 生成START 。
- 发送从机地址 :
0xA0,等待ACK。 - 发送地址 :高5位
0x01(00001),低8位0x00,等待ACK。 - 连续发送32字节数据 :从
0x00到0x1F,每发送一字节后均等待ACK。 - 生成STOP 。
在此过程中,AT24C64内部地址计数器从 0x0100 自动递增至 0x011F 。若发送33字节,第33字节将写入 0x0100 ,覆盖首字节。
5.3 随机读(Random Read)流程
随机读需先“定位”,再“读取”:
- 生成START 。
- 发送从机地址(写模式) :
0xA0,等待ACK。此步骤仅用于发送地址,不写入数据。 - 发送目标地址 :高5位
0x01,低8位0x23,等待ACK。 - 生成REPEATED START :SCL=1, SDA由1→0。
- 发送从机地址(读模式) :
0x51(10100001),R/W位为1,即0xA1,等待ACK。 - 读取数据 :主机在SCL高电平时采样SDA,共8次。在第8位数据采样后,主机不发送ACK,而是发送NACK(SDA保持高),然后生成STOP。
此流程的关键在于 REPEATED START ,它避免了总线释放,确保地址定位与数据读取的原子性。
在实际项目中,我曾因省略 REPEATED START 而踩坑:在发送完地址后直接发 0xA1 ,结果EEPROM将 0xA1 误认为是下一个地址字节,导致读取位置偏移。这个教训让我深刻体会到,I²C的每一个比特,都承载着不可替代的协议语义。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐



所有评论(0)