I2C总线——EEPROM读写——51单片机模拟通讯

15号开始看串口和I2C通信,看了一遍视频跟着写了一次代码感觉一头雾水。尤其是主从应答的顺序,始终没搞懂。

I2C基础归纳

两根信号线,一根数据一根时序,主从模式,一应一答。龙顺宇讲stm8时举的例子:衙门断案,非常形象。今天在书店偶然看到,仔细翻阅了一下,收获很大。

我觉得这个难点主要在于应答位的掌握,究竟是主机应答还是从机应答,因为有的时候即便应答位设置错误,也能正常写入。这就导致了没有示波器情况下,无法简单的通过应答位来判断读写是否成功。所幸Proteus有I2C debug功能,今天一天终于分析出了可以稳定使用的模拟方式。还有就是接收,写入,开始,停止等子函数运行后SCL和SDA高低电平影响下一个子函数的问题。

I2C时序

我按照书上的几个时序分解画了一下,datasheet没太看懂。。。

借此机会,发现了一个画时序图很好用的软件TimeGen。

I2C总线——EEPROM读写——51单片机模拟通讯_第1张图片

需要注意的就是,主从应答信号前一个操作结束,应该在SCL = 0后,将SDA = 1,不然从机信号无法发送。不,主要是因为线与的关系,都是漏极开路,无法检测信号。

其余的没啥难度,下降沿触发开始,上升沿结束,有效数据要保持5us左右。然后就可以根据这些时序,将子函数依次写出来。

  • 全部程序

    #include
    #include
    
    sbit SDA = P2^0;
    sbit SCL = P2^1;
    unsigned char ACK_flag = 0;
    
    void delay_5us();
    void I2C_init();
    void I2C_Start();
    void I2C_Stop();
    void I2C_Send(unsigned char byte);
    unsigned char I2C_Get();
    void I2C_ACK_Send(bit A);
    bit I2C_ACK_Get();
    
    void I2C_Write(unsigned char add_7,unsigned char add,unsigned char byte);
    unsigned char I2C_Read(unsigned char add_7,unsigned char add);
    
    
    void delay(unsigned int z)
    {
    	unsigned int i;
    	unsigned char j;
    	for(i = z;i>0;i--)
    		for(j =  114;j>0;j--);
    }
    
    
    
    void main()
    {
    	I2C_init();			//初始化
    	/*	写入段	*/
    	I2C_Write(0xa0,0x20,0x55);
    						
    	delay(40);
    
    	/*	读取段	*/
    	P1 = I2C_Read(0xa0,0x20);
    	if(ACK_flag) P1 = 0x00;		//校验是否出现无应答
    	while(1);
    }
    
    /*	延时5微秒	*/
    void delay_5us()
    {
    	_nop_();
    }
    
    /*	I2C初始化	*/
    void I2C_init()		
    {
    	SCL = 1;  			//拉高SDA和SCL
    	_nop_();	
    	SDA = 1;		
    	delay_5us();		
    }
    
    /*	I2C开始信号	*/
    void I2C_Start()
    {
    	SDA = 1;
    	SCL = 1;			 //打开时钟
    	delay_5us();
    	SDA = 0;			 //产生SDA下降沿,触发开始信号
    	delay_5us();
    }
    
    /*	I2C结束信号	*/
    void I2C_Stop()
    {
    	SCL = 0;
    	SDA = 0;
    	_nop_();
    	SCL = 1;		   	//打开时钟
    	delay_5us();
    	SDA = 1;	   		//产生SDA上升沿,触发结束信号
    	delay_5us();
    }
    
    /*	I2C数据发送	*/
    void I2C_Send(unsigned char byte)
    {
    	unsigned char i,temp;
    	temp = byte;
    	for(i = 0;i<8;i++)
    	{
    		SCL = 0;		//关闭时钟准备数据变化
    		if(temp & 0x80)	//从最高位发送 1000 0000
    		{
    			SDA = 1;
    		}
    		else
    		{
    			SDA = 0;
    		}
    		delay_5us();
    		SCL = 1;	  	//打开时钟发送数据
    		delay_5us();
    		temp <<= 1;
    	}
    }
    
    /*	I2C数据接收	*/
    unsigned char I2C_Get()
    {
    	unsigned char i,byte;
    	for(i = 0;i<8;i++)
    	{
    		SCL = 0;  		//关闭时钟准备数据变化
    		_nop_();
    		SCL = 1;	   	//打开时钟接收数据
    		delay_5us();
    		if(SDA) byte++;	//从最高位接收
    		SCL = 0;		//接收完毕关闭时钟
    		if(i == 7) return byte; 
    		byte <<= 1;
    	}
    	return 0x50;		
    }
    
    /*	I2C主机应答 */
    void I2C_ACK_Send(bit A)
    {
    	SCL = 0;
    	_nop_();
    	if(A)				//如果i = 1那么拉低数据总线,表示主机应答。
    	{
    		SDA = 0;
    	}				//如果i = 0发送非应答
    	else
    	{
    		SDA = 1;
    	}
    	delay_5us();
    	SCL = 1;
    	_nop_();
    	SCL = 0;
    	_nop_();
    	SDA = 1;
    	_nop_();
    }
    /*	I2C从机应答*/
    bit I2C_ACK_Get()
    {
    	bit flag;
    	SCL = 0;
    	SDA = 1;
    	_nop_();
    	SCL = 1;
    	_nop_();
     	flag = SDA;
    	_nop_();
    	SCL = 0;
    	return flag; 
    }
    
    /*	写入段	*/
    void I2C_Write(unsigned char add_7,unsigned char add,unsigned char byte)
    {
    	I2C_Start();					//开始
    	I2C_Send(add_7+0);				//写eeprom
    	if(I2C_ACK_Get()) ACK_flag = 1;	 		//接收从机ACK
    	I2C_Send(add);					//选择内存地址
    	if(I2C_ACK_Get()) ACK_flag = 1;			//接收从机ACK
    	I2C_Send(byte);					//写数据
    	if(I2C_ACK_Get()) ACK_flag = 1;			//接收从机ACK
    	I2C_Stop();					//主机停止
    }
    /*	读取段	*/
    unsigned char I2C_Read(unsigned char add_7,unsigned char add)
    {
    	unsigned char message;
    	I2C_Start();					//开始
    	I2C_Send(add_7+0); 				//写eeprom
    	if(I2C_ACK_Get()) ACK_flag = 1;	 		//接收从机ACK
    	I2C_Send(add);					//选择内存地址
    	if(I2C_ACK_Get()) ACK_flag = 1;			//接收从机ACK
    
    	I2C_Start();					//重开始
    	I2C_Send(add_7+1);				//读eeprom
    	if(I2C_ACK_Get()) ACK_flag = 1;			//接收从机ACK
    	message = I2C_Get();			        //接收从机数据
    	I2C_ACK_Send(0);				//主机发送ACK
    	I2C_Stop();					//主机停止
    	return message;	
    }
    
    

     

  • Proteus仿真

I2C总线——EEPROM读写——51单片机模拟通讯_第2张图片

I2C总线——EEPROM读写——51单片机模拟通讯_第3张图片

I2C debug还是很好用的,但是示波器一直搞不懂怎么用。还好有视频教程的例程,分析了半天发现问题主要在第二段上。然而把两部分开执行都可以正常运行,之前在开发板上运行的时候也是这样。用例程的读取EEPROM程序也能读出之前写入的值。

最后的原因是,EEPROM写入可擦除存储部分需要花10ms的时间,datasheet上有写,但是没看懂。

你可能感兴趣的:(学科总结)