飞道的博客

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

203人阅读  评论(0)

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

I2C向EEPROM写入一字节数据(I2C硬件)

相关链接:
I2C物理层介绍
I2C协议层介绍
I2C固件库介绍
STM32的I2C特性及架构介绍
STM32的EEPROM简介

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

1 实现功能

本实验所要完成的任务是通过I2C向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. 优化代码(超时,故障代码)

4 代码分析

  1. I2C初始化函数
void IIC_Init(void)
{
   			
  	GPIO_InitTypeDef  GPIO_InitStructure;						//定义GPIO结构体
	
	I2C_InitTypeDef   I2C_InitStructure;						//定义I2C结构体

    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);		//使能GPIOB时钟
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);		//使能I2C1时钟
	
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource8,GPIO_AF_I2C1);		//复用PB8至I2C
	
	GPIO_PinAFConfig(GPIOB,GPIO_PinSource9,GPIO_AF_I2C1);		//复用PB9至I2C
	

  //GPIOB8初始化SCL
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;					//普通输出模式
  GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;				//开漏输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;			//100MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;					//上拉
  GPIO_Init(GPIOB, &GPIO_InitStructure);						//初始化

	
  //GPIOB9初始化SDA
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;					//普通输出模式
  GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;				//开漏输出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;			//100MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;					//上拉
  GPIO_Init(GPIOB, &GPIO_InitStructure);						//初始化
	
	//初始化I2C结构体
	I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;					//使能ask响应
	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit ;	//设定7为地址
	I2C_InitStructure.I2C_ClockSpeed = 400000;					//设置时钟速度
	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2  ;		//指定时钟占空比1/2与9/16选择
	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;					//设置为I2C模式
	I2C_InitStructure.I2C_OwnAddress1 = 0x78;					//指定自身的I2C设备地址。另一种说法:作为主机时可以不用写?
	
	I2C_Init(I2C1,&I2C_InitStructure);							//初始化结构体
	
	I2C_Cmd(I2C1,ENABLE);										//I2C使能
	
}
  1. I2C写入数据函数
//========================================================================================

//定义一个超时常数
#define TIME_OUT 0x000FFFFF

uint32_t count_wait = TIME_OUT;

//========================================================================================

//addr:是要写入存储单元的地址
//date:是要写入的数据
uint8_t EEPROM_Byte_Write(uint8_t addr, uint8_t date)
{
   
	
//========================================================================================
	
//产生起始条件
	I2C_GenerateSTART(I2C1,ENABLE);							//产生开始信号
	
	//重置count_wait
	count_wait = TIME_OUT;
	
	//等待EV5事件,直到成功
	//checkevent函数为专门用来检查EVx标志位的函数
	while(I2C_CheckEvent(I2C1,I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS )
	{
   
		
		count_wait--;										//循环次数自减
				
		if(count_wait ==  0)								//当循环次数减为0,标志程序运行着超时
		{
   
			return Error_Back(1);//表示程序执行卡死在EV5
		}
		
	}
	
//========================================================================================
	
	//要发送的EEPROM设别地址 高四位:1010固定 低四位:A2 A1 A0 R/W 0 0 0 0  1010 0000 = 0xa0
	I2C_Send7bitAddress(I2C1,0xa0,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(2);
		}
		
	}
	
//========================================================================================
	
	//发送要写入的存储单元的地址
	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(3);
		}
		
	}
	
//========================================================================================
	
	//发送要写入的数据
	I2C_SendData(I2C1,date);
	
	//重置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(4);
		}		
		
	}
		
//========================================================================================
	
	//产生结束信号
	I2C_GenerateSTOP(I2C1,ENABLE);

	//表示程序执行正常	
	return 0; 
}
  1. 主函数
int main(void)
{
    

	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);		//设置系统中断优先级分组2
	
	delay_init(168);    								//初始化延时函数
	
	uart_init(115200);									//初始化串口波特率为115200
	
	LED_Init();											//初始化LED 
	
 	LCD_Init();											//LCD初始化 
	
	KEY_Init(); 										//按键初始化  
	
	IIC_Init();											//I2C初始化
	
	EEPROM_Byte_Write(0x05, 0x22);						//向EEPROM的0x05的地址中写入0x22
	
	printf("okok");										//若上一行代码没有卡死将输出okok
}

5 效果展示

  1. 程序正常时:串口助手上显示okok,表明数据已经传输。

  1. 我们将等待EV6事件完成时将条件更改,模拟EV6卡死在循环中,下载程序,串口助手显示错误3,我们便可以在程序中很快定位到EV6,错误3的地方进行检查。其余的以此类推。

6 小结

这个实验不难,主要是熟练以下寄存器的使用已经一个设计的流程,其次,代码功能的优化也是非常重要的,在文中,错误代码的显示以及程序超时这两个功能(从野火那儿学习来的)都是属于代码优化。所以我们要放宽视野,多学学人家良好的编程习惯。谢谢!请各位大佬不吝赐教!


转载:https://blog.csdn.net/qq_33693310/article/details/115534102
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场