人若无名 便可潜心练剑.|

hazy1k

园龄:7个月粉丝:14关注:0

2024-09-04 10:29阅读: 36评论: 0推荐: 0

第18章 硬件I2C

第十八章 硬件I2C

EEPROM是一种掉电后数据不丢失的存储器,常用来存储一些配置信息,以便系统重新上电的时候加载之。 EEPOM芯片最常用的通讯方式就是I2C协议, 本小节以EEPROM的读写实验为大家讲解STM32的I2C使用方法。 实验中STM32的I2C外设采用主模式,分别用作主发送器和主接收器,通过查询事件的方式来确保正常通讯。

1. 硬件设计

本实验板中的EEPROM芯片(型号:AT24C02)的SCL及SDA引脚连接到了STM32对应的I2C引脚中,结合上拉电阻, 构成了I2C通讯总线,它们通过I2C总线交互。EEPROM芯片的设备地址一共有7位,其中高4位固定为:1010 b, 低3位则由A0/A1/A2信号线的电平决定,图中的R/W是读写方向位,与地址无关。

按照我们此处的连接,A0/A1/A2均为0,所以EEPROM的7位设备地址是:101 0000b,即0x50。 由于I2C通讯时常常是地址跟读写方向连在一起构成一个8位数,且当R/W位为0时,表示写方向, 所以加上7位地址,其值为“0xA0”,常称该值为I2C设备的“写地址”;当R/W位为1时,表示读方向, 加上7位地址,其值为“0xA1”,常称该值为“读地址”。

EEPROM芯片中还有一个WP引脚,具有写保护功能,当该引脚电平为高时,禁止写入数据,当引脚为低电平时, 可写入数据,我们直接接地,不使用写保护功能。

2. 软件设计

2.1 编程大纲

  1. I2C及EEPROM相关参数宏定义,配置I2C GPIO及工作模式

  2. 编写I2C EEPROM进入待机模式和错误代码回调函数

  3. 编写I2C读写EEPROM基本函数

  4. 编写测试程序,对读写数据进行校验。

2.2 代码分析

2.2.1 I2C及EEPROM相关参数宏定义

// I2C相关参数宏定义
#define  I2Cx         I2C1   
#define  I2C_SCL_PORT GPIOB   
#define  I2C_SCL_PIN  GPIO_Pin_6
#define  I2C_SDA_PORT GPIOB 
#define  I2C_SDA_PIN  GPIO_Pin_7
#define  I2C_Speed    400000 

// 这个地址只要与STM32外挂的I2C器件地址不一样即可
#define I2Cx_OWN_ADDRESS7 0X0A  

// 等待超时时间
#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)

// AT24C01/02每页有8个字节
#define I2C_PageSize   8
/* 
AT24C02 2kb = 2048bit = 2048/8 B = 256 B
32 pages of 8 bytes each
Device Address
 1 0 1 0 A2 A1 A0 R/W
 1 0 1 0 0  0  0  0 = 0XA0
 1 0 1 0 0  0  0  1 = 0XA1 
*/
// EEPROM地址宏定义
#define EEPROM_Block0_ADDRESS 0xA0   
//#define EEPROM_Block1_ADDRESS 0xA2 
//#define EEPROM_Block2_ADDRESS 0xA4  
//#define EEPROM_Block3_ADDRESS 0xA6  

以上代码根据硬件连接,把与EEPROM通讯使用的I2C号 、引脚号都以宏封装起来,并且定义了自身的I2C地址及通讯速率,以便配置模式的时候使用。

2.2.2 初始化I2C配置工作参数

// I2C GPIO配置
void I2C_GPIO_Config(void)
{
  GPIO_InitTypeDef  GPIO_InitStructure; 
  // 使能时钟
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
  // I2C_SCL、I2C_SDA配置
  GPIO_InitStructure.GPIO_Pin = I2C_SCL_PIN;  
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;      
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;	       
  GPIO_Init(I2C_SCL_PORT, &GPIO_InitStructure);   
  GPIO_InitStructure.GPIO_Pin = I2C_SDA_PIN;      
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;      
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;	       
  GPIO_Init(I2C_SDA_PORT, &GPIO_InitStructure);	  
}

// I2C工作参数配置
static void I2C_Mode_Configu(void)
{
  I2C_InitTypeDef  I2C_InitStructure; 
  I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; // I2C模式
  I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; // 400khz
  I2C_InitStructure.I2C_OwnAddress1 = I2Cx_OWN_ADDRESS7; // 设置I2C自己的地址
  I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; // 使能应答
  I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; // 7位地址模式
  I2C_InitStructure.I2C_ClockSpeed = I2C_Speed; // 快速模式
	// I2C 初始化
  I2C_Init(I2Cx, &I2C_InitStructure);
  I2C_Cmd(I2Cx, ENABLE);   
}

// I2C 外设(EEPROM)初始化
void I2C_EE_Init(void)
{
  I2C_GPIO_Config();  
  I2C_Mode_Configu();
  // 根据头文件I2C.h中的定义来选择EEPROM要写入的地址
#ifdef EEPROM_Block0_ADDRESS
  EEPROM_ADDRESS = EEPROM_Block0_ADDRESS; // 选择 EEPROM Block0 来写入
#endif
#ifdef EEPROM_Block1_ADDRESS  
  EEPROM_ADDRESS = EEPROM_Block1_ADDRESS;
#endif
#ifdef EEPROM_Block2_ADDRESS  
  EEPROM_ADDRESS = EEPROM_Block2_ADDRESS;
#endif
#ifdef EEPROM_Block3_ADDRESS  
  EEPROM_ADDRESS = EEPROM_Block3_ADDRESS;
#endif
}

2.2.3 错误信息回调及I2C待机模式函数

// 等待I2C EEPROM进入待机模式
void I2C_EE_WaitEepromStandbyState(void)      
{
  vu16 Temp_SR = 0;
  do
  {
    I2C_GenerateSTART(I2Cx, ENABLE); // 发送I2C开始信号
    Temp_SR = I2C_ReadRegister(I2Cx, I2C_Register_SR1); // 读取I2C状态寄存器1
    I2C_Send7bitAddress(I2Cx, EEPROM_ADDRESS, I2C_Direction_Transmitter); // 发送I2C地址信号
  }while(!(I2C_ReadRegister(I2Cx, I2C_Register_SR1) & 0x0002)); // 等待I2C状态寄存器1的BUSY位清零
  I2C_ClearFlag(I2Cx, I2C_FLAG_AF); // 清除I2C忙标志   
  I2C_GenerateSTOP(I2Cx, ENABLE); // I2C停止信号 
}

// 错误代码回调函数
uint32_t I2C_TIMEOUT_UserCallback(uint8_t errorCode)
{
  EEPROM_ERROR("I2C 等待超时!errorCode = %d",errorCode); // 通过不同的错误码,可以判断哪部分超时
  return 0;
}
  • 读取 I2C 状态寄存器1的值,并将其存储在 Temp_SR 中。SR1 状态寄存器包含了 I2C 总线的一些状态信息,例如总线是否忙(BUSY 标志)、是否发生应答失败(AF 标志)等。
  • 发送 7 位的设备地址(EEPROM_ADDRESS)和操作方向(I2C_Direction_Transmitter),指定此操作是对 EEPROM 进行写操作。地址后面带有一个方向位,I2C_Direction_Transmitter 表示发送数据。

2.2.4 I2C写入EEPROM单个字节函数

初始化好I2C外设后,就可以使用I2C通讯,我们看看如何向EEPROM写入一个字节的数据

/*
  I2C写数据基本流程:
  1.发送I2C开始信号
  2.发送I2C写命令
  3.发送要写入的地址
  4.发送要写入的数据
  5.发送I2C停止信号,进入待机模式
*/
// 通过I2C从EEPROM中写入一个字节
// 函数参数:pBuffer,要写入的数据,WriteAddr,要写入的地址
uint32_t I2C_EE_ByteWrite(u8* pBuffer, u8 WriteAddr) 
{
  I2C_GenerateSTART(I2Cx, ENABLE); // 发送I2C开始信号
  I2CTimeout = I2CT_FLAG_TIMEOUT;  // 超时时间初始化 
  while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT)) // 等待I2C空闲状态  
  {
    if((I2CTimeout--) == 0) 
      return I2C_TIMEOUT_UserCallback(0); // 返回错误码0,代表发送I2C开始信号超时
  } 
  I2CTimeout = I2CT_FLAG_TIMEOUT; // 超时时间初始化
  I2C_Send7bitAddress(I2Cx, EEPROM_ADDRESS, I2C_Direction_Transmitter); // 发送I2C写命令
  while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
  {
    if((I2CTimeout--) == 0) 
      return I2C_TIMEOUT_UserCallback(1); // 返回错误码1,代表发送I2C地址信号时超时
  }  
  I2C_SendData(I2Cx, WriteAddr); // 发送要写入的地址
  I2CTimeout = I2CT_FLAG_TIMEOUT; 
  while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
  {
    if((I2CTimeout--) == 0) 
      return I2C_TIMEOUT_UserCallback(2); // 返回错误码2,代表发送I2C要写入的地址时超时
  } 
  I2C_SendData(I2Cx, *pBuffer); // 发送要写入的字节 
  I2CTimeout = I2CT_FLAG_TIMEOUT;  
  while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
  {
    if((I2CTimeout--) == 0) 
      return I2C_TIMEOUT_UserCallback(3); // 返回错误码3,代表发送I2C要写入的字节时超时
  } 
  I2C_GenerateSTOP(I2Cx, ENABLE); // 发送字节字节完毕,停止信号
  return 1;
}

分析I2C_EE_ByteWrite函数,这个函数实现了前面讲的I2C主发送器通讯流程

  1. 使用库函数I2C_GenerateSTART产生I2C起始信号, 其中的I2Cx宏是前面硬件定义相关的I2C编号;

  2. 对I2CTimeout变量赋值为宏I2CT_FLAG_TIMEOUT,这个I2CTimeout变量在下面的while循环中每次循环减1, 该循环通过调用库函数I2C_CheckEvent检测事件,若检测到事件,则进入通讯的下一阶段,若未检测到事件则停留在此处一直检测, 当检测I2CT_FLAG_TIMEOUT次都还没等待到事件则认为通讯失败,调用前面的I2C_TIMEOUT_UserCallback输出调试信息,并退出通讯;

  3. 调用库函数I2C_Send7bitAddress发送EEPROM的设备地址, 并把数据传输方向设置为I2C_Direction_Transmitter(即发送方向), 这个数据传输方向就是通过设置I2C通讯中紧跟地址后面的R/W位实现的。发送地址后以同样的方式检测EV6标志;

  4. 调用库函数I2C_SendData向EEPROM发送要写入的内部地址, 该地址是I2C_EE_ByteWrite函数的输入参数,发送完毕后等待EV8事件。 要注意这个内部地址跟上面的EEPROM地址不一样,上面的是指I2C总线设备的独立地址,而此处的内部地址是指EEPROM内数据组织的地址, 也可理解为EEPROM内存的地址或I2C设备的寄存器地址;

  5. 调用用库函数I2C_SendData向EEPROM发送要写入的数据, 该数据是I2C_EE_ByteWrite函数的输入参数,发送完毕后等待EV8事件;

  6. 一个I2C通讯过程完毕, 调用I2C_GenerateSTOP发送停止信号。

在这个通讯过程中,STM32实际上通过I2C向EEPROM发送了两个数据,但为何第一个数据被解释为EEPROM的内存地址,这是由EEPROM的自己定义的单字节写入时序

EEPROM的单字节时序规定,向它写入数据的时候,第一个字节为内存地址,第二个字节是要写入的数据内容。 所以我们需要理解:命令、地址的本质都是数据,对数据的解释不同,它就有了不同的功能。

总结:

这个函数通过 I2C 协议向 EEPROM 写入一个字节。整个过程包括:

  1. 启动 I2C 通信。
  2. 等待 I2C 总线空闲,并发送 EEPROM 的地址。
  3. 发送 EEPROM 内部的写入地址。
  4. 发送数据字节。
  5. 发送停止信号结束通信。

2.2.5 I2C写入EEPROM多个字节函数

// 在EEPROM的一个写循环中可以写多个字节,但一次写入的字节数,不能超过EEPROM页的大小,AT24C02每页有8个字节
// 函数参数:pBuffer,要写入的数据,WriteAddr,要写入的地址,NumByteToWrite,要写入的字节数
uint32_t I2C_EE_PageWrite(u8* pBuffer, u8 WriteAddr, u8 NumByteToWrite)
{
  I2CTimeout = I2CT_LONG_TIMEOUT; // 超时时间初始化
  while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY)) // 等待I2C空闲状态
  {
    if((I2CTimeout--) == 0) 
      return I2C_TIMEOUT_UserCallback(4); // 返回错误码4,代表写入多个数据时等待I2C空闲状态超时
  } 
  I2C_GenerateSTART(I2Cx, ENABLE); // I2C开始信号
  I2CTimeout = I2CT_FLAG_TIMEOUT; 
  while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT))  
  {
    if((I2CTimeout--) == 0) 
      return I2C_TIMEOUT_UserCallback(5); // 返回错误码5,代表写入多个数据发送I2C开始信号超时
  } 
  I2C_Send7bitAddress(I2Cx, EEPROM_ADDRESS, I2C_Direction_Transmitter); // 发送I2C写命令
  I2CTimeout = I2CT_FLAG_TIMEOUT;
  while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))  
  {
    if((I2CTimeout--) == 0) 
      return I2C_TIMEOUT_UserCallback(6); // 返回错误码6,代表写入多个数据发送I2C地址信号超时
  } 
  I2C_SendData(I2Cx, WriteAddr); // 发送要写入的地址  
  I2CTimeout = I2CT_FLAG_TIMEOUT;
  while(! I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
  {
    if((I2CTimeout--) == 0) 
      return I2C_TIMEOUT_UserCallback(7); // 返回错误码7,代表写入多个数据发送I2C要写入的地址超时
  } 
  while(NumByteToWrite--) // 写入多个字节  
  {
    I2C_SendData(I2Cx, *pBuffer); // 发送要写入的字节 
    pBuffer++; // 数据指针递增
    I2CTimeout = I2CT_FLAG_TIMEOUT;
    while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
    {
      if((I2CTimeout--) == 0) 
        return I2C_TIMEOUT_UserCallback(8); // 返回错误码8,代表写入多个数据发送I2C要写入的字节超时
    } 
  }
  I2C_GenerateSTOP(I2Cx, ENABLE); // 发送多字节完毕,停止信号
  return 1;
}

这个函数支持通过 I2C 向 EEPROM 写入多个字节数据,主要步骤如下:

  1. 等待 I2C 总线空闲,发送开始信号。
  2. 发送设备地址和写命令。
  3. 发送写入地址,确定写入起始位置。
  4. 在 NumByteToWrite 次循环中写入多个字节。
  5. 完成后发送停止信号,结束通信。

2.2.7 将缓冲区的数据写入到EEPROM中

// 将缓冲区中的数据写到I2C EEPROM中
// 函数参数:pBuffer,要写入的数据,WriteAddr,要写入的地址,NumByteToWrite,要写入的字节数
void I2C_EE_BufferWrite(u8* pBuffer, u8 WriteAddr, u16 NumByteToWrite) 
{
  u8 NumOfPage = 0; // 页数
  u8 NumOfSingle = 0; // 单独的字节
  u8 Addr = 0; // 地址
  u8 count = 0; // 计数器
  Addr = WriteAddr % I2C_PageSize; // 计算要写入的地址在页中的偏移
  count = I2C_PageSize - Addr; // 计算页剩余空间大小
  NumOfPage =  NumByteToWrite / I2C_PageSize; // 计算页数
  NumOfSingle = NumByteToWrite % I2C_PageSize; // 计算单独的字节
  if(Addr == 0) // 如果一页可以写入完
  {
    if(NumOfPage == 0) // 如果NumByteToWrite < I2C_PageSize 
    {
      I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle); // 写入单独的字节
      I2C_EE_WaitEepromStandbyState(); // 等待EEPROM进入待机模式
    }
    else // 如果NumByteToWrite > I2C_PageSize 
    {
      while(NumOfPage--)
      {
        I2C_EE_PageWrite(pBuffer, WriteAddr, I2C_PageSize);  
    	  I2C_EE_WaitEepromStandbyState();  
        WriteAddr +=  I2C_PageSize;
        pBuffer += I2C_PageSize;
      }
      if(NumOfSingle!=0) // 如果还有单独的字节
      {
        I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
        I2C_EE_WaitEepromStandbyState();
      }
    }
  }
  else // 如果一页不能写入完 
  {
    if(NumOfPage== 0) // 如果If NumByteToWrite < I2C_PageSize
    {
      I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
      I2C_EE_WaitEepromStandbyState();
    }
    else // If NumByteToWrite > I2C_PageSize
    {
      NumByteToWrite -= count;
      NumOfPage =  NumByteToWrite / I2C_PageSize;
      NumOfSingle = NumByteToWrite % I2C_PageSize;	
      if(count != 0)
      {  
        I2C_EE_PageWrite(pBuffer, WriteAddr, count);
        I2C_EE_WaitEepromStandbyState();
        WriteAddr += count;
        pBuffer += count;
      } 
      while(NumOfPage--)
      {
        I2C_EE_PageWrite(pBuffer, WriteAddr, I2C_PageSize);
        I2C_EE_WaitEepromStandbyState();
        WriteAddr +=  I2C_PageSize;
        pBuffer += I2C_PageSize;  
      }
      if(NumOfSingle != 0)
      {
        I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle); 
        I2C_EE_WaitEepromStandbyState();
      }
    }
  }  
}

屏幕截图 20250116 154503png

2.2.8 读取缓冲区的数据到EEPROM中

// 从I2C EEPROM中读取数据到缓冲区中
// 函数参数:pBuffer,要读取的数据,ReadAddr,要读取的地址,NumByteToRead,要读取的字节数
uint32_t I2C_EE_BufferRead(u8* pBuffer, u8 ReadAddr, u16 NumByteToRead)
{  
  I2CTimeout = I2CT_LONG_TIMEOUT; // 超时时间初始化
  while(I2C_GetFlagStatus(I2Cx, I2C_FLAG_BUSY)) // 等待I2C空闲状态
  {
    if((I2CTimeout--) == 0) 
      return I2C_TIMEOUT_UserCallback(9); // 返回错误码9,代表读取多个数据时等待I2C空闲状态超时
  }
  I2C_GenerateSTART(I2Cx, ENABLE); // I2C开始信号
  I2CTimeout = I2CT_FLAG_TIMEOUT;
  while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
  {
    if((I2CTimeout--) == 0) 
      return I2C_TIMEOUT_UserCallback(10); // 返回错误码10,代表读取多个数据发送I2C开始信号超时
  }
  I2C_Send7bitAddress(I2Cx, EEPROM_ADDRESS, I2C_Direction_Transmitter); // 发送I2C读命令
  I2CTimeout = I2CT_FLAG_TIMEOUT;
  while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
  {
    if((I2CTimeout--) == 0) 
      return I2C_TIMEOUT_UserCallback(11); // 返回错误码11,代表读取多个数据发送I2C地址信号超时
  }  
  I2C_Cmd(I2Cx, ENABLE); // 使能I2C
  I2C_SendData(I2Cx, ReadAddr); // 发送要读取的地址
  I2CTimeout = I2CT_FLAG_TIMEOUT;
  while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
  {
    if((I2CTimeout--) == 0) 
      return I2C_TIMEOUT_UserCallback(12); // 返回错误码12,代表读取多个数据发送I2C要读取的地址超时
  }
  I2C_GenerateSTART(I2Cx, ENABLE); // 发送I2C开始信号
  I2CTimeout = I2CT_FLAG_TIMEOUT;
  while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
  {
    if((I2CTimeout--) == 0) 
      return I2C_TIMEOUT_UserCallback(13); // 返回错误码13,代表读取多个数据发送I2C开始信号超时
  }
  I2C_Send7bitAddress(I2Cx, EEPROM_ADDRESS, I2C_Direction_Receiver); // 发送I2C读命令
  I2CTimeout = I2CT_FLAG_TIMEOUT;
  while(!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))
  {
    if((I2CTimeout--) == 0) 
      return I2C_TIMEOUT_UserCallback(14); // 返回错误码14,代表读取多个数据发送I2C地址信号超时
  }
  while(NumByteToRead) // 循环读入数据 
  {
    if(NumByteToRead == 1) // 最后一次读入数据
    {
      I2C_AcknowledgeConfig(I2Cx, DISABLE); // 禁止I2C应答
      I2C_GenerateSTOP(I2Cx, ENABLE); // 发送I2C停止信号
    }
    I2CTimeout = I2CT_LONG_TIMEOUT;
		while(I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED) == 0)   
		{
			if((I2CTimeout--) == 0) 
        return I2C_TIMEOUT_UserCallback(3); // 返回错误码3,代表读取多个数据接收I2C数据超时
		} 
    {      
      *pBuffer = I2C_ReceiveData(I2Cx); // 接收I2C数据
      pBuffer++;  // 数据指针递增
      NumByteToRead--; // 读取字节数递减        
    }   
  }
  I2C_AcknowledgeConfig(I2Cx, ENABLE); // 使能I2C应答
  return 1;
}

这个函数实现了通过 I2C 从 EEPROM 中读取多个字节的数据,并将其存储到缓冲区。函数步骤如下:

  1. 检查 I2C 总线是否空闲。
  2. 发送开始信号、设备地址以及读取地址,初始化 I2C 通信。
  3. 再次发送开始信号并切换到接收模式。
  4. 循环接收数据并存储到缓冲区中,直到所有字节读取完成。
  5. 处理最后一个字节的读取和停止信号。

2.2.9 main()测试

完成基本的读写函数后,接下来我们编写一个读写测试函数来检验驱动程序

#include "stm32f10x.h"
#include "led.h"
#include "usart.h"
#include "I2C.h"
#include <string.h>

#define  EEP_Firstpage     0x00 // EEPROM的起始地址
uint8_t I2c_Buf_Write[256]; // 写入缓冲区
uint8_t I2c_Buf_Read[256];  // 读取缓冲区
uint8_t I2C_Test(void);

int main(void)
{ 
  	LED_Init(); 
  	LED_BLUE(); 
  	USART_Config(); 
  	I2C_EE_Init();
	if(I2C_Test() == 1)
	{
		LED_GREEN();
	} 
	else
	{	
		LED_RED();
	} 
  while (1)
  {      
  }
}

// I2C EEPROM读写测试
uint8_t I2C_Test(void)
{
	uint16_t i;
	printf("要写入的数据n\r");
	for ( i=0; i <= 255; i++)
    {   
		I2c_Buf_Write[i] = i; 
    	printf("0x%02X ", I2c_Buf_Write[i]); 
    	if(i % 16 == 15)   
        	printf("\n\r");    
    }
	I2C_EE_BufferWrite(I2c_Buf_Write, EEP_Firstpage, 256); // 写入缓冲区的数据到EEPROM
  	EEPROM_INFO("\n\r写入成功\r");  
  	EEPROM_INFO("\n\rEEPROM读出的数据n\r");  
  	I2C_EE_BufferRead(I2c_Buf_Read, EEP_Firstpage, 256);
	for(i = 0; i < 256; i++) // 打印出来,看是否数据读写相同
	{	
		if(I2c_Buf_Read[i] != I2c_Buf_Write[i]) // 如果数据不相同
		{
			EEPROM_ERROR("0x%02X ", I2c_Buf_Read[i]);
			EEPROM_ERROR("错误:两次读出的数据不相同\n\r");
			return 0;
		}
    printf("0x%02X ", I2c_Buf_Read[i]); // 打印出来读出的数据
    if(i % 16 == 15)  
        printf("\n\r");
	}
  return 1;
}

代码中先填充一个数组,数组的内容为1,2,3至N,接着把这个数组的内容写入到EEPROM中,写入时可以采用单字节写入的方式或页写入的方式。 写入完毕后再从EEPROM的地址中读取数据,把读取得到的与写入的数据进行校验,若一致说明读写正常,否则读写过程有问题或者EEPROM芯片不正常。 其中代码用到的EEPROM_INFO跟EEPROM_ERROR宏类似,都是对printf函数的封装,使用和阅读代码时把它直接当成printf函数就好。

3. 小结

啊,说了怎么多,本质还是一个配置的事,因为stm32标准库的强大,我们想要使用功能只用配置就好了,接下来的一个模拟I2C可能就没那么顺利了。根据传统,现在我们需要总结一下程序了:

  • 首先是要配置好串口和led了,目的是方便观察实验效果,串口和led我们都已经见过啦

  • 接下来就是I2C和外设的宏定义了,和以往不同的是,我们还宏定义了信息输出模板

/*信息输出*/
#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)
  • 正式开始配置:GPIO基础配置、I2C基础配置、I2C工作模式配置、外设(EEPROM)初始化

  • 配置里面当然包含外设有关函数:写入数据、写入字节、循环写入、取出数据(如果搞不懂,这些我们复制粘贴就好了)

  • 主函数测试:调用我们写好的函数,再写一个测试函数,外设正常绿灯亮、错误红灯亮


2024.9.4 第一次修订,后期不再维护

2025.1.16 程序大修,内容简化

本文作者:hazy1k

本文链接:https://www.cnblogs.com/hazy1k/p/18395964

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   hazy1k  阅读(36)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起