一文搞懂STM32的IIC通信协议

目前,IIC总线已经成为了一种通用的通信协议,广泛应用于各种类型的电子设备中。IIC总线的稳定性、可靠性和灵活性都得到了广泛的认可,并且随着技术的不断发展,IIC总线仍然在不断地演化和升级,以适应新的应用场景和需求。


 前言

        随着嵌入式人工智能的不断发展,学习stm32单片机这门技术也越来越重要,很多人都开启了stm32单片机的学习,本文就介绍了stm32单片机中一个比较重要的通信协议。

 一、IIC的通信方式

         通俗来说,IIC其实就是一个片上外设,主要通过GPIO模拟IIC的时序进行通信。再深入往底层来讲,IIC是由总线拉低或拉高来控制发送信号。并且IIC支持多主机多从机进行通信

二、总线的作用

         总线是由时钟线(SCL)和数据线(SDA)两根线相辅相成,而总线上必须接两个上拉电阻,用于空闲时拉高总线。当多个主机同时发起IIC通信时,由总线仲裁决定每个设备都有唯一的ID,只有主机能够发起IIC

        时钟线SCL的作用:当SCL为高电平时,SDA上的数据才有效;当SCL为低电平时,SDA上的数据无效

        总线仲裁则是指遵循低电平优先原则,一是看哪个设备先发出低电平,二是看哪个设备发出的低电平时间长

 三、IIC协议的7个信号

 1 . 起始信号(谁发起信号谁就是主机,主机能指定地址与该设备通信)

         SCL为高电平期间,SDA由高电平变低电平,产生下降沿

 2 . 停止信号

        SCL为高电平期间,SDA由低电平变高电平,产生上升沿

 3 . 应答信号

        SCL为高电平时,SDA产生一个低电平

 4 . 非应答信号

        SCL为高电平时,SDA产生一个高电平

 5 . 等待应答

        拉高SCL线,等待SDA线上的高电平或低电平

 6 . 发送数据

        当SCL为高电平时,SDA上数据是稳定的,当SCL为低电平时,SDA上的数据可以改变

 7 . 接收数据    

        当SCL为高电平时,SDA上数据是稳定的,当SCL为低电平时,SDA上的数据可以改变

 四、IIC的时钟线初始化分析

 1 . SCL时钟线初始化(GPIOB6)

        设置开漏输出模式是因为确保没有数据传输时,两条线都处于高电平,所以外接有上拉电阻,也就是可以设置开漏输出了

 2 . SDA数据线初始化(两条SDA)

        设置浮空输入模式是因为数据传输时会把SDA拉高或拉低,而拉高表示传输完成,所以为了确保SDA传输完成时没有被拉高,所以外部电路会负责来拉高

 五、IIC通信在不同设备的时序

 1 . 主要分为起始条件、停止条件、ACK信号

        a . 起始条件是指SDA数据线从高电平切换到低电平,同时SCL时钟线保持高电平的状态

        b . 停止条件是指SDA数据线从低电平切换到高电平,同时SCL时钟线保持高电平的状态

        c . ACK信号是指接收方通过拉低SDA线来确认已成功接收到数据的信号

 2 . I2C_CR1和I2C_SR1寄存器

        它们是用来设置和监测I2C通信协议的状态,并使用起始条件、停止条件和ACK信号等时序来确保正确的数据传输

 六、IIC通信的内部传参

 1 . 主机向从机发送写操作:

1. 发送I2C起始信号
IIC_START();
这个函数调用发送I2C起始信号,用于启动I2C通信。

2. 发送AT24C0X的I2C地址
IIC_SendByteData(0Xa0);
这个函数调用发送AT24C0X的I2C地址,用于与AT24C0X建立连接。0xa0是AT24C0X的I2C地址,因为它是一个7位地址,所以需要将它左移一位,以便在最后一位添加读写标志位。

3. 等待从设备的ACK信号
if(ACK!=IIC_WaitACK()){	//等待
	IIC_STOP();
	return;
}
这个函数用于等待从设备的ACK信号。如果从设备没有响应ACK信号,函数将停止I2C通信,并返回。

4. 发送要写入的地址
IIC_SendByteData(ADDR);
这个函数用于发送要写入的地址。它告诉AT24C0X从哪个地址开始写入数据。

5. 等待从设备的ACK信号
if(ACK!=IIC_WaitACK()){	//等待从机ACK
	IIC_STOP();
	return;
}
这个函数用于等待从设备的ACK信号。如果从设备没有响应ACK信号,函数将停止I2C通信,并返回。

6. 发送要写入的数据
IIC_SendByteData(DATA);
这个函数用于发送要写入的数据。它告诉AT24C0X需要写入的数据。

7. 等待从设备的ACK信号
if(ACK!=IIC_WaitACK()){	//等待从机ACK
	IIC_STOP();
	return;
}
这个函数用于等待从设备的ACK信号。如果从设备没有响应ACK信号,函数将停止I2C通信,并返回。

8. 发送I2C停止信号
IIC_STOP();
这个函数用于发送I2C停止信号,以结束I2C通信。

总体来说,这段代码的作用是向AT24C0X EEPROM存储器中写入一个字节的数据。
如果写入过程中出现错误,函数将停止I2C通信,并返回

 2 .  主机向从机发送读操作

它使用了I2C通信协议进行数据传输。

- uint8_t:
	这个函数返回一个8位的无符号整数,用于存储读取的数据。
- AT24C0X_ReadByteData(uint32_t ADDR):
	这个函数带有一个参数ADDR,它是要读取的数据在EEPROM中的地址。
- IIC_START():
	这个函数用于发送起始信号,标志着通信的开始。
- IIC_SendByteData(0xa0):
	这个函数用于向I2C总线发送一个字节的数据,其中0xa0表示AT24C0X EEPROM存储器的地址。
这是一个写入请求,因为最后一位是0。
- ACK!=IIC_WaitACK():
	这个语句用于等待从设备的应答信号。ACK表示应答信号,如果IIC_WaitACK()函数返回的值
不是ACK,则表示没有收到应答信号,需要停止通信。
- IIC_SendByteData(ADDR):
	这个函数用于向EEPROM发送要读取的数据的地址。
- IIC_START():
	这个函数用于发送起始信号,标志着通信的开始。
- IIC_SendByteData(0xa1):
	这个函数用于向I2C总线发送一个字节的数据,其中0xa1表示AT24C0X EEPROM存储器的地址。
这是一个读取请求,因为最后一位是1。
- Rxdata = IIC_RxByteData(NoACK):
	这个语句用于接收从EEPROM发送来的数据。NoACK表示不需要发送应答信号,因为这是最后一个字节。
- IIC_STOP():这个函数用于发送停止信号,标志着通信的结束。

 七、IIC通信的详细实现步骤

  1 . IIC的7个时序函数

#include "drv_iic.h"
#include "drv_systick.h"

//SCL时钟线初始化
void IIC_Config(void){
	 GPIO_InitTypeDef GPIO_InitStruct;
	//1、打开时钟GPIOB(采用引脚模拟IIC通信)
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	//2、初始化GPIO
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;//SCL
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;//开漏输出(外接有两个上拉电阻)
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_Init(GPIOB, &GPIO_InitStruct);
}

//SDA数据线输出的初始化
void IIC_SDA_OUT(void){
	GPIO_InitTypeDef GPIO_InitStruct;
	//初始化GPIO
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;//开漏输出(外接有两个上拉电阻)
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_Init(GPIOB, &GPIO_InitStruct);
}
//SDA数据线输入的初始化
void IIC_SDA_IN(void){
	GPIO_InitTypeDef GPIO_InitStruct;
	//初始化GPIO
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
	GPIO_Init(GPIOB, &GPIO_InitStruct);
}

//起始信号:SCL为高电平期间,SDA由高电平变低电平,产生下降沿
//发送信号都是由主机
void IIC_START(void){
	//1、拉低SCL线,保证在改变SDA线时不会产生其他信号
	SCL_LOW;
	//2、输出起始信号,将SDA设置成输出模式
	IIC_SDA_OUT();
	//3、准备好有效数据后,拉高SDA线
	SDA_HIGH;
	//4、拉高SCL线让SDA的高电平有效
	SCL_HIGH;
	//5、保证两者的高电平维持5us有效
	set_Nudelay(5);
	//6、跳变低电平,产生下降沿,发起IIC通信
	SDA_LOW;
	//7、保证下降沿维持5us有效
	set_Nudelay(5);
	//8、发起IIC后让其他主机不能再发起
	SCL_LOW;
}
//停止信号:SCL为高电平期间,SDA由低电平变高电平,产生上升沿
//发送信号都是由主机
void IIC_STOP(void){
	//1、拉低SCL线,保证在改变SDA线时不会产生其他信号
	SCL_LOW;
	//2、输出停止信号,将SDA设置成输出模式
	IIC_SDA_OUT();
	//3、准备好有效数据后,拉低SDA线
	SDA_LOW;
	//4、拉高SCL线让SDA的低电平有效
	SCL_HIGH;
	//5、保证两者的电平差维持5us有效
	set_Nudelay(5);
	//6、跳变高电平,产生上升沿,发起停止
	SDA_HIGH;
	//7、保证上升沿维持5us有效
	set_Nudelay(5);
}
//应答信号:  SCL为高电平时,SDA产生一个低电平
void IIC_SendACK(void){
	//1、拉低SCL线,保证在改变SDA线时不会产生其他信号
	SCL_LOW;
	//2、输出应答信号,将SDA设置成输出模式
	IIC_SDA_OUT();
	//3、准备好有效数据后,拉低SDA线
	SDA_LOW;	//SDA先拉低防止产生下降沿或上升沿
	//4、拉高SCL线让SDA的低电平有效
	SCL_HIGH;
	//5、保证两者的电平差维持5us有效
	set_Nudelay(5);
	//6、让其他主机不能再发起
	SCL_LOW;
}
//非应答信号:  SCL为高电平时,SDA产生一个高电平
void IIC_SendNoACK(void){
	//1、拉低SCL线,保证在改变SDA线时不会产生其他信号
	SCL_LOW;
	//2、输出非应答信号,将SDA设置成输出模式
	IIC_SDA_OUT();
	//3、准备好有效数据后,拉低SDA线
	SDA_HIGH;
	//4、拉高SCL线让SDA的高电平有效
	SCL_HIGH;
	//5、保证两者的高电平维持5us有效
	set_Nudelay(5);
	//6、让其他主机不能再发起
	SCL_LOW;
}
//等待应答:  拉高SCL线,等待SDA线上的高电平或低电平
uint8_t IIC_WaitACK(void){
	//1、超时等待的变量
	uint8_t temp = 0;
	//2、拉低SCL线,保证在改变SDA线时不会产生其他信号
	SCL_LOW;
	//3、等待应答信号,将SDA设置成输入模式,即接收
	IIC_SDA_IN();
	//4、拉高SCL线等待
	SCL_HIGH;
	set_Nudelay(5);
	//5、等待高电平或低电平
	while(SDA_READ){		//高电平表示非应答,低电平表示应答
		temp++;
		//如果没有应答,则停止IIC通信
		if(temp>250){
			IIC_STOP();
			return NoACK;
		}
	}
	//如果成功应答则拉低SCL线,让其他主机不能再发起IIC
	SCL_LOW;
	return ACK;
}
//发送数据:  当SCL为高电平时,SDA上的数据是稳定的,  当SCL为低电平时,SDA上的数据可以改变
//主机向从机发送数据,先发送地址,确认后再发送数据
void IIC_SendByteData(uint8_t data){
	uint8_t i;
	//拉低SCL线,保证在改变SDA线时不会产生其他信号
	SCL_LOW;
	//输出非应答信号,将SDA设置成输出模式
	IIC_SDA_OUT();
	//IIC是串行通信,所以一个字节分8次发送
	for(i=0;i<8;i++){
		//IIC是高位先行,先判断最高位的电平
		if(data&0x80){	//SCL以拉低,SDA改变不影响,所以可以用来区分数据0/1
			//高电平时SDA=1
			SDA_HIGH;
		}else{
			//低电平时SDA=0
			SDA_LOW;
		}
		//拉高SCL线让SDA发送数据有效
		SCL_HIGH;
		//维持5us有效时间
		set_Nudelay(5);
		//发送完前一个数据,拉低SCL才能等待发送下一个数据
		SCL_LOW;
		//维持5us有效时间
		set_Nudelay(5);
		//发送完最高位左移发送下一位
		data = data<<1;
	}
	//让其他主机不能再发起IIC
	SCL_LOW;
}
//接收数据:同发送数据一样
//主机接收从机发送过来的地址
uint8_t IIC_RxByteData(uint8_t ackflag){
	uint8_t i;
	uint8_t Rxdata=0;
	//拉低SCL线,保证在改变SDA线时不会产生其他信号
	SCL_LOW;
	//接收数据,将SDA设置成输入模式
	IIC_SDA_IN();
	//IIC是串行通信,所以一个字节分8次接收
	for(i=0;i<8;i++){
		//左移要放判断前面,否则数据多左移了一次
		Rxdata=Rxdata<<1;
		//拉高SCL,让从机发出数据
		SCL_HIGH;
		//维持5us有效时间
		set_Nudelay(5);
		//IIC是高位先行,先判断最高位的电平
		if(SDA_READ){
			//如果是高电平则记录下来
			Rxdata=Rxdata|0x01;
		}
		//拉低SCL线,让从机准备下一个数据发送
		SCL_LOW;
		//维持5us有效时间
		set_Nudelay(5);
	}
	//让其他主机不能再发起IIC
	SCL_LOW;
	return Rxdata;
}

 2 .  IIC的读写操作

#include "drv_at24c02.h"
#include "drv_iic.h"

void AT24C0X_WriteByteData(uint8_t ADDR,uint8_t DATA){
	IIC_START();
	IIC_SendByteData(0Xa0);
	if(ACK!=IIC_WaitACK()){	//等待
		IIC_STOP();
		return;
	}

	IIC_SendByteData(ADDR);	//告诉从机往哪个地址写入数据
	if(ACK!=IIC_WaitACK()){	//等待从机ACK
		IIC_STOP();
		return;
	}

	IIC_SendByteData(DATA);	//告诉从机需要写入的数据
	if(ACK!=IIC_WaitACK()){	//等待从机ACK
		IIC_STOP();
		return;
	}

	IIC_STOP();
}

uint8_t AT24C0X_ReadByteData(uint32_t ADDR){
	uint8_t Rxdata = 0;
	//第一个通信周期(发送读取请求)
	IIC_START();
	IIC_SendByteData(0xa0);//从设备的地址
	if(ACK!=IIC_WaitACK()){//等待应答
		IIC_STOP();
		return 0;
	}

	IIC_SendByteData(ADDR);//要读取的数据的地址
	if(ACK!=IIC_WaitACK()){//等待应答
		IIC_STOP();
		return 0;
	}
	
	//第二个通信周期(发送要读取的数据)
	IIC_START();

	IIC_SendByteData(0xa1);//从设备的地址(最后一位是读取标志)
	if(ACK!=IIC_WaitACK()){//等待应答
		IIC_STOP();
		return 0;
	}

	Rxdata = IIC_RxByteData(NoACK);//接收从机发送来的数据
	IIC_STOP();
	return Rxdata;
}

总结

  1. IIC总线由两根线组成:一个是数据线SDA,一个是时钟线SCL。

  2. IIC通信是基于主从模式的,一个主设备可以控制多个从设备。

  3. IIC通信的传输速率比较低,通常在100kHz或400kHz左右。

  4. IIC通信的协议非常简单,通信的过程是由起始信号、地址、数据、停止信号四个部分组成。

  5. IIC通信需要注意时序,例如数据的传输和接收都需要按照一定的时序进行。

  6. IIC通信在硬件上需要使用IIC接口的控制器和外设芯片。

  7. 在程序开发中,需要按照IIC协议的通信过程进行编写,以实现数据的读写和设备的控制。

总的来说,IIC通信是一种简单、可靠、广泛应用的通信协议,对于嵌入式系统的开发和应用具有重要的意义。

  • 3
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值