STM32模拟IIC读写EEPROM

本人在最近的项目中,需要读写EEPROM里面的内容并分析。该EEPROM芯片为Microchip Technology公司的24LC02B系列。用STM32F103芯片做主机通过IIC接口去和它通信。

首先介绍芯片的基本特性,容量为 :256 x 8bit ,2Kbit共有256字节 ;地址和数据都是8bit;电源供电2.5V-5.5V;IIC支持最高速率400K;支持单字节写和按页写两种,写数据后最大需要延时5ms;支持连续读,单字节读和随机读取3种读方式。总的说来这颗IC很大众,操作简单。

主机STM32F1本来想用硬件IIC,找了例程,网上资料一大堆,也没成功,所以用的模拟IIC。波形虽然不太好看,但还是挺好用。

主机模拟IIC用的PB10和PB11,SCL时钟的周期为7us.代码如下:

#define sEE_I2C_CLK                      RCC_APB1Periph_I2C2
#define sEE_I2C_SCL_PIN                  GPIO_Pin_10                 /* PB.10 */
#define sEE_I2C_SCL_GPIO_PORT            GPIOB                       /* GPIOB */
#define sEE_I2C_SCL_GPIO_CLK             RCC_APB2Periph_GPIOB
#define sEE_I2C_SDA_PIN                  GPIO_Pin_11                  /* PB.11 */
#define sEE_I2C_SDA_GPIO_PORT            GPIOB                       /* GPIOB */
#define sEE_I2C_SDA_GPIO_CLK             RCC_APB2Periph_GPIOB


#define SDA_IN()  	{sEE_I2C_SCL_GPIO_PORT->CRH&=0XFFFF0FFF;sEE_I2C_SCL_GPIO_PORT->CRH|=(u32)8<<12;}
#define SDA_OUT() 	{sEE_I2C_SCL_GPIO_PORT->CRH&=0XFFFF0FFF;sEE_I2C_SCL_GPIO_PORT->CRH|=(u32)3<<12;} //IO操作函数	 
#define READ_SDA   	GPIO_ReadInputDataBit(sEE_I2C_SCL_GPIO_PORT,sEE_I2C_SDA_PIN)//输入SDA 
#define IIC_SDA_UP      GPIO_SetBits(sEE_I2C_SCL_GPIO_PORT,sEE_I2C_SDA_PIN)
#define IIC_SDA_DOWN    GPIO_ResetBits(sEE_I2C_SCL_GPIO_PORT,sEE_I2C_SDA_PIN)
#define IIC_SCL_UP      GPIO_SetBits(sEE_I2C_SCL_GPIO_PORT,sEE_I2C_SCL_PIN)
#define IIC_SCL_DOWN    GPIO_ResetBits(sEE_I2C_SCL_GPIO_PORT,sEE_I2C_SCL_PIN)

#define EEPROM_ADDR 	0xA0            //EEPROM的器件地址
#define PAGE_SIZE 	8		//按页写时,一页大小为8字节
#define ROM_SIZE        256		//整个ROM大小是256字节

#define I2C_BUF_LEN 256


uint8_t I2C_Rec[I2C_BUF_LEN];


static void delay_us(u32 t)
{
	u8 i = 5;

	while(t--)
	{
		i = 5;
		while(i--);
	}
}
static void delay_ms(u32 t)
{
	u16 i = 8000;

	while(t--)
	{
		i = 8000;
		while(i--);
	}
}


void IIC_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(	sEE_I2C_SCL_GPIO_CLK, ENABLE );

	GPIO_InitStructure.GPIO_Pin = sEE_I2C_SCL_PIN|sEE_I2C_SDA_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;   //推挽输出	
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;//GPIO_Speed_50MHz;

	GPIO_Init(sEE_I2C_SCL_GPIO_PORT, &GPIO_InitStructure);

	IIC_SCL_UP;
	IIC_SDA_UP;
}

//产生IIC起始信号
void IIC_Start(void)
{	
	SDA_OUT();     //sda线输出
	IIC_SDA_UP;
	IIC_SCL_UP;
	delay_us(4);
	IIC_SDA_DOWN;//START:when CLK is high,DATA change form high to low
	delay_us(4);
	IIC_SCL_DOWN; //钳住I2C总线,准备发送或接收数据 
}	
void IIC_Stop(void)
{	
	IIC_SCL_DOWN;
	delay_us(1);
	SDA_OUT();//sda线输出
	IIC_SDA_DOWN;//STOP:when CLK is high DATA change form low to high 
	delay_us(4);	
	IIC_SCL_UP;
	delay_us(2);
	IIC_SDA_UP;//发送I2C总线结束信号	
	delay_us(4);
}

//等待应答信号到来//返回值:1,接收应答失败//        0,接收应答成功
u8 IIC_Wait_Ack(void)
{	
	u16 ucErrTime=0;

	IIC_SCL_DOWN;
	SDA_IN();	   //SDA设置为输入
	delay_us(4);
	IIC_SCL_UP;
	delay_us(4);		
	while(READ_SDA) 
	{		
	    ucErrTime++;		
	    if(ucErrTime>250)//180us		
	    {			
		IIC_Stop();
		return 1;
	    }
	}
	IIC_SCL_DOWN; //时钟输出0 
	SDA_OUT();
	return 0;
} 

//产生ACK应答
void IIC_Ack(void)
{	
	IIC_SCL_DOWN; 
	SDA_OUT();
	IIC_SDA_DOWN;	
	delay_us(2);
	IIC_SCL_UP;
	delay_us(2);	
	IIC_SCL_DOWN; 
}
//不产生ACK应答		    
void IIC_NAck(void)
{	
	IIC_SCL_DOWN; 	
	SDA_OUT();	
	IIC_SDA_UP;
	delay_us(2);	
	IIC_SCL_UP;
	delay_us(2);
	IIC_SCL_DOWN;
}		

void IIC_Send_Byte(u8 txd)
{  
	u8 t;   	
	//SDA_OUT(); //此处打开,会有毛刺       
	IIC_SCL_DOWN; //拉低时钟开始数据传输  
	SDA_OUT();
	delay_us(2);
	for(t=0;t<8;t++)   
	{                   
        if((txd&0x80)>>7)
        {
	    IIC_SDA_UP;
        }
        else
        {
	    IIC_SDA_DOWN;
        }
		txd<<=1; 	  		
		delay_us(1);   //对TEA5767这三个延时都是必须的	
		IIC_SCL_UP;		
		delay_us(4);//delay_us(2); 	
		IIC_SCL_DOWN;			
		delay_us(2);
	}	
} 	

//读1个字节,ack=1时,发送ACK,ack=0,发送nACK 
u8 IIC_Read_Byte(unsigned char ack)
{	
#if 1
	unsigned char i,receive=0;	
	SDA_IN();//SDA设置为输入 
	for(i=0;i<8;i++ )	
	{   
		IIC_SCL_DOWN;  
		delay_us(2);		
		IIC_SCL_UP;
		receive<<=1;   
		if(READ_SDA)
		receive++;   		
		delay_us(1);  
	}					
	if (!ack)			
		IIC_NAck();//发送nACK	
	else			
		IIC_Ack(); //发送ACK  
	return receive;
#endif	
} 

void I2C_WriteByte(uint8_t word_addr, uint8_t data)
{	
	IIC_Start(); 
			
	IIC_Send_Byte(EEPROM_ADDR);	    //发器件地址	
	IIC_Wait_Ack(); 

	delay_us(6);//add ok

	IIC_Send_Byte(word_addr);   //发送低地址	

	IIC_Wait_Ack(); 
	delay_us(6);//add ok

	IIC_Send_Byte(data);	   //发送字节	

	IIC_Wait_Ack(); 

	IIC_Stop();//产生一个停止条件

	delay_ms(5);//写操作时,最后的延时不得小于3ms
}

void I2C_WriteBytePage(uint8_t word_addr,const uint8_t *pdata)
{	
	uint8_t i=0;
	uint8_t txdata;
	IIC_Start(); 
			
	IIC_Send_Byte(EEPROM_ADDR);	    //发器件地址	

	IIC_Wait_Ack(); 
	delay_us(6);//add ok
	IIC_Send_Byte(word_addr);   //发送寄存器地址
	IIC_Wait_Ack(); 
	delay_us(6);

	while(i < PAGE_SIZE)
	{
		txdata = pdata[i];
		IIC_Send_Byte(txdata);     //发送字节	
		IIC_Wait_Ack(); 
        i++;
		delay_us(4); 
	}

	IIC_Stop();//产生一个停止条件

	delay_ms(5);//写操作时,最后的延时不得小于3ms
}


void ReadLenByte(u8 *pbuf,u16 len)
{
	u16 t;

	IIC_Start(); 

	IIC_Send_Byte(EEPROM_ADDR);	   //发送写命令	
	IIC_Wait_Ack();
	delay_us(5);
	IIC_Send_Byte(0x0);    //发送寄存器地址
	IIC_Wait_Ack();	
	delay_us(5);

	IIC_Start();  	 	  
	IIC_Send_Byte(0XA1);           //进入接收模式	

	IIC_Wait_Ack();	        
	
	
	for(t=0;t<len-1;t++)
	{		
		*(pbuf)=IIC_Read_Byte(1);	    //读一个字节 
		pbuf++;
		//delay_us(200);
	}
	*(pbuf)=IIC_Read_Byte(0);	    //读一个字节 

	IIC_Stop(); 
	
}

这是引用网上的基础上,我自己稍作改动。大家可以参考。如果要使用我的代码要注意

这两个函数要根据自己pin脚去修改。

采集的波形如下

最后总结遇到的问题:

1.刚开始没有注意到写操作后延时,只给了200us,造成写完一笔数据后,IIC的读写操作都失败,后来给了10ms的延时就OK了;

2.网上找的代码不够严谨,很多毛刺,需要自己再调试;

3.STM32F1XX的硬件IIC比较复杂,我以前只用过STM32F0的,刚开始不知道,调了2天也没搞定硬件IIC通讯,建议对IIC或STM32F1不熟悉,谨慎使用其硬件IIC。

4.由于我的平台限制,STM32供电3.3V,EEPROM供电为5v。虽然读写也成功了,但是建议读者最好使二者电平匹配的。

5.IIC的通讯线要接上拉电阻,要不然没有输出。

发布了12 篇原创文章 · 获赞 4 · 访问量 6387

猜你喜欢

转载自blog.csdn.net/yilizhihu/article/details/104538816