本文还有配套的精品资源,点击获取
简介:IIC是一种广泛应用于嵌入式系统中的串行通信协议,适用于微控制器与传感器、存储器等外设之间的低速数据交换。本文聚焦于基于ARM Cortex-M3内核的LM3S9B96微控制器,详细讲解如何通过IIC协议与EEPROM进行可靠的数据读写操作。内容涵盖IIC总线原理、寄存器配置、通信流程控制及错误处理机制,并结合eeprom.c源码展示从初始化到数据传输的完整实现过程。该项目对掌握嵌入式系统中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表示响应。
此结构构成了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的工作机制如下:
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 为例:
典型初始化代码:
// 使能主模式
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 ADRSZ DATARDY 典型轮询等待代码:
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);
逻辑分析与参数说明:
SYSCTL_RCGC2_R |= SYSCTL_RCGC2_GPIOB; while(...) GPIO_PORTB_AFSEL_R |= ... GPIO_PORTB_PCTL_R = ... ⚠️ 注意:
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) 虽然可以启用内部上拉,但在实际应用中建议使用 外部精密上拉电阻 (通常为4.7kΩ),原因如下:
- 内部上拉阻值较大且不一致,影响上升沿时间;
- 多设备挂载时总线负载增加,需根据总线电容重新计算最佳上拉值;
- 提升抗干扰能力,尤其是在长距离布线场景中。
此外,还需注意:
- 上拉电压应与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 I2C_SPEED tpr I2C0_MTPR_R 例如,当系统时钟为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 I2C_MCR_LPBK 一旦设置 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Ω 是常见做法。
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; // 成功
}
函数返回码说明:
该函数实现了参数化配置(支持不同速率)、错误反馈、资源使能完整闭环。
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 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
注意 :
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外设的低端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)中:
可通过以下代码检测:
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 {
// 继续后续数据传输
}
应答处理流程表
重要规则 :主设备在最后一次读取前应主动发送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 字节写入的四步操作序列解析
字节写允许主设备向指定内存地址写入单个字节数据。完整流程如下:
- 发送起始信号
- 发送设备写地址(0xA0)
- 发送目标内存地址(Address High + Low)
- 发送要写入的数据字节
- 等待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 随机读取中的地址重发送技巧
随机读允许主设备从任意地址读取数据。其流程分为两个阶段:
- 写阶段 :发送设备地址 + 写命令,接着发送目标内存地址;
- 读阶段 :不发送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 i2c_wait_for_done i2c_clear_bus 这些函数构成了清晰的功能树,便于开发者按需调用或扩展。
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;
}
性能优势对比表
连续读大幅减少了协议握手次数,尤其适合传感器日志回传、固件更新等场景。
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信号(低电平)
常见异常现象及对应原因如下表所示:
此外,使用 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数据时:
DMA配置关键步骤如下:
1. 将IIC数据寄存器映射为DMA外设源/目标端口
2. 设置传输方向(内存←IIC_RX 或 IIC_TX→内存)
3. 配置FIFO触发阈值(如半满中断)
4. 启动DMA通道并使能IIC模块DMA请求位(I2CCTL.DMAEN)
最终实现零CPU干预下的后台批量数据搬运,显著释放处理器资源用于其他任务调度。
本文还有配套的精品资源,点击获取
简介:IIC是一种广泛应用于嵌入式系统中的串行通信协议,适用于微控制器与传感器、存储器等外设之间的低速数据交换。本文聚焦于基于ARM Cortex-M3内核的LM3S9B96微控制器,详细讲解如何通过IIC协议与EEPROM进行可靠的数据读写操作。内容涵盖IIC总线原理、寄存器配置、通信流程控制及错误处理机制,并结合eeprom.c源码展示从初始化到数据传输的完整实现过程。该项目对掌握嵌入式系统中IIC通信的实际应用具有重要实践价值。
本文还有配套的精品资源,点击获取














