《STM32从零开始学习历程》——I2C向EEPROM读写多个字节数据(I2C硬件)

《STM32从零开始学习历程》@EnzoReventon

I2C向EEPROM读写多个字节数据(I2C硬件)

相关链接:
I2C物理层介绍
I2C协议层介绍
I2C固件库介绍
STM32的I2C特性及架构介绍
STM32的EEPROM简介
I2C向EEPROM写入一字节数据(I2C硬件)
I2C向EEPROM读取一字节数据(I2C硬件

参考资料:
[野火EmbedFire]《STM32库开发实战指南——基于野火霸天虎开发板》
[正点原子]STM32F4开发指南-库函数版本_V1.2
[ST]《STM32F4xx中文参考手册》
[ATMEL]《AT24C02说明书》
开发板硬件原理图;EEPROM原理图。

程序源码:
【下载地址】

0 引言

程序总体与《I2C向EEPROM写入一字节数据(I2C硬件)》、《I2C向EEPROM读取一字节数据(I2C硬件》基本一致,为了适应多页数据的连续读写,在上文程序的基础上加入了连续读写的函数。

1 实现功能

写入EEPROM多个数据,并通过串口调试助手显示读取的数据。

2 硬件设计

本实验采用的开发板为“正点原子”探索者F4开发板,核心芯片为F407ZGT6。
使用到的外设及硬件为:USART1,I2C1,EEPROM
USART1:PA9(T)—》RXE;PA10(R) —》TXE,将引脚使用跳线帽相连接即可。

3 软件设计流程

  1. GPIO功能复用
  2. 初始化GPIO
  3. I2C初始化
  4. I2C使能
  5. 定义写入数据函数
  6. 根据EEPROM写数据流程调用函数。
    EEPROM写数据流程请参考:《STM32从零开始学习历程》——STM32的EEPROM简介。此处不做过多的详解。
  7. 定义随机读取函数
  8. 定义等待ERROR内部写入操作完成函数
  9. 定义连续写函数
  10. 定义连续读函数
  11. 编写主函数,并发送数据。
  12. 优化代码(超时,故障代码,发送数据对齐)

4 代码分析

本文主要展示新增的代码。

  • 页写入功能函数
//addr:要写入的存储单元首地址
//data:要写入的数据的指针
//size:要写入多少个数据(size小于等于8)
//return:0表示正常,非0为失败
uint8_t EEPROM_Page_Write(uint8_t addr, uint8_t* data, uint8_t size)
{
	//产生起始信号
	I2C_GenerateSTART(I2C1, ENABLE);
	
	//重置 count_wait
	count_wait = TIME_OUT;
	//等待EV5事件,直到检测成功
	while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS)
	{
		count_wait--;
		if(count_wait == 0 )
		{
			return Error_Back(12);
		}
	}	
	
	//要发送的EEPROM设备地址,并设置为写方向
	I2C_Send7bitAddress(I2C1, EEPROM_I2C_ADDR, I2C_Direction_Transmitter);
	
	//重置 count_wait
	count_wait = TIME_OUT;
	//等待EV6事件,直到检测成功
	while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS)
	{
		count_wait--;
		if(count_wait == 0 )
		{
			return Error_Back(13);
		}
	}
	
	//发送要写入的存储单元地址
	I2C_SendData(I2C1,addr);
	
	//重置 count_wait
	count_wait = TIME_OUT;	
	//等待EV8_2事件,直到检测成功
	while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS)
	{
		count_wait--;
		if(count_wait == 0 )
		{
			return Error_Back(14);
		}
	}
	
	while(size--)
	{
		//发送要写入的数据
		I2C_SendData(I2C1,*data);
		
		//重置 count_wait
		count_wait = TIME_OUT;		
		//等待EV8_2事件,直到检测成功
		while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS)
		{
			count_wait--;
			if(count_wait == 0 )
			{
				return Error_Back(15);
			}
		}
		
		data++;
	}
	
	//产生结束信号
	I2C_GenerateSTOP(I2C1, ENABLE);
	
	//等待写入完成
	return 	Wait_For_Standby();

}
  • 写入多字节功能函数
//addr:要写入的存储单元首地址
//data:要写入的数据的指针
//size:要写入多少个数据
//return:0表示正常,非0为失败
uint8_t EEPROM_Buffer_Write(uint8_t addr, uint8_t* data, uint16_t size)
{

	// single_adrr = 1 = 1 % 8时  写入 EEPROM_PAGE_SIZE-single_adrr = 7
	uint8_t single_adrr = addr%EEPROM_PAGE_SIZE;
	
	if(single_adrr == 0)
	{	
		uint8_t num_of_page = size/EEPROM_PAGE_SIZE;	
		uint8_t single_byte = size%EEPROM_PAGE_SIZE; //取模 9/8余1
		
		while(num_of_page--)
		{
			//调用页写入函数
			EEPROM_Page_Write(addr, data, EEPROM_PAGE_SIZE);
			
			//等待写入完成
			Wait_For_Standby();
			
			addr += EEPROM_PAGE_SIZE;
			data += EEPROM_PAGE_SIZE;
		}
			
		//调用页写入函数
		EEPROM_Page_Write(addr, data, single_byte);
		
		//等待写入完成
		Wait_For_Standby();	
	}
	else //addr不对齐
	{
		uint8_t num_of_page,single_byte,left_size;
		//第一次写入不对齐的数据
		uint8_t first_size = EEPROM_PAGE_SIZE - single_adrr;
		
		//调用页写入函数
		EEPROM_Page_Write(addr, data, first_size);
		
		//等待写入完成
		Wait_For_Standby();
		
		addr += first_size;
		data += first_size;
		
		//剩下要写入的数据
		left_size = size - first_size;
		
		num_of_page = left_size/EEPROM_PAGE_SIZE;	
		single_byte = left_size%EEPROM_PAGE_SIZE;	
	
		
		while(num_of_page--) 
		{
			//调用页写入函数
			EEPROM_Page_Write(addr, data, EEPROM_PAGE_SIZE);
			
			//等待写入完成
			Wait_For_Standby();
			
			addr += EEPROM_PAGE_SIZE;
			data += EEPROM_PAGE_SIZE;
		}
			
		//调用页写入函数
		EEPROM_Page_Write(addr, data, single_byte);
		
		//等待写入完成
		Wait_For_Standby();		
	
	}
	
	return 0;

}

该功能的函数主要在于连续的写入多个字节。本程序段的主要功能是实现对输入的数据进行分页,并且对没有完整占用整个字节的数据进行对其。
通过“整除”计算要写入的数据Size 能写满多少“完整的页”,计算得的值存储在num_of_page 中,但有时数据不是刚好能写满完整页的,会多一点出来,通过“求余”计算得出“不满一页的数据个数”就存储在single_byte 中。计算后通过按页传输num_of_page 次整页数据及最后的single_byte 个数据,使用页传输,比之前的单个字节数据传输要快很多。
考虑好关于分页的概念之后,我们对数据进行对齐的时候通常还需要考虑首地址的问题。若首地址不是刚好对齐到页的首地址,会需要一个first_size值,用于存储从该首地址开始写满该地址所在的页,还能写多少个数据。实际传输时,先把这部分first_size个数据先写入,填满该页,然后把剩余的数据(num_of_page),再重复上述求出num_of_pagesingle_byte的过程,按页传输到EEPROM。

  • 连续读取多字节数据功能函数
//addr:要读取的存储单元首地址
//data:用来存储 读取到的数据 的指针
//size:要读取多少个数据
//return:0表示正常,非0为失败
uint8_t EEPROM_Buffer_Read(uint8_t addr, uint8_t *data, uint16_t size)
{
	//产生起始信号
	I2C_GenerateSTART(I2C1, ENABLE);
	
	//重置 count_wait
	count_wait = TIME_OUT;
	//等待EV5事件,直到检测成功
	while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS)
	{
		count_wait--;
		if(count_wait == 0 )
		{
			return Error_Back(16);
		}
	}	
	
	//要发送的EEPROM设备地址,并设置为写方向
	I2C_Send7bitAddress(I2C1, EEPROM_I2C_ADDR, I2C_Direction_Transmitter);
	
	//重置 count_wait
	count_wait = TIME_OUT;
	//等待EV6事件,直到检测成功
	while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS)
	{
		count_wait--;
		if(count_wait == 0 )
		{
			return Error_Back(17);
		}
	}
	
	//发送要读取的存储单元地址
	I2C_SendData(I2C1,addr);
	
	//重置 count_wait
	count_wait = TIME_OUT;	
	//等待EV8_2事件,直到检测成功
	while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS)
	{
		count_wait--;
		if(count_wait == 0 )
		{
			return Error_Back(18);
		}
	}
	
	//--------------------------------
	//产生第二次起始信号	
	I2C_GenerateSTART(I2C1, ENABLE);
	
	//重置 count_wait
	count_wait = TIME_OUT;
	//等待EV5事件,直到检测成功
	while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS)
	{
		count_wait--;
		if(count_wait == 0 )
		{
			return Error_Back(19);
		}
	}	
	
	//************读方向***********
	//要发送的EEPROM设备地址,并设置为读方向
	I2C_Send7bitAddress(I2C1, EEPROM_I2C_ADDR, I2C_Direction_Receiver);
	
	//重置 count_wait
	count_wait = TIME_OUT;
	//等待EV6事件,直到检测成功
	while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) != SUCCESS)
	{
		count_wait--;
		if(count_wait == 0 )
		{
			return Error_Back(20);
		}
	}
	
		
	while(size--)
	{
		if(size==0)
		{
			//作出非应答信号
			I2C_AcknowledgeConfig(I2C1, DISABLE);
		}
		else
		{
			//作出应答信号
			I2C_AcknowledgeConfig(I2C1, ENABLE);
		}		
		//重置 count_wait
		count_wait = TIME_OUT;		
		//等待EV8_2事件,直到检测成功
		while(I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS)
		{
			count_wait--;
			if(count_wait == 0 )
			{
				return Error_Back(21);
			}
		}
		//接收数据
		*data = I2C_ReceiveData(I2C1);
		data++;
	}
		
	//产生结束信号
	I2C_GenerateSTOP(I2C1, ENABLE);
	
	return 0;

}
  • 主函数

#define TEST_SIZE  255				//定义测试数据的大小

int main(void)
{ 

	uint8_t data[TEST_SIZE];	
	
	uint8_t buff[TEST_SIZE];
	
	uint16_t i;
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
	
	delay_init(168);    //初始化延时函数
	
	uart_init(115200);	//初始化串口波特率为115200
	
	//给buff赋值
	for(i=0;i<TEST_SIZE;i++)
	{
	buff[i] = i;
	}
	
	IIC_Init();
	
	EEPROM_Byte_Write(0x01, 0x12);
	
	EEPROM_Random_Read(0x01,data);
	
	printf("\r\nox%x\r\n",data[0]);
	
//	EEPROM_Page_Write(0x00,buff,TEST_SIZE);
	
	EEPROM_Buffer_Write(0x01,buff,TEST_SIZE);

	EEPROM_Buffer_Read(0x01,data,TEST_SIZE);
	
	for(i=0;i<TEST_SIZE;i++)
	{
		printf("0x%02x ",data[i]);
	}
	
	while(1)
	{
	}

}

5 结果展示

《STM32从零开始学习历程》——I2C向EEPROM读写多个字节数据(I2C硬件)_第1张图片
通过串口调试助手能够接收到256个递增的数据。

你可能感兴趣的:(STM32,ARM,I2C,stm32,嵌入式,单片机)