2024年C C++最新STM32硬件I2C与软件模拟I2C超详解_stm32 i2c,2024年最新C C++开发实战讲解

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

选择I2C的寻址模式是7位还是10位地址。这需要根据实际连接到I2C总线上设备的地址进行选择,这个成员的配置也影响到I2C_OwnAddress1成员,只有这里设置成10位模式时,I2C_OwnAddress1才支持10位地址。
在这里插入图片描述
配置完成之后调用一下I2C初始化函数就搞定

记得使能I2C外设
在这里插入图片描述
在这里插入图片描述

五.EEPROM简介

EEPROM全称: electrically-erasable, and programmable read-only memory --》可电擦除的可编程的只读存储器,这里的只读并不是只能读,是以前ROM不能写只能读,现在的EEPROM已经是可读写的啦,为什么还叫可读:只不过是保留下来的名字而已。

在这里插入图片描述
原理图:
在这里插入图片描述
在这里插入图片描述
WP引脚直接
在这里插入图片描述

EEPROM的设备地址(作为从机)
在这里插入图片描述

EEPROM中硬件I2C
在这里插入图片描述
EEPROM通信的时候也遵循I2C协议,向产生起始信号,停止信号,应答什么的都一样的。

1.STM32向从机EEPROM写入一个字节

在这里插入图片描述

2.STM32向从机EEPROM写入多个字节(页写入)

在这里插入图片描述
写入的8个字节是连续的地址,不连续的话不能使用页写入
在这里插入图片描述
在这里插入图片描述

总结:

  • 进行页写入时,写入的存储器地址要对齐到8,也就是说只能写入地址为 0 8 16 32… 能整除8
  • 页写如只能一次写入8个字节

规定就是规定我也没有办法,不然就会出错

  • 确认EEPROM是否写入完成:

在这里插入图片描述
这段话什么意思呢:EEPROM做为我们的非易失存储器(掉电不会丢失数据),相当于我们电脑中的硬盘,它的读写速度是非常慢的,所以STM32把数据发送过去之后,必须等待EEPROM去把数据写入自己内部的存储器才能写入下一波数据(可以是单字节写入也可以是页写入),如果不等待EEPROM把上一次的数据写完又去写入EEPROM是不会搭理你的,也就是说EEPROM处于忙碌状态。

检测EEPROM数据是否写入完成:
STM32主机不断向EEPROM发送起始信号,然后发送EEPROM的设备的地址等待EEPROM的应答信号,如果不应答,重复在来一遍,直到EEPROM应答则代表EEPROM上一次的数据写入完成,然后才可以传输下一次的数据!!!

3.STM32随机读取EEPROM内部任何地址的数据

在这里插入图片描述
在这里插入图片描述

4.STM32随机顺序读取EEPROM内部任何地址的数据

在这里插入图片描述
EEPROM一共有256个字节对应的地址为(0~255)
当读取到最后一个字节,也就是255地址,第256个字节,在读取又会从头(第一个字节数据)开始读取。

六.硬件I2C读写EEPROM实验

实验目的

STM32作为主机向从机EEPROM存储器写入256个字节的数据
STM32作为主机向从机EEPROM存储器读取写入的256个字节的数据

读写成功亮绿灯,读写失败亮红灯

实验原理

  • 硬件设计
    原理图
    在这里插入图片描述
    实物图
    在这里插入图片描述

编程要点
(1) 配置通讯使用的目标引脚为开漏模式;
(2) 编写模拟 I2C 时序的控制函数;
(3) 编写基本 I2C 按字节收发的函数;
(4) 编写读写 EEPROM 存储内容的函数;
(5) 编写测试程序,对读写数据进行校验。

两个引脚PB6,PB7都要配置成复用的开漏输出
这里有一个注意的点,你配置成输出模式,并不会影响引脚的输入功能

详情请看——>GPIO端口的八种工作模式
在这里插入图片描述

源码

i2c_ee.h
前面理论已经讲得已经很详细了,直接上代码叭!!

#ifndef \_\_IIC\_EE\_H
#define \_\_IIC\_EE\_H

#include "stm32f10x.h"
#include <stdio.h>
//IIC1
#define EEPROM\_I2C I2C1
#define EEPROM\_I2C\_CLK RCC\_APB1Periph\_I2C1
#define EEPROM\_I2C\_APBxClkCmd RCC\_APB1PeriphClockCmd
#define EEPROM\_I2C\_BAUDRATE 400000

// IIC1 GPIO 引脚宏定义
#define EEPROM\_I2C\_SCL\_GPIO\_CLK (RCC\_APB2Periph\_GPIOB)
#define EEPROM\_I2C\_SDA\_GPIO\_CLK (RCC\_APB2Periph\_GPIOB)
#define EEPROM\_I2C\_GPIO\_APBxClkCmd RCC\_APB2PeriphClockCmd
     
#define EEPROM\_I2C\_SCL\_GPIO\_PORT GPIOB 
#define EEPROM\_I2C\_SCL\_GPIO\_PIN GPIO\_Pin\_6
#define EEPROM\_I2C\_SDA\_GPIO\_PORT GPIOB
#define EEPROM\_I2C\_SDA\_GPIO\_PIN GPIO\_Pin\_7

//STM32自身地址1 与从机设备地址不相同即可(7位地址)
#define STM32\_I2C\_OWN\_ADDR 0x6f
//EEPROM设备地址
#define EEPROM\_I2C\_Address 0XA0
#define I2C\_PageSize 8


//等待次数
#define I2CT\_FLAG\_TIMEOUT ((uint32\_t)0x1000)
#define I2CT\_LONG\_TIMEOUT ((uint32\_t)(10 \* I2CT\_FLAG\_TIMEOUT))



/\*信息输出\*/
#define EEPROM\_DEBUG\_ON 0
#define EEPROM\_INFO(fmt,arg...) printf("<<-EEPROM-INFO->> "fmt"\n",##arg)
#define EEPROM\_ERROR(fmt,arg...) printf("<<-EEPROM-ERROR->> "fmt"\n",##arg)
#define EEPROM\_DEBUG(fmt,arg...) do{\
 if(EEPROM\_DEBUG\_ON)\
 printf("<<-EEPROM-DEBUG->> [%d]"fmt"\n",\_\_LINE\_\_, ##arg);\
 }while(0)

void I2C\_EE\_Config(void);
void EEPROM\_Byte\_Write(uint8\_t addr,uint8\_t data);	
uint32\_t  EEPROM\_WaitForWriteEnd(void);	
uint32\_t  EEPROM\_Page\_Write(uint8\_t addr,uint8\_t \*data,uint16\_t Num_ByteToWrite);																					
uint32\_t  EEPROM\_Read(uint8\_t \*data,uint8\_t addr,uint16\_t Num_ByteToRead);
void I2C\_EE\_BufferWrite(uint8\_t\* pBuffer,uint8\_t WriteAddr, uint16\_t NumByteToWrite);
#endif /\* \_\_IIC\_EE\_H \*/


i2c_ee.c

#include "i2c\_ee.h"


//设置等待时间
static __IO uint32\_t  I2CTimeout = I2CT_LONG_TIMEOUT;   

//等待超时,打印错误信息
static uint32\_t I2C\_TIMEOUT\_UserCallback(uint8\_t errorCode);


void I2C\_EE\_Config(void)
{
	GPIO_InitTypeDef    GPIO_InitStuctrue;
	I2C_InitTypeDef     I2C_InitStuctrue;
	//开启GPIO外设时钟
	EEPROM\_I2C\_GPIO\_APBxClkCmd(EEPROM_I2C_SCL_GPIO_CLK|EEPROM_I2C_SDA_GPIO_CLK,ENABLE);
	//开启IIC外设时钟
	EEPROM\_I2C\_APBxClkCmd(EEPROM_I2C_CLK,ENABLE);
	
	//SCL引脚-复用开漏输出
  GPIO_InitStuctrue.GPIO_Mode=GPIO_Mode_AF_OD;
  GPIO_InitStuctrue.GPIO_Pin=EEPROM_I2C_SCL_GPIO_PIN;
	GPIO_InitStuctrue.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO\_Init(EEPROM_I2C_SCL_GPIO_PORT,&GPIO_InitStuctrue);
	//SDA引脚-复用开漏输出
	GPIO_InitStuctrue.GPIO_Mode = GPIO_Mode_AF_OD;
	GPIO_InitStuctrue.GPIO_Pin = EEPROM_I2C_SDA_GPIO_PIN;
	GPIO_InitStuctrue.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO\_Init(EEPROM_I2C_SDA_GPIO_PORT,&GPIO_InitStuctrue);
	
	//IIC结构体成员配置
   I2C_InitStuctrue.I2C_Ack=I2C_Ack_Enable;
	I2C_InitStuctrue.I2C_AcknowledgedAddress=I2C_AcknowledgedAddress_7bit;
	I2C_InitStuctrue.I2C_ClockSpeed=EEPROM_I2C_BAUDRATE;
	I2C_InitStuctrue.I2C_DutyCycle=I2C_DutyCycle_2;
	I2C_InitStuctrue.I2C_Mode=I2C_Mode_I2C;
	I2C_InitStuctrue.I2C_OwnAddress1=STM32_I2C_OWN_ADDR;
	I2C\_Init(EEPROM_I2C,&I2C_InitStuctrue);
	I2C\_Cmd(EEPROM_I2C,ENABLE);

}

//向EEPROM写入一个字节
void  EEPROM\_Byte\_Write(uint8\_t addr,uint8\_t data)
{
	//发送起始信号
	I2C\_GenerateSTART(EEPROM_I2C,ENABLE);
	//检测EV5事件
	while( I2C\_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT)==ERROR);
	//发送设备写地址
	I2C\_Send7bitAddress(EEPROM_I2C,EEPROM_I2C_Address,I2C_Direction_Transmitter);
	//检测EV6事件
	while( I2C\_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);
	//发送要操作设备内部的地址
	I2C\_SendData(EEPROM_I2C,addr);
	while( I2C\_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTING )==ERROR);
  I2C\_SendData(EEPROM_I2C,data);
	//检测EV8\_2事件
	while( I2C\_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTED )==ERROR);
	//发送停止信号
	I2C\_GenerateSTOP(EEPROM_I2C,ENABLE);
	
}

//向EEPROM写入多个字节
uint32\_t  EEPROM\_Page\_Write(uint8\_t addr,uint8\_t \*data,uint16\_t Num_ByteToWrite)
{
	
	 I2CTimeout = I2CT_LONG_TIMEOUT;
	//判断IIC总线是否忙碌
	while(I2C\_GetFlagStatus(EEPROM_I2C, I2C_FLAG_BUSY))   
	{
		if((I2CTimeout--) == 0) return I2C\_TIMEOUT\_UserCallback(1);
	} 
	//重新赋值
	I2CTimeout = I2CT_FLAG_TIMEOUT;
	//发送起始信号
	I2C\_GenerateSTART(EEPROM_I2C,ENABLE);
	//检测EV5事件
	while( I2C\_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT)==ERROR)
	{
		 if((I2CTimeout--) == 0) return I2C\_TIMEOUT\_UserCallback(2);
	} 
	I2CTimeout = I2CT_FLAG_TIMEOUT;
	//发送设备写地址
	I2C\_Send7bitAddress(EEPROM_I2C,EEPROM_I2C_Address,I2C_Direction_Transmitter);
	//检测EV6事件
	while( I2C\_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR)
	{
		 if((I2CTimeout--) == 0) return I2C\_TIMEOUT\_UserCallback(3);
	} 

	I2CTimeout = I2CT_FLAG_TIMEOUT;
	//发送要操作设备内部的地址
	I2C\_SendData(EEPROM_I2C,addr);
	//检测EV8事件
	while( I2C\_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTING )==ERROR)
	{
		 if((I2CTimeout--) == 0) return I2C\_TIMEOUT\_UserCallback(4);
	} 

	while(Num_ByteToWrite)
	{
		I2C\_SendData(EEPROM_I2C,\*data);
		I2CTimeout = I2CT_FLAG_TIMEOUT;
		while( I2C\_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTING )==ERROR)
		{
				if((I2CTimeout--) == 0) return   I2C\_TIMEOUT\_UserCallback(5);
		} 
		 Num_ByteToWrite--;
		 data++;
	}

	I2CTimeout = I2CT_FLAG_TIMEOUT;
	//检测EV8\_2事件
	while( I2C\_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTED )==ERROR)
	{
				if((I2CTimeout--) == 0) return I2C\_TIMEOUT\_UserCallback(6);
	 } 
	//发送停止信号
	I2C\_GenerateSTOP(EEPROM_I2C,ENABLE);
	 return 1;
}

//向EEPROM读取多个字节
uint32\_t EEPROM\_Read(uint8\_t \*data,uint8\_t addr,uint16\_t Num_ByteToRead)
{
	 I2CTimeout = I2CT_LONG_TIMEOUT;
  //判断IIC总线是否忙碌
  while(I2C\_GetFlagStatus(EEPROM_I2C, I2C_FLAG_BUSY))   
  {
    if((I2CTimeout--) == 0) return I2C\_TIMEOUT\_UserCallback(1);
  } 
	
	I2CTimeout = I2CT_FLAG_TIMEOUT;
	//发送起始信号
	I2C\_GenerateSTART(EEPROM_I2C,ENABLE);
	//检测EV5事件
	while( I2C\_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT )==ERROR)
  {
        if((I2CTimeout--) == 0) return I2C\_TIMEOUT\_UserCallback(7);
   } 
	
	I2CTimeout = I2CT_FLAG_TIMEOUT;
	//发送设备写地址
	I2C\_Send7bitAddress(EEPROM_I2C,EEPROM_I2C_Address,I2C_Direction_Transmitter);
	//检测EV6事件等待从机应答
	while( I2C\_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED )==ERROR)
 {
        if((I2CTimeout--) == 0) return I2C\_TIMEOUT\_UserCallback(8);
  }
  
	I2CTimeout = I2CT_FLAG_TIMEOUT;
	//发送要操作设备内部存储器的地址
	I2C\_SendData(EEPROM_I2C,addr);
	//检测EV8事件
	while( I2C\_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_TRANSMITTING )==ERROR)
 {
        if((I2CTimeout--) == 0) return I2C\_TIMEOUT\_UserCallback(9);
  }
	I2CTimeout = I2CT_FLAG_TIMEOUT;
	//发送起始信号
	I2C\_GenerateSTART(EEPROM_I2C,ENABLE);
	//检测EV5事件
	while( I2C\_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_MODE_SELECT )==ERROR)
	{
        if((I2CTimeout--) == 0) return I2C\_TIMEOUT\_UserCallback(10);
   }
	I2CTimeout = I2CT_FLAG_TIMEOUT;	 
	//发送设备读地址
	I2C\_Send7bitAddress(EEPROM_I2C,EEPROM_I2C_Address,I2C_Direction_Receiver);
	//检测EV6事件
	while( I2C\_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED )==ERROR)
	{
       if((I2CTimeout--) == 0) return I2C\_TIMEOUT\_UserCallback(10);
   }
	 
	while(Num_ByteToRead--)
	{
		//是否是最后一个字节,若是则发送非应答信号
		if( Num_ByteToRead==0)
	 {
		 //发送非应答信号
		 I2C\_AcknowledgeConfig(EEPROM_I2C,DISABLE);
		 //发送停止信号
	   I2C\_GenerateSTOP(EEPROM_I2C,ENABLE);
	 }
	 
	 I2CTimeout = I2CT_FLAG_TIMEOUT;	 
	 //检测EV7事件
   while( I2C\_CheckEvent(EEPROM_I2C,I2C_EVENT_MASTER_BYTE_RECEIVED )==ERROR)
  {
       if((I2CTimeout--) == 0) return I2C\_TIMEOUT\_UserCallback(10);
   }
	 
    \*data=I2C\_ReceiveData(EEPROM_I2C);
	  data++; 
	 
	}
	
	//重新开启应答信号
	I2C\_AcknowledgeConfig(EEPROM_I2C,ENABLE);
  return 1;
}
void I2C\_EE\_BufferWrite(uint8\_t\* pBuffer,uint8\_t WriteAddr, uint16\_t NumByteToWrite)
{
  u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;
  //I2C\_PageSize=8
  Addr = WriteAddr % I2C_PageSize;
  count = I2C_PageSize - Addr;
  NumOfPage =  NumByteToWrite / I2C_PageSize;
  NumOfSingle = NumByteToWrite % I2C_PageSize;
 
  /\* 写入数据的地址对齐,对齐数为8 \*/
  if(Addr == 0) 
  {
    /\* 如果写入的数据个数小于8 \*/
    if(NumOfPage == 0) 
    {
      EEPROM\_Page\_Write(WriteAddr, pBuffer, NumOfSingle);
      EEPROM\_WaitForWriteEnd();
    }
    /\* 如果写入的数据个数大于8 \*/
    else  
    {
			//按页写入
      while(NumOfPage--)
      {
        EEPROM\_Page\_Write(WriteAddr, pBuffer, I2C_PageSize); 
    	  EEPROM\_WaitForWriteEnd();
        WriteAddr +=  I2C_PageSize;
        pBuffer += I2C_PageSize;
      }
      //不足一页(8个)单独写入
      if(NumOfSingle!=0)
      {
        EEPROM\_Page\_Write(WriteAddr, pBuffer, NumOfSingle);
        EEPROM\_WaitForWriteEnd();
      }
    }
  }
  /\*写的数据的地址不对齐\*/
  else 
  {
      NumByteToWrite -= count;
      NumOfPage =  NumByteToWrite / I2C_PageSize;
      NumOfSingle = NumByteToWrite % I2C_PageSize;	
      
      if(count != 0)
      {  
        EEPROM\_Page\_Write(WriteAddr, pBuffer, count);
        EEPROM\_WaitForWriteEnd();
        WriteAddr += count;
        pBuffer += count;
      } 
      
      while(NumOfPage--)
      {
        EEPROM\_Page\_Write(WriteAddr, pBuffer, I2C_PageSize);
        EEPROM\_WaitForWriteEnd();
        WriteAddr +=  I2C_PageSize;
        pBuffer += I2C_PageSize;  
      }
      if(NumOfSingle != 0)
      {
        EEPROM\_Page\_Write(WriteAddr, pBuffer, NumOfSingle); 
        EEPROM\_WaitForWriteEnd();
      }
    } 
}

uint32\_t EEPROM\_WaitForWriteEnd(void)
{
	I2CTimeout = I2CT_FLAG_TIMEOUT;	
	
	do
	{
		  I2CTimeout = I2CT_FLAG_TIMEOUT;
			//发送起始信号
			I2C\_GenerateSTART(EEPROM_I2C,ENABLE);
			//检测EV5事件
			while( I2C\_GetFlagStatus(EEPROM_I2C,I2C_FLAG_SB )==RESET)
			{
					 if((I2CTimeout--) == 0) return I2C\_TIMEOUT\_UserCallback(10);
			 }
			I2CTimeout = I2CT_FLAG_TIMEOUT;	
			//发送设备写地址
			I2C\_Send7bitAddress(EEPROM_I2C,EEPROM_I2C_Address,I2C_Direction_Transmitter);
		
	}while( (I2C\_GetFlagStatus(EEPROM_I2C,I2C_FLAG_ADDR )==RESET) && (I2CTimeout--) );
	
	//发送停止信号
	I2C\_GenerateSTOP(EEPROM_I2C,ENABLE);
	return 1;
}



static  uint32\_t I2C\_TIMEOUT\_UserCallback(uint8\_t errorCode)
{
  /\* Block communication and all processes \*/
  EEPROM\_ERROR("I2C 等待超时!errorCode = %d",errorCode);
  
  return 0;
}


main.c

#include "stm32f10x.h"
#include "led.h"
#include "./i2c/i2c\_ee.h"
#include <string.h>
#include "usart.h"
#define SOFT\_DELAY Delay(0x0FFFFF);

void Delay(__IO u32 nCount); 

//声明I2C测试函数
uint8\_t I2C\_EE\_Test(void);
int main(void)
{	
	//初始化IIC
   I2C\_EE\_Config();
   //初始化USART 
   Usart\_Config();
	//初始化LED
   LED\_GPIO\_Config();
	printf("\r\nIIC读写EEPROM测试实验\r\n");
	
	//读写成功亮绿灯,失败亮红灯
   if( I2C\_EE\_Test()==1 )
	 {
		 LED\_G(NO);
	 }
	 else
	 {
		 LED\_R(NO);
	 }
	
while(1)
{
;
}
 
 }
	 uint8\_t I2C\_EE\_Test(void)
	 {	
		  uint8\_t ReadData[256]={0};
      uint8\_t WriteDdta[256]={0};
		  uint16\_t i;
		  //初始化写入数组
		   for(i=0;i<256;i++)
	    {
		    WriteDdta[i]=i; 
	     }
			 //向EEPROM从地址为0开始写入256个字节的数据 
				I2C\_EE\_BufferWrite(WriteDdta,0,256);
				//等待EEPROM写入数据完成 
				EEPROM\_WaitForWriteEnd();	 
			 //向EEPROM从地址为0开始读出256个字节的数据
				EEPROM\_Read(ReadData,0,256);

			 for (i=0; i<256; i++)
				{	
				 if(ReadData[i] != WriteDdta[i])
					{
						EEPROM\_ERROR("0x%02X ", ReadData[i]);
						EEPROM\_ERROR("错误:I2C EEPROM写入与读出的数据不一致\n\r");
						return 0;
					}
					 printf("0x%02X ", ReadData[i]);
					 if(i%16 == 15)    
					 printf("\n\r");   
				}
				EEPROM\_INFO("I2C(AT24C02)读写测试成功\n\r");
				return 1;
	 }

void Delay(__IO uint32\_t nCount)	 //简单的延时函数
{
	for(; nCount != 0; nCount--);
}


重点讲一下,如何解决以下页写入问题,实现连续写入

  • 进行页写入时,写入的存储器地址要对齐到8,也就是说只能写入地址为 0 8 16 32… 能整除8
  • 页写如只能一次写入8个字节

现在来解释代码中下图函数如何解决问题
在这里插入图片描述

如果地址对齐:
在这里插入图片描述
在这里插入图片描述
如果地址不对齐:
在这里插入图片描述

实验效果

请添加图片描述

七.软件模式I2C协议

实验目的

STM32作为主机向从机EEPROM存储器写入256个字节的数据
STM32作为主机向从机EEPROM存储器读取写入的256个字节的数据

读写成功亮绿灯,读写失败亮红灯

实验原理

在这里插入图片描述
软件模式I2C由我们CPU来控制引脚产生I2C时序,所以我们随便选引脚都可以,不过你选择的引脚肯定要连接到通信的EEPROM的SCL,SDA引脚上。这里是用了PC12,PC11充当主机STM32SCL,SDA引脚。

  • 主机产生起始信号
    在这里插入图片描述
  • 主机产生停止信号
    在这里插入图片描述
  • 主机产生应答信号或非应答信号
    在这里插入图片描述
    在这里插入图片描述
  • 等待从机EEPROM应答

在这里插入图片描述

  • 主机发送一个字节给从机
    在这里插入图片描述
  • 主机向EEPROM接收一个字节
    在这里插入图片描述
    value应该初始化为0,我忘了sorry

源码

i2c_gpio.h

#ifndef \_I2C\_GPIO\_H
#define \_I2C\_GPIO\_H


#include "stm32f10x.h"

#define EEPROM\_I2C\_WR 0 /\* 写控制bit \*/
#define EEPROM\_I2C\_RD 1 /\* 读控制bit \*/

#define EEPROM\_GPIO\_PORT\_I2C GPIOB
#define EEPROM\_RCC\_I2C\_PORT RCC\_APB2Periph\_GPIOB
#define EEPROM\_I2C\_SCL\_PIN GPIO\_Pin\_6
#define EEPROM\_I2C\_SDA\_PIN GPIO\_Pin\_7

/\*当 STM32 的 GPIO 配置成开漏输出模式时,它仍然可以通过读取
GPIO 的输入数据寄存器获取外部对引脚的输入电平,也就是说它同时具有浮空输入模式的
功能\*/

#define EEPROM\_I2C\_SCL\_1() EEPROM\_GPIO\_PORT\_I2C->BSRR |= EEPROM\_I2C\_SCL\_PIN /\* SCL = 1 \*/
#define EEPROM\_I2C\_SCL\_0() EEPROM\_GPIO\_PORT\_I2C->BRR |= EEPROM\_I2C\_SCL\_PIN /\* SCL = 0 \*/
	
#define EEPROM\_I2C\_SDA\_1() EEPROM\_GPIO\_PORT\_I2C->BSRR |= EEPROM\_I2C\_SDA\_PIN /\* SDA = 1 \*/
#define EEPROM\_I2C\_SDA\_0() EEPROM\_GPIO\_PORT\_I2C->BRR |= EEPROM\_I2C\_SDA\_PIN /\* SDA = 0 \*/

#define EEPROM\_I2C\_SDA\_READ() ((EEPROM\_GPIO\_PORT\_I2C->IDR & EEPROM\_I2C\_SDA\_PIN)!=0 ) /\* 读SDA口线状态 \*/


void i2c\_Start(void);
void i2c\_Stop(void);
void i2c\_Ack(void);
void i2c\_NAcK(void);
uint8\_t i2c\_WaitAck(void);
void i2c\_SendByte(uint8\_t data);
uint8\_t i2c\_ReadByte(void);
uint8\_t i2c\_CheckDevice(uint8\_t Address);
#endif /\* \_I2C\_GPIO\_H \*/


i2c_gpio.c

#include "i2c\_gpio.h"

#include "stm32f10x.h"

void I2c\_gpio\_config(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
	RCC\_APB2PeriphClockCmd(EEPROM_RCC_I2C_PORT, ENABLE);
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_InitStructure.GPIO_Pin = EEPROM_I2C_SCL_PIN | EEPROM_I2C_SDA_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO\_Init(EEPROM_GPIO_PORT_I2C, &GPIO_InitStructure);
	
	/\* 给一个停止信号, 复位I2C总线上的所有设备到待机模式 \*/
	i2c\_Stop();
}

static void i2c\_Delay(void)
{
	uint8\_t i;
	for(i=0;i<10;i++)
	{
	}
}


void i2c\_Start(void)
{
	EEPROM\_I2C\_SCL\_1();
	EEPROM\_I2C\_SDA\_1();
	i2c\_Delay();
	EEPROM\_I2C\_SDA\_0();
	i2c\_Delay();
	EEPROM\_I2C\_SCL\_0();
	i2c\_Delay();
}

void i2c\_Stop(void)
{
	EEPROM\_I2C\_SDA\_0();
	EEPROM\_I2C\_SCL\_1();
	i2c\_Delay();
	EEPROM\_I2C\_SDA\_1();
	i2c\_Delay();
}

void i2c\_Ack(void)
{
	EEPROM\_I2C\_SCL\_0();
	i2c\_Delay();
	EEPROM\_I2C\_SDA\_0();
	i2c\_Delay();
	EEPROM\_I2C\_SCL\_1();
	i2c\_Delay();
	EEPROM\_I2C\_SCL\_0();
	i2c\_Delay();
	EEPROM\_I2C\_SDA\_1();
	i2c\_Delay();

}

void i2c\_NAcK(void)
{
	EEPROM\_I2C\_SDA\_1();
	i2c\_Delay();
	EEPROM\_I2C\_SCL\_1();
	i2c\_Delay();
	EEPROM\_I2C\_SCL\_0();
	i2c\_Delay();

}

uint8\_t i2c\_WaitAck(void)
{
	uint8\_t ret;
	EEPROM\_I2C\_SDA\_1();
	EEPROM\_I2C\_SCL\_1();
	i2c\_Delay();
	if( EEPROM\_I2C\_SDA\_READ() )
	{
		ret=1;
	}
	else
	{
		ret=0;
	}
	EEPROM\_I2C\_SCL\_0();
	i2c\_Delay();
  return ret;

}
	

void i2c\_SendByte(uint8\_t data)
{
	uint8\_t i;
	for(i=0;i<8;i++)
	{
		if( data&0x80 )
	 {
		  EEPROM\_I2C\_SDA\_1();
	 }
	 else
	 {
		  EEPROM\_I2C\_SDA\_0();
	 }
	 i2c\_Delay();
	 EEPROM\_I2C\_SCL\_1();
	 i2c\_Delay();
	 EEPROM\_I2C\_SCL\_0();
	 i2c\_Delay();
	 if( i==7 )
	 {
		 EEPROM\_I2C\_SDA\_1();
		 i2c\_Delay();
	 }
	 data=data<<1;
	}
	
}

uint8\_t i2c\_ReadByte(void)
{
	uint8\_t value=0;
	uint8\_t i;
	for(i=0;i<8;i++)
	{
		value=value<<1;
		EEPROM\_I2C\_SCL\_1();
	  i2c\_Delay();
		if( EEPROM\_I2C\_SDA\_READ() )
	  {
	 	  value++;
	  }
	  EEPROM\_I2C\_SCL\_0();
	  i2c\_Delay();
	}
	return value;
}

uint8\_t i2c\_CheckDevice(uint8\_t Address)
{
	uint8\_t ucACK;
	I2c\_gpio\_config();
	i2c\_Start();
	i2c\_SendByte(Address|EEPROM_I2C_WR);
	ucACK=i2c\_WaitAck();
	i2c\_Stop();
  return ucACK;	
	
}



i2c_ee.h

#ifndef \_I2C\_EE\_H
#define \_I2C\_EE\_H


#include "stm32f10x.h"


#define EEPROM\_DEV\_ADDR 0xA0 /\* 24xx02的设备地址 \*/
#define EEPROM\_PAGE\_SIZE 8 /\* 24xx02的页面大小 \*/
#define EEPROM\_SIZE 256 /\* 24xx02总容量 \*/


uint8\_t ee\_Checkok(void);
uint8\_t  ee\_ReadByte( uint8\_t \*pReaddata,uint16\_t Address,uint16\_t num );
uint8\_t  ee\_WriteByte( uint8\_t \*Writepdata,uint16\_t Address,uint16\_t num );
uint8\_t ee\_WaitStandby(void);
uint8\_t ee\_WriteBytes(uint8\_t \*_pWriteBuf, uint16\_t _usAddress, uint16\_t _usSize);
uint8\_t ee\_ReadBytes(uint8\_t \*_pReadBuf, uint16\_t _usAddress, uint16\_t _usSize);
uint8\_t ee\_Test(void) ;
#endif /\* \_I2C\_EE\_H\*/


i2c_ee.c

#include "i2c\_ee.h"
#include "i2c\_gpio.h"

//检测EEPORM是否忙碌
uint8\_t ee\_Checkok(void)
{
	if(i2c\_CheckDevice(EEPROM_DEV_ADDR)==0)
	{
		return 1;
	}
	else
	{
    i2c\_Stop();  
		return 0;
 	}
}	
//检测EEPROM写入数完成
uint8\_t ee\_WaitStandby(void)
{
	uint32\_t wait_count = 0;
	
	while(i2c\_CheckDevice(EEPROM_DEV_ADDR))
	{
		//若检测超过次数,退出循环
		if(wait_count++>0xFFFF)
		{
			//等待超时
			return 1;
		}
	}
	//等待完成
	return 0;
}


//向EEPROM写入多个字节
uint8\_t ee\_WriteBytes(uint8\_t \*_pWriteBuf, uint16\_t _usAddress, uint16\_t _usSize)
{
	uint16\_t i,m;
	uint16\_t addr;
	addr=_usAddress;
  for(i=0;i<_usSize;i++)
	{
		  //当第一次或者地址对齐到8就要重新发起起始信号和EEPROM地址
		  //为了解决8地址对齐问题
			if(i==0 || (addr % EEPROM_PAGE_SIZE)==0 )
			{
				 //循环发送起始信号和EEPROM地址的原因是为了等待上一次写入的一页数据\
 写入完成
				 for(m=0;m<1000;m++)
				 {
					 //发送起始地址
					 i2c\_Start();
					 //发送设备写地址
					 i2c\_SendByte(EEPROM_DEV_ADDR|EEPROM_I2C_WR);
					 //等待从机应答
					 if( i2c\_WaitAck()==0 )
					 {
						break;
					 }
				 } 
				  //若等待的1000次从机还未应答,等待超时
				  if( m==1000 )
			  	{
					goto cmd_fail;
			   	}	
				//EEPROM应答后发送EEPROM的内部存储器地址
				i2c\_SendByte((uint8\_t)addr);
				//等待从机应答
				if( i2c\_WaitAck()!=0 )
				{
					goto cmd_fail;
					
				}	
			}
		 //发送数据
		 i2c\_SendByte(_pWriteBuf[i]);
		 //等待应答
	   if( i2c\_WaitAck()!=0 )
	   {
		  goto cmd_fail;			
     }
		 //写入地址加1
		 addr++;		
	}
	
	i2c\_Stop();
	return 1;
	
	cmd_fail:
	i2c\_Stop();
	return 0;
}


uint8\_t ee\_ReadBytes(uint8\_t \*_pReadBuf, uint16\_t _usAddress, uint16\_t _usSize)
{
	uint16\_t i;
	
	  i2c\_Start();
		i2c\_SendByte(EEPROM_DEV_ADDR|EEPROM_I2C_WR);
	 if( i2c\_WaitAck()!=0 )
	 {
			 goto cmd_fail;		
	  }
		i2c\_SendByte((uint8\_t)_usAddress);
	 if( i2c\_WaitAck()!=0 )
	 {
			  goto cmd_fail;
	  }
		i2c\_Start();
		i2c\_SendByte(EEPROM_DEV_ADDR|EEPROM_I2C_RD);
		 if( i2c\_WaitAck()!=0 )
		 {
				  goto cmd_fail;				
	   }
	 for(i=0;i<_usSize;i++)
	{	
		_pReadBuf[i]=i2c\_ReadByte();
		/\* 每读完1个字节后,需要发送Ack, 最后一个字节不需要Ack,发Nack \*/
		if (i != _usSize - 1)
		{
// i2c\_NAcK(); /\* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) \*/
			i2c\_Ack();	/\* 中间字节读完后,CPU产生ACK信号(驱动SDA = 0) \*/
		}
		else
		{
			i2c\_NAcK();	/\* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) \*/
		}
	}
	i2c\_Stop();
	return 1;
	
	cmd_fail:
	i2c\_Stop();
	return 0;
}



![img](https://img-blog.csdnimg.cn/img_convert/ff3ef071174d826154eb55b760867017.png)
![img](https://img-blog.csdnimg.cn/img_convert/3f8c39b5ce7515a1295797581d77f9ad.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**

_SendByte(EEPROM_DEV_ADDR|EEPROM_I2C_WR);
	 if( i2c\_WaitAck()!=0 )
	 {
			 goto cmd_fail;		
	  }
		i2c\_SendByte((uint8\_t)_usAddress);
	 if( i2c\_WaitAck()!=0 )
	 {
			  goto cmd_fail;
	  }
		i2c\_Start();
		i2c\_SendByte(EEPROM_DEV_ADDR|EEPROM_I2C_RD);
		 if( i2c\_WaitAck()!=0 )
		 {
				  goto cmd_fail;				
	   }
	 for(i=0;i<_usSize;i++)
	{	
		_pReadBuf[i]=i2c\_ReadByte();
		/\* 每读完1个字节后,需要发送Ack, 最后一个字节不需要Ack,发Nack \*/
		if (i != _usSize - 1)
		{
// i2c\_NAcK(); /\* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) \*/
			i2c\_Ack();	/\* 中间字节读完后,CPU产生ACK信号(驱动SDA = 0) \*/
		}
		else
		{
			i2c\_NAcK();	/\* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) \*/
		}
	}
	i2c\_Stop();
	return 1;
	
	cmd_fail:
	i2c\_Stop();
	return 0;
}



[外链图片转存中...(img-mRxqjCd6-1715554168420)]
[外链图片转存中...(img-U7pcZ5Oo-1715554168421)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**

  • 12
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值