小熊派gd32f303学习之旅(8)— 使用软件模拟I2C读写EEPROM

小熊派gd32f303学习之旅(8)— 使用软件模拟I2C读写EEPROM

一、前言

IIC是一种非常常用的低速通信总线,广泛被用于连接微控制器及其外围设备。
关于IIC的介绍可以参考:IIC通信协议详解
这里我们用软件模拟 IIC来读写AT24C02这个EEPROM。芯片AT24C02的总容量是 256 个字节,该芯片通过 IIC 总线与外部连接。
如下所示,是E53_SC1拓展板上AT24C02的硬件连接图,不过这里有一个很大的问题,就是通过其地址设置引脚的接线我们得到其7位地址为0x51,可实际上,三个地址设置引脚都接到了地,即其7位地址为0x50;不知道是不是因为改版的原因,所以在使用的时候最好从实际硬件去确认一下其器件地址。
在这里插入图片描述
接下来我们查看小熊派的原理图,可以看到其使用的IIC引脚
在这里插入图片描述

二、编写软件模拟I2C驱动程序

接下来我们就要编写软件模拟I2C的驱动程序了,首先,将PB6和PB7引脚初始化,

/* 描述:软件模拟IIC引脚初始化
 * 参数:无
 * 返回值:无*/	
void Soft_I2C_Init(void)
{
    
    
    rcu_periph_clock_enable(RCU_GPIOB);	/* 使能GPIOB时钟 */

	/* 配置 IIC_SCL --> PB6 引脚为推挽输出 */ 
    gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_6);
	/* 配置 IIC_SDA --> PB7 引脚为推挽输出 */ 
    gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_7);

    gpio_bit_set(GPIOB, GPIO_PIN_6);
	gpio_bit_set(GPIOB, GPIO_PIN_7);
}

因为模拟IIC通信里需要用到us延时,所以,编写一个us延时函数

/* 描述:us级延时函数
 * 参数nus:需要延时的us数
 * 返回值:无*/		    								   
void delay_us(uint32_t nus)
{
    
    		
	uint32_t ticks;
    uint32_t told, tnow, tcnt = 0;
    uint32_t reload = SysTick->LOAD;		/* 滴答定时器的重装载值 */
    ticks = nus * 120; 						/* 需要的节拍数 */
    told = SysTick->VAL;        			/* 刚进入时的计数器值 */

    while(1)
    {
    
    
        tnow = SysTick->VAL;

        if(tnow != told)
        {
    
    
            if(tnow < told)tcnt += told - tnow;
            else tcnt += reload - tnow + told;
            if(tcnt >= ticks)break;			 /* 时间超过/等于要延迟的时间,则退出. */
			told = tnow;
        }
    }  
}

然后就是模拟IIC通信的函数

/* 描述:启动I2C总线,即发送I2C起始条件. 
 * 参数:  无
 * 返回值:无						*/
void IIC_Start(void)
{
    
    
	SDA_OUT();
	IIC_SDA(1);
	IIC_SCL(1);
	delay_us(4);	
	IIC_SDA(0);
	delay_us(4); 
	IIC_SCL(0);
}
 
/* 描述:结束I2C总线,即发送I2C结束条件.  
 * 参数:  无
 * 返回值:无						*/
void IIC_Stop(void)
{
    
    
	SDA_OUT();
	IIC_SCL(0);
	IIC_SDA(0);  
	delay_us(4);	
	IIC_SCL(1);
	delay_us(4);
	IIC_SDA(1);
	delay_us(4);
}
 
/* 描述:发送应答 ACK 
 * 参数:  无
 * 返回值:无		*/
void IIC_ACK(void)
{
    
    
	SDA_OUT();
	IIC_SCL(0);
	delay_us(2); 
	IIC_SDA(0);
	delay_us(2);     
	IIC_SCL(1);
	delay_us(2);                  
	IIC_SCL(0);                     
	delay_us(1);    
}

/* 描述:发送非应答 NACK 
 * 参数:  无
 * 返回值:无		*/
void IIC_NACK(void)
{
    
    
	SDA_OUT();
	IIC_SCL(0);
	delay_us(2); 
	IIC_SDA(1);
	delay_us(2);      
	IIC_SCL(1);
	delay_us(2);                   
	IIC_SCL(0);                     
	delay_us(1);    
}

/* 描述:等待ACK 
 * 参数:  无
 * 返回值:等待应答返回0,没有等待到应答返回1	*/
uint8_t IIC_wait_ACK(void)
{
    
    
    uint8_t t = 200;
    SDA_OUT();
    IIC_SDA(1);		
    delay_us(1);
    IIC_SCL(0);
    delay_us(1); 
    SDA_IN();		/* 数据发送完后释放数据线,准备接收应答位 */
    delay_us(1); 
    while(READ_SDA)	/* 等待IIC应答*/
    {
    
    
		t--;
		delay_us(1); 
		if(t==0)
		{
    
    
			IIC_SCL(0);
			return 1;
		}
		delay_us(1); 
    }
    delay_us(1);      
    IIC_SCL(1);
    delay_us(1);
    IIC_SCL(0);             
    delay_us(1);    
    return 0;	
}

/* 描述:一个字节数据发送函数               
 * 参数:  无
 * 返回值:无		*/
void IIC_SendByte(uint8_t byte)
{
    
    
	uint8_t BitCnt;
	SDA_OUT();
	IIC_SCL(0);
	for(BitCnt=0;BitCnt<8;BitCnt++) /* 要传送的数据长度为8位 */
	{
    
    
		if(byte&0x80) IIC_SDA(1);	/* 判断发送位 */
		else IIC_SDA(0); 
		byte<<=1;
		delay_us(2); 
		IIC_SCL(1);
		delay_us(2);
		IIC_SCL(0);
		delay_us(2);
	}
}

/* 描述:一个字节数据接收函数               
 * 参数:  无
 * 返回值:接收到的字节数据		*/   
uint8_t IIC_RcvByte(void)
{
    
    
	uint8_t retc;
	uint8_t BitCnt;
	retc=0; 
	SDA_IN();			/* 设置数据线为输入方式 */
	delay_us(1);                    
	for(BitCnt=0;BitCnt<8;BitCnt++)
	{
    
      
		IIC_SCL(0);		/* 设置时钟线为低,准备接收数据位	*/
		delay_us(2);               
		IIC_SCL(1);		/* 设置时钟线为高使数据线上数据有效  */              
		retc=retc<<1;
		if(READ_SDA) retc |=1;	/* 读数据位,接收的数据位放入retc中 */
		delay_us(1);
	}
	IIC_SCL(0);    
	return(retc);
}

三、编写AT24C02控制函数

通过查看AT24C02的数据手册可以看到其读写时序如下
在这里插入图片描述
然后编写AT24C02的读取和写入函数

/* 描述:AT24C02初始化
 * 参数:无
 * 返回值:无*/	
void at24c02_init(void)
{
    
    
	Soft_I2C_Init();
}

/* 描述:在AT24C02指定地址读出一个字节的数据
 * 参数:ReadAddr: 需要读出数据的地址
 *		 ReadByte: 读出的数据值的存放指针
 * 返回值:0:读取成功 		其他:读取错误*/
uint8_t AT24C02_Read_Byte(uint16_t ReadAddr, uint8_t *ReadByte)
{
    
    	
	uint8_t err = 0;
    IIC_Start();  
	IIC_SendByte((AT24C02_Addr<<1) | WRITE_BIT);   	/* 发送器件地址,写数据 */ 	 
	err |= IIC_wait_ACK(); 
    IIC_SendByte(ReadAddr);   						/* 发送要读出数据的地址 */
	err |= (IIC_wait_ACK() << 1);
	IIC_Start();  	 	   
	IIC_SendByte((AT24C02_Addr<<1) | READ_BIT);     /* 进入接收模式	*/		   
	err |= (IIC_wait_ACK() << 2);
    *ReadByte = IIC_RcvByte();
	IIC_NACK();										/* 发送非应答 NACK */
    IIC_Stop();										/*产生一个停止条件*/	    
	return err;
}

/* 描述:在AT24C02指定地址写入一个字节的数据
 * 参数:WriteAddr: 需要写入数据的地址
 *		 WriteByte: 要写入的数据
 * 返回值:0:写入成功 		其他:写入错误*/
uint8_t AT24C02_Write_Byte(uint16_t WriteAddr,uint16_t WriteByte)
{
    
    	
	uint8_t err = 0;	
    IIC_Start();  
	IIC_SendByte((AT24C02_Addr<<1) | WRITE_BIT);   	/* 发送器件地址,写数据 */ 	 	 
	err |= IIC_wait_ACK(); 
    IIC_SendByte(WriteAddr);   						/* 发送要写入数据的地址 */
	err |= (IIC_wait_ACK() << 1);									  		   
	IIC_SendByte(WriteByte);     					/* 写入数据 */					   
	err |= (IIC_wait_ACK() << 2);
    IIC_Stop();//产生一个停止条件 
	return err;
}

四、编写主函数

int main(void)
{
    
    
	uint8_t buff;
	uint8_t err;
	/* 配置系统时钟 */
	systick_config();
	/* 初始化LED */
	// led_init();
	/* 初始化USART0 */
	uart0_init(115200);
	/* 初始化AT24C02 */
	at24c02_init();
	
	/* 通过串口打印 Hello world! */
	u0_printf("Hello world! ");
	u0_printf("I am William. \r\n");
	
	err = AT24C02_Write_Byte(0x0a, 0xa5);
	if(err == 0)
		printf("Write 0xa5 to addr 0x0a ok \r\n");
	else
	{
    
    
		printf("Write 0xa5 to addr 0x0a err \r\n");
		printf("err num : 0x%x \r\n",err);
	}
	
	if(AT24C02_Read_Byte(0x0a, &buff) == 0)
		printf("Read data: 0x%x from addr 0x0a ok \r\n", buff);
	else
		printf("Read data from addr 0x0a err \r\n");
	
    while(1)
	{
    
    
		if(UART0_RX_STAT > 0)
		{
    
    
			UART0_RX_STAT = 0;
			u0_printf("RECEIVE %d data:%s \r\n", UART0_RX_NUM, UART0_RX_BUF);
		}
		
		delay_1ms(10);
		
	}
}

五、功能验证

编译链接烧录到小熊派开发板,然后观察串口输出情况,可以看到读取和写入都成功了
在这里插入图片描述

六、附录

完整代码我存放在码云,可以查看:https://gitee.com/william_william/BearPi-GD32F303RGT6.git
上一篇:小熊派gd32f303学习之旅(7)—使用PWM实现LED呼吸灯
下一篇:小熊派gd32f303学习之旅(9)— 使用硬件I2C读写EEPROM

猜你喜欢

转载自blog.csdn.net/qq_38113006/article/details/108967448