当前位置: 首页> 财经> 金融 > 硬件I2C和软件I2C(模拟I2C)的区别

硬件I2C和软件I2C(模拟I2C)的区别

时间:2025/8/24 12:46:03来源:https://blog.csdn.net/qq_65582400/article/details/141232001 浏览次数:0次

硬件I2C和软件I2C是两种不同的实现I2C(Inter-Integrated Circuit,集成电路间)通信协议的方式,它们在实现方式、性能特点以及应用场景上存在显著差异。

一、实现方式

  • 硬件I2C:通过专门的硬件电路实现,这些电路通常由微控制器或其他集成电路上的硬件模块提供支持。硬件I2C可以直接调用内部寄存器进行配置,利用芯片中的硬件I2C外设,自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,从而减轻CPU的负担。
  • 软件I2C:通过软件控制GPIO(通用输入输出)管脚来模拟I2C协议的时序。这通常涉及到在程序中控制SCL(串行时钟)和SDA(串行数据)线的电平状态,以模拟I2C通信的起始、停止、数据发送和接收等过程。

二、性能特点

  • 硬件I2C

    • 高速传输:由于使用专门的硬件电路,硬件I2C可以实现较高的数据传输速度,通常可以达到400kHz或更高。
    • 低占用率:传输过程由硬件电路完成,不需要CPU的直接参与,因此可以释放CPU资源,降低系统负载。
    • 稳定性高:时序控制由硬件电路完成,不易受到外部干扰的影响,具有较高的通信稳定性。
    • 占用CPU资源少:由于硬件I2C的传输过程不需要CPU的干预,因此可以显著减少CPU的占用率。
    • 可靠性高:硬件I2C的实现符合I2C标准,具有较高的可靠性。

    然而,硬件I2C也存在一些缺点,如外设数量限制和异常处理难度较大。

  • 软件I2C

    • 灵活性高:可以使用任意的GPIO管脚来实现,适应不同的硬件平台和需求。
    • 可移植性强:不依赖于特定的硬件电路,可以在不同的平台上进行移植和使用。
    • 适用范围广:在没有硬件I2C支持的情况下,软件I2C可以作为替代方案,也可以用于扩展硬件I2C的功能。
    • 软件I2C通常也被称为模拟I2C:这是因为软件I2C是通过软件编程来控制GPIO(通用输入输出)引脚来模拟I2C通信协议的时序和信号。它不使用微控制器或其他集成电路上的硬件I2C模块,而是通过编写代码来手动控制SCL(串行时钟)和SDA(串行数据)线的电平状态,从而模拟出I2C通信的起始条件、停止条件、数据发送和接收等过程。

      模拟I2C(即软件I2C)的优点在于其灵活性和可移植性,因为它不依赖于特定的硬件电路,可以在任何具有GPIO引脚的微控制器或处理器上实现。然而,与硬件I2C相比,模拟I2C的通信速度可能较慢,且由于需要CPU的参与来生成时序,因此会占用较多的CPU资源。此外,模拟I2C的实现可能不如硬件I2C稳定,因为它更容易受到外部干扰和程序错误的影响。

    然而,软件I2C的速度和稳定性可能不如硬件I2C,且需要CPU的参与,因此会占用较多的CPU资源。此外,软件I2C的实现可能不符合I2C标准,导致可靠性较低。

三、应用场景

  • 硬件I2C:适用于对传输速度和稳定性要求较高的场景,如高速数据传输、实时性要求较高的系统等。
  • /** @description		: 初始化I2C,波特率100KHZ* @param - base 	: 要初始化的IIC设置* @return 			: 无*/
    void i2c_init(I2C_Type *base)
    {/* 1、配置I2C */base->I2CR &= ~(1 << 7); /* 要访问I2C的寄存器,首先需要先关闭I2C *//* 设置波特率为100K* I2C的时钟源来源于IPG_CLK_ROOT=66Mhz* IC2 时钟 = PERCLK_ROOT/dividison(IFDR寄存器)* 设置寄存器IFDR,IFDR寄存器参考IMX6UL参考手册P1260页,表29-3,* 根据表29-3里面的值,挑选出一个还是的分频数,比如本例程我们* 设置I2C的波特率为100K, 因此当分频值=66000000/100000=660.* 在表29-3里面查找,没有660这个值,但是有640,因此就用640,* 即寄存器IFDR的IC位设置为0X15*/base->IFDR = 0X15 << 0;/** 设置寄存器I2CR,开启I2C* bit[7] : 1 使能I2C,I2CR寄存器其他位其作用之前,此位必须最先置1*/base->I2CR |= (1<<7);
    }/** @description			: 发送重新开始信号* @param - base 		: 要使用的IIC* @param - addrss		: 设备地址* @param - direction	: 方向* @return 				: 0 正常 其他值 出错*/
    unsigned char i2c_master_repeated_start(I2C_Type *base, unsigned char address,  enum i2c_direction direction)
    {/* I2C忙并且工作在从模式,跳出 */if(base->I2SR & (1 << 5) && (((base->I2CR) & (1 << 5)) == 0))		return 1;/** 设置寄存器I2CR* bit[4]: 1 发送* bit[2]: 1 产生重新开始信号*/base->I2CR |=  (1 << 4) | (1 << 2);/** 设置寄存器I2DR* bit[7:0] : 要发送的数据,这里写入从设备地址*            参考资料:IMX6UL参考手册P1249*/ base->I2DR = ((unsigned int)address << 1) | ((direction == kI2C_Read)? 1 : 0);return 0;
    }/** @description			: 发送开始信号* @param - base 		: 要使用的IIC* @param - addrss		: 设备地址* @param - direction	: 方向* @return 				: 0 正常 其他值 出错*/
    unsigned char i2c_master_start(I2C_Type *base, unsigned char address,  enum i2c_direction direction)
    {if(base->I2SR & (1 << 5))			/* I2C忙 */return 1;/** 设置寄存器I2CR* bit[5]: 1 主模式* bit[4]: 1 发送*/base->I2CR |=  (1 << 5) | (1 << 4);/** 设置寄存器I2DR* bit[7:0] : 要发送的数据,这里写入从设备地址*            参考资料:IMX6UL参考手册P1249*/ base->I2DR = ((unsigned int)address << 1) | ((direction == kI2C_Read)? 1 : 0);return 0;
    }/** @description		: 检查并清除错误* @param - base 	: 要使用的IIC* @param - status	: 状态* @return 			: 状态结果*/
    unsigned char i2c_check_and_clear_error(I2C_Type *base, unsigned int status)
    {/* 检查是否发生仲裁丢失错误 */if(status & (1<<4)){base->I2SR &= ~(1<<4);		/* 清除仲裁丢失错误位 			*/base->I2CR &= ~(1 << 7);	/* 先关闭I2C 				*/base->I2CR |= (1 << 7);		/* 重新打开I2C 				*/return I2C_STATUS_ARBITRATIONLOST;} else if(status & (1 << 0))     	/* 没有接收到从机的应答信号 */{return I2C_STATUS_NAK;		/* 返回NAK(No acknowledge) */}return I2C_STATUS_OK;
    }/** @description		: 停止信号* @param - base	: 要使用的IIC* @param			: 无* @return 			: 状态结果*/
    unsigned char i2c_master_stop(I2C_Type *base)
    {unsigned short timeout = 0xffff;/** 清除I2CR的bit[5:3]这三位*/base->I2CR &= ~((1 << 5) | (1 << 4) | (1 << 3));/* 等待忙结束 */while((base->I2SR & (1 << 5))){timeout--;if(timeout == 0)	/* 超时跳出 */return I2C_STATUS_TIMEOUT;}return I2C_STATUS_OK;
    }/** @description		: 发送数据* @param - base 	: 要使用的IIC* @param - buf		: 要发送的数据* @param - size	: 要发送的数据大小* @param - flags	: 标志* @return 			: 无*/
    void i2c_master_write(I2C_Type *base, const unsigned char *buf, unsigned int size)
    {/* 等待传输完成 */while(!(base->I2SR & (1 << 7))); /*这里判断base->I2SR的bit7,可以理解为i2c有没有被占用,手册1467中说这个位由最后一个字节传输的第9个时钟的下降沿设置,完成为一。我们要考虑当传输多个字节时,每传输完一个字节(不是最后一个字节),bit7并不会被置一,而bit1每传输完一个字节(包括最后一个字节)都会被置一,因此在要开始传输时判断bit7也就是i2c有没有被占用,在传输多个字节的过程中每传输完一个字节要判断bit1并置0。*/base->I2SR &= ~(1 << 1); 	/* 清除标志位 */base->I2CR |= 1 << 4;		/* 发送数据 */while(size--){base->I2DR = *buf++; 	/* 将buf中的数据写入到I2DR寄存器 */while(!(base->I2SR & (1 << 1))); 	/* 等待传输完成 */	base->I2SR &= ~(1 << 1);			/* 清除标志位 *//* 检查ACK */if(i2c_check_and_clear_error(base, base->I2SR))break;}base->I2SR &= ~(1 << 1);i2c_master_stop(base); 	/* 发送停止信号 */
    }/** @description		: 读取数据* @param - base 	: 要使用的IIC* @param - buf		: 读取到数据* @param - size	: 要读取的数据大小* @return 			: 无*/
    void i2c_master_read(I2C_Type *base, unsigned char *buf, unsigned int size)
    {volatile uint8_t dummy = 0;dummy++; 	/* 防止编译报错 *//* 等待传输完成 */while(!(base->I2SR & (1 << 7))); base->I2SR &= ~(1 << 1); 				/* 清除中断挂起位 */base->I2CR &= ~((1 << 4) | (1 << 3));	/* 接收数据 *//* 如果只接收一个字节数据的话发送NACK信号 */if(size == 1)base->I2CR |= (1 << 3);dummy = base->I2DR; /* 假读:假读触发下一个字节的传输 */while(size--){while(!(base->I2SR & (1 << 1))); 	/* 等待传输完成 */	base->I2SR &= ~(1 << 1);			/* 清除标志位 */if(size == 0){i2c_master_stop(base); 			/* 发送停止信号 */}if(size == 1){base->I2CR |= (1 << 3);}*buf++ = base->I2DR;}
    }/** @description	: I2C数据传输,包括读和写* @param - base: 要使用的IIC* @param - xfer: 传输结构体* @return 		: 传输结果,0 成功,其他值 失败;*/
    unsigned char i2c_master_transfer(I2C_Type *base, struct i2c_transfer *xfer)
    {unsigned char ret = 0;enum i2c_direction direction = xfer->direction;	base->I2SR &= ~((1 << 1) | (1 << 4));			/* 清除标志位 *//* 等待传输完成 */while(!((base->I2SR >> 7) & 0X1)){}; /* 如果是读的话,要先发送寄存器地址,所以要先将方向改为写 */if ((xfer->subaddressSize > 0) && (xfer->direction == kI2C_Read)){direction = kI2C_Write;}ret = i2c_master_start(base, xfer->slaveAddress, direction); /* 发送开始信号 */if(ret){	return ret;}while(!(base->I2SR & (1 << 1))){};			/* 等待传输完成 */ret = i2c_check_and_clear_error(base, base->I2SR);	/* 检查是否出现传输错误 */if(ret){i2c_master_stop(base); 						/* 发送出错,发送停止信号 */return ret;}/* 发送寄存器地址 */if(xfer->subaddressSize){do{base->I2SR &= ~(1 << 1);			/* 清除标志位 */xfer->subaddressSize--;				/* 地址长度减一 */base->I2DR =  ((xfer->subaddress) >> (8 * xfer->subaddressSize)); //向I2DR寄存器写入子地址while(!(base->I2SR & (1 << 1)));  	/* 等待传输完成 *//* 检查是否有错误发生 */ret = i2c_check_and_clear_error(base, base->I2SR);if(ret){i2c_master_stop(base); 				/* 发送停止信号 */return ret;}  } while ((xfer->subaddressSize > 0) && (ret == I2C_STATUS_OK));if(xfer->direction == kI2C_Read) 		/* 读取数据 */{base->I2SR &= ~(1 << 1);			/* 清除中断挂起位 */i2c_master_repeated_start(base, xfer->slaveAddress, kI2C_Read); /* 发送重复开始信号和从机地址 */while(!(base->I2SR & (1 << 1))){};/* 等待传输完成 *//* 检查是否有错误发生 */ret = i2c_check_and_clear_error(base, base->I2SR);if(ret){ret = I2C_STATUS_ADDRNAK;i2c_master_stop(base); 		/* 发送停止信号 */return ret;  }}}	/* 发送数据 */if ((xfer->direction == kI2C_Write) && (xfer->dataSize > 0)){i2c_master_write(base, xfer->data, xfer->dataSize);}/* 读取数据 */if ((xfer->direction == kI2C_Read) && (xfer->dataSize > 0)){i2c_master_read(base, xfer->data, xfer->dataSize);}return 0;	
    }
  • 软件I2C:适用于没有硬件I2C支持或需要扩展硬件I2C功能的场景,如低成本、低功耗的嵌入式系统、小型设备等。
  • //初始化IIC
    void IIC_Init(void)
    {			GPIO_InitTypeDef  GPIO_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB时钟//GPIOB8,B9初始化设置GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHzGPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化IIC_SCL=1;IIC_SDA=1;
    }
    //产生IIC起始信号
    void IIC_Start(void)
    {SDA_OUT();     //sda线输出IIC_SDA=1;	  	  IIC_SCL=1;delay_us(4);IIC_SDA=0;//START:when CLK is high,DATA change form high to low delay_us(4);IIC_SCL=0;//钳住I2C总线,准备发送或接收数据 
    }	  
    //产生IIC停止信号
    void IIC_Stop(void)
    {SDA_OUT();//sda线输出IIC_SCL=0;IIC_SDA=0;//STOP:when CLK is high DATA change form low to highdelay_us(4);IIC_SCL=1; IIC_SDA=1;//发送I2C总线结束信号delay_us(4);							   	
    }
    //等待应答信号到来
    //返回值:1,接收应答失败
    //        0,接收应答成功
    u8 IIC_Wait_Ack(void)
    {u8 ucErrTime=0;SDA_IN();      //SDA设置为输入  IIC_SDA=1;delay_us(1);	   IIC_SCL=1;delay_us(1);	 while(READ_SDA){ucErrTime++;if(ucErrTime>250){IIC_Stop();return 1;}}IIC_SCL=0;//时钟输出0 	   return 0;  
    } 
    //产生ACK应答
    void IIC_Ack(void)
    {IIC_SCL=0;SDA_OUT();IIC_SDA=0;delay_us(2);IIC_SCL=1;delay_us(2);IIC_SCL=0;
    }
    //不产生ACK应答		    
    void IIC_NAck(void)
    {IIC_SCL=0;SDA_OUT();IIC_SDA=1;delay_us(2);IIC_SCL=1;delay_us(2);IIC_SCL=0;
    }					 				     
    //IIC发送一个字节
    //返回从机有无应答
    //1,有应答
    //0,无应答			  
    void IIC_Send_Byte(u8 txd)
    {                        u8 t;   SDA_OUT(); 	    IIC_SCL=0;//拉低时钟开始数据传输for(t=0;t<8;t++){              IIC_SDA=(txd&0x80)>>7;txd<<=1; 	  delay_us(2);   //对TEA5767这三个延时都是必须的IIC_SCL=1;delay_us(2); IIC_SCL=0;	delay_us(2);}	 
    } 	    
    //读1个字节,ack=1时,发送ACK,ack=0,发送nACK   
    u8 IIC_Read_Byte(unsigned char ack)
    {unsigned char i,receive=0;SDA_IN();//SDA设置为输入for(i=0;i<8;i++ ){IIC_SCL=0; delay_us(2);IIC_SCL=1;receive<<=1;if(READ_SDA)receive++;   delay_us(1); }					 if (!ack)IIC_NAck();//发送nACKelseIIC_Ack(); //发送ACK   return receive;
    }
    

综上所述,硬件I2C和软件I2C各有其优势和适用场景。在选择时,需要根据具体的应用需求和系统资源来权衡利弊,选择最合适的实现方式。

关键字:硬件I2C和软件I2C(模拟I2C)的区别

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: