云计算百科
云计算领域专业知识百科平台

STM32开发之IIC通信详解:从协议基础到FM24CL04实战

1. IIC概述

IIC(Inter-Integrated Circuit,集成电路间总线)是一种由飞利浦公司(现恩智浦半导体)开发的两线式串行通信总线,广泛应用于微控制器与各类外设(如 EEPROM、传感器、RTC 时钟芯片等)之间的低速通信。

IIC总线仅需两根信号线即可完成数据传输:

  • SDA(Serial Data Line,串行数据线):用于传输数据,双向传输,数据在 SCL 高电平期间必须保持稳定。
  • SCL(Serial Clock Line,串行时钟线):用于传输时钟信号,由主机产生,控制数据传输的节奏。

IIC总线具有以下显著特点:

  • 两线制:仅需 SDA 和 SCL 两根线,节省引脚资源。
  • 多主机多从机:总线上可挂载多个设备,每个设备具有唯一的 7 位或 10 位地址。
  • 半双工通信:同一时刻只能有一个方向的数据传输。
  • 速率适中:标准模式 100kbit/s,快速模式 400kbit/s,高速模式 3.4Mbit/s。
  • 开漏输出:SDA 和 SCL 均为开漏结构,需外接上拉电阻。

在 STM32 中,IIC 外设通常集成在芯片内部,支持硬件 IIC 和软件模拟 IIC 两种实现方式。硬件 IIC 由芯片内部外设自动处理时序,软件模拟 IIC 则通过 GPIO 翻转电平实现,两者各有优劣。

2. IIC协议

2.1 总线空闲状态

当总线处于空闲状态时,SDA 和 SCL 均为高电平。此时总线上没有任何设备占用通信,主机可以发起通信。

2.2 起始条件(START)

起始条件是 IIC 通信的启动标志,由主机产生。其时序要求为:在 SCL 保持高电平期间,SDA 由高电平跳变为低电平。

起始条件产生后,总线进入忙碌状态,其他设备不能再发起通信。

2.3 停止条件(STOP)

停止条件是 IIC 通信的结束标志,同样由主机产生。其时序要求为:在 SCL 保持高电平期间,SDA 由低电平跳变为高电平。

停止条件产生后,总线恢复空闲状态,释放总线控制权。

2.4 数据有效性

IIC 协议规定,数据在传输过程中必须满足以下时序要求:

  • 数据稳定期:在 SCL 高电平期间,SDA 上的数据必须保持稳定,不允许发生变化。
  • 数据变化期:在 SCL 低电平期间,SDA 上的数据才允许发生变化。

这一规定确保了接收方在 SCL 高电平采样时,读取到的数据是稳定可靠的。

2.5 应答机制(ACK/NACK)

IIC协议采用应答机制来确认数据接收是否成功。每传输一个字节(8位数据)后,接收方需要返回一个应答位:

  • ACK(应答):接收方在第9个时钟周期将SDA拉低,表示成功接收数据,可以继续传输。
  • NACK(非应答):接收方在第9个时钟周期保持SDA为高电平,表示未成功接收或不再接收数据。

主机在发送数据后,需要释放SDA并等待从机的应答信号;从机在接收数据后,需要主动拉低SDA以产生应答。

2.6 完整数据传输时序

一次完整的IIC数据传输过程如下:

  • 主机产生起始条件(START)。
  • 主机发送从机地址(7位地址 + 1位读写标志位),共8位。
  • 从机返回应答位(ACK)。
  • 主机发送寄存器地址或数据(每字节后跟一个应答位)。
  • 根据读写方向,继续传输数据或切换方向。
  • 主机产生停止条件(STOP),结束通信。
  • 2.7 时序图说明

    IIC时序图是理解协议的关键工具,下面用文字描述标准时序图的各个阶段:

    起始阶段:SCL为高,SDA从高跳变为低,产生START信号。

    地址传输阶段:主机在SCL的驱动下,逐位发送从机地址,先发送最高位(MSB),共7位地址,随后发送1位读写标志(0表示写,1表示读)。

    应答阶段:第9个时钟周期,从机拉低SDA,产生ACK应答。

    数据传输阶段:主机继续发送数据字节,每个字节8位,每字节后跟一个ACK应答位。数据在SCL高电平期间保持稳定,在SCL低电平期间变化。

    停止阶段:SCL为高,SDA从低跳变为高,产生STOP信号,总线释放。

    2.8 读写操作示例

    写操作流程:主机发送START → 发送从机地址+写标志(0)→ 等待ACK → 发送寄存器地址 → 等待ACK → 发送数据 → 等待ACK → 发送STOP。

    读操作流程:主机发送START → 发送从机地址+写标志(0)→ 等待ACK → 发送寄存器地址 → 等待ACK → 重新发送START(重复起始条件)→ 发送从机地址+读标志(1)→ 等待ACK → 读取数据 → 主机发送NACK → 发送STOP。

    3. STM32中的IIC实现

    3.1 硬件IIC

    STM32系列芯片内置IIC外设,支持主机模式和从机模式,具备中断和DMA传输能力。硬件IIC的优点是CPU占用低、时序精确,但配置相对复杂,且部分型号的硬件IIC存在兼容性问题。

    3.2 软件模拟IIC

    软件模拟IIC通过GPIO引脚手动翻转电平,按照IIC时序要求逐位传输数据。其优点是灵活、兼容性好、不受芯片型号限制,缺点是占用CPU资源较多。

    3.3 上拉电阻

    由于IIC总线采用开漏输出结构,SDA和SCL必须外接上拉电阻。典型上拉电阻值为4.7kΩ(标准模式)或2.2kΩ(快速模式),具体取值需根据总线电容和通信速率确定。

    4. FM24CL04 EEPROM 详解

    FM24CL04 是 Cypress(现 Infineon)推出的一款4Kbit 铁电存储器(FRAM),采用 IIC 接口与主机通信。与传统的 EEPROM 相比,FRAM 具有写入速度快、擦写寿命长、功耗低等显著优势,非常适合需要频繁读写数据的应用场景。

    4.1 主要特性

    • 存储容量:4Kbit(512 × 8 位),组织为两个 256 字节的存储区。
    • 接口类型:标准 IIC 接口,支持 100kHz 标准模式和 400kHz 快速模式。
    • 写入速度:无需等待写入周期,写入后立即可读,写入速度远快于传统 EEPROM。
    • 擦写寿命:高达 10 亿次读写循环,远超普通 EEPROM 的 100 万次。
    • 数据保持:掉电后数据可保持 10 年以上。
    • 工作电压:2.7V ~ 3.6V,兼容 3.3V 系统。
    • 封装形式:常见 SOP-8 封装,引脚与标准 24C04 EEPROM 兼容。

    4.2 器件地址与存储区映射

    FM24CL04 的从机地址为 7 位,格式为 1010 A2 A1 A0,其中 A2、A1 为硬件地址引脚,A0 为存储区选择位。由于芯片内部划分为两个 256 字节的存储区,A0 位用于选择访问哪个存储区:

    • A0 = 0:访问存储区 0(地址 0x00 ~ 0xFF)。
    • A0 = 1:访问存储区 1(地址 0x00 ~ 0xFF)。

    因此,FM24CL04 的完整从机地址为 0xA0(写)/ 0xA1(读)(对应存储区 0),或 0xA2(写)/ 0xA3(读)(对应存储区 1),具体取决于 A2、A1 引脚的接法。

    4.3 写操作时序

    FM24CL04 的写操作与标准 IIC EEPROM 类似,但无需等待内部写周期。具体流程如下:

  • 主机发送起始条件(START)。
  • 主机发送从机地址 + 写标志(如 0xA0)。
  • 从机返回应答位(ACK)。
  • 主机发送字节地址(0x00 ~ 0xFF,对应所选存储区内的地址)。
  • 从机返回应答位(ACK)。
  • 主机发送数据字节,每字节后从机返回 ACK。
  • 主机发送停止条件(STOP),写入完成。
  • 由于 FRAM 无需写周期,主机可以在发送 STOP 后立即发起下一次读写操作,无需像传统 EEPROM 那样轮询 ACK 或等待延时。

    4.4 读操作时序

    FM24CL04 支持两种读操作方式:当前地址读和随机地址读。

    随机地址读流程:

  • 主机发送起始条件(START)。
  • 主机发送从机地址 + 写标志(如 0xA0)。
  • 从机返回应答位(ACK)。
  • 主机发送字节地址(目标读取地址)。
  • 从机返回应答位(ACK)。
  • 主机重新发送起始条件(重复起始条件)。
  • 主机发送从机地址 + 读标志(如 0xA1)。
  • 从机返回应答位(ACK)。
  • 主机读取数据字节,每字节后主机发送 ACK(除最后一字节发送 NACK)。
  • 主机发送停止条件(STOP),读取完成。
  • 4.5 与 STM32 的硬件连接

    FM24CL04 与 STM32 的连接非常简单,只需两根 IIC 信号线加上拉电阻:

    • SDA:连接到 STM32 的 IIC 数据引脚(如 PB7)。
    • SCL:连接到 STM32 的 IIC 时钟引脚(如 PB6)。
    • VCC:连接到 3.3V 电源。
    • GND:连接到地。
    • WP:写保护引脚,接地表示允许写入,接高电平表示写保护。
    • A2、A1:地址引脚,通常接地。

    SDA 和 SCL 需要外接上拉电阻(典型值 4.7kΩ),与 STM32 的 IIC 外设或软件模拟 IIC 配合使用。

    4.6 应用场景

    FM24CL04 凭借其高速写入和超长寿命,广泛应用于以下场景:

    • 数据采集系统:频繁记录传感器数据,无需担心擦写寿命。
    • 参数存储:保存系统配置参数、校准数据等。
    • 掉电保护:在系统掉电瞬间快速保存关键状态。
    • 日志记录:记录运行日志、故障信息等。

    5. STM32F4xx I2C控制器

    STM32F4xx系列微控制器内置了功能完善的I2C外设控制器,支持主机模式和从机模式,具备中断、DMA传输以及多主机仲裁等高级特性。本节将详细介绍STM32F4xx I2C控制器的内部结构、寄存器配置以及实际应用方法。

    5.1 I2C控制器内部结构

    STM32F4xx的I2C外设主要由以下功能模块组成:

    • 时钟控制单元:负责产生SCL时钟信号,支持标准模式(100kHz)、快速模式(400kHz)以及快速模式+(1MHz)。
    • 数据移位寄存器:实现SDA数据的串行发送与接收,支持7位和10位地址模式。
    • 地址比较器:在从机模式下自动比较接收到的地址与自身地址,匹配时产生中断。
    • 状态寄存器:记录通信过程中的各种状态标志,如起始条件、停止条件、应答状态等。
    • 控制寄存器:配置I2C的工作模式、中断使能、DMA请求等参数。
    • 数据寄存器:用于读写待发送或已接收的数据字节。

    5.2 I2C引脚配置

    STM32F4xx的I2C外设通常可以映射到多个GPIO引脚,以I2C1为例,其引脚映射如下:

    • I2C1_SCL:可映射到 PB6、PB8 等引脚。
    • I2C1_SDA:可映射到 PB7、PB9 等引脚。
    • I2C2_SCL:可映射到 PB10、PF1 等引脚。
    • I2C2_SDA:可映射到 PB11、PF0 等引脚。
    • I2C3_SCL:可映射到 PA8、PH7 等引脚。
    • I2C3_SDA:可映射到 PC9、PH8 等引脚。

    在使用I2C外设前,需要将对应的GPIO引脚配置为复用功能(AF)模式,并设置为开漏输出、带上拉电阻。以I2C1使用PB6和PB7为例,GPIO配置代码如下:

    // 使能GPIOB和I2C1时钟
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);

    // 配置PB6为SCL,PB7为SDA,复用功能AF4
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_OpenDrain;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    // 将PB6、PB7连接到I2C1的复用功能
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_I2C1);
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_I2C1);

    5.3 I2C初始化配置

    完成GPIO配置后,需要对I2C外设进行初始化,设置通信速率、地址模式等参数。使用标准外设库的初始化代码如下:

    I2C_InitTypeDef I2C_InitStructure;

    // I2C1配置为快速模式,400kHz
    I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
    I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
    I2C_InitStructure.I2C_OwnAddress = 0x00; // 主机模式,自身地址可设为0
    I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
    I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    I2C_InitStructure.I2C_ClockSpeed = 400000; // 400kHz

    I2C_Init(I2C1, &I2C_InitStructure);
    I2C_Cmd(I2C1, ENABLE);

    在配置I2C时钟频率时,需要注意APB1总线的时钟频率。I2C的时钟由APB1时钟分频得到,计算公式为:

    // I2C时钟频率 = APB1时钟频率 / (2 × (CCR + 1)),快速模式
    // 或 I2C时钟频率 = APB1时钟频率 / (2 × CCR),标准模式

    5.4 主机发送数据

    作为主机发送数据时,需要依次完成起始条件、发送从机地址、发送数据、停止条件等步骤。以下是一个向FM24CL04写入数据的完整示例:

    void I2C_WriteByte(uint8_t slaveAddr, uint8_t regAddr, uint8_t data)
    {
    // 发送起始条件
    I2C_GenerateSTART(I2C1, ENABLE);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));

    // 发送从机地址 + 写标志
    I2C_Send7bitAddress(I2C1, slaveAddr, I2C_Direction_Transmitter);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

    // 发送寄存器地址
    I2C_SendData(I2C1, regAddr);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

    // 发送数据
    I2C_SendData(I2C1, data);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

    // 发送停止条件
    I2C_GenerateSTOP(I2C1, ENABLE);
    }

    5.5 主机接收数据

    主机接收数据时,需要先发送从机地址和寄存器地址,然后重新发送起始条件并切换为接收模式。以下是从FM24CL04读取一个字节的示例:

    uint8_t I2C_ReadByte(uint8_t slaveAddr, uint8_t regAddr)
    {
    uint8_t data = 0;

    // 发送起始条件
    I2C_GenerateSTART(I2C1, ENABLE);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));

    // 发送从机地址 + 写标志(先写寄存器地址)
    I2C_Send7bitAddress(I2C1, slaveAddr, I2C_Direction_Transmitter);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

    // 发送寄存器地址
    I2C_SendData(I2C1, regAddr);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

    // 重新发送起始条件(重复起始)
    I2C_GenerateSTART(I2C1, ENABLE);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));

    // 发送从机地址 + 读标志
    I2C_Send7bitAddress(I2C1, slaveAddr, I2C_Direction_Receiver);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));

    // 读取数据,发送NACK后停止
    I2C_AcknowledgeConfig(I2C1, DISABLE);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED));
    data = I2C_ReceiveData(I2C1);
    I2C_GenerateSTOP(I2C1, ENABLE);
    I2C_AcknowledgeConfig(I2C1, ENABLE);

    return data;
    }

    5.6 使用DMA进行批量传输

    对于大量数据的传输,使用DMA可以显著降低CPU负载。STM32F4xx的I2C外设支持DMA请求,配置DMA传输的步骤如下:

  • 配置DMA通道,设置数据传输方向、数据宽度和传输数量。
  • 使能I2C的DMA请求功能。
  • 启动DMA传输,I2C会自动触发DMA请求。
  • 传输完成后,DMA产生中断通知CPU。
  • 以下是一个使用DMA向FM24CL04写入多个字节的示例:

    void I2C_DMA_Write(uint8_t slaveAddr, uint8_t regAddr, uint8_t *buffer, uint16_t len)
    {
    // 配置DMA1通道6用于I2C1发送
    DMA_InitTypeDef DMA_InitStructure;
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);

    DMA_InitStructure.DMA_Channel = DMA_Channel_0;
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&I2C1->DR;
    DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)buffer;
    DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
    DMA_InitStructure.DMA_BufferSize = len;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
    DMA_Init(DMA1_Stream6, &DMA_InitStructure);

    // 发送起始条件和从机地址
    I2C_GenerateSTART(I2C1, ENABLE);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
    I2C_Send7bitAddress(I2C1, slaveAddr, I2C_Direction_Transmitter);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

    // 发送寄存器地址
    I2C_SendData(I2C1, regAddr);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

    // 使能DMA请求并启动传输
    I2C_DMACmd(I2C1, ENABLE);
    DMA_Cmd(DMA1_Stream6, ENABLE);

    // 等待传输完成
    while (!DMA_GetFlagStatus(DMA1_Stream6, DMA_FLAG_TCIF6));
    DMA_Cmd(DMA1_Stream6, DISABLE);
    I2C_DMACmd(I2C1, DISABLE);

    // 发送停止条件
    I2C_GenerateSTOP(I2C1, ENABLE);
    }

    5.7 状态标志函数

    在标准外设库中,I2C_GetFlagStatus 和 I2C_ClearFlag 是用于查询和清除 I2C 外设状态标志的两个重要函数,常用于轮询方式判断通信状态或处理异常情况。

    I2C_GetFlagStatus 用于读取 I2C 外设的指定状态标志位,其函数原型如下:

    FlagStatus I2C_GetFlagStatus(I2C_TypeDef* I2Cx, uint32_t I2C_FLAG);

    该函数返回 SET 或 RESET,用于判断对应标志是否置位。常用标志包括:

    • I2C_FLAG_BUSY:总线忙标志,置位表示总线正在通信。
    • I2C_FLAG_AF:应答失败标志,置位表示从机未应答。
    • I2C_FLAG_BTF:字节发送完成标志,置位表示数据寄存器已空且移位寄存器已发送完毕。
    • I2C_FLAG_TXE:数据寄存器空标志,置位表示可以写入下一个数据。
    • I2C_FLAG_RXNE:数据寄存器非空标志,置位表示已接收到数据。

    使用示例:

    // 等待数据发送完成
    while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BTF) == RESET);

    // 检查从机是否应答
    if (I2C_GetFlagStatus(I2C1, I2C_FLAG_AF) == SET)
    {
    // 从机无应答,处理错误
    }

    I2C_ClearFlag 用于手动清除指定的状态标志位,其函数原型如下:

    void I2C_ClearFlag(I2C_TypeDef* I2Cx, uint32_t I2C_FLAG);

    当通信异常导致某些标志(如应答失败标志 AF、总线错误标志 BERR)一直保持置位时,需要调用该函数清除标志,以便恢复正常通信。使用示例:

    // 清除应答失败标志
    I2C_ClearFlag(I2C1, I2C_FLAG_AF);

    // 清除总线错误标志
    I2C_ClearFlag(I2C1, I2C_FLAG_BERR);

    在实际开发中,建议在每次通信开始前先清除相关标志,避免上一次通信遗留的标志影响本次判断;在通信过程中则通过 I2C_GetFlagStatus 轮询关键标志,确保每一步操作都正确完成。

    5.8 常见问题与调试技巧

    在使用STM32F4xx I2C控制器时,可能会遇到以下常见问题:

    • 总线忙(BUSY)标志无法清除:当通信异常中断时,I2C总线的BUSY标志可能一直保持置位。解决方法是将I2C外设复位(I2C_SoftwareResetCmd),或切换GPIO引脚电平强制释放总线。
    • SCL时钟频率不准确:检查APB1时钟频率配置是否正确,以及CCR寄存器的分频值是否计算准确。
    • 从机无应答(NACK):检查从机地址是否正确、上拉电阻是否接好、从机是否处于正常工作状态。
    • DMA传输数据错位:确保DMA配置的数据宽度与I2C数据寄存器一致(均为8位),并正确设置传输方向。
    • 中断优先级冲突:当I2C中断与DMA中断同时使用时,需要合理设置NVIC优先级,避免数据丢失。

    调试I2C通信时,建议使用逻辑分析仪或示波器观察SDA和SCL的波形,确认起始条件、地址、数据和停止条件是否符合协议要求。

    5.9 软件模拟 IIC 示例(GPIO 位操作)

    除了使用 STM32 硬件 I2C 外设外,也可以通过 GPIO 引脚手动翻转电平来软件模拟 IIC 时序。这种方式不依赖芯片内部 I2C 外设,兼容性更好,常用于调试或对时序要求不高的场景。下面给出一个完整的软件模拟 IIC 读写示例。

    首先定义 SCL 和 SDA 引脚,其中 SDA 需要同时支持输入和输出:

    #define SCL PBout(8)
    #define SDA_R PBin(9)
    #define SDA_W PBout(9)

    起始条件:在 SCL 保持高电平期间,SDA 由高电平跳变为低电平。建立时间 Tsu 至少为 4.7us,保持时间 Thd 至少为 4.0us,因此延时取 4us 以上:

    void i2c_start(void)
    {
    SCL = 1;
    SDA_W = 1;
    delay_us(4);
    SDA_W = 0;
    delay_us(4);
    }

    停止条件:在 SCL 保持高电平期间,SDA 由低电平跳变为高电平:

    void i2c_stop(void)
    {
    SCL = 0;
    SDA_W = 0;
    delay_us(4);
    SCL = 1;
    SDA_W = 1;
    delay_us(4);
    }

    主机发送一个字节:数据在 SCL 低电平期间变化,在 SCL 高电平期间保持稳定,从高位(MSB)开始逐位发送:

    void i2c_send_byte(uint8_t c)
    {
    int i;
    SCL = 0; // 拉低时钟线,允许 SDA 改变
    delay_us(2);
    for (i = 0; i < 8; i++)
    {
    SDA_W = !!(c & (1 << (7 – i))); // 先发高位
    delay_us(2);
    SCL = 1; // 锁存数据,让从机可以识别
    delay_us(2);
    SCL = 0;
    delay_us(2);
    }
    SDA_W = 1; // 一个字节发送完毕,释放数据线,等待对方回 ACK
    delay_us(2);
    }

    主机接收一个字节:同样在 SCL 低电平期间允许 SDA 变化,在高电平期间采样读取,从高位开始逐位接收:

    uint8_t i2c_recv_byte(void)
    {
    uint8_t recv = 0;
    int i;
    for (i = 0; i < 8; i++)
    {
    SCL = 0; // 只有在时钟线为低电平时,SDA 才能改变
    delay_us(2);

    SCL = 1; // 时钟线为高电平,让 SDA 保持稳定
    delay_us(2);

    recv |= SDA_R << (7 – i); // 先接收高位(大端式)
    }
    SCL = 0; // 拉低时钟线,允许对方继续发数据
    return recv;
    }

    主机发送应答位(ACK/NACK):在 SCL 低电平期间设置 SDA 电平,高电平期间锁存给从机识别:

    void i2c_ack(uint8_t ack) // 回 ACK
    {
    SCL = 0;
    delay_us(2);
    SDA_W = ack;
    delay_us(2);
    SCL = 1;
    delay_us(2);
    SCL = 0; // 拉低时钟线,允许对方继续发数据
    }

    主机等待从机应答:释放 SDA 后拉高 SCL,检测 SDA 是否被从机拉低(ACK)。若超时未应答,则发送停止条件并返回错误:

    uint8_t i2c_wait_ack(void)
    {
    uint8_t timeout = 0;
    SCL = 1; // 锁存数据,使 SDA 保持稳定
    delay_us(2);
    while (SDA_R) // 等待 SDA_R 变为 0
    {
    timeout++;
    if (timeout > 250)
    {
    i2c_stop();
    return 1; // 超时,从机无应答
    }
    }
    SCL = 0; // 拉低时钟线,允许对方继续发数据
    return 0;
    }

    以从机地址 0b0101110(7 位地址)为例,读取一个字节时,主机先发送从机地址 + 读标志(1),即 0b0101110 1,随后等待从机应答,再逐位读取数据。上述代码中的延时参数(4us、2us)已满足标准模式 100kHz 下建立时间 Tsu ≥ 4.7us、保持时间 Thd ≥ 4.0us、数据有效时间 ≥ 3.4us 的时序要求。

    6. 总结

    本文围绕 STM32 的 IIC 通信展开,从协议基础到实际工程应用进行了系统梳理。核心要点可以归纳为以下几个方面:

    • 协议层面:IIC 是两线制、半双工的串行通信总线,仅需 SDA 和 SCL 两根信号线。通信过程由起始条件(START)、从机地址、应答位(ACK/NACK)、数据字节和停止条件(STOP)组成,数据在 SCL 高电平期间保持稳定、低电平期间变化。
    • 实现方式:STM32 支持硬件 IIC 和软件模拟 IIC 两种方案。硬件 IIC 由芯片外设自动处理时序,CPU 占用低、时序精确,但配置复杂且部分型号存在兼容性问题;软件模拟 IIC 通过 GPIO 翻转电平实现,灵活通用,但占用 CPU 资源较多。
    • 典型器件:以 FM24CL04 铁电存储器(FRAM)为例,其采用 IIC 接口,写入无需等待写周期、擦写寿命高达 10 亿次,非常适合频繁读写和掉电保护等场景。器件地址为 1010 A2 A1 A0,通过 A0 位选择两个 256 字节存储区。
    • 工程实践:STM32F4xx 的 I2C 控制器支持主机/从机模式、中断和 DMA 传输。开发时需正确配置 GPIO 复用功能、初始化 I2C 外设,并通过 I2C_GetFlagStatus 和 I2C_ClearFlag 轮询与清除状态标志,确保通信可靠。
    • 调试要点:遇到总线忙、从机无应答、DMA 数据错位等问题时,应优先检查地址、上拉电阻、时钟分频和中断优先级,并借助逻辑分析仪或示波器观察 SDA/SCL 波形定位问题。

    总体而言,IIC 协议本身并不复杂,难点在于时序控制和异常处理。建议读者先通过软件模拟 IIC 理解协议细节,再切换到硬件 IIC 提升效率,并结合实际器件(如 FM24CL04)完成读写验证,逐步掌握 STM32 的 IIC 开发。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » STM32开发之IIC通信详解:从协议基础到FM24CL04实战
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!