第10章 I2C介绍及应用

第十章 I2C介绍及应用

1. I2C简介

如果我们直接控制 STM32 的两个 GPIO 引脚,分别用作 SCL 及 SDA,按照上述信号的时序要求,直接像控制 LED 灯那样控制引脚的输出 (若是接收数据时则读取 SDA 电平),就可以实现 I2C 通讯。同样,假如我们按照 USART 的要求去控制引脚,也能实现 USART 通讯。所以只要遵守协议,就是标准的通讯,不管您如何实现它,不管是 ST 生产的控制器还是 ATMEL 生产的存储器,都能按通讯标准交互。

由于直接控制 GPIO 引脚电平产生通讯时序时,需要由 CPU 控制每个时刻的引脚状态,所以称之为“软件模拟协议”方式。

相对地,还有“硬件协议”方式, STM32 的 I2C 片上外设专门负责实现 I2C 通讯协议,只要配置好该外设,它就会自动根据协议要求产生通讯信号,收发数据并缓存起来, CPU 只要检测该外设的状态和访问数据寄存器,就能完成数据收发。这种由硬件外设处理 I2C 协议的方式减轻了CPU 的工作,且使软件设计更加简单。

屏幕截图 20250629 084331png

2. I2C初始化结构体

跟其它外设一样, STM32 标准库提供了 I2C 初始化结构体及初始化函数来配置 I2C 外设。初始化结构体及函数定义在库文件“stm32f10x_i2c.h”及“stm32f10x_i2c.c”中

typedef struct {
    uint32_t I2C_ClockSpeed; // 设置 SCL 时钟频率,此值要低于 400000
    uint16_t I2C_Mode; //指定工作模式,可选 I2C 模式及 SMBUS 模式 
    uint16_t I2C_DutyCycle; // 指定时钟占空比,可选 low/high = 2:1 及 16:9 模式
    uint16_t I2C_OwnAddress1; // 指定自身的 I2C 设备地址 
    uint16_t I2C_Ack; // 使能或关闭响应 (一般都要使能) 
    uint16_t I2C_AcknowledgedAddress; // 指定地址的长度,可为 7 位及 10 位 
}I2C_InitTypeDef;

1. I2C_ClockSpeed (uint32_t)

  • 说明:该字段用于设置 SCL (时钟线) 的频率。I2C 总线的时钟频率应该小于 400 kHz,适用于标准模式 (100 kHz) 或快速模式 (400 kHz)。这个值决定了数据传输的速率。
  • 典型值:例如,设置为 100000 表示 100 kHz 时钟,设置为 400000 表示 400 kHz 时钟。

2. I2C_Mode (uint16_t)

  • 说明:该字段用于指定 I2C 工作模式。可以选择以下几种模式:

    • I2C_Mode_I2C:I2C 模式,常用模式。
    • I2C_Mode_SMBUS:SMBus 模式,SMBus 是 I2C 的一个扩展,主要用于更低功耗和更简单的设备。

    该字段控制了外设的工作模式,具体设置依据应用需求而定。

3. I2C_DutyCycle (uint16_t)

  • 说明:该字段指定 I2C 时钟的占空比,即高电平与低电平的比例。I2C 时钟信号的占空比决定了信号的波形,通常有两种选择:

    • I2C_DutyCycle_2:占空比为 2:1,表示高电平的时间是低电平的两倍。
    • I2C_DutyCycle_16_9:占空比为 16:9,表示高电平的时间是低电平的 16/9 倍。

    在高频模式下(例如 400 kHz),使用 16:9 的占空比能提供更高的稳定性。

4. I2C_OwnAddress1 (uint16_t)

  • 说明:该字段设置设备的 I2C 地址。I2C 总线上的每个设备都有一个唯一的地址,用于区分不同设备。
    • I2C 地址通常为 7 位或 10 位。此字段设置设备的 7 位地址(或者低 7 位,10 位地址的配置需要额外设置)。

5. I2C_Ack (uint16_t)

  • 说明:该字段用于使能或禁用响应(ACK)。在 I2C 通信中,ACK 是设备与主机之间交换数据时的响应信号。

    • I2C_Ack_Enable:使能 ACK 响应,通常都是启用此选项。
    • I2C_Ack_Disable:禁用 ACK 响应,通常用于一些特殊的通信场景。

    使能 ACK 是 I2C 通信的标准操作。

6. I2C_AcknowledgedAddress (uint16_t)

  • 说明:该字段指定设备的地址长度,支持 7 位或 10 位地址:

    • I2C_AcknowledgedAddress_7bit:7 位地址模式。
    • I2C_AcknowledgedAddress_10bit:10 位地址模式。

    地址的位数决定了设备能够连接到 I2C 总线的数量。在选择时,需要根据实际硬件的地址规格进行配置。

3. I2C使用示例

EEPROM 是一种掉电后数据不丢失的存储器,常用来存储一些配置信息,以便系统重新上电的时候加载之。 EEPOM 芯片最常用的通讯方式就是 I2C 协议,本小节以 EEPROM 的读写实验为大家讲解 STM32 的 I2C 使用方法。实验中 STM32 的 I2C 外设采用主模式,分别用作主发送器和主接收器,通过查询事件的方式来确保正常通讯。

屏幕截图 20250629 090335png

本实验板中的 EEPROM 芯片 (型号: AT24C02) 的 SCL 及 SDA 引脚连接到了 STM32 对应的 I2C引脚中,结合上拉电阻,构成了 I2C 通讯总线,它们通过 I2C 总线交互。 EEPROM 芯片的设备地址一共有 7 位,其中高 4 位固定为: 1010 b,低 3 位则由 A0/A1/A2 信号线的电平决定

屏幕截图 20250629 092452png

按照我们此处的连接, A0/A1/A2 均为 0,所以 EEPROM 的 7 位设备地址是: 101 0000b,即 0x50。由于 I2C 通讯时常常是地址跟读写方向连在一起构成一个 8 位数,且当 R/W 位为 0 时,表示写方向,所以加上 7 位地址,其值为“0xA0”,常称该值为 I2C 设备的“写地址”;当 R/W 位为 1时,表示读方向,加上 7 位地址,其值为“0xA1”,常称该值为“读地址”。

EEPROM 芯片中还有一个 WP 引脚,具有写保护功能,当该引脚电平为高时,禁止写入数据,当引脚为低电平时,可写入数据,我们直接接地,不使用写保护功能。

3.1 I2C_EE相关参数宏定义

#ifndef I2C_EEPROM_H
#define I2C_EEPROM_H

#include "stm32f10x.h"

// 硬件配置
#define EEPROM_I2C             I2C1
#define EEPROM_I2C_CLK         RCC_APB1Periph_I2C1
#define EEPROM_GPIO_PORT       GPIOB
#define EEPROM_GPIO_CLK        RCC_APB2Periph_GPIOB
#define EEPROM_SCL_PIN         GPIO_Pin_6
#define EEPROM_SDA_PIN         GPIO_Pin_7
#define EEPROM_ADDRESS         0xA0    // 7位地址 (0x50 << 1)
#define EEPROM_PAGE_SIZE       8       // AT24C02页大小
/* 
    AT24C02 2kb = 2048bit = 2048/8 B = 256 B
    32 pages of 8 bytes each
    Device Address
    1 0 1 0 A2 A1 A0 R/W
    1 0 1 0 0  0  0  0 = 0XA0
    1 0 1 0 0  0  0  1 = 0XA1 
    EEPROM地址宏定义
    #define EEPROM_Block0_ADDRESS 0xA0   
    #define EEPROM_Block1_ADDRESS 0xA2 
    #define EEPROM_Block2_ADDRESS 0xA4  
    #define EEPROM_Block3_ADDRESS 0xA6
*/  

// 超时设置
#define I2C_TIMEOUT            100000  // 超时计数器值
// 初始化函数
void I2C_EEPROM_Init(void);
// 读写函数
uint8_t I2C_EEPROM_WriteByte(uint16_t addr, uint8_t data);
uint8_t I2C_EEPROM_ReadByte(uint16_t addr, uint8_t* data);
uint8_t I2C_EEPROM_WritePage(uint16_t addr, uint8_t* data, uint8_t len);
uint8_t I2C_EEPROM_ReadBuffer(uint16_t addr, uint8_t* buffer, uint16_t len);

#endif // I2C_EEPROM_H

3.2 I2C_EE初始化

// I2C初始化
void I2C_EEPROM_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct;
    I2C_InitTypeDef I2C_InitStruct;

    // 1. 使能时钟
    RCC_APB1PeriphClockCmd(EEPROM_I2C_CLK, ENABLE);
    RCC_APB2PeriphClockCmd(EEPROM_GPIO_CLK, ENABLE);

    // 2. 配置GPIO为开漏输出
    GPIO_InitStruct.GPIO_Pin = EEPROM_SCL_PIN | EEPROM_SDA_PIN;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(EEPROM_GPIO_PORT, &GPIO_InitStruct);

    // 3. 配置I2C参数
    I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
    I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
    I2C_InitStruct.I2C_OwnAddress1 = 0x0A; // 主机地址,可设为任意值
    I2C_InitStruct.I2C_Ack = I2C_Ack_Enable;
    I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    I2C_InitStruct.I2C_ClockSpeed = 400000; // 400kHz

    I2C_Init(EEPROM_I2C, &I2C_InitStruct);
    I2C_Cmd(EEPROM_I2C, ENABLE);
}

3.3 I2C_EE基本工作函数

// 超时处理
static uint8_t I2C_CheckTimeout(uint32_t timeout) {
    while (timeout--) {
        if (!I2C_GetFlagStatus(EEPROM_I2C, I2C_FLAG_BUSY)) {
            return 1; // 操作成功
        }
    }
    return 0; // 超时
}

// 单字节写入
uint8_t I2C_EEPROM_WriteByte(uint16_t addr, uint8_t data) {
    // 1. 生成START条件
    I2C_GenerateSTART(EEPROM_I2C, ENABLE);
    if (!I2C_CheckTimeout(I2C_TIMEOUT)) return 0;

    // 2. 发送设备地址+写模式
    I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDRESS, I2C_Direction_Transmitter);
    if (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) 
        return 0;

    // 3. 发送内存地址高字节 (对于16位地址)
    I2C_SendData(EEPROM_I2C, (addr >> 8) & 0xFF);
    if (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTING))
        return 0;

    // 4. 发送内存地址低字节
    I2C_SendData(EEPROM_I2C, addr & 0xFF);
    if (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTING))
        return 0;

    // 5. 发送数据
    I2C_SendData(EEPROM_I2C, data);
    if (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
        return 0;

    // 6. 生成STOP条件
    I2C_GenerateSTOP(EEPROM_I2C, ENABLE);
    return 1;
}

// 单字节读取
uint8_t I2C_EEPROM_ReadByte(uint16_t addr, uint8_t* data) {
    // 1. 生成START条件(写模式)
    I2C_GenerateSTART(EEPROM_I2C, ENABLE);
    if (!I2C_CheckTimeout(I2C_TIMEOUT)) return 0;

    // 2. 发送设备地址+写模式
    I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDRESS, I2C_Direction_Transmitter);
    if (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) 
        return 0;

    // 3. 发送内存地址高字节
    I2C_SendData(EEPROM_I2C, (addr >> 8) & 0xFF);
    if (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTING))
        return 0;

    // 4. 发送内存地址低字节
    I2C_SendData(EEPROM_I2C, addr & 0xFF);
    if (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
        return 0;

    // 5. 重新生成START条件(读模式)
    I2C_GenerateSTART(EEPROM_I2C, ENABLE);
    if (!I2C_CheckTimeout(I2C_TIMEOUT)) return 0;

    // 6. 发送设备地址+读模式
    I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDRESS, I2C_Direction_Receiver);
    if (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))
        return 0;

    // 7. 准备接收单个字节(禁用ACK,生成STOP)
    I2C_AcknowledgeConfig(EEPROM_I2C, DISABLE);
    I2C_GenerateSTOP(EEPROM_I2C, ENABLE);

    // 8. 等待数据接收完成
    while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_RECEIVED));

    // 9. 读取数据
    *data = I2C_ReceiveData(EEPROM_I2C);

    // 10. 重新使能ACK
    I2C_AcknowledgeConfig(EEPROM_I2C, ENABLE);
    return 1;
}

// 页写入
uint8_t I2C_EEPROM_WritePage(uint16_t startAddr, uint8_t* data, uint8_t len) {
    // 检查长度是否超过页边界
    uint8_t pageOffset = startAddr % EEPROM_PAGE_SIZE;
    if (len > (EEPROM_PAGE_SIZE - pageOffset)) 
        return 0;

    // 1. 生成START条件
    I2C_GenerateSTART(EEPROM_I2C, ENABLE);

    // 等待START完成
    uint32_t timeout = I2C_TIMEOUT;
    while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_MODE_SELECT)) {
        if (timeout-- == 0) return 0;
    }

    // 2. 发送设备地址+写模式
    I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDRESS, I2C_Direction_Transmitter);

    // 等待地址发送完成
    timeout = I2C_TIMEOUT;
    while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) {
        if (timeout-- == 0) return 0;
    }

    // 3. 发送8位内存地址(AT24C02只需要一个字节地址)
    I2C_SendData(EEPROM_I2C, startAddr & 0xFF);

    // 等待地址发送完成
    timeout = I2C_TIMEOUT;
    while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) {
        if (timeout-- == 0) return 0;
    }

    // 4. 发送数据
    for (uint8_t i = 0; i < len; i++) {
        I2C_SendData(EEPROM_I2C, data[i]);

        // 等待数据发送完成
        timeout = I2C_TIMEOUT;
        while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTING)) {
            if (timeout-- == 0) return 0;
        }
    }

    // 5. 等待最后一个字节传输完成
    timeout = I2C_TIMEOUT;
    while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) {
        if (timeout-- == 0) return 0;
    }

    // 6. 生成STOP条件
    I2C_GenerateSTOP(EEPROM_I2C, ENABLE);

    return 1;
}

// 多字节读取(适用于8位地址的EEPROM)
uint8_t I2C_EEPROM_ReadBuffer(uint16_t startAddr, uint8_t* buffer, uint16_t len) 
{
    // 1. 生成START条件(写模式)
    I2C_GenerateSTART(EEPROM_I2C, ENABLE);

    // 等待START完成
    uint32_t timeout = I2C_TIMEOUT;
    while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_MODE_SELECT)) {
        if (timeout-- == 0) return 0;
    }

    // 2. 发送设备地址+写模式
    I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDRESS, I2C_Direction_Transmitter);

    // 等待地址发送完成
    timeout = I2C_TIMEOUT;
    while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) {
        if (timeout-- == 0) return 0;
    }

    // 3. 发送8位内存地址
    I2C_SendData(EEPROM_I2C, startAddr & 0xFF);

    // 等待地址发送完成 - 使用更通用的状态检查
    timeout = I2C_TIMEOUT;
    while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_TRANSMITTING)) {
        if (timeout-- == 0) return 0;
    }

    // 4. 重新生成START条件(读模式)
    I2C_GenerateSTART(EEPROM_I2C, ENABLE);

    // 等待START完成
    timeout = I2C_TIMEOUT;
    while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_MODE_SELECT)) {
        if (timeout-- == 0) return 0;
    }

    // 5. 发送设备地址+读模式
    I2C_Send7bitAddress(EEPROM_I2C, EEPROM_ADDRESS, I2C_Direction_Receiver);

    // 等待地址发送完成
    timeout = I2C_TIMEOUT;
    while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)) {
        if (timeout-- == 0) return 0;
    }

    // 6. 读取多个字节
    for (uint16_t i = 0; i < len; i++) {
        if (i == len - 1) {
            // 在接收最后一个字节前禁用ACK并生成STOP
            I2C_AcknowledgeConfig(EEPROM_I2C, DISABLE);
            I2C_GenerateSTOP(EEPROM_I2C, ENABLE);
        }

        // 等待数据接收完成
        timeout = I2C_TIMEOUT;
        while (!I2C_CheckEvent(EEPROM_I2C, I2C_EVENT_MASTER_BYTE_RECEIVED)) {
            if (timeout-- == 0) return 0;
        }

        // 读取数据
        buffer[i] = I2C_ReceiveData(EEPROM_I2C);
    }

    // 7. 重新使能ACK
    I2C_AcknowledgeConfig(EEPROM_I2C, ENABLE);
    return 1;
}

3.4 主函数测试

#include "stm32f10x.h"
#include "led.h"
#include "SysTick.h"
#include "usart.h"
#include "i2c_ee.h"
#include <stdio.h>

#define EEPROM_Firstpage 0x00 // EEPROM的起始地址
#define PAGE_SIZE 8           // AT24C02页大小
#define BUFFER_SIZE 256
#define I2C_TIMEOUT 10000     // 超时计数器值

uint8_t I2c_Buf_Write[BUFFER_SIZE]; // 写入缓冲区
uint8_t I2c_Buf_Read[BUFFER_SIZE];  // 读取缓冲区

uint8_t I2C_TEST(void);

int main(void) 
{
    SysTick_Init();
    BSP_LED_Init();
    USARTx_Init();
    I2C_EEPROM_Init();

    // 等待所有初始化完成
    Delay_ms(100);

    if(I2C_TEST() == 1)
    {
        LED_ON(GREEN_LED_Pin);
    }
    else
    {
        LED_ON(RED_LED_Pin);
    }
    while(1);
}

// I2C EEPROM读写测试
uint8_t I2C_TEST(void)
{
    uint16_t i = 0;

    // 初始化写入缓冲区
    printf("Preparing write data:\r\n");
    for(i = 0; i < BUFFER_SIZE; i++)
    {   
        I2c_Buf_Write[i] = i;
        printf("0x%02X ", I2c_Buf_Write[i]); 
        if(i % 16 == 15)   
            printf("\r\n");    
    }
    printf("\r\n");

    // 分页写入(AT24C02页大小为8字节)
    printf("Writing data to EEPROM...\r\n");
    uint16_t addr = EEPROM_Firstpage;
    uint8_t writeSuccess = 1;

    for(i = 0; i < BUFFER_SIZE; i += PAGE_SIZE)
    {
        uint8_t bytesToWrite = (BUFFER_SIZE - i) > PAGE_SIZE ? PAGE_SIZE : (BUFFER_SIZE - i);

        if(!I2C_EEPROM_WritePage(addr, &I2c_Buf_Write[i], bytesToWrite))
        {
            printf("Write failed at address 0x%02X\r\n", addr);
            writeSuccess = 0;
            break;
        }

        addr += bytesToWrite;
        Delay_ms(5); // EEPROM需要5ms写入时间
        printf(".");
    }

    if(!writeSuccess)
    {
        printf("\r\nWrite operation failed!\r\n");
        return 0;
    }

    printf("\r\nWrite completed successfully!\r\n");

    // 等待EEPROM完成所有内部写入
    Delay_ms(20);

    // 读取数据
    printf("Reading data from EEPROM...\r\n");
    if(!I2C_EEPROM_ReadBuffer(EEPROM_Firstpage, I2c_Buf_Read, BUFFER_SIZE))
    {
        printf("Read failed!\r\n");
        return 0;
    }
    printf("Read completed successfully!\r\n");

    // 验证数据
    printf("\r\nVerifying data:\r\n");
    uint8_t errors = 0;
    for(i = 0; i < BUFFER_SIZE; i++)
    {   
        if(I2c_Buf_Read[i] != I2c_Buf_Write[i])
        {
            errors++;
            printf("Error at 0x%02X: Wrote 0x%02X, Read 0x%02X\r\n", 
                  i, I2c_Buf_Write[i], I2c_Buf_Read[i]);
        }
        else 
        {
            printf("0x%02X ", I2c_Buf_Read[i]);
        }

        if(i % 16 == 15)  
            printf("\r\n");
    }

    if(errors > 0)
    {
        printf("\r\n%d errors found!\r\n", errors);
        return 0;
    }

    printf("\r\nAll data verified successfully!\r\n");
    return 1;
}

4. I2C常见函数(STD库)

4.1 I2C 配置步骤

4.1.1 使能时钟

RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);  // 使能I2C1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 使能GPIO时钟

4.1.2 配置GPIO引脚

GPIO_InitTypeDef GPIO_InitStruct;
// SCL引脚配置(PB6)
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;  // 复用开漏输出
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);

// SDA引脚配置(PB7)
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
GPIO_Init(GPIOB, &GPIO_InitStruct);

4.1.3 I2C参数配置

I2C_InitTypeDef I2C_InitStruct;
I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;               // I2C模式
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;       // 时钟占空比
I2C_InitStruct.I2C_OwnAddress1 = 0x30;                // 本设备地址
I2C_InitStruct.I2C_Ack = I2C_Ack_Enable;              // 使能ACK
I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; // 7位地址
I2C_InitStruct.I2C_ClockSpeed = 100000;               // 100kHz时钟

I2C_Init(I2C1, &I2C_InitStruct);  // 应用配置
I2C_Cmd(I2C1, ENABLE);            // 使能I2C

4.2 核心操作函数

4.2.1 主模式发送

// 1. 产生起始条件
I2C_GenerateSTART(I2C1, ENABLE);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); // 等待EV5

// 2. 发送从机地址(写)
I2C_Send7bitAddress(I2C1, 0xA0, I2C_Direction_Transmitter);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); // 等待EV6

// 3. 发送数据
I2C_SendData(I2C1, data_byte);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); // 等待EV8_2

// 4. 产生停止条件
I2C_GenerateSTOP(I2C1, ENABLE);

4.2.2 主模式接收

// 1. 产生起始条件
I2C_GenerateSTART(I2C1, ENABLE);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); // EV5

// 2. 发送从机地址(读)
I2C_Send7bitAddress(I2C1, 0xA0, I2C_Direction_Receiver);
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); // EV6

// 3. 接收数据
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)); // EV7
uint8_t data = I2C_ReceiveData(I2C1);

// 4. 产生停止条件
I2C_GenerateSTOP(I2C1, ENABLE);

4.2.3 从模式配置

// 设置从机地址
I2C_InitStruct.I2C_OwnAddress1 = 0x30;  // 7位地址
I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;

// 使能地址匹配中断
I2C_ITConfig(I2C1, I2C_IT_ADDR, ENABLE);

4.3 事件检测宏

事件宏 描述
I2C_EVENT_MASTER_MODE_SELECT 起始条件已发送 (EV5)
I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED 地址发送完成+ACK (EV6)
I2C_EVENT_MASTER_BYTE_TRANSMITTED 数据发送完成+ACK (EV8_2)
I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED 地址发送完成+ACK (EV6)
I2C_EVENT_MASTER_BYTE_RECEIVED 数据接收完成 (EV7)

4.4 中断配置

4.4.1 使能中断

// 使能事件和错误中断
I2C_ITConfig(I2C1, I2C_IT_EVT | I2C_IT_ERR | I2C_IT_BUF, ENABLE);

// 配置NVIC
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = I2C1_EV_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);

4.4.2 中断服务函数

void I2C1_EV_IRQHandler(void) {
    switch (I2C_GetLastEvent(I2C1)) {
        case I2C_EVENT_SLAVE_RECEIVER_ADDRESS_MATCHED: // 从机地址匹配
            // 准备接收数据
            break;

        case I2C_EVENT_SLAVE_BYTE_RECEIVED:           // 从机接收数据
            uint8_t data = I2C_ReceiveData(I2C1);
            // 处理数据
            break;

        case I2C_EVENT_SLAVE_STOP_DETECTED:           // 检测到停止位
            I2C_ClearFlag(I2C1, I2C_FLAG_STOPF);
            break;
    }
}

4.5 DMA 配置

4.5.1 使能I2C DMA请求

// 使能I2C DMA发送请求
I2C_DMACmd(I2C1, I2C_DMAReq_Tx, ENABLE);

// 使能I2C DMA接收请求
I2C_DMACmd(I2C1, I2C_DMAReq_Rx, ENABLE);

4.5.2 配置DMA通道

// 以I2C1发送为例(DMA1通道6)
DMA_InitTypeDef DMA_InitStruct;
DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&I2C1->DR;
DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)tx_buffer;
DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralDST;  // 内存->外设
// ...其他DMA配置
DMA_Init(DMA1_Channel6, &DMA_InitStruct);

4.6 错误处理

4.6.1 错误标志检测

if (I2C_GetFlagStatus(I2C1, I2C_FLAG_AF)) { // 应答失败
    I2C_ClearFlag(I2C1, I2C_FLAG_AF);
    // 错误处理
}

if (I2C_GetFlagStatus(I2C1, I2C_FLAG_BERR)) { // 总线错误
    I2C_ClearFlag(I2C1, I2C_FLAG_BERR);
    // 错误处理
}

4.6.2 超时机制

#define I2C_TIMEOUT 100000

uint32_t timeout = I2C_TIMEOUT;
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) {
    if (timeout-- == 0) {
        // 超时处理
        I2C_GenerateSTOP(I2C1, ENABLE);
        return ERROR_TIMEOUT;
    }
}
posted @ 2025-06-29 10:52  hazy1k  阅读(41)  评论(0)    收藏  举报