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

硬件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;//100MHz
      GPIO_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 high
     	delay_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();//发送nACK
        else
            IIC_Ack(); //发送ACK   
        return receive;
    }
    

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值