STM32--I2C通信协议(超详解)

 目录

前言

一.I2C协议简介

I2C作为一个通讯协议,它必须在硬件和软件上,都做出规定,用硬件规定连接线路,用软件规定操作总线

I2C协议硬件规定

一主多从模型中协议的规定

硬件电路

1.所有I2C设备(主机和从机)的SCL连在一起,SDA连在一起(接线要求)

2.设备的SCL和SDA均要配置成开漏输出模式

3.SCL 和 SDA 各添加一个上拉电阻,阻值一般为4.7KΩ左右(弱上拉)

从机地址

从机地址的格式

 7位地址格式

10位地址格式

从机地址的分配

I2C协议软件规定

I2C时序基本单元

起始条件

终止条件

发送一个字节

接收一个字节

发送应答

接收应答

I2C完整时序类型

指定地址写

当前地址读

指定地址读

总结


前言

最近学习总结,对I2C通信协议有大概的了解,花了点时间总结,很高兴能分享出来,感谢支持,有空互三!

希望对大家能有所帮助,欢迎大家指教。


一.I2C协议简介

I2C 也叫 I²C、IIC (Inter-Integrated Circuit) ,集成电路总线是由 Phiilps 公司开发的两线式串行总线,用于连接微控制器及其外围设备,可以实现单片机与外部硬件设备之间的数据传输、控制和监测等功能,采用同步半双工通信方式,I2C 是带数据应答的协议,支持总线挂载多设备(一般采用 “一主多从” 模型本文章介绍的就是该模型),I2C协议的核心是通过两条信号线实现设备之间的数据传输。这两条信号线分别是:

两条信号线:

SDA(Serial Data,串行数据线)

SDA线半双工通信方式用于传输数据,一根线兼具发送和接收,是I2C通信中的数据通道

SCL(Serial Clock,串行时钟线)

I2C 采用同步的时序,SCL线用于提供时钟信号,是I2C通信中的时钟通道 

 对同步半双工通信有疑问的可以看这篇文章:

51单片机--串口通信(详细教程)_51单片机串口通信-CSDN博客


I2C作为一个通讯协议,它必须在硬件和软件上,都做出规定,用硬件规定连接线路,用软件规定操作总线


I2C协议硬件规定

一主多从模型中协议的规定

  • 主机的权利很大,在任何时刻下,主机对SCL线都是完全的控制,另外在空闲状态下,主机也可以主动发起对SDA线的控制,只有在从机发送数据或从机应答时,主机才会转交SDA的控制权给从机。
  • 从机的权利比较小,在任何时刻下对SCL线都只能被动的读取,从机不允许控制SCL线,对于SDA线,从机不允许主动发起对SDA线的控制,只有主机主动转交SDA线控制权给从机,才能得到暂时的控制权 。

硬件电路

1.所有I2C设备(主机和从机)的SCL连在一起,SDA连在一起(接线要求)

2.设备的SCL和SDA均要配置成开漏输出模式

开漏输出模式的工作原理

在开漏输出模式下,输出引脚可以处于两种状态

  • 低电平(0):输出引脚被拉低,电流从电源通过引脚流向地。
  • 高电平(1):输出引脚处于高阻态(High-Z),不提供电流,信号线的高电平由外部上拉电阻提供。

这种模式的关键在于 ➡️ 信号线的高电平状态是由外部上拉电阻维持的,而不是由输出引脚直接驱动的。因此,多个设备可以共享同一信号线,而不会发生冲突。

弱上拉开漏输出的好处:

  • 杜绝电源短路现象
  • 避免引脚模式频繁切换
  • 产生“线与”现象,有任意一个设备低电平总线就是低电平,只有所有设备都高电平总线才是高电平,利用此特性可以执行多主机模式下的时钟同步和总线仲裁。

3.SCL 和 SDA 各添加一个上拉电阻,阻值一般为4.7KΩ左右(弱上拉)

结合上述电路连接的3个要求

这样设计的原因的👀是主机在接收时输入,发送时输出,从机也一样,如果时序没协调好,就可能会发生两引脚同时处于输出,并产生短路。所以I2C设计为禁止所有设备输出强上拉的高电平,所以设备的SCL和SDA均要配置成开漏输出模式,还有SCL和SDA需各添加一个上拉电阻,阻值一般为4.7kΩ左右。开漏输出就是不输出电压,控制输出低电平0时引脚接地,控制输出高电平1时引脚既不输出高电平,也不输出低电平,为高阻态(不能正常输出1)。

从机地址

在I2C(Inter-Integrated Circuit)通信协议中,从机地址(Slave Address)是用于唯一标识总线上的从设备(Slave Device)的重要参数。主设备(Master Device)通过从机地址来选择与之通信的从设备。

从机地址的格式

I2C协议支持两种地址格式:

  • 7位地址:这是最常用的地址格式。7位地址加上一个读/写位(R/W),总共8位

  • 10位地址:虽然较少使用,但I2C协议也支持10位地址。10位地址加上一个读/写位,总共11位。

 7位地址格式

  • 地址长度:7位地址。

  • 读/写位:第8位是读/写位(R/W),用于指示操作类型:

    • 0:写操作(Write),主设备向从设备发送数据。

    • 1:读操作(Read),主设备从从设备读取数据。

10位地址格式

  • 地址长度:10位地址。

  • 读/写位:第11位是读/写位(R/W),用于指示操作类型:

    • 0:写操作(Write),主设备向从设备发送数据。

    • 1:读操作(Read),主设备从从设备读取数据。

  • 总长度:10位地址 + 1位读/写位 = 11位。

从机地址的分配

  • 固定地址:许多I2C设备在出厂时已经分配了固定的7位或10位地址。这些地址通常在设备的数据手册中指定。
  • 可配置地址:一些I2C设备允许通过引脚或寄存器配置地址。这使得多个相同类型的设备可以连接到同一总线上,而不会发生地址冲突。

如MPU6050是一款常用的三轴加速度和陀螺仪传感器,它通过I2C协议与主控制器通信。为了避免I2C总线上的地址冲突,MPU6050采用了以下方法来配置其从机地址:

MPU6050的I2C地址由其AD0引脚的状态决定。AD0引脚可以连接到地(GND)或电源(VCC),从而改变其I2C地址:

I2C协议软件规定

I2C时序基本单元

在 I2C 总线处于空闲状态时,SCL 和 SDA 都处于高电平状态🙌

// 初始化I2C接口
void IIC_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA时钟
    GPIO_InitTypeDef GPIO_InitStruct; // 定义GPIO初始化结构体
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD; // 设置为开漏输出模式
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4; // 设置引脚为SCL和SDA
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; // 设置引脚速度为50MHz
    GPIO_Init(GPIOA, &GPIO_InitStruct); // 初始化GPIOA
    // IIC处于空闲状态,释放SDA、SCL控制线
    GPIO_SetBits(GPIOA, GPIO_Pin_3 | GPIO_Pin_4); // 将SCL和SDA设置为高电平
}

起始条件

SCL高电平期间,SDA从高电平切换到低电平

// 设置SCL(串行时钟线)引脚的电平状态
void IIC_W_SCL(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOA, GPIO_Pin_3, (BitAction)BitValue); // 写入SCL引脚的电平状态
    Delay_us(10);   // 短暂延时,确保从机能够跟上引脚状态的变化
}

// 设置SDA(串行数据线)引脚的电平状态
void IIC_W_SDA(uint8_t BitValue)
{
    GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue); // 写入SDA引脚的电平状态
    Delay_us(10);   // 短暂延时,确保从机能够跟上引脚状态的变化
}

// 读取SDA引脚的电平状态
uint8_t IIC_R_SDA(void)
{
    uint8_t BitValue;
    BitValue = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_4); // 读取SDA引脚的电平状态
    Delay_us(10);   // 短暂延时,确保从机能够跟上引脚状态的变化
    return BitValue; // 返回读取到的电平状态
}

// 生成I2C起始信号
void IIC_Start(void)
{
    IIC_W_SDA(1); // 将SDA设置为高电平
    IIC_W_SCL(1); // 将SCL设置为高电平
    IIC_W_SDA(0); // 将SDA拉低,产生起始信号
    IIC_W_SCL(0); // 保持SCL为低电平
}

终止条件

SCL高电平期间,SDA从低电平切换到高电平

// 生成I2C停止信号
void IIC_Stop(void)
{
    // 对SDA先拉低,确保SDA能够产生上升沿
    IIC_W_SDA(0); // 将SDA拉低
    IIC_W_SCL(1); // 将SCL设置为高电平
    IIC_W_SDA(1); // 释放SDA,产生停止信号
}

发送一个字节

SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节(SCL低电平SDA放数据,SCL高电平SDA读数据)

// 发送一个字节的数据
void IIC_SendByte(uint8_t Byte)
{
    uint8_t i;
    for (i = 0; i < 8; i++) // 循环8次,发送8位数据
    {
        IIC_W_SDA(Byte & (0x80 >> i)); // 发送最高位
        IIC_W_SCL(1); // 将SCL设置为高电平,允许数据传输
        IIC_W_SCL(0); // 将SCL设置为低电平,准备发送下一位数据
    }
}

接收一个字节

SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)

// 接收一个字节的数据
uint8_t IIC_ReceiveByte(void)
{
    uint8_t Byte = 0x00; // 初始化接收到的字节
    uint8_t i;
    IIC_W_SDA(1); // 将SDA设置为高电平,准备接收数据
    for (i = 0; i < 8; i++) // 循环8次,接收8位数据
    {
        IIC_W_SCL(1); // 将SCL设置为高电平,开始接收数据
        Byte = (Byte << 1) | IIC_R_SDA(); // 读取SDA引脚的电平状态,并左移一位
        IIC_W_SCL(0); // 将SCL设置为低电平,准备接收下一位数据
    }
    return Byte; // 返回接收到的字节
}

注:其中SDA线实线部分是主机控制的电平,虚线部分是主机控制的电平,该过程变化形象的展示了主机主动转交SDA线控制权给从机

发送应答

主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答

// 发送应答信号
void IIC_SendAck(uint8_t AckBit)
{
    // 0表示应答,1非应答
    IIC_W_SDA(AckBit); // 设置SDA引脚的电平状态
    IIC_W_SCL(1); // 将SCL设置为高电平,发送应答信号
    IIC_W_SCL(0); // 将SCL设置为低电平,结束应答信号
}

接收应答

主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)

// 接收应答信号
uint8_t IIC_ReceiveAck(void)
{
    uint8_t AckBit;
    IIC_W_SDA(1); // 将SDA设置为高电平,准备接收应答信号
    IIC_W_SCL(1); // 将SCL设置为高电平,开始接收应答信号
    // 0表示应答,1非应答
    AckBit = IIC_R_SDA(); // 读取SDA引脚的电平状态
    IIC_W_SCL(0); // 将SCL设置为低电平,结束接收应答信号
    return AckBit; // 返回接收到的应答信号
}

---------------------------------------------------------------------------------------------------------------------------------

 ➡️ 观察每一个I2C时序基本单元,每一个时序基本单元完成其相对应的操作,主机都会拉低拽下SCL,一方面是占用这个总线,另一方面是为了方便这些基本单元的拼接
 ➡️ 我们保证了除起始和终止条件外,每个时序单元的SCL都是以低电平开始,低电平结束,然后方便这些单元可以拼接起来,SCL可以可以续的上。

I2C完整时序类型

指定地址写

对于指定设备(Slave Address),在指定地址(Reg Address)下,写入指定数据(Data)

分析:起始条件下,I2C发送的第一个字节的定义是高7位表示从机地址,最低位表示读写位,0表示写入操作,1表示读就是出操作,紧跟的单元就是接受从机应答位,第二个字节就可以送到指定设备的内部,一般是寄存器地址或指令控制字等,接着从机应答,第三个字节就可以是寄存器地址下主机所想写入的内容,如果主机不需要再传输了,就可以产生停止条件。

void IIC_WriteReg(uint8_t RegAddress, uint8_t Data)
{
	IIC_Start();
	IIC_SendByte(Peripheral_Addr);
	IIC_ReceiveAck();
	IIC_SendByte(RegAddress);
	IIC_ReceiveAck();
	IIC_SendByte(Data);
	IIC_ReceiveAck();
	IIC_Stop();
}

当前地址读

对于指定设备,在当前地址指针指示的地址下,读取从机数据

分析:起始条件下,I2C发送的第一个字节的定义是高7位表示从机地址,最低位表示读写位,读写位置0,紧跟的单元就是接受从机应答位,开始第二个字节时,主机将SDA的控制权转交给从机,主机接受其所发送的字节数据,此时读取数据的地址为当前指针所指示的地址,但做不到指定地址进行读取数据。

uint8_t IIC_Read()
{
	uint8_t Data;
	IIC_Start();
	IIC_SendByte(Peripheral_Addr);
	IIC_ReceiveAck();
	
	Data = IIC_ReceiveByte();
	IIC_SendAck(1);
	IIC_Stop();
	
	return Data;
}

指定地址读

对于指定设备,在指定地址下,读取从机数据

分析:该I2C时序是由指定地址写和当前地址读复合而成的,先由指定地址写的前半部分的指定地址时序来确定读取数据的地址,在加上当前地址读时序对指定地址进行读取操作  ➡️(因为是两个时序合成而成的复合时序,其中会存在两个起始条件)。

uint8_t IIC_ReadReg(uint8_t RegAddress)
{
	uint8_t Data;
	IIC_Start();
	IIC_SendByte(Peripheral_Addr);
	IIC_ReceiveAck();
	IIC_SendByte(RegAddress);
	IIC_ReceiveAck();
	
	IIC_Start();
	IIC_SendByte(Peripheral_Addr| 0x01);
	IIC_ReceiveAck();
	Data = IIC_ReceiveByte();
	IIC_SendAck(1);
	IIC_Stop();
	
	return Data;
}

总结

以上就是学习的关于I2C通信协议的相关内容,通过本文的介绍,我们就学习到I2C的一些常用知识,构成此通信协议最基本的时序单元等,但是在应用实现的过程中理解IC2协议这个才是最重要的。希望大家收获满满,在此感谢大家的观看!!!

最后如果有什么疑问或者文章内容有错的来评论区一起讨论讨论!!!

                                  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

回不去的bug

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值