2024年最全STM32硬件I2C与软件模拟I2C详解,助力程序员正确成长

img
img

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

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

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

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;
    }
    
    uint8\_t ee\_Test(void) 
    {
      uint16\_t i;
    	uint8\_t write_buf[EEPROM_SIZE];
      uint8\_t read_buf[EEPROM_SIZE];
      
    /\*-----------------------------------------------------------------------------------\*/  
      if (i2c\_CheckDevice(EEPROM_DEV_ADDR) == 1)
    	{
    		/\* 没有检测到EEPROM \*/
    		printf("没有检测到串行EEPROM!\r\n");
    				
    		return 0;
    	}
    /\*------------------------------------------------------------------------------------\*/  
      /\* 填充测试缓冲区 \*/
    	for (i = 0; i < EEPROM_SIZE; i++)
    	{		
    		write_buf[i] = i;
    	}
    /\*------------------------------------------------------------------------------------\*/  
      if (ee\_WriteBytes(write_buf, 0, EEPROM_SIZE) == 0)
    	{
    		printf("写EEPROM出错!\r\n");
    		return 0;
    	}
    	else
    	{		
    		printf("写EEPROM成功!\r\n");
    	}  
    
    /\*-----------------------------------------------------------------------------------\*/
      if (ee\_ReadBytes(read_buf, 0, EEPROM_SIZE) == 0)
    	{
    		printf("EEPROM出错!\r\n");
    		return 0;
    	}
    	else
    	{		
    		printf("EEPROM成功,数据如下:\r\n");
    	}
    /\*-----------------------------------------------------------------------------------\*/  
      for (i = 0; i < EEPROM_SIZE; i++)
    	{
    		if(read_buf[i] != write_buf[i])
    		{
    			printf("0x%02X ", read_buf[i]);
    			printf("错误:EEPROM读出与写入的数据不一致");
    			return 0;
    		}
        printf(" %02X", read_buf[i]);
    		
    		if ((i & 15) == 15)
    		{
    			printf("\r\n");	
    		}		
    	}
      printf("EEPROM读写测试成功\r\n");
      return 1;
    }
    
    
    
    

    main

    #include "stm32f10x.h"
    
    
    ![img](https://i3.wp.com/img-blog.csdnimg.cn/img_convert/f754a0b6dc156951eda27688b81a0e5d.png)
    ![img](https://i3.wp.com/img-blog.csdnimg.cn/img_convert/3955ab88c1c22f530954a7631bc7ffc7.png)
    
    **既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
    
    **由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
    
    **[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**
    
    	
    		printf("写EEPROM成功!\r\n");
    	}  
    
    /\*-----------------------------------------------------------------------------------\*/
      if (ee\_ReadBytes(read_buf, 0, EEPROM_SIZE) == 0)
    	{
    		printf("EEPROM出错!\r\n");
    		return 0;
    	}
    	else
    	{		
    		printf("EEPROM成功,数据如下:\r\n");
    	}
    /\*-----------------------------------------------------------------------------------\*/  
      for (i = 0; i < EEPROM_SIZE; i++)
    	{
    		if(read_buf[i] != write_buf[i])
    		{
    			printf("0x%02X ", read_buf[i]);
    			printf("错误:EEPROM读出与写入的数据不一致");
    			return 0;
    		}
        printf(" %02X", read_buf[i]);
    		
    		if ((i & 15) == 15)
    		{
    			printf("\r\n");	
    		}		
    	}
      printf("EEPROM读写测试成功\r\n");
      return 1;
    }
    
    
    
    

    main

    #include "stm32f10x.h"
    
    
    [外链图片转存中...(img-iOLQcz0T-1715530888210)]
    [外链图片转存中...(img-XPEcuprk-1715530888210)]
    
    **既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
    
    **由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
    
    **[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**
    
    

    作者:普通网友

    物联沃分享整理
    物联沃-IOTWORD物联网 » 2024年最全STM32硬件I2C与软件模拟I2C详解,助力程序员正确成长

    发表回复