GD32 IIC(I2C)通信详解:从原理到实战

1. 引言

IIC(Inter-Integrated Circuit,也称 I2C)是一种广泛使用的串行通信协议,由飞利浦公司于上世纪 80 年代推出。GD32 系列微控制器内置了硬件 IIC 外设,支持主机和从机模式,可用于连接传感器、存储器、显示屏等外围设备。本文将从 IIC 协议基础出发,结合 GD32 的硬件特性,介绍 IIC 通信的配置方法、代码实现和常见问题排查。

2. IIC 协议基础

IIC 总线由两根线组成:串行数据线 SDA 和串行时钟线 SCL。所有设备通过这两根线并联连接,每个设备都有唯一的地址。通信由主机发起,主机负责产生时钟信号并控制数据传输方向。

IIC 通信的基本时序包括以下几个关键环节:

  • 起始条件:SCL 为高电平时,SDA 由高变低,表示通信开始。
  • 停止条件:SCL 为高电平时,SDA 由低变高,表示通信结束。
  • 地址帧:主机发送 7 位从机地址加 1 位读写标志位。
  • 应答信号:接收方在收到每个字节后发送 ACK(低电平)或 NACK(高电平)。
  • 数据帧:每个字节 8 位,高位在前,传输完成后等待应答。

IIC 支持多种传输速率,标准模式为 100 kbit/s,快速模式为 400 kbit/s,高速模式可达 3.4 Mbit/s。GD32 的硬件 IIC 支持标准模式和快速模式,实际速率可通过时钟配置寄存器调整。

3. GD32 IIC 硬件特性

GD32 系列微控制器的 IIC 外设具有以下主要特性:

  • 支持主机模式和从机模式,可动态切换。
  • 支持 7 位和 10 位地址模式。
  • 内置发送和接收缓冲区,支持中断和 DMA 传输。
  • 支持多主机仲裁和时钟同步。
  • 具备错误检测功能,包括总线错误、应答错误、仲裁丢失等。

以 GD32F103 系列为例,其 IIC0 和 IIC1 外设分别挂载在不同的总线上,引脚可通过复用功能映射到多个 GPIO 端口。开发者需要根据实际硬件连接选择合适的引脚和复用配置。

4. 硬件连接与引脚配置

在使用 GD32 的 IIC 外设之前,需要完成以下硬件准备工作:

  • 确认 SDA 和 SCL 引脚对应的 GPIO 端口和引脚号。
  • 在 SDA 和 SCL 线上各接一个上拉电阻(通常为 4.7 kΩ 或 10 kΩ),保证空闲状态为高电平。
  • 确认从机设备的 IIC 地址和供电电压,避免电平不匹配。

以 GD32F103 的 IIC0 为例,其默认引脚为 PB6(SCL)和 PB7(SDA),复用功能为 AF1。初始化代码如下:

c 复制代码
void iic_gpio_init(void)
{
    gpio_af_set(GPIOB, GPIO_AF_1, GPIO_PIN_6 | GPIO_PIN_7);
    gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_6 | GPIO_PIN_7);
    gpio_output_options_set(GPIOB, GPIO_OTYPE_OD, GPIO_OSPEED_50MHZ, GPIO_PIN_6 | GPIO_PIN_7);
}

这里将引脚配置为复用功能、开漏输出并启用内部上拉。开漏输出是 IIC 协议的要求,允许多个设备共享总线。

5. IIC 外设初始化

完成 GPIO 配置后,需要初始化 IIC 外设本身。GD32 的 IIC 初始化主要涉及时钟使能、速率配置和模式设置。以下代码演示了 IIC0 的初始化过程:

c 复制代码
void iic_periph_init(void)
{
    rcu_periph_clock_enable(RCU_GPIOB);
    rcu_periph_clock_enable(RCU_I2C0);

    i2c_clock_config(I2C0, 100000, I2C_DTCY_2);
    i2c_enable(I2C0);
}

其中 i2c_clock_config 的第一个参数指定 IIC 外设,第二个参数为目标速率(单位 Hz),第三个参数为占空比配置。对于标准模式,占空比通常设置为 2:1。

如果需要使用中断方式接收数据,还需要配置 NVIC 并使能相应中断:

c 复制代码
nvic_irq_enable(I2C0_EV_IRQn, 0, 0);
nvic_irq_enable(I2C0_ER_IRQn, 1, 0);
i2c_interrupt_enable(I2C0, I2C_INT_ERR | I2C_INT_BUF | I2C_INT_EV);

6. 主机发送数据

作为主机发送数据时,需要依次完成起始条件、发送从机地址、发送数据字节和停止条件。GD32 提供了状态标志位来同步每一步操作。以下是一个向从机写入多个字节的示例:

c 复制代码
void iic_master_write(uint8_t slave_addr, uint8_t reg_addr, uint8_t *data, uint8_t len)
{
    i2c_start_on_bus(I2C0);
    while (!i2c_flag_get(I2C0, I2C_FLAG_SBSEND));

    i2c_master_addressing(I2C0, slave_addr, I2C_TRANSMITTER);
    while (!i2c_flag_get(I2C0, I2C_FLAG_ADDSEND));

    i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND);

    i2c_data_transmit(I2C0, reg_addr);
    while (!i2c_flag_get(I2C0, I2C_FLAG_TBE));

    for (uint8_t i = 0; i < len; i++) {
        i2c_data_transmit(I2C0, data[i]);
        while (!i2c_flag_get(I2C0, I2C_FLAG_TBE));
    }

    i2c_stop_on_bus(I2C0);
    while (!i2c_flag_get(I2C0, I2C_FLAG_STPDET));
    i2c_flag_clear(I2C0, I2C_FLAG_STPDET);
}

这段代码先发送起始条件,等待 SBSEND 标志置位后发送从机地址。ADDSEND 标志表示地址已发送并收到应答,需要手动清除。随后依次发送寄存器地址和数据字节,每发送一个字节都要等待发送缓冲区为空(TBE 标志)。最后发送停止条件并等待停止检测标志。

7. 主机接收数据

主机从从机读取数据时,需要先发送从机地址(写方向)和寄存器地址,然后重新发送起始条件,再发送从机地址(读方向),最后读取数据。以下示例演示了读取多个字节的过程:

c 复制代码
void iic_master_read(uint8_t slave_addr, uint8_t reg_addr, uint8_t *buf, uint8_t len)
{
    i2c_start_on_bus(I2C0);
    while (!i2c_flag_get(I2C0, I2C_FLAG_SBSEND));

    i2c_master_addressing(I2C0, slave_addr, I2C_TRANSMITTER);
    while (!i2c_flag_get(I2C0, I2C_FLAG_ADDSEND));
    i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND);

    i2c_data_transmit(I2C0, reg_addr);
    while (!i2c_flag_get(I2C0, I2C_FLAG_TBE));

    i2c_start_on_bus(I2C0);
    while (!i2c_flag_get(I2C0, I2C_FLAG_SBSEND));

    i2c_master_addressing(I2C0, slave_addr, I2C_RECEIVER);
    while (!i2c_flag_get(I2C0, I2C_FLAG_ADDSEND));
    i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND);

    for (uint8_t i = 0; i < len; i++) {
        while (!i2c_flag_get(I2C0, I2C_FLAG_RBNE));
        if (i == len - 1) {
            i2c_ack_config(I2C0, I2C_ACK_DISABLE);
        }
        buf[i] = i2c_data_receive(I2C0);
    }

    i2c_stop_on_bus(I2C0);
    while (!i2c_flag_get(I2C0, I2C_FLAG_STPDET));
    i2c_flag_clear(I2C0, I2C_FLAG_STPDET);
    i2c_ack_config(I2C0, I2C_ACK_ENABLE);
}

读取最后一个字节前需要关闭应答(NACK),通知从机不再发送数据,然后发送停止条件。读取完成后重新使能应答,以便后续通信使用。

8. 中断方式接收数据

对于需要实时响应的应用场景,可以使用中断方式接收数据。GD32 的 IIC 中断分为事件中断和错误中断。以下是一个简单的接收中断处理示例:

c 复制代码
volatile uint8_t rx_buffer[16];
volatile uint8_t rx_index = 0;

void I2C0_EV_IRQHandler(void)
{
    if (i2c_interrupt_flag_get(I2C0, I2C_INT_FLAG_RBNE)) {
        rx_buffer[rx_index++] = i2c_data_receive(I2C0);
        if (rx_index >= 16) {
            rx_index = 0;
        }
    }
}

在中断处理函数中,通过检查 RBNE 标志判断接收缓冲区是否有数据,读取后存入缓冲区。实际项目中还需要处理地址匹配、停止条件等更多事件,建议根据 GD32 标准外设库的示例代码进行扩展。

9. 常见问题与排查

IIC 通信在实际调试中经常遇到以下问题,这里给出对应的排查思路:

问题现象 可能原因 排查方法
总线一直为低电平 SDA 或 SCL 被从机拉低,或上拉电阻缺失 检查上拉电阻,断开从机逐个排查
发送地址后无应答 从机地址错误或从机未上电 核对地址,用逻辑分析仪抓取波形
数据接收乱码 速率不匹配或时钟线干扰 降低速率,缩短线缆长度,检查布线
偶尔通信失败 缺少错误处理或总线竞争 增加重试机制,检查多主机仲裁

调试 IIC 通信时,逻辑分析仪是最有效的工具。通过抓取 SDA 和 SCL 的波形,可以直观地看到起始条件、地址帧、数据帧和应答信号是否正常,快速定位问题所在。

10. 总结

GD32 的硬件 IIC 外设功能完善,配合标准外设库可以快速实现可靠的 IIC 通信。本文从协议基础、硬件配置、主机收发、中断处理和问题排查几个方面进行了介绍,希望能帮助开发者少走弯路。实际项目中建议优先使用硬件 IIC 而非软件模拟,以获得更好的稳定性和更低的 CPU 占用。遇到问题时,结合逻辑分析仪和分步排查法,通常能较快定位并解决。

相关推荐
lhbweilai1 小时前
深挖FreeRTOS内核实现(上)
stm32·单片机·链表
Brilliantwxx2 小时前
【STM32】 DMA 源码分析与实战(下):DMA 收发状态机与 HAL 分层
android·开发语言·stm32·单片机·嵌入式硬件·mcu
铅笔小新z2 小时前
【stm32】DMA 源码分析与实战
stm32·单片机·嵌入式硬件
笨笨饿4 小时前
#141_ Codex 本地 AI 代理工具链工作流重构:Headroom、Scrapling、Archify 统一接入
开发语言·人工智能·stm32·单片机·嵌入式硬件·ui·重构
新思维软件5 小时前
非机动车管理系统设计 | STM32+RFID+MQTT | X504291项目编号 X504291
spring boot·stm32·单片机·嵌入式硬件
木白CPP14 小时前
SELinux 介绍和基本使用
嵌入式硬件·安卓·安全架构
女神下凡15 小时前
芯参谋(30):EMMC eMCP 软件设计规范
arm开发·单片机·嵌入式硬件·设计规范
xingzhemengyou116 小时前
STM32F427将函数设定在固定位置
stm32·单片机·嵌入式硬件
SKH.17 小时前
ARM-uart
arm开发·单片机