I2C协议代码的实现关键

    I2C总线我已经用很久了,也用了很多次,但每到下一次使用时,都会或多或少的发现一些小问题,比如读写单个字节时没有问题,在连续读写大量数据时却出现读写不正确的现象,下面来总结一下模拟I2C驱动代码的实现关键

1、起始信号start:这个一般不会出错,在SCL=1时,让SDA出现一个下降沿,即SDA=1 --->SDA=0;

2、停止信号stop:这个一般也不会出错,在SCL=1时,让SDA出现一个上升沿,即SDA=0 --->SDA=1;

3、主机(如单片机)检测应答信号:I2C要求,接收方在接收到一个字节后,在第9个时钟回一个应答信号,主机检测这个应答信号应这样操作,SCL=0时,释放SDA(即SDA=1),然后让SCL=1,接收方在检测到SCL=1后会让SDA=0作为应答信号,随后发送方就可检测SDA的状态;

4、主机回复应答信号:主机在接收从机发来的一个字节后,要回一个应答信号,主机应这样操作,在SCL=0时,使SDA=0,然后让SCL=1,随后从机会检测到SDA的状态;

5、主机回复非应答信号:主机在接收数据后,在stop信号前,回一个非应答信号,主机应该这样操作,在SCL=0时,使SDA=1,然后让SCL=1,随后从机会检测到SDA的状态;

    以上5点用过几次I2C后一般都会掌握。

 

以下几点最为容易忽视,导致I2C时序出错,连续读写大量数据时容易丢包

1、特别注意SCL=1时,SDA的任何变化都会使时序出错,从机将视为起始信号或停止信号,所以要特别注意start 、write/read、ack/no_ack、stop衔接的前后操作,不要出现有SCL=1改变SDA状态的情况;为此,建议在以上几个函数的开始处后退出前,使SLC=0,这样在函数衔接时不会出现SCL跳变的情况,也不会在SCL=1时误操作SDA. 

2、start 、write/read、ack/no_ack、stop函数前后衔接时,要保持SCL的状态一致性,否则读写过程中可能会导致时钟个数错误,这个问题在读写单个字节时可能不会出现,但在多次读写中就会出现。

3、部分用户在连续读时没发现出错,觉得自己的时序是正确的,但连续写时就会出错,感觉莫名其妙,这是因为大多数器件的数据读出要比写入的速度要快,在连续写时,应在中间要延时一段时间;

4、 由于硬件上要求SCL和SDA都接上拉电阻,stop信号时在读写时最后的一个操作,所以,stop退出前,建议使SDA=1,SCL=1,从而不让上拉电阻两端有高低电位差,以降低功耗,当然,也要保证SLC=1时不要改变SDA,否则设备会认为是一个新的起始或停止信号。

 

下面时实用例程C代码


void I2C_delay(void)
{
	u16 i;
	/*以STM32  72MHz为例
        循环10次,SCL频率为20K
        循环7次,SCL频率为340K
        循环5次,SCL频率为420K
	*/
	for(i=0;i<5;i++);
}


void E2PROM_I2C_start(void)
{
	
	I2C_SetOut_Mode();	
	E2PROM_I2C_SCL=0;
	E2PROM_I2C_SDA_WR=1;
	E2PROM_I2C_SCL=1;
	I2C_delay();
	E2PROM_I2C_SDA_WR=0;
	E2PROM_I2C_SCL=0;
	I2C_delay();
}

void E2PROM_I2C_stop(void)
{
	
	I2C_SetOut_Mode();
	E2PROM_I2C_SCL=0;
	E2PROM_I2C_SDA_WR=0;
	E2PROM_I2C_SCL=1;
	I2C_delay();
	E2PROM_I2C_SDA_WR=1;
	E2PROM_I2C_SCL=0;
	I2C_delay();
	E2PROM_I2C_SDA_WR=1;//拉高数据和时钟线,以降低功耗
	E2PROM_I2C_SCL=1;  

}

u8 E2PROM_I2C_check_ack(void)
{
	u8 ack_status=1;
	I2C_SetIn_Mode();
	E2PROM_I2C_SCL=0;
	E2PROM_I2C_SDA_WR=1;
	E2PROM_I2C_SCL=1;
	I2C_delay();

	ack_status=0x01&E2PROM_I2C_SDA_RE;
	E2PROM_I2C_SCL=0;
	I2C_delay();

	return ack_status;
}


void E2PROM_I2C_ack(void)
{
	I2C_SetOut_Mode();
	E2PROM_I2C_SCL=0;
	E2PROM_I2C_SDA_WR=0;
	E2PROM_I2C_SCL=1;
	I2C_delay();
	E2PROM_I2C_SCL=0;
	I2C_delay();
	E2PROM_I2C_SDA_WR=1;
	
}

void E2PROM_I2C_NoAck(void)
{
	I2C_SetOut_Mode();
	E2PROM_I2C_SCL=0;
	E2PROM_I2C_SDA_WR=1;
	I2C_delay();
	E2PROM_I2C_SCL=1;
	I2C_delay();
	E2PROM_I2C_SCL=0;
	I2C_delay();
	
}


void E2PROM_I2C_write_char(u8 dat)
{
	u8 i=0;
	
	E2PROM_I2C_SCL=0;
	I2C_SetOut_Mode();
	for(i=0;i<8;i++)
	{
		
		if(dat&0x80) E2PROM_I2C_SDA_WR=1;
		else E2PROM_I2C_SDA_WR=0;
		I2C_delay();
		E2PROM_I2C_SCL=1;
		I2C_delay();
		E2PROM_I2C_SCL=0;
		dat<<=1;
	}
	
}

u8 E2PROM_I2C_read_char(void)
{
	u8 i=0,dat=0;
	E2PROM_I2C_SCL=0;
	I2C_SetIn_Mode();
	for(i=0;i<8;i++)
	{
	
		dat<<=1;
		E2PROM_I2C_SCL=1;
		I2C_delay();
		if(E2PROM_I2C_SDA_RE) dat|=0x01;
		E2PROM_I2C_SCL=0;
		I2C_delay();
	
	}
	return dat;
}



void AT24CXX_Write(u8 DeviceAddr,u8 ByteAddr,u8 dat)
{
	E2PROM_I2C_start();
	E2PROM_I2C_write_char(DeviceAddr);
	E2PROM_I2C_check_ack();
	E2PROM_I2C_write_char(ByteAddr);
	E2PROM_I2C_check_ack();
	E2PROM_I2C_write_char(dat);
	E2PROM_I2C_check_ack();
	E2PROM_I2C_stop();
	
	
}

unsigned char AT24CXX_Read(u8 DeviceAddr,u8 ByteAdrr)
{
	unsigned char dat=0;
	E2PROM_I2C_start();
	E2PROM_I2C_write_char(DeviceAddr);
	E2PROM_I2C_check_ack();
	E2PROM_I2C_write_char(ByteAdrr);
	E2PROM_I2C_check_ack();
	
	
	E2PROM_I2C_start();
	E2PROM_I2C_write_char(DeviceAddr|0x01);
	E2PROM_I2C_check_ack();
	dat=E2PROM_I2C_read_char();
	E2PROM_I2C_NoAck();
	E2PROM_I2C_stop();
	return dat;
	
}


u8 E2PROM_buffer[256];

int main(void)
{
    u16 i,j,k;
    for(i=0;i<256;i++)
	{
		AT24CXX_Write(I2C_24C08_write_cmd,i,i);
		Delay_ms(2);
	}
	
	k=0;
	printf("\r\n");
	for(i=0;i<16;i++)
	{
		for(j=0;j<16;j++)
		{
			E2PROM_buffer[k]=AT24CXX_Read(I2C_24C08_write_cmd,k);
			printf("%d ",E2PROM_buffer[k]);
			k++;

		}
		printf("\r\n");
	}

	printf("\r\n");
	
	for(i=0;i<255;i++)
	{
		if(E2PROM_buffer[i+1]-E2PROM_buffer[i] != 1) 
        {
            printf(" \r\n E2PROM TEST error !!! \r\n" );
            break;
	    }
     }
}

 

你可能感兴趣的:(单片机,嵌入式STM32应用)