欢迎光临
我们一直在努力

STM32的IIC通信指南

基于野火霸道V2开发板IIC通信完全开发指南

野火霸道V2是一款基于STM32F103ZET6芯片的经典入门级开发板,板载硬件I2C外设,同时支持软件模拟IIC通信,可适配EEPROM、OLED、MPU6050、温湿度传感器等绝大多数IIC协议外设。IIC通信凭借双线串行、半双工、多设备挂载、引脚占用少等优势,成为嵌入式开发中最常用的短距离板级通信协议之一。本文结合野火霸道V2的硬件特性,系统讲解IIC通信原理、硬件配置、软件/硬件驱动实现、实操案例及问题排查,既适合零基础入门学习,也可作为工程开发参考。


一、IIC通信核心原理

1.1 总线硬件架构

IIC总线仅需两根信号线即可完成通信:SCL(串行时钟线)和SDA(串行数据线),配合GND共地即可实现多设备互连。其总线特性如下:

  • 总线拓扑:支持单主机多从机架构。野火霸道V2作为主机,可同时挂载多个IIC从设备,所有设备共用SCL和SDA总线,通过唯一的设备地址区分通信目标。

  • 电气特性:SCL和SDA引脚必须配置为开漏输出模式,总线需外接上拉电阻(开发板已板载上拉电路,无需额外焊接)。总线空闲时保持高电平,通信时通过拉低电平实现数据传输。

  • 通信速率:标准模式为100 kHz(最通用),快速模式为400 kHz。STM32F103硬件IIC最高支持400 kHz,可满足绝大多数外设需求。

  • 寻址方式:默认使用7位设备地址,总线最多可挂载127个从设备,覆盖常规嵌入式外设。

1.2 核心通信时序

IIC所有通信操作均基于以下四种基础时序,它们是软硬件驱动开发的根本依据:

  • 起始信号(START):SCL为高电平期间,SDA由高电平跳变至低电平,标志通信开始。

  • 停止信号(STOP):SCL为高电平期间,SDA由低电平跳变至高电平,标志通信结束。

  • 应答信号(ACK):数据传输后,从机拉低SDA表示接收成功;无应答(NACK)则SDA保持高电平,表示通信异常。

  • 数据位传输:SCL低电平期间切换SDA数据,SCL高电平期间从机读取/主机写入数据。每字节传输包含8位数据加1位应答位。


二、野火霸道V2 IIC硬件资源配置

2.1 硬件引脚定义

野火霸道V2搭载STM32F103ZET6,内置I2C1和I2C2两个硬件外设,板载引脚固定,无需手动焊接。核心引脚分配如下:

IIC外设时钟引脚(SCL)数据引脚(SDA)常用场景
I2C1 PB6 PB7 默认外设,适配OLED、AT24C02、MPU6050
I2C2 PB10 PB11 扩展外设,多设备同时挂载场景

开发板硬件已为对应引脚配置上拉电阻,完全满足IIC总线电气要求,外接设备时无需额外电路。

2.2 硬件配置核心要点

无论是使用硬件IIC还是软件模拟IIC,都必须遵循以下配置规则,否则会出现通信失败或数据乱码:

  • GPIO模式:SCL和SDA统一配置为开漏输出(OD),严禁使用推挽输出,以保证总线电平可被多个设备拉低。

  • 时钟使能:务必开启GPIOB时钟以及对应IIC外设时钟(硬件方式)或仅GPIO时钟(软件方式)。

  • 关键参数:启用应答机制、使用7位地址模式、设置标准100 kHz通信速率(通用默认值)。

  • 设备地址:严格匹配从设备手册中的地址,部分设备地址可通过硬件引脚修改,需在程序中同步配置。

  • IIC 写操作时序图(主机写入从机)

    IIC 读操作时序图(主机读取从机)


    三、两种IIC实现方式对比

    野火霸道V2支持硬件IIC和软件模拟IIC两种开发方式,分别适配不同场景。主要区别如下:

    实现方式优点缺点适用场景
    硬件IIC 硬件时序生成,占用CPU少,传输速率高,稳定性强 部分芯片存在硬件bug,引脚固定,调试稍复杂 批量工程、高速传输、多设备长期运行
    软件模拟 引脚任意配置,时序可控,调试简单,无硬件兼容问题 占用CPU资源,速率较低,依赖精准延时 新手入门、低速外设、引脚资源受限

    四、软件模拟IIC完整实现(新手推荐)

    软件模拟IIC通过GPIO电平翻转和微秒延时来模拟标准IIC时序,不依赖芯片硬件外设,可适配野火霸道V2的所有IO口,代码通用性强,适合初学者深入理解IIC通信本质。以下为基于PB6、PB7引脚的完整驱动代码。

    4.1 头文件配置(bsp_iic.h)

    c

    #ifndef __BSP_IIC_H
    #define __BSP_IIC_H

    #include "stm32f10x.h"

    // 自定义IIC引脚(野火霸道V2默认PB6、PB7)
    #define IIC_SCL_PIN GPIO_Pin_6
    #define IIC_SDA_PIN GPIO_Pin_7
    #define IIC_GPIO_PORT GPIOB
    #define IIC_GPIO_RCC RCC_APB2Periph_GPIOB

    // 电平操作宏
    #define IIC_SCL_HIGH GPIO_SetBits(IIC_GPIO_PORT, IIC_SCL_PIN)
    #define IIC_SCL_LOW GPIO_ResetBits(IIC_GPIO_PORT, IIC_SCL_PIN)
    #define IIC_SDA_HIGH GPIO_SetBits(IIC_GPIO_PORT, IIC_SDA_PIN)
    #define IIC_SDA_LOW GPIO_ResetBits(IIC_GPIO_PORT, IIC_SDA_PIN)
    #define IIC_SDA_READ GPIO_ReadInputDataBit(IIC_GPIO_PORT, IIC_SDA_PIN)

    // 函数声明
    void IIC_Init(void); // IIC引脚初始化
    void IIC_Start(void); // 起始信号
    void IIC_Stop(void); // 停止信号
    uint8_t IIC_Write_Byte(uint8_t dat); // 写入单字节
    uint8_t IIC_Read_Byte(uint8_t ack); // 读取单字节,ack=1应答,0无应答

    #endif

    4.2 驱动函数实现(bsp_iic.c)

    c

    #include "bsp_iic.h"
    #include "delay.h"

    // IIC引脚初始化
    void IIC_Init(void)
    {
    GPIO_InitTypeDef GPIO_InitStruct;
    RCC_APB2PeriphClockCmd(IIC_GPIO_RCC, ENABLE);

    GPIO_InitStruct.GPIO_Pin = IIC_SCL_PIN | IIC_SDA_PIN;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(IIC_GPIO_PORT, &GPIO_InitStruct);

    // 总线初始化为高电平(空闲状态)
    IIC_SCL_HIGH;
    IIC_SDA_HIGH;
    }

    // 起始信号
    void IIC_Start(void)
    {
    IIC_SDA_HIGH;
    IIC_SCL_HIGH;
    delay_us(4);
    IIC_SDA_LOW; // SCL高电平时,SDA下降沿
    delay_us(4);
    IIC_SCL_LOW; // 拉低时钟,准备传输数据
    }

    // 停止信号
    void IIC_Stop(void)
    {
    IIC_SDA_LOW;
    IIC_SCL_HIGH;
    delay_us(4);
    IIC_SDA_HIGH; // SCL高电平时,SDA上升沿
    delay_us(4);
    }

    // 写入单字节,返回0表示应答成功,1表示无应答
    uint8_t IIC_Write_Byte(uint8_t dat)
    {
    uint8_t i;
    for (i = 0; i < 8; i++) {
    IIC_SCL_LOW;
    delay_us(2);
    if (dat & 0x80)
    IIC_SDA_HIGH;
    else
    IIC_SDA_LOW;
    dat <<= 1;
    delay_us(2);
    IIC_SCL_HIGH;
    delay_us(2);
    }
    // 释放SDA,等待从机应答
    IIC_SCL_LOW;
    IIC_SDA_HIGH;
    delay_us(2);
    IIC_SCL_HIGH;
    delay_us(2);
    return IIC_SDA_READ ? 1 : 0; // 1:无应答,0:应答成功
    }

    // 读取单字节,ack=1发送应答,ack=0发送无应答
    uint8_t IIC_Read_Byte(uint8_t ack)
    {
    uint8_t i, dat = 0;
    for (i = 0; i < 8; i++) {
    dat <<= 1;
    IIC_SCL_HIGH;
    delay_us(2);
    if (IIC_SDA_READ)
    dat |= 0x01;
    IIC_SCL_LOW;
    delay_us(2);
    }
    // 主机发送应答/无应答
    if (ack)
    IIC_SDA_LOW;
    else
    IIC_SDA_HIGH;
    delay_us(2);
    IIC_SCL_HIGH;
    delay_us(2);
    return dat;
    }


    五、硬件IIC标准实现(工程优选)

    硬件IIC直接调用STM32标准库外设驱动,由芯片硬件自动生成通信时序,CPU无需干预,稳定性和传输效率远优于软件模拟,适合正式项目开发。以下为野火霸道V2 I2C1的标准库配置。

    5.1 硬件IIC初始化

    c

    #include "stm32f10x.h"
    #include "i2c.h"

    void I2C_Hardware_Init(void)
    {
    GPIO_InitTypeDef GPIO_InitStruct;
    I2C_InitTypeDef I2C_InitStruct;

    // 开启GPIOB和I2C1时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);

    // PB6(SCL)、PB7(SDA)复用开漏输出
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStruct);

    // I2C参数配置
    I2C_InitStruct.I2C_ClockSpeed = 100000; // 100 kHz
    I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
    I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2; // 占空比2:1
    I2C_InitStruct.I2C_OwnAddress1 = 0x00; // 主机地址忽略
    I2C_InitStruct.I2C_Ack = I2C_Ack_Enable; // 使能应答
    I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;

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

    5.2 硬件IIC读写函数

    c

    // 向从设备写入单字节数据
    void I2C_Write_Data(uint8_t dev_addr, uint8_t reg_addr, uint8_t data)
    {
    while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY)); // 等待总线空闲

    I2C_GenerateSTART(I2C1, ENABLE);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));

    I2C_Send7bitAddress(I2C1, dev_addr, I2C_Direction_Transmitter);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

    I2C_SendData(I2C1, reg_addr);
    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);
    }

    // 从从设备读取单字节数据
    uint8_t I2C_Read_Data(uint8_t dev_addr, uint8_t reg_addr)
    {
    uint8_t data = 0;
    while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY));

    // 发送写地址,指定寄存器
    I2C_GenerateSTART(I2C1, ENABLE);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));

    I2C_Send7bitAddress(I2C1, dev_addr, I2C_Direction_Transmitter);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

    I2C_SendData(I2C1, reg_addr);
    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, dev_addr, I2C_Direction_Receiver);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));

    // 最后一字节无应答
    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;
    }


    六、实操案例:AT24C02 EEPROM读写实验

    AT24C02是最常用的IIC存储外设,支持掉电数据保存。本实验采用软件模拟IIC实现读写测试,搭配串口打印结果,直观验证通信功能。

    6.1 硬件连接

    • AT24C02 SCL → 霸道V2 PB6

    • AT24C02 SDA → 霸道V2 PB7

    • AT24C02 VCC → 3.3V

    • AT24C02 GND → 开发板GND

    AT24C02默认设备地址:写地址 0xA0,读地址 0xA1。

    6.2 主测试代码

    c

    #include "stm32f10x.h"
    #include "bsp_iic.h"
    #include "usart.h"
    #include "led.h"
    #include "delay.h"

    int main(void)
    {
    uint8_t write_data = 0x55;
    uint8_t read_data = 0x00;

    LED_Init();
    USART_Init();
    IIC_Init();
    printf("野火霸道V2 IIC通信测试启动\\r\\n");

    // 写入0x01地址
    IIC_Start();
    IIC_Write_Byte(0xA0); // 设备写地址
    IIC_Write_Byte(0x01); // 存储地址
    IIC_Write_Byte(write_data);
    IIC_Stop();
    delay_ms(10); // 等待写入完成

    // 读取0x01地址
    IIC_Start();
    IIC_Write_Byte(0xA0);
    IIC_Write_Byte(0x01);
    IIC_Start(); // 重启
    IIC_Write_Byte(0xA1); // 设备读地址
    read_data = IIC_Read_Byte(0); // 无应答
    IIC_Stop();

    printf("写入数据:0x%02X\\r\\n", write_data);
    printf("读取数据:0x%02X\\r\\n", read_data);

    // 结果指示
    if (read_data == write_data) {
    LED_GREEN;
    printf("IIC通信测试成功!\\r\\n");
    } else {
    LED_RED;
    printf("IIC通信测试失败!\\r\\n");
    }

    while (1) {}
    }

    6.3 实验现象

    程序下载运行后,串口助手打印写入和读取的数据一致,开发板绿色LED点亮,表示IIC通信正常;若数据不一致或无法输出,说明通信异常,需检查硬件接线或代码配置。


    七、常见问题排查与优化方案

    7.1 通信失败、无数据返回

    • 检查接线:确认SCL、SDA正确对应,必须共地,供电为3.3V(禁止接入5V外设)。

    • 检查引脚模式:确保GPIO为开漏输出,推挽输出会导致总线失效。

    • 核对设备地址:区分写地址和读地址,避免地址错误。

    7.2 数据乱码或读写不一致

    • 延时精度:软件模拟时微秒延时必须准确,延时过短会破坏时序。

    • 降低速率:可降低通信速率至100 kHz,增加抗干扰能力。

    • 应答处理:严格遵循时序中的ACK/NACK规则,尤其在多字节传输时。

    7.3 硬件IIC总线卡死

    STM32F1系列硬件IIC存在经典的总线卡死问题。建议方案:通信前检测总线忙状态,增加超时判断,异常时复位IIC外设,防止程序永久阻塞。


    八、总结与拓展

    野火霸道V2的IIC通信是嵌入式外设开发的基础核心。软件模拟IIC适合新手理解时序原理,且引脚可灵活配置;硬件IIC则更适合工程落地,兼顾效率与稳定性。本文提供的驱动代码可直接适配OLED、MPU6050、AHT10等常见IIC外设,只需修改设备地址和寄存器地址即可快速移植。

    赞(0)
    未经允许不得转载:171主机测评 » STM32的IIC通信指南
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址