STM32软件IIC与硬件IIC实战指南:标准库操作及踩坑经验分享

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失败) 原因:时序延时不足、从机地址错误或总线冲突。 解决:

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

确认从机地址是否为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_SR1和I2C_SR2寄存器状态。

代码技巧:

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

作者:Camel1ia_

相关手记

问道坐骑哪个好用?平民玩家坐骑选择推荐!
[科普中国]-低等动物
天枢下载