欢迎光临
我们一直在努力

IIC型是什么型号基于ARM架构的IIC通信实现与EEPROM读写实战

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:IIC是一种广泛应用于嵌入式系统中的串行通信协议,适用于微控制器与传感器、存储器等外设之间的低速数据交换。本文聚焦于基于ARM Cortex-M3内核的LM3S9B96微控制器,详细讲解如何通过IIC协议与EEPROM进行可靠的数据读写操作。内容涵盖IIC总线原理、寄存器配置、通信流程控制及错误处理机制,并结合eeprom.c源码展示从初始化到数据传输的完整实现过程。该项目对掌握嵌入式系统中IIC通信的实际应用具有重要实践价值。
基于ARM的IIC

IIC总线采用两根双向信号线:SDA(串行数据线)和SCL(串行时钟线),均需外接上拉电阻以实现开漏输出电平控制。所有设备通过这两条线连接至总线,支持多主控与多从设备架构。通信由主设备发起,通过电平同步机制实现数据传输。SCL负责提供时钟节拍,SDA在SCL低电平时准备数据,在高电平时保持稳定以确保有效采样。

// 示例:模拟起始信号(START)
SDA_LOW();  // 在SCL高时拉低SDA,表示开始
SCL_LOW();  // 随后拉低SCL,进入数据传输阶段

该时序逻辑严格遵循IIC规范,是建立可靠通信的前提。

IIC数据以字节为单位传输,每字节含8位数据加1位应答位(ACK/NACK)。传输过程中,SDA在SCL上升沿被采样,下降沿改变,确保数据稳定。每次通信以“起始信号”开始,发送从设备地址(7位或10位)并附加读写位(R/W),从机通过拉低SDA返回ACK表示响应。

字段 长度 说明 起始信号 1 SDA由高→低(SCL高) 设备地址 7/10 目标从机唯一标识 R/W位 1 0=写,1=读 ACK 1 从机确认接收成功

此结构构成了IIC寻址与交互的基础。

IIC支持多设备挂载于同一总线,依靠唯一的设备地址区分。标准模式使用7位地址,可寻址128个设备(地址范围0x00~0x7F),其中部分保留用于广播等特殊功能。10位地址扩展了可用空间,兼容旧设备并通过特定前缀标识。

当主机发送目标地址后,所有从机对比自身地址并决定是否响应。这种基于硬件地址的仲裁机制避免冲突,配合时钟同步与应答反馈,形成高效、可靠的低速通信体系,为嵌入式系统中传感器、EEPROM等外设互联提供了简洁解决方案。

LM3S9B96是德州仪器(TI)推出的基于ARM Cortex-M3内核的微控制器,广泛应用于工业控制、传感器接口和嵌入式通信系统中。其集成的I²C(Inter-Integrated Circuit)模块具备完整的主从双模式支持,能够实现高效、稳定的串行通信。该模块不仅支持标准模式(100 kbps)和快速模式(400 kbps),还具备中断驱动与DMA协同处理能力,适用于对实时性和数据吞吐量有要求的应用场景。深入理解LM3S9B96 I²C模块的内部架构,是构建可靠驱动程序的前提。

本章将从硬件功能组成、寄存器映射、工作模式配置以及中断与DMA机制四个方面全面剖析该I²C模块的设计逻辑与运行机制。通过分析其底层结构,揭示如何通过软件精确控制总线行为,为后续在具体外设如EEPROM、温度传感器等设备上的应用打下坚实基础。

LM3S9B96的I²C模块是一个高度集成的通信外设,设计用于在多设备共享总线上进行低速但高可靠性的数据交换。它不仅仅是一个简单的数据收发器,而是一个包含状态机、缓冲区、仲裁逻辑和错误检测机制的完整子系统。其核心功能由三大部分构成:主控/从设备双模式支持、内部FIFO与中断机制、以及多主竞争下的总线仲裁逻辑。

2.1.1 IIC主控器与从设备模式支持

I²C总线采用主从架构,其中主设备负责发起通信并提供时钟信号(SCL),从设备响应请求并通过SDA线传输数据。LM3S9B96的I²C模块可在运行时动态切换为主控模式或从设备模式,极大提升了系统的灵活性。

主控模式 下,MCU主动发起通信流程:
– 生成起始条件(START)
– 发送目标从设备地址 + 读写位
– 控制数据传输方向
– 生成停止条件(STOP)

而在 从设备模式 下,MCU监听总线上的地址匹配事件,仅当接收到自身地址时才参与通信。此时,它可以作为接收方或发送方响应主机命令。

模式切换由控制寄存器 I2CCTL 中的 Master 位决定:

// 设置为主控模式
HWREG(I2C0_BASE + I2C_O_MCR) |= I2C_MCR_MFE;       // 使能主功能
HWREG(I2C0_BASE + I2C_O_MSA) = (slave_addr << 1);   // 设置从设备地址

参数说明
I2C0_BASE :I²C模块0的基地址(通常为 0x40020000)
I2C_O_MCR :主控制寄存器偏移地址
I2C_MCR_MFE :主功能使能位
I2C_O_MSA :主目标地址寄存器
slave_addr :7位从设备物理地址(例如0x50用于EEPROM)

该设计允许同一MCU既可作为传感器采集节点(主控读取多个传感器),也可作为系统中的从属设备被其他主控访问(如调试接口)。这种双向角色适配能力在复杂嵌入式系统中尤为重要。

2.1.2 内部FIFO缓冲区与中断机制

为了减轻CPU负担并提高数据吞吐效率,LM3S9B96的I²C模块配备了深度为8字节的发送和接收FIFO队列。每个FIFO均可存储最多8个数据字节,在连续读写操作中显著减少中断触发频率。

FIFO的工作机制如下:

操作类型 FIFO作用 触发条件 发送数据 数据预加载至TX FIFO,自动逐字节输出 TX FIFO空于阈值(可设为1~8) 接收数据 接收到的数据暂存于RX FIFO RX FIFO达到设定水位
graph TD
    A[CPU写入I2CDR] --> B{TX FIFO是否满?}
    B -- 否 --> C[数据入队]
    B -- 是 --> D[等待中断或轮询]
    C --> E[SCL驱动SDA输出]
    F[SCL上升沿采样SDA] --> G[RX FIFO存储接收到的字节]
    G --> H{是否达到水位?}
    H -- 是 --> I[触发RXIM中断]

代码示例:启用FIFO并设置中断水位

// 使能FIFO功能
HWREG(I2C0_BASE + I2C_O_FIFOCTL) = 
    (1 << 4) |        // TX FIFO触发级别: 1字节
    (1 << 0);         // RX FIFO触发级别: 1字节

// 使能相关中断
HWREG(I2C0_BASE + I2C_O_IMR) |= 
    I2C_IMR_RXIM |    // 接收中断
    I2C_IMR_TXIM;     // 发送中断

逻辑分析
I2C_O_FIFOCTL 寄存器用于设置FIFO中断触发阈值
– 高四位控制TX FIFO,低四位控制RX FIFO
– 值为0表示禁用FIFO,1~8表示对应触发字节数
I2C_O_IMR 是中断屏蔽寄存器,置1表示允许该类中断上报

结合NVIC中断向量注册后,即可实现非阻塞式通信。例如,在接收大量数据时,仅需在首次启动后等待最后一次中断完成,避免频繁上下文切换。

2.1.3 多主控竞争与仲裁逻辑

在复杂的系统中可能存在多个主设备共用一条I²C总线的情况(如两个MCU都需要访问同一个实时时钟芯片)。由于I²C总线是开漏结构,所有设备都能拉低线路电平,因此必须有一套仲裁机制防止数据冲突。

LM3S9B96 I²C模块内置了 位级仲裁器(Bit Arbitration Logic) ,其原理基于“谁先释放SCL谁输”的规则:

  • 所有主设备同步输出SCL时钟
  • 在SDA线上,每个主设备边发送边监听
  • 若某主设备试图输出高电平但检测到总线为低,则判定自己失去仲裁权
  • 失败方立即转入从模式或停止操作,避免干扰获胜方通信

此过程完全由硬件完成,无需软件干预。以下是典型仲裁场景的状态转移图:

stateDiagram-v2
    [*] --> Idle
    Idle --> Arbitrate_Start : START发出
    Arbitrate_Start --> Compare_Bit : 每bit比较
    Compare_Bit --> Lose_Arbitration : 输出1但检测0
    Compare_Bit --> Continue_Transmit : 匹配成功
    Lose_Arbitration --> Switch_to_Slave : 转为从机或退出
    Continue_Transmit --> End_of_Message : 完成帧传输
    End_of_Message --> Idle

该机制确保即使多个主设备同时尝试通信,也只会有一个成功完成事务,其余自动退避。这对于构建容错性强的分布式控制系统至关重要。

LM3S9B96的I²C模块通过一组专用寄存器实现对通信全过程的精细控制。这些寄存器位于统一的内存映射空间内,可通过直接访问基地址加偏移的方式进行读写。正确理解和使用这些寄存器是编写稳定I²C驱动的关键。

2.2.1 控制寄存器(I2CCTL)详解

控制寄存器(实际名为 I2C_MCR I2C_SCSR 等,不同模式下名称不同)是整个I²C模块的指挥中心,决定了模块的基本行为模式。

以主模式下的主控制寄存器 I2C_MCR 为例:

位域 名称 功能描述 [0] MFE 主功能使能(Master Function Enable) [4] LPBK 回环测试模式(Loopback Test Mode)

典型初始化代码:

// 使能主模式
HWREG(I2C0_BASE + I2C_O_MCR) = I2C_MCR_MFE;

参数说明
I2C_O_MCR 偏移地址为 0x020
– 必须先使能MFE才能使用主功能
– 回环模式可用于自检,不连接外部设备时验证逻辑正确性

此外,在执行单次传输时还需配合 I2C_MASTER_CMD 寄存器发送指令:

// 发送START + 地址 + 数据 + STOP
HWREG(I2C0_BASE + I2C_O_MCS) = 
    I2C_MCS_RUN |      // 开始运行
    I2C_MCS_START |    // 产生START
    I2C_MCS_STOP |     // 结束时产生STOP
    I2C_MCS_ACK;       // 接收最后一个字节后发送ACK

逻辑分析
RUN 表示启动一次传输周期
START/STOP 控制信号边界
ACK 决定是否在接收最后一个字节后应答

一旦写入此寄存器,硬件状态机会自动执行相应动作,并更新状态寄存器。

2.2.2 目标地址寄存器(I2CSlaveAddr)配置

目标地址寄存器( I2C_MSA )用于指定当前通信的目标从设备地址。

格式如下:

+-----------------------------+
| R/W (1bit) | Addr[6:0] (7b) |
+-----------------------------+
           8 bits

写入方式:

#define EEPROM_ADDR 0x50
HWREG(I2C0_BASE + I2C_O_MSA) = (EEPROM_ADDR << 1); // 左移一位留出R/W位

注意 :虽然名字叫“目标地址”,但它并不包含读写位;该位在后续命令中单独指定。

若要进行写操作:

HWREG(I2C0_BASE + I2C_O_MDR) = data_byte;
HWREG(I2C0_BASE + I2C_O_MCS) = I2C_MCS_RUN | I2C_MCS_STOP;

若要读操作,需在命令中显式指出:

HWREG(I2C0_BASE + I2C_O_MCS) = 
    I2C_MCS_RUN | I2C_MCS_START | I2C_MCS_STOP | I2C_MCS_SINGLE;

2.2.3 数据寄存器(I2CData)读写机制

数据寄存器( I2C_MDR )是用户与I²C模块之间交换数据的主要通道。

  • 写操作:CPU将待发送数据写入 I2C_MDR
  • 读操作:CPU从 I2C_MDR 读取已接收的数据

重要特性:
– 访问宽度为8位
– 必须在状态机就绪后操作
– 受FIFO影响时可批量访问

代码片段:

// 写一字节
if(HWREG(I2C0_BASE + I2C_O_MCS) & I2C_MCS_IDLE) {
    HWREG(I2C0_BASE + I2C_O_MDR) = 0x8F;
    HWREG(I2C0_BASE + I2C_O_MCS) = I2C_MCS_RUN | I2C_MCS_STOP;
}

安全检查 :必须确认前一次操作已完成(IDLE状态),否则可能引发总线错误。

2.2.4 状态寄存器(I2CStat)标志位分析

状态寄存器( I2C_MCS )反映当前I²C模块的运行状态:

标志位 含义 使用建议 BUSY 总线忙 判断是否可发起新操作 ERROR 通信错误 检查NACK、超时等 ADRSZ 地址阶段完成 可开始数据传输 DATARDY 数据就绪 可读取I2C_MDR

典型轮询等待代码:

while(HWREG(I2C0_BASE + I2C_O_MCS) & I2C_MCS_BUSY);
if(HWREG(I2C0_BASE + I2C_O_MCS) & I2C_MCS_ERROR) {
    // 处理错误:可能是设备未响应
    HWREG(I2C0_BASE + I2C_O_MCS) = I2C_MCS_STOP; // 清除错误
}

健壮性建议 :加入超时机制防止死循环

uint32_t timeout = 1000;
while((HWREG(I2C0_BASE + I2C_O_MCS) & I2C_MCS_BUSY) && --timeout);
if(timeout == 0) return I2C_ERR_TIMEOUT;

2.3.1 主模式与从模式切换机制

模式切换需重新配置控制寄存器并清除状态:

// 切换到主模式
HWREG(I2C0_BASE + I2C_O_MCR) |= I2C_MCR_MFE;
HWREG(I2C0_BASE + I2C_O_SCSR) &= ~I2C_SCSR_DA; // 禁止从功能

// 切换到从模式
HWREG(I2C0_BASE + I2C_O_SCSR) |= I2C_SCSR_EN;
HWREG(I2C0_BASE + I2C_O_SOAR) = MY_SLAVE_ADDR;

2.3.2 波特率发生器与I2CFrequency寄存器设置

I²C时钟频率由 I2C_PBCLK 经分频得到:

// 设系统时钟为50MHz,期望400kbps
uint32_t period = (50000000 / (16 + 2 * 400000)) - 1; // 公式来自Datasheet
HWREG(I2C0_BASE + I2C_O_MTPR) = period;

2.3.3 系统时钟与IIC时钟分频关系

依赖 RCGC_IIC 使能时钟源:

SYSCTL_RCGCI2C_R |= SYSCTL_RCGCI2C_R0; // 使能I2C0时钟

2.4.1 发送完成、接收就绪中断触发条件

中断使能:

I2C0_IM_R = I2C_IM_DMAUDMA | I2C_IM_RXIM | I2C_IM_TXIM;

ISR示例:

void I2C0_IRQHandler(void) 
}

2.4.2 使用DMA提升大数据量传输效率

配置uDMA通道:

UDMA_CHANNEL_SELECT(UDMA_CH0_I2C0RX);
uDMA_Enable();

DMA传输避免CPU介入,适合音频流、图像传感器等大数据场景。

在嵌入式系统开发中,IIC(Inter-Integrated Circuit)总线的稳定运行依赖于精确的底层硬件初始化。对于基于ARM架构的LM3S9B96微控制器而言,其片上IIC模块虽具备高度集成的功能特性,但若未经过正确的GPIO引脚配置、时钟使能和寄存器设置,则无法实现可靠的数据通信。本章节将深入探讨IIC通信驱动从零开始的完整初始化流程,涵盖引脚复用控制、开漏输出配置、模块使能机制、频率设定原则以及电气特性的保障策略。通过系统性地构建初始化函数 iic_init() ,并结合实际代码实现与错误反馈机制设计,为后续高阶通信操作打下坚实基础。

IIC通信使用两条信号线:SDA(串行数据线)和SCL(串行时钟线),它们必须连接到微控制器支持IIC功能的专用引脚上,并通过寄存器配置将其切换至外设模式。这一过程涉及多个关键步骤,包括引脚功能选择、电气属性设置及物理层保护措施的设计。

3.1.1 引脚功能选择(AFSEL与PCTL寄存器)

在LM3S9B96中,每个GPIO端口都具有多种可选功能,需通过特定寄存器启用IIC外设模式。核心涉及两个寄存器: GPIO_AFSEL GPIO_PCTL

  • GPIO_AFSEL (Alternate Function Select Register)用于启用引脚的替代功能。
  • GPIO_PCTL (Port Control Register)则进一步指定具体使用哪个外设功能(如I2C0、I2C1等)。

以I2C0模块为例,通常使用Port B的第2(SCL)和第3(SDA)引脚:

// 启用Port B的时钟
SYSCTL_RCGC2_R |= SYSCTL_RCGC2_GPIOB;

// 等待时钟稳定
while ((SYSCTL_PRGPIO_R & SYSCTL_PRGPIO_R1) == 0) {}

// 配置PB2和PB3为替代功能
GPIO_PORTB_AFSEL_R |= (1 << 2) | (1 << 3);

// 设置PCTL寄存器,将PB2/PB3映射到I2C0功能(值为3)
GPIO_PORTB_PCTL_R = (GPIO_PORTB_PCTL_R & ~0xFF00) | (3 << 8) | (3 << 12);
逻辑分析与参数说明:
行号 代码片段 功能解释 1 SYSCTL_RCGC2_R |= SYSCTL_RCGC2_GPIOB; 开启Port B的时钟电源,否则无法访问其寄存器 2–4 while(...) 延迟等待外设时钟同步完成,避免未定义行为 5 GPIO_PORTB_AFSEL_R |= ... 将PB2和PB3设置为替代功能模式 6–7 GPIO_PORTB_PCTL_R = ... 指定这两个引脚对应I2C0模块(编码值为3)

⚠️ 注意: PCTL 寄存器是每4位控制一个引脚,因此 PB2 对应第8~11位,PB3 对应第12~15位。

该配置确保了引脚不再作为普通GPIO工作,而是交由IIC模块接管,形成真正的硬件级串行通信通路。

graph TD
    A[启动系统时钟] --> B[配置AFSEL启用替代功能]
    B --> C[通过PCTL指定IIC外设编号]
    C --> D[设置开漏输出与上拉电阻]
    D --> E[完成引脚复用配置]

此流程图展示了从时钟使能到最终引脚功能绑定的全过程,强调了顺序执行的重要性。

3.1.2 开漏输出与上拉电阻配置必要性

IIC协议规定 SDA 和 SCL 必须采用 开漏输出 (Open-Drain)结构,这意味着器件只能将线路拉低或释放(高阻态),不能主动驱动高电平。高电平状态由外部 上拉电阻 提供。

为什么需要开漏?
  • 支持多主设备竞争时的“线与”逻辑;
  • 实现应答(ACK/NACK)机制中的双向电平控制;
  • 防止总线短路风险(多个设备同时输出高低电平时不会烧毁);

在LM3S9B96中,可通过以下代码配置开漏模式:

// 设置PB2(SCL)和PB3(SDA)为开漏输出
GPIO_PORTB_ODR_R |= (1 << 2) | (1 << 3);

// 启用内部上拉(可选,推荐使用外部精密电阻)
GPIO_PORTB_PUR_R |= (1 << 2) | (1 << 3);
参数说明与电路考量:
寄存器 作用 ODR (Output Drive Register) 启用开漏输出模式 PUR (Pull-Up Resistor Enable) 启用内部上拉电阻(约几kΩ)

虽然可以启用内部上拉,但在实际应用中建议使用 外部精密上拉电阻 (通常为4.7kΩ),原因如下:

  1. 内部上拉阻值较大且不一致,影响上升沿时间;
  2. 多设备挂载时总线负载增加,需根据总线电容重新计算最佳上拉值;
  3. 提升抗干扰能力,尤其是在长距离布线场景中。

此外,还需注意:

  • 上拉电压应与I/O容忍电压匹配(如3.3V系统不得接5V上拉);
  • 若存在电平转换芯片(如TXS0108E),则需分层设计上下拉网络。

完成GPIO配置后,下一步是激活IIC模块本身,并进行主模式的基本参数初始化。

3.2.1 启动IIC模块时钟(RCGC_IIC寄存器)

所有外设在Tiva C系列MCU中均需显式开启时钟才能工作。IIC模块也不例外,需通过 SYSCTL_RCGCI2C_R 寄存器使能。

// 使能I2C0模块时钟
SYSCTL_RCGCI2C_R |= SYSCTL_RCGCI2C_R0;

// 等待时钟稳定
while ((SYSCTL_PRI2C_R & SYSCTL_PRI2C_R0) == 0) {}
逐行解析:
  • 第一行写入 RCGCI2C_R0 位,表示启动I2C0的时钟源;
  • 第二行轮询 PRI2C_R0 标志位,确认外设已准备好;
  • 此延迟不可省略,否则后续寄存器访问可能失败。

3.2.2 设置I2CFrequency计算标准时钟频率

IIC通信速率由 I2C_MTPR (Master Time Period Register)决定,该寄存器控制SCL的周期分频。其计算公式如下:

TPR = leftlfloor frac{f_{sys} }{ 2 imes ( ext{desired_speed} + f_{rise} ) imes ext{SCL_LP} }
ight
floor – 1

其中:
– $ f_{sys} $:系统时钟频率(如50MHz)
desired_speed :目标波特率(如100 kbps)
– $ f_{rise} $:SCL上升时间(典型值为0.3 μs)
SCL_LP :低周期比例,默认为6(即6个时钟周期为低)

简化常用场景下的估算方式:

#define SYSTEM_CLOCK 50000000UL
#define I2C_SPEED    100000UL   // 100 kHz

uint32_t tpr = (SYSTEM_CLOCK / (2 * 10 * I2C_SPEED)) - 1;
if (tpr > 255) tpr = 255;

I2C0_MTPR_R = tpr;
参数说明表:
变量 典型值 说明 SYSTEM_CLOCK 50,000,000 Hz 微控制器主频 I2C_SPEED 100,000 bps 标准模式速度 tpr 计算得出 分频系数,范围0~255 I2C0_MTPR_R 写入TPR值 控制SCL高低时间

例如,当系统时钟为50MHz,目标速率为100kHz时:

TPR = frac{50,000,000}{2 imes 10 imes 100,000} – 1 = 24

故设置 I2C0_MTPR_R = 24 即可实现接近100kHz的通信速率。

3.2.3 初始化I2CCTL以启用主控模式

最后一步是通过控制寄存器 I2C_MCR I2C_MCR 来启用主模式。

// 启用I2C模块为主模式
I2C0_MCR_R = I2C_MCR_MFE;        // 主功能使能
I2C0_SWE_NML_CTL_R = 0;          // 禁用从模式(如不需要)
关键字段解析:
字段 位名 含义 I2C_MCR_MFE BIT0 Master Function Enable,开启主模式 I2C_MCR_LPBK BIT1 Loopback Test Mode,调试用,正常关闭

一旦设置 MFE=1 ,IIC模块即进入主控状态,可发起起始信号、发送地址、读写数据。

尽管软件配置正确,若忽略物理层设计,仍可能导致通信失败或不稳定。

3.3.1 上拉电阻值计算与稳定性优化

理想的上拉电阻 $ R_p $ 应满足:

R_p geq frac{V_{DD} – V_{OL}}{I_{OL}}
quad ext{且} quad
R_p leq frac{t_r}{0.8473 imes C_b}

其中:
– $ V_{OL} $:低电平最大输出电压(典型0.4V)
– $ I_{OL} $:灌电流能力(如3mA)
– $ t_r $:允许的最大上升时间(100kHz模式下≤1000ns)
– $ C_b $:总线总电容(单位pF)

举例:假设 $ C_b = 400pF $,$ t_r = 1000ns $

R_p leq frac{1000 imes 10^{-9}}{0.8473 imes 400 imes 10^{-12}} approx 2.95kOmega

但太小的电阻会增大功耗,一般折中选择 4.7kΩ 是常见做法。

总线长度 推荐上拉电阻 备注 < 10cm 10kΩ 低功耗优先 10–30cm 4.7kΩ 平衡性能与功耗 > 30cm 2.2kΩ 或缓冲器 需考虑信号完整性

3.3.2 总线电容限制与信号完整性考量

IIC规范规定标准模式下最大总线电容为 400pF 。超过此值会导致:

  • 上升/下降沿变缓;
  • 误判起始/停止信号;
  • ACK检测失败;

影响总线电容的因素包括:

  • PCB走线长度(≈1pF/cm);
  • 连接器件输入电容(每个设备约5–10pF);
  • 接插件与屏蔽层分布电容;
示例计算:

若有5个设备,平均输入电容8pF,PCB走线20cm:

C_{total} = 5 imes 8 + 20 imes 1 = 60pF

远低于限值,安全。

但若扩展至20个设备+1m布线:

C_{total} = 20 imes 8 + 100 imes 1 = 260pF

接近极限,建议加入 IIC缓冲器(如PCA9515B) 进行隔离。

flowchart LR
    subgraph "IIC Bus Segment"
        direction TB
        MCU --"RP=4.7k"--> SDA
        MCU --"RP=4.7k"--> SCL
        SDA --> EEPROM
        SDA --> Sensor
        SDA --> RTC
        SCL --> EEPROM
        SCL --> Sensor
        SCL --> RTC
    end

上述拓扑显示典型的多设备IIC总线布局,强调了共享上拉与分布电容累积的风险。

综合以上步骤,封装完整的初始化函数是工程实践的关键。

3.4.1 封装初始化函数iic_init()的设计思路

目标:构建一个可复用、带错误检测的初始化接口。

int iic_init(uint32_t speed_khz) 

    return 0;  // 成功
}
函数返回码说明:
返回值 含义 0 初始化成功 -1 波特率超出支持范围 -2 模块未响应,可能存在硬件故障

该函数实现了参数化配置(支持不同速率)、错误反馈、资源使能完整闭环。

3.4.2 错误检测与初始化状态反馈机制

为了增强鲁棒性,可在初始化过程中加入更多诊断信息:

volatile uint32_t timeout = 100000;
while ((I2C0_MCR_R & I2C_MCR_MFE) == 0 && timeout--) {
    // 等待主模式真正生效
}
if (timeout == 0) return -3;

还可通过状态寄存器 I2C_MSTAT_R 获取更详细的启动结果:

状态位 名称 含义 BUSY 总线忙 当前有事务正在进行 ERROR 错误标志 NACK、超时等 ADCA 地址检测成功 仅从模式有用

这些信息可用于日志输出或调试工具集成。

sequenceDiagram
    participant User as Application
    participant Driver as iic_init()
    participant Hardware as I2C Module

    User->>Driver: 调用iic_init(100)
    Driver->>Hardware: 使能时钟与GPIO
    Driver->>Hardware: 配置AFSEL/PCTL/ODR
    Driver->>Hardware: 设置TPR并启用MFE
    Hardware-->>Driver: 返回状态码
    alt 初始化成功
        Driver-->>User: 返回0
    else 失败(速率超限)
        Driver-->>User: 返回-1
    end

该序列图清晰展现了调用关系与异常处理路径。

综上所述,IIC底层驱动的初始化不仅是寄存器配置的堆叠,更是软硬件协同设计的艺术体现。只有在充分理解电气特性、时序约束与微控制器架构的基础上,才能构建出既高效又稳定的通信基础。

在嵌入式系统中,IIC(Inter-Integrated Circuit)总线协议因其仅需两根信号线(SDA 和 SCL)、支持多主控、具备地址寻址能力以及良好的电气兼容性,被广泛用于连接低速外设,如温度传感器、实时时钟芯片和EEPROM等。然而,要实现稳定可靠的IIC通信,除了对硬件模块进行正确初始化之外,更关键的是掌握其 核心操作流程 ——包括起始/停止信号生成、设备寻址、应答处理、数据收发控制等。这些操作构成了每一次IIC事务的基础单元,任何一环出错都可能导致通信失败或总线锁定。

本章将从理论出发,深入剖析IIC通信的关键步骤,并结合LM3S9B96微控制器的寄存器机制,展示如何通过软件精确控制这些流程。我们将以常见的AT24C系列串行EEPROM为例,详细解析写入和读取操作的具体实现路径,涵盖字节写、页写、随机读和连续读等多种模式。通过对底层时序逻辑与寄存器交互关系的细致拆解,帮助开发者建立清晰的操作模型,为后续驱动代码的设计与调试提供坚实支撑。

IIC总线的所有通信都始于一个 起始信号 (START),终于一个 停止信号 (STOP)。这两个特殊信号不携带有效数据,但它们定义了每一次IIC事务的边界。由于SDA和SCL均为开漏结构,依赖外部上拉电阻维持高电平,因此起始和停止信号通过特定的电平跳变来识别。

4.1.1 控制寄存器I2CCTL中START/STOP位操作

在LM3S9B96的IIC模块中,起始和停止信号并非由直接操控GPIO引脚产生,而是通过配置控制寄存器 I2CCTL 中的特定控制位由硬件自动完成。该寄存器是整个IIC操作的核心入口之一。

// 示例:设置I2CCTL寄存器以触发起始信号
#define I2C0_BASE     0x40020000
#define I2CCTL_OFFSET 0x01C
volatile unsigned long *I2C_CTL = (unsigned long *)(I2C0_BASE + I2CCTL_OFFSET);

// 发送起始信号并启动主发送模式
*I2C_CTL |= (1 << 0) | (1 << 2);  // 设置RUN=1, START=1
位域 名称 功能说明 BIT0 (RUN) 运行使能 置1表示启动一次传输操作 BIT1 (ACK) 应答控制 控制是否在接收到最后一个字节后发送ACK BIT2 (STOP) 停止信号 置1后在当前字节传输完成后发出STOP BIT3 (START) 起始信号 置1后立即生成START条件

注意 START STOP 是一次性触发位,写入后由硬件自动清零;而 RUN 必须保持为1直到本次事务结束。

当程序向 I2CCTL 写入 (1<<0)|(1<<3) 时,IIC控制器会在检测到SCL为高期间拉低SDA,从而生成标准的起始信号。随后,总线进入“忙”状态,准备发送目标地址。

起始信号生成流程图(Mermaid)
sequenceDiagram
    participant MCU as 微控制器
    participant BUS as IIC总线(SDA/SCL)
    MCU->>BUS: SCL = 高, SDA = 高 (空闲状态)
    Note right of MCU: 检测总线空闲
    MCU->>BUS: SCL保持高,SDA由高→低
    Note right of MCU: 生成START信号
    BUS-->>MCU: 总线进入忙状态

此流程确保了即使多个主设备存在,也能通过仲裁机制避免冲突。只有成功发出起始信号的主机才能继续通信。

4.1.2 硬件自动与软件手动触发对比

虽然现代IIC控制器大多支持硬件自动生成起始/停止信号,但在某些情况下仍可采用“软件模拟”方式,即通过配置GPIO为开漏模式并严格按照时序手动翻转电平来构造这些信号。

对比维度 硬件自动模式 软件模拟模式 实现复杂度 低,只需设置寄存器 高,需精确延时控制 时序精度 高,符合IIC规范 依赖CPU频率与延时函数准确性 CPU占用率 低,可配合中断/DMA使用 高,需持续轮询或阻塞等待 可靠性 强,抗干扰能力强 易受中断延迟影响导致时序偏差 适用场景 主流应用,尤其大数据量传输 无专用IIC模块的老型号MCU

例如,在没有IIC外设的低端MCU上,常使用如下代码片段模拟起始信号:

void iic_start(void) {
    SDA_HIGH();   delay_us(5);
    SCL_HIGH();   delay_us(5);  // 确保总线空闲
    SDA_LOW();    delay_us(5);  // 下降沿 -> START
    SCL_LOW();    delay_us(5);  // 锁定时钟,准备发送数据
}

其中 delay_us() 函数必须根据系统时钟精确计算,否则可能违反IIC最小高/低电平时间要求(如标准模式下tHD;STA ≥ 4.0μs)。

相比之下,硬件模式的优势在于:
– 自动遵守IIC时序参数(由I2CFrequency寄存器决定)
– 支持中断驱动,释放CPU资源
– 内建错误检测(如NACK、超时)
– 支持多主竞争与仲裁

因此,在具备IIC模块的ARM Cortex-M3架构(如LM3S9B96)中,应优先使用硬件模式。

寄存器操作逻辑分析

回顾前面的起始信号代码:

*I2C_CTL |= (1 << 0) | (1 << 3);  // RUN=1, START=1

逐行解释如下:
1. *I2C_CTL |= ... :对控制寄存器执行“置位”操作,保留原有配置。
2. (1 << 0) :启用 RUN 位,通知IIC模块开始一次传输周期。
3. (1 << 3) :设置 START 位,指示在下一个时钟周期生成起始条件。
4. 硬件检测到这两个标志后,在SCL高电平时主动拉低SDA,完成START生成。
5. 此后,若启用了中断,则可通过检查状态寄存器 I2CStat 是否进入“地址已发送”状态来推进下一步。

这种设计使得上层代码无需关心具体电平变化,只需关注状态机流转即可,极大提升了开发效率与可靠性。

完成起始信号后,下一步是向总线上广播目标从设备的地址,并等待其响应。这是IIC通信中实现“选择性通信”的关键环节。

4.2.1 7位地址左移并合并读写位操作

大多数IIC设备采用 7位地址编码 ,例如AT24C02的默认地址为 1010000b (0x50)。但在实际传输中,这个7位地址需要扩展为8位字节,第8位用于指示操作方向: 0 表示写(Write), 1 表示读(Read)。

因此,正确的地址格式为:

[ A6 A5 A4 A3 A2 A1 A0 R/W ]

在C语言中,通常这样构造地址字节:

#define EEPROM_ADDR_7BIT  0x50
uint8_t dev_addr_write = (EEPROM_ADDR_7BIT << 1) | 0;  // 0xA0
uint8_t dev_addr_read  = (EEPROM_ADDR_7BIT << 1) | 1;  // 0xA1

然后将该字节写入 I2CData 寄存器并启动传输:

volatile uint8_t *I2C_DATA = (uint8_t*)(I2C0_BASE + 0x018);
*I2C_DATA = dev_addr_write;
*I2C_CTL |= (1 << 0);  // 启动RUN,发送地址+方向

此时,IIC控制器会自动将该字节逐位输出到SDA线上,同时SCL提供同步时钟。每个比特在一个SCL周期内传输,MSB先行。

4.2.2 接收ACK/NACK判断从设备响应状态

在地址字节传输完成后,发起方(主设备)会释放SDA线(即进入输入模式),并由从设备在第9个时钟周期拉低SDA以表示确认——这就是 应答信号 (ACK)。如果从设备未响应(如未连接、忙或地址不匹配),SDA将保持高电平,形成 非应答 (NACK)。

在LM3S9B96中,这一状态反映在状态寄存器 I2CStat RXACK 位(Receive Acknowledge)中:

RXACK 值 含义 0 收到ACK(从设备响应) 1 收到NACK(无响应)

可通过以下代码检测:

volatile uint32_t *I2C_STAT = (uint32_t*)(I2C0_BASE + 0x014);

while (!(*I2C_STAT & (1 << 1)));  // 等待传输完成(BUSY=0 或 INT=1)

if (*I2C_STAT & (1 << 7)) {       // 检查NACK标志
    // 处理错误:设备未响应
    handle_device_not_responding();
} else {
    // 继续后续数据传输
}
应答处理流程表
阶段 主设备动作 从设备动作 SDA第9位 状态含义 地址发送后 释放SDA(输入) 拉低表示ACK 低 设备存在且就绪 数据字节发送后 释放SDA 拉低表示接收成功 低 数据已被接收 最终读取前 主接收器发送NACK 不再发送数据,释放SDA 高 通知从设备结束传输 任意阶段无响应 —— 未拉低SDA 高 通信失败

重要规则 :主设备在最后一次读取前应主动发送NACK,以便通知从设备停止发送数据,随后再发出STOP。

Mermaid 流程图:地址发送与ACK检测
graph TD
    A[发送START] --> B[写入地址+R/W到I2CData]
    B --> C[启动RUN]
    C --> D[等待I2CStat更新]
    D --> E{RXACK == 0 ?}
    E -->|是| F[继续数据传输]
    E -->|否| G[报错:NACK received]

该流程体现了典型的事件驱动模式,适用于轮询或中断上下文。

EEPROM作为典型的IIC从设备,其写操作具有严格的时序要求。本节将以AT24C02为例,详解两种常见写法: 字节写 页写

4.3.1 字节写入的四步操作序列解析

字节写允许主设备向指定内存地址写入单个字节数据。完整流程如下:

  1. 发送起始信号
  2. 发送设备写地址(0xA0)
  3. 发送目标内存地址(Address High + Low)
  4. 发送要写入的数据字节
  5. 等待EEPROM内部写周期完成(约5ms)

对应代码实现:

int eeprom_byte_write(uint16_t mem_addr, uint8_t data) 

参数说明
mem_addr : 目标存储地址(0~255 for AT24C02)
data : 要写入的字节值
– 返回值:0表示成功,-1表示NACK错误

该函数执行一次完整的字节写事务,最后发送STOP信号结束通信。

4.3.2 页写入限制与跨页写保护规避策略

为了提高写入效率,AT24C系列支持 页写 (Page Write),即一次最多写入一页(如16或32字节)。但必须注意 页边界问题 :若起始地址位于页末尾附近,后续数据可能“回卷”到页首,覆盖已有数据。

例如,AT24C64每页32字节,若从地址 0x1F 开始写入5字节,则实际写入位置为 0x1F, 0x20, 0x21, 0x22, 0x00 —— 最后一字节写到了页首!

解决方法是在驱动层添加边界检查:

#define PAGE_SIZE 32

void eeprom_page_write(uint16_t start_addr, uint8_t *data, int len)  else {
        eeprom_page_write_core(start_addr, data, len);
    }
}

该策略确保不会发生意外回卷,提升数据完整性。

IIC读操作比写操作更复杂,因为它涉及 地址重发送 重复起始 (Repeated START)技术。

4.4.1 随机读取中的地址重发送技巧

随机读允许主设备从任意地址读取数据。其流程分为两个阶段:

  1. 写阶段 :发送设备地址 + 写命令,接着发送目标内存地址;
  2. 读阶段 :不发送STOP,而是再次发送START(Re-START),切换为读模式。

代码实现如下:

uint8_t eeprom_random_read(uint16_t mem_addr) {
    volatile uint32_t *I2C_CTL = (uint32_t*)(I2C0_BASE + 0x01C);
    volatile uint8_t  *I2C_DAT = (uint8_t* )(I2C0_BASE + 0x018);
    volatile uint32_t *I2C_STA = (uint32_t*)(I2C0_BASE + 0x014);

    // Phase 1: Write address
    *I2C_DAT = 0xA0;
    *I2C_CTL = (1<<3)|(1<<0);  // START + RUN
    wait_for_done();

    *I2C_DAT = (uint8_t)mem_addr;
    *I2C_CTL = (1<<0);
    wait_for_done();

    // Phase 2: Repeated START + Read
    *I2C_DAT = 0xA1;
    *I2C_CTL = (1<<3)|(1<<0);  // Re-START + RUN
    wait_for_done();

    // Switch to receive mode
    *I2C_CTL &= ~(1<<4);  // Clear TX
    *I2C_CTL |= (1<<0);   // RUN to clock in data
    wait_for_done();

    uint8_t result = *I2C_DAT;

    // Send NACK and STOP
    *I2C_CTL = (1<<1)|(1<<2)|(1<<0);  // ACK=0, STOP=1, RUN=1
    return result;
}

关键点:
– 使用相同的设备地址(0xA0 → 0xA1)完成模式切换;
– 不插入STOP,防止其他主设备抢占总线;
– 最后一次读取前发送NACK,再发STOP。

4.4.2 连续读取模式下的应答控制逻辑

连续读允许多字节读取,每次接收一字节后主设备决定是否继续请求更多数据:

  • 若发送 ACK ,则从设备继续发送下一字节;
  • 若发送 NACK ,则从设备知道这是最后一次读取,随后释放总线。
void eeprom_sequential_read(uint16_t addr, uint8_t *buf, int len) 
}

这一体系充分体现了IIC协议的灵活性与高效性。

在嵌入式系统开发中,IIC(Inter-Integrated Circuit)总线驱动的质量直接影响外设通信的稳定性与可维护性。 eeprom.c 作为典型的IIC设备驱动模块,不仅封装了底层硬件操作逻辑,还体现了良好的抽象层次设计、状态管理机制和错误处理策略。本章将围绕该文件展开逐行级深度解析,剖析其函数结构、控制流程、状态机应用以及健壮性保障手段,揭示工业级驱动程序的设计精髓。

eeprom.c 的核心目标是为外部IIC EEPROM(如AT24C系列)提供一套简洁、高效且可移植的读写接口。其设计遵循“分层抽象”原则:上层提供面向用户的应用编程接口(API),下层对接微控制器专用的IIC寄存器操作。这种分层模式使得更换平台时只需修改底层驱动部分,而业务逻辑保持不变。

5.1.1 模块化组织与头文件依赖关系

完整的 eeprom.c 通常配合 eeprom.h 共同工作。头文件定义了公共接口原型、宏常量及返回码枚举:

// eeprom.h
#ifndef EEPROM_H_
#define EEPROM_H_

#include <stdint.h>

// 设备地址(7位)
#define EEPROM_ADDR    0x50

// 返回状态码
typedef enum {
    EEPROM_OK = 0,
    EEPROM_ERROR_BUSY,
    EEPROM_ERROR_NACK,
    EEPROM_ERROR_TIMEOUT,
    EEPROM_ERROR_INVALID_PARAM
} eeprom_status_t;

// API 声明
eeprom_status_t eeprom_write_byte(uint16_t addr, uint8_t data);
eeprom_status_t eeprom_read_byte(uint16_t addr, uint8_t *data);
eeprom_status_t eeprom_page_write(uint16_t start_addr, const uint8_t *buf, uint8_t len);
eeprom_status_t eeprom_seq_read(uint16_t start_addr, uint8_t *buf, uint16_t len);

#endif // EEPROM_H_

上述设计通过枚举类型明确区分不同错误类别,极大提升了调试效率。同时,使用 uint16_t 表示地址支持大于256字节的EEPROM型号(如AT24C512),增强了通用性。

5.1.2 主要函数职责划分

函数名 职责描述 eeprom_write_byte 向指定内存地址写入单个字节 eeprom_read_byte 从指定地址读取一个字节 eeprom_page_write 执行页写操作,自动处理跨页边界问题 eeprom_seq_read 连续读取多个字节,利用IIC连续接收模式提升性能 i2c_wait_for_done 内部辅助函数,轮询等待I2C事务完成 i2c_clear_bus 总线恢复函数,在死锁后尝试重启SCL时钟

这些函数构成了清晰的功能树,便于开发者按需调用或扩展。

5.1.3 状态机在IIC事务中的核心作用

由于IIC通信具有严格的时序要求,不能简单地顺序执行指令。 eeprom.c 采用 有限状态机(Finite State Machine, FSM) 来管理每一次通信过程的状态流转。例如一次写操作涉及以下阶段:

stateDiagram-v2
    [*] --> Idle
    Idle --> StartSent: 发送起始信号
    StartSent --> AddrSent: 发送设备地址+写位
    AddrSent --> DataSent: 发送存储地址高/低字节
    DataSent --> ByteWrite: 发送数据字节
    ByteWrite --> StopSent: 发送停止信号
    StopSent --> Idle
    AddrSent --> NACKReceived: 收到NACK → 错误处理
    DataSent --> NACKReceived: 收到NACK

该状态图展示了每个步骤之间的依赖关系与异常跳转路径。实际代码中通过检查 I2CStat 寄存器的状态标志位来判断当前所处阶段,并据此决定下一步动作。

5.2.1 单字节写入: eeprom_write_byte

这是最基础也是最关键的写操作函数,其实现直接决定了整个驱动的可靠性。

eeprom_status_t eeprom_write_byte(uint16_t addr, uint8_t data) 

    // 步骤2:发送起始信号
    REG(I2C_BASE + I2C_CTL_OFFSET) = (1 << 7) | (1 << 4); // START + RUN

    // 步骤3:发送设备地址(写模式)
    REG(I2C_BASE + I2C_DATA_OFFSET) = (EEPROM_ADDR << 1) | 0; // W=0
    i2c_wait_for_done();

    if (REG(I2C_BASE + I2C_STAT_OFFSET) & (1 << 1)) { // NACK detected
        REG(I2C_BASE + I2C_CTL_OFFSET) |= (1 << 6); // STOP
        return EEPROM_ERROR_NACK;
    }

    // 步骤4:发送存储地址(高字节)
    REG(I2C_BASE + I2C_DATA_OFFSET) = (addr >> 8) & 0xFF;
    i2c_wait_for_done();
    if (check_nack()) return EEPROM_ERROR_NACK;

    // 步骤5:发送存储地址(低字节)
    REG(I2C_BASE + I2C_DATA_OFFSET) = addr & 0xFF;
    i2c_wait_for_done();
    if (check_nack()) return EEPROM_ERROR_NACK;

    // 步骤6:发送数据字节
    REG(I2C_BASE + I2C_DATA_OFFSET) = data;
    i2c_wait_for_done();
    if (check_nack()) return EEPROM_ERROR_NACK;

    // 步骤7:发送停止信号
    REG(I2C_BASE + I2C_CTL_OFFSET) |= (1 << 6); // STOP

    // 步骤8:等待EEPROM内部写周期完成(典型5ms)
    delay_ms(6);

    return EEPROM_OK;
}
代码逐行解读与参数说明
  • 第4行 :读取 I2CStat 寄存器第0位(BUSY标志),若总线正忙则立即返回 EEPROM_ERROR_BUSY ,避免冲突。
  • 第9行 :设置控制寄存器 I2CCTL START 位(bit 7)和 RUN 位(bit 4),触发起始信号并启动传输。
  • 第13行 :将7位设备地址左移一位,并清零最低位表示“写操作”。此为IIC标准寻址格式。
  • 第15行 :调用 i2c_wait_for_done() 阻塞等待本次发送完成,由硬件置位中断或状态位通知。
  • 第17–19行 :检测是否收到NACK。若从机未应答,则主动发出STOP信号终止通信,防止总线挂起。
  • 第22、27、32行 :依次发送内存地址的高字节、低字节和数据本身。每步都需等待完成并检查响应。
  • 第37行 :发出STOP信号结束事务。
  • 第40–41行 :加入延时以等待EEPROM完成内部写入(典型时间为5ms)。若省略可能导致后续读取失败。

⚠️ 注意:某些高端EEPROM支持“仲裁写周期检测”,可通过轮询方式替代固定延时。但此处使用保守策略确保兼容性。

5.2.2 页写操作优化: eeprom_page_write

为提高批量写入效率,应利用EEPROM的页缓冲机制(如AT24C64每页32字节)。以下为优化后的页写函数:

eeprom_status_t eeprom_page_write(uint16_t start_addr, const uint8_t *buf, uint8_t len) 

    // 复用 write_byte 流程,仅发送一次地址前缀
    REG(I2C_BASE + I2C_CTL_OFFSET) = (1 << 7) | (1 << 4);
    REG(I2C_BASE + I2C_DATA_OFFSET) = (EEPROM_ADDR << 1) | 0;
    i2c_wait_for_done(); if (check_nack()) goto err_stop;

    REG(I2C_BASE + I2C_DATA_OFFSET) = (start_addr >> 8) & 0xFF;
    i2c_wait_for_done(); if (check_nack()) goto err_stop;

    REG(I2C_BASE + I2C_DATA_OFFSET) = start_addr & 0xFF;
    i2c_wait_for_done(); if (check_nack()) goto err_stop;

    for (int i = 0; i < len; i++) 

    REG(I2C_BASE + I2C_CTL_OFFSET) |= (1 << 6); // STOP
    delay_ms(6);
    return EEPROM_OK;

err_stop:
    REG(I2C_BASE + I2C_CTL_OFFSET) |= (1 << 6);
    return EEPROM_ERROR_NACK;
}
关键优化点分析
  • 禁止跨页写 :第6–9行强制限制写入长度不超过当前页剩余空间。若需跨页,应在上层拆分为多次调用。
  • 减少重复寻址开销 :相比多次调用 write_byte ,此函数只发送一次设备地址和内存地址,显著降低协议开销。
  • 统一错误处理路径 :使用 goto err_stop 集中释放资源,符合嵌入式编程规范。

5.3.1 随机读取流程:地址重发送技巧

IIC EEPROM的随机读需要两次传输:第一次写入目标地址,第二次发起读操作。这称为“复合读”或“重开始(Repeated Start)”。

eeprom_status_t eeprom_read_byte(uint16_t addr, uint8_t *data) 
重要细节说明
  • 第18行 :再次发送 START 信号而不发 STOP ,形成“重开始”,维持主控权。
  • 第19行 :发送设备地址+读位(LSB=1),切换为读模式。
  • 第24行 :清除 ACK 位(即设置 CLR_ACK ),告知从机下一个字节将是最后一个,应返回NACK以便主控正确终止。
  • 第27行 :读取接收到的数据后必须发送 STOP ,否则总线可能被锁定。

5.3.2 连续读取:最大化吞吐量

对于大量数据读取,应启用IIC的连续接收模式:

eeprom_status_t eeprom_seq_read(uint16_t start_addr, uint8_t *buf, uint16_t len) 
        i2c_wait_for_done();
        buf[i] = REG(I2C_BASE + I2C_DATA_OFFSET);

        if (i < len - 1) {
            REG(I2C_BASE + I2C_CTL_OFFSET) |= (1 << 2); // ACK next
        }
    }

    REG(I2C_BASE + I2C_CTL_OFFSET) |= (1 << 6); // STOP
    return EEPROM_OK;
}
性能优势对比表
读取方式 每字节开销(时钟周期) 是否支持DMA 典型带宽(kbps) 单字节读 ~40 否 10–20 连续读 ~10 可扩展支持 80–100

连续读大幅减少了协议握手次数,尤其适合传感器日志回传、固件更新等场景。

5.4.1 轮询等待函数的安全封装

原始轮询容易导致死循环,因此引入超时机制:

#define MAX_I2C_WAIT_CYCLES 10000

static eeprom_status_t i2c_wait_for_done(void) 
        __asm__("nop");
    }
    return EEPROM_OK;
}
参数说明
  • MAX_I2C_WAIT_CYCLES :根据系统主频估算最大等待时间。例如主频50MHz,每个循环约20ns,10000次 ≈ 200μs,足以覆盖多数IIC帧。
  • i2c_clear_bus() :当检测到超时,尝试通过模拟9个SCL脉冲释放SDA线,解决因从机拉低SDA导致的死锁。

5.4.2 总线恢复算法实现

void i2c_clear_bus(void) 

    // Generate STOP condition
    gpio_set_as_input_with_pullup(SDA_PIN);
    gpio_set_high(SCL_PIN);
    delay_us(5);
    gpio_set_as_output(SDA_PIN);
    gpio_set_low(SDA_PIN);
    delay_us(5);
    gpio_set_high(SDA_PIN); // Release SDA
}

该函数通过软件模拟GPIO翻转,强制让从设备释放总线,是应对“SDA卡死”的关键补救措施。

综上所述, eeprom.c 不仅仅是一个简单的驱动文件,而是集成了 协议理解、硬件适配、状态管理、容错恢复 于一体的综合性解决方案。其代码结构清晰、错误反馈完善、性能优化到位,充分展现了嵌入式C语言工程实践的最佳范式。通过对每一行代码的深入剖析,可以深刻体会到高质量驱动开发背后的设计智慧与严谨思维。

在嵌入式系统开发中,IIC通信故障往往表现为“看似正确配置却无法通信”或“偶发性数据错误”。此时,依赖串口打印或寄存器轮询已不足以定位问题,必须借助外部工具——逻辑分析仪(Logic Analyzer)对SCL和SDA信号进行实时采样。

以Saleae Logic Pro 8为例,连接LM3S9B96的PB2(SCL)和PB3(SDA)引脚,并设置采样率不低于1MHz(建议为IIC时钟频率的10倍以上),可清晰捕获完整的IIC事务。以下是典型EEPROM写操作的波形结构:

[S] 0xA0 W [ACK] 0x00 [ACK] 0x05 [ACK] [P]
 ↑         ↑          ↑          ↑     ↑
起始   设备地址    存储地址     数据字节  停止

通过分析仪软件解码功能,可自动识别IIC协议层信息,包括:
– 起始/停止条件是否符合标准时序(t_SU,STA ≥ 4.7μs)
– 地址帧是否包含正确的7位设备地址(如0b1010000)并左移+写标志
– 每个字节后是否存在从设备返回的ACK信号(低电平)

常见异常现象及对应原因如下表所示:

波形异常 可能原因 排查方法 SCL被拉低无法释放 从设备未释放总线 复位从设备或检查其固件状态 SDA无ACK响应 地址错误、设备未上电或损坏 验证设备地址、测量供电电压 起始信号缺失 主控未正确置位I2CCTL.START 检查控制寄存器配置顺序 数据跳变发生在SCL高电平期间 违反IIC建立/保持时间要求 增加上拉电阻值或降低波特率 停止信号不完整 中断抢占导致STOP位未生效 启用DMA或关闭高优先级中断

此外,使用 Mermaid流程图 可描述典型调试决策路径:

graph TD
    A[通信失败] --> B{是否有起始信号?}
    B -- 否 --> C[检查I2C模块使能与GPIO复用]
    B -- 是 --> D{地址帧正确?}
    D -- 否 --> E[验证7位地址左移+R/W位]
    D -- 是 --> F{收到ACK吗?}
    F -- 否 --> G[确认设备存在且电源正常]
    F -- 是 --> H[继续监测后续数据流]
    H --> I[分析应答时机与时序合规性]

IIC是开漏输出结构,依赖外部上拉电阻将信号拉高。若设计不当,易引发上升沿缓慢、信号振铃或总线竞争等问题。

上拉电阻计算公式:

R_{pull-up} geq frac{V_{DD} – V_{OL}}{I_{OL}}, quad R_{pull-up} leq frac{t_r}{0.8473 imes C_{bus}}
其中:
– $ V_{OL} $:器件低电平输出电压(通常0.4V)
– $ I_{OL} $:最大灌电流(如3mA)
– $ t_r $:允许的最大上升时间(标准模式下1000ns)
– $ C_{bus} $:总线总电容(包括PCB走线与器件输入电容)

假设$ C_{bus} = 400pF $,$ V_{DD}=3.3V $,则:
R_{min} = frac{3.3 – 0.4}{0.003} ≈ 967Ω,quad R_{max} = frac{1000 imes 10^{-9}}{0.8473 imes 400 imes 10^{-12}} ≈ 2.95kΩ
推荐选用 1.8kΩ ~ 2.2kΩ 的精密电阻。

对于长距离布线或多设备挂载场景,还可采用以下增强方案:
– 使用双绞线减少电磁干扰
– 在靠近主控端加装磁珠抑制高频噪声
– 采用IIC缓冲器(如PCA9515B)实现总线隔离与驱动增强

不同型号EEPROM支持的最高速率不同,例如AT24C02最高支持400kHz,而CAT24M01仅支持100kHz。为提升兼容性,可在初始化阶段实施 波特率自适应探测机制

// 动态探测安全IIC频率
uint32_t iic_probe_safe_frequency(uint8_t dev_addr) {
    uint32_t freq_list[] = {400000, 300000, 200000, 100000};
    for (int i = 0; i < 4; i++) 
    }
    return 100000; // 默认降级至100kHz
}

参数说明 I2C_O_MTPR 为波特率分频寄存器,计算公式为 TPR = (SysClk / (2 * SCL_CLK)) - 1

为进一步提升大数据量传输效率,应启用 中断+DMA协同机制 。例如,在连续读取1KB EEPROM数据时:

传输方式 CPU占用率 平均延迟 吞吐量 轮询模式 95% 高 18 KB/s 中断模式 45% 中 32 KB/s DMA模式 <5% 低 78 KB/s

DMA配置关键步骤如下:
1. 将IIC数据寄存器映射为DMA外设源/目标端口
2. 设置传输方向(内存←IIC_RX 或 IIC_TX→内存)
3. 配置FIFO触发阈值(如半满中断)
4. 启动DMA通道并使能IIC模块DMA请求位(I2CCTL.DMAEN)

最终实现零CPU干预下的后台批量数据搬运,显著释放处理器资源用于其他任务调度。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:IIC是一种广泛应用于嵌入式系统中的串行通信协议,适用于微控制器与传感器、存储器等外设之间的低速数据交换。本文聚焦于基于ARM Cortex-M3内核的LM3S9B96微控制器,详细讲解如何通过IIC协议与EEPROM进行可靠的数据读写操作。内容涵盖IIC总线原理、寄存器配置、通信流程控制及错误处理机制,并结合eeprom.c源码展示从初始化到数据传输的完整实现过程。该项目对掌握嵌入式系统中IIC通信的实际应用具有重要实践价值。

本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

赞(0)
未经允许不得转载:上海聚慕医疗器械有限公司 » IIC型是什么型号基于ARM架构的IIC通信实现与EEPROM读写实战

登录

找回密码

注册