STM32 I2C通信详解:从原理到实战应用

105 阅读6分钟

I2C(Inter-Integrated Circuit)是Philips公司开发的一种简单、高效的双线制串行通信总线,广泛应用于各种嵌入式设备中。掌握I2C通信是嵌入式开发者的必备技能。

1. I2C基础概念

1.1 什么是I2C?

I2C是一种同步、半双工的串行通信协议,仅需两根信号线即可实现设备间的通信:

  • SCL:串行时钟线,用于同步数据传输
  • SDA:串行数据线,用于实际的数据传输

1.2 I2C通信特点

  • 多设备支持:支持总线挂载多个设备(一主多从、多主多从)
  • 地址寻址:每个从设备有唯一的7位或10位地址
  • 速率灵活:标准模式(100kHz)、快速模式(400kHz)、高速模式(3.4MHz)

2. I2C时序基本单元

2.1 通信控制时序

  • 起始条件:SCL高电平期间,SDA从高电平切换到低电平
  • 终止条件:SCL高电平期间,SDA从低电平切换到高电平

2.2 数据传输时序

  • 发送字节:SCL低电平期间放置数据,SCL高电平期间保持稳定
  • 接收字节:SCL低电平期间准备数据,SCL高电平期间读取数据
  • 应答机制:每个字节传输后都需要应答确认

2.3 寄存器访问

  • 指定地址写:向从设备的特定寄存器写入配置数据
  • 指定地址读:从从设备的特定寄存器读取数据

3. 软件I2C vs 硬件I2C

特性软件I2C硬件I2C
实现方式GPIO引脚 + 软件延时专用I2C硬件电路
CPU占用(CPU忙于控制时序)(硬件自动处理)
通信速度慢(通常<100kHz)快(可达400kHz-1MHz)
时序精度依赖软件延时精度硬件保证,精确稳定
引脚选择任意GPIO引脚固定专用引脚
开发难度复杂(要写底层驱动)简单(调用库函数)
可靠性中等(受中断影响)(硬件保证)
多主机支持❌ 很难实现✅ 硬件支持

4. 软件I2C驱动代码详解

4.1 引脚控制函数

#include "stm32f10x.h"                  // STM32F10x系列头文件
#include "Delay.h"                       // 延时函数头文件

// 写SCL时钟线函数
void MyI2C_W_SCL(uint8_t BitValue)	
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);  // 设置PB10引脚电平
	Delay_us(10);                                            // 延时10微秒,保持时序稳定
}

// 写SDA数据线函数  
void MyI2C_W_SDA(uint8_t BitValue)	
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);  // 设置PB11引脚电平
	Delay_us(10);                                            // 延时10微秒,保持时序稳定
}

// 读SDA数据线函数
uint8_t MyI2C_R_SDA(void)
{
	uint8_t BitValue;
	BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);    // 读取PB11引脚电平状态
	Delay_us(10);                                            // 延时10微秒
	return BitValue;                                         // 返回读取到的电平值(0或1)
}

4.2 I2C初始化函数

// I2C初始化函数
void MyI2C_Init(void)
{
	// 开启GPIOB时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	// 配置GPIO初始化结构体
	GPIO_InitTypeDef GPIO_InitsStructure;
	GPIO_InitsStructure.GPIO_Mode = GPIO_Mode_Out_OD;              // 开漏输出模式(重要!)
	GPIO_InitsStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_10;      // PB10(SCL)和PB11(SDA)
	GPIO_InitsStructure.GPIO_Speed = GPIO_Speed_50MHz;             // 50MHz速度
	GPIO_Init(GPIOB, &GPIO_InitsStructure);                        // 应用GPIO配置
	
	// 将SCL和SDA引脚置高(总线空闲状态)
	GPIO_SetBits(GPIOB, GPIO_Pin_11 | GPIO_Pin_10);	
}

4.3 基本时序控制函数

// I2C起始信号函数
void MyI2C_Start(void)
{
	MyI2C_W_SCL(1);    // SCL高电平(确保总线空闲)
	MyI2C_W_SDA(1);    // SDA高电平(确保总线空闲)
	MyI2C_W_SDA(0);    // SDA从高变低,产生起始条件
	MyI2C_W_SCL(0);    // SCL拉低,准备数据传输
}

// I2C停止信号函数
void MyI2C_Stop(void)
{
	MyI2C_W_SDA(0);    // SDA低电平(确保稳定状态)
	MyI2C_W_SCL(1);    // SCL高电平
	MyI2C_W_SDA(1);    // SDA从低变高,产生停止条件
}

// I2C发送字节函数
void MyI2C_SendByte(uint8_t Byte)
{
	uint8_t i;
	for (i = 0; i < 8; i++)                                // 循环8次,发送8个bit
	{
		MyI2C_W_SDA(Byte & (0x80 >> i));                   // 从高位到低位依次设置SDA
		MyI2C_W_SCL(1);                                    // SCL拉高,从机采样数据
		MyI2C_W_SCL(0);                                    // SCL拉低,准备下一个bit
	}
}

// I2C接收字节函数
uint8_t MyI2C_ReceiveByte(void)
{
	uint8_t i, Byte = 0x00;
	MyI2C_W_SDA(1);                                        // 释放SDA线,设置为输入模式
	for (i = 0; i < 8; i++)
	{	
		MyI2C_W_SCL(1);                                    // SCL拉高,准备读取数据
		if(MyI2C_R_SDA() == 1)                             // 读取SDA电平
		{
			Byte |= (0x80 >> i);                           // 如果为高电平,设置对应位
		}
		MyI2C_W_SCL(0);                                    // SCL拉低,准备下一个bit
	}	
	return Byte;                                           // 返回接收到的字节
}

4.4 应答控制函数

// I2C发送应答信号函数
void MyI2C_SendAck(uint8_t AckBit)
{
	MyI2C_W_SDA(AckBit);                                   // 设置应答电平(0=应答,1=非应答)
	MyI2C_W_SCL(1);                                        // SCL拉高,从机采样应答
	MyI2C_W_SCL(0);                                        // SCL拉低
}

// I2C接收应答信号函数
uint8_t MyI2C_ReceiveAck(void)
{
	uint8_t AckBit;
	MyI2C_W_SDA(1);                                        // 释放SDA线,设置为输入模式
	MyI2C_W_SCL(1);                                        // SCL拉高
	AckBit = MyI2C_R_SDA();                                // 读取从机的应答信号
	MyI2C_W_SCL(0);                                        // SCL拉低
	return AckBit;                                         // 返回应答位(0=应答,1=非应答)
}

5. I2C通信流程解析

5.1 完整的数据写入流程

  1. 发送起始条件
  2. 发送从机地址(写模式)
  3. 等待应答
  4. 发送寄存器地址
  5. 等待应答
  6. 发送数据
  7. 等待应答
  8. 发送停止条件

5.2 完整的数据读取流程

  1. 发送起始条件
  2. 发送从机地址(写模式)  - 用于指定寄存器地址
  3. 等待应答
  4. 发送要读取的寄存器地址
  5. 等待应答
  6. 发送重复起始条件
  7. 发送从机地址(读模式)
  8. 等待应答
  9. 读取数据
  10. 发送非应答
  11. 发送停止条件

6. 使用注意事项

6.1 软件I2C的要点

  • 开漏输出:必须配置为开漏模式,支持线与功能
  • 延时精度:延时时间影响通信速率和稳定性
  • 中断影响:软件延时可能被中断打断,影响时序

6.2 常见问题解决

  • 通信失败:检查地址是否正确、上拉电阻是否接好
  • 时序错误:调整延时时间,确保满足时序要求
  • 应答异常:检查从设备是否正常工作

7. 总结

I2C通信是嵌入式系统中非常重要的通信方式,通过掌握软件I2C的实现:

  • 深入理解I2C协议:通过手动控制时序,加深对协议的理解
  • 灵活选择引脚:不受硬件I2C引脚限制
  • 调试方便:可以单步调试,观察每个时序状态