STM32软件IIC与硬件IIC:标准库下的实战与踩坑记录

引言

在STM32开发中,IIC通信是连接各类外设的核心技能。然而,面对标准库(Standard Peripheral Library)的硬件IIC配置和软件IIC的手动时序模拟,许多开发者常因时序混乱、配置复杂等问题陷入困境。本文将基于标准库,分享我在软件IIC和硬件IIC开发中的实战经验,并提供代码示例与调试技巧。

一、IIC协议核心回顾

IIC协议核心不变,但标准库的实现细节需特别注意:

  • 起始/停止条件:硬件IIC由外设自动生成,软件IIC需手动控制GPIO。

  • 应答机制:标准库需通过状态寄存器(如I2C_CheckEvent())判断ACK/NACK。


二、软件IIC:基于标准库的GPIO模拟

1. 实现思路

通过GPIO手动拉高/拉低SCL和SDA引脚,模拟IIC时序。优势是灵活选择引脚,缺点是需消耗CPU资源且时序调试复杂。

2. 核心代码(标准库版)

// 定义GPIO引脚(以PB6为SCL,PB7为SDA为例)
#define IIC_SCL_GPIO_PORT    GPIOB
#define IIC_SCL_PIN          GPIO_Pin_6
#define IIC_SDA_GPIO_PORT    GPIOB
#define IIC_SDA_PIN          GPIO_Pin_7

// 初始化GPIO为开漏输出
void Soft_I2C_Init() {
    GPIO_InitTypeDef GPIO_InitStruct;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;  // 开漏输出
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStruct.GPIO_Pin = IIC_SCL_PIN | IIC_SDA_PIN;
    GPIO_Init(IIC_SCL_GPIO_PORT, &GPIO_InitStruct);
    
    // 初始拉高总线
    GPIO_SetBits(IIC_SCL_GPIO_PORT, IIC_SCL_PIN);
    GPIO_SetBits(IIC_SDA_GPIO_PORT, IIC_SDA_PIN);
}

// 生成起始信号
void I2C_Start() {
    SDA_HIGH();
    SCL_HIGH();
    Delay_us(5);
    SDA_LOW();
    Delay_us(5);
    SCL_LOW();
}

// 发送单字节(需包含ACK检查)
uint8_t I2C_WriteByte(uint8_t data) {
    for (uint8_t i = 0; i < 8; i++) {
        (data & 0x80) ? SDA_HIGH() : SDA_LOW();
        data <<= 1;
        SCL_HIGH();
        Delay_us(5);
        SCL_LOW();
        Delay_us(5);
    }
    // 释放SDA线,等待ACK
    SDA_HIGH(); 
    SCL_HIGH();
    if (GPIO_ReadInputDataBit(IIC_SDA_GPIO_PORT, IIC_SDA_PIN)) {
        return 1; // NACK
    }
    SCL_LOW();
    return 0; // ACK
}

 

3. 常见问题

  • 问题:从机无应答(ACK失败)
    原因:时序延时不足、从机地址错误或总线冲突。
    解决

    1. 用逻辑分析仪校准延时(通常SCL高电平保持4μs以上)。

    2. 确认从机地址是否为7位(需左移1位并补读写位)。

三、硬件IIC:标准库配置与实战

1. 标准库配置步骤

步骤1:启用GPIO和I2C时钟

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // SCL:PB6, SDA:PB7
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);

 步骤2:配置GPIO为复用开漏模式

GPIO_InitTypeDef GPIO_InitStruct;
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);

步骤3:初始化I2C外设

I2C_InitTypeDef I2C_InitStruct;
I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;       // 50%占空比
I2C_InitStruct.I2C_OwnAddress1 = 0x00;                // 主机地址(可忽略)
I2C_InitStruct.I2C_Ack = I2C_Ack_Enable;              // 使能ACK
I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStruct.I2C_ClockSpeed = 100000;               // 100kHz
I2C_Init(I2C1, &I2C_InitStruct);
I2C_Cmd(I2C1, ENABLE);                                // 使能I2C

2. 数据收发核心代码

发送数据到从机(以AT24C02为例)

void I2C_WriteData(uint8_t devAddr, uint8_t regAddr, uint8_t data) {
    // 发送START条件
    I2C_GenerateSTART(I2C1, ENABLE);
    while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); 

    // 发送从机地址(写模式)
    I2C_Send7bitAddress(I2C1, devAddr, 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));

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

3. 硬件IIC的“深坑”与填坑

  • 问题I2C_CheckEvent卡死
    原因:总线被锁(如未收到ACK或异常中断)。
    解决

    // 强制复位I2C总线
    I2C_SoftwareResetCmd(I2C1, ENABLE);
    I2C_SoftwareResetCmd(I2C1, DISABLE);
    I2C_Cmd(I2C1, ENABLE); // 重新使能

    四、软件IIC vs 硬件IIC(标准库对比)

    对比项软件IIC硬件IIC(标准库)
    开发复杂度高(需逐行调试时序)中(需熟悉状态机与事件检查)
    性能低(受CPU速度限制)高(支持DMA,最高1MHz)
    稳定性依赖延时精度硬件保证时序,抗干扰强
    适用场景低速、多引脚需求高速、复杂多设备通信

    五、调试技巧

  • 调试工具

    • 逻辑分析仪:确认起始/停止信号、ACK波形。

    • ST-Link Debugger:通过断点监控I2C_SR1I2C_SR2寄存器状态。

  • 代码技巧

    • 封装超时检测:避免while(I2C_CheckEvent)死循环。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值