STM32HAL库学习笔记:I2C通信实战

HAL库快速部署I2C

本文主要介绍如何使用STM32CubeMX快速部署I2C通信,并与EEPROM进行数据收发。


文章目录

  • HAL库快速部署I2C
  • I2C简介
  • EEPROM简介
  • HAL库部署IIC通信实现多字节写入
  • 一、CubeMX配置
  • 二、代码编写

  • I2C简介

    I2C是一种串行同步半双工通信方式。
    I2C物理层是由一条双向数据总线SDA和一条双向时间总线SCL组成,I2C总线上可以挂载多个从机设备。

    stm32f103的引脚说明,来自leung——STM32CubeMX学习笔记(9)PB8 PB9 为重映射。这里是引用

    I2C协议层定义了通讯的起始和停止信号、数据有效性、响应、仲裁、时钟同步和地址广播等环节。通信流程如下:

    起始信号发出后,先发出设备地址决定与哪个从机通信,WRITE位为0表示写入为1表示读出。主机每发送完一个字节数据,都要等待从机的应答信号 (ACK),只有接收到应答信号后,主机才能继续发送或接收数据。然后开始写入/读出数据,对于EEPROM写入/读出的第一个数据是写入/读出内存的地址。若接收端希望结束数据传输,则向对方发送“非应答 (NACK)”信号,发送方接收到该信号后会产生一个停止信号,结束信号传输。


    EEPROM简介

    EEPROM 是一种掉电后数据不丢失的存储器,最常用的通讯方式就是 I2C 协议,STM32F103开发板中使用的 EEPROM 芯片型号是AT24C02。

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

    AT24C02有2K的存储容量,2K=2*1024=2048位,所以最多可以往AT24C02存储器里面写2048bit,可以写256个8位字节,写或者读的地址是0X00–0X7FF。

    值得一提的是根据AT24C02的规格说明书,写入ROM是比较耗时的,写周期最大5ms,没写入完就指令写入下一组数据会出现错误,这点在编程时需要注意。

    EEPROM的页写入:虽然I2C通信是串行通信,一次传输一个字节,但对于EEPROM定义了一种页写入的时序。我们希望向连续地址写入多个数据的时候,只要告诉 EEPROM 第一个内存地址 address1,后面的数据按次序写入到address2、address3…这样可以节省通讯的内容,加快速度。AT24C02 型号的芯片页写入时序最多可以一次发送 8 个数据 (即 n = 8 ),该值也称为页大小,某些型号的芯片每个页写入时序最多可传输 16 个数据。

    24系列的eeprom提供缓存的技术,写入一页的数据,它先保存起来,当你停止对它操作时(stop信号后),eeprom再把缓存的数据写好。所以页操作的最大值时受限于IC的缓存区大小的。 ——来自:EEPROM的写入操作解析
    当写入数据溢出页大小限制时,溢出数据会从页起始地址再写入,如果该地址原本有数据,会被覆盖,导致写入异常。
    很好的分析AT24C02的博客:stm32专题十七:AT24C02

    EEPROM的页写入

    HAL库部署IIC通信实现多字节写入

    AT24C02 型EEPROM一页最多写入8个数据,所以一般来说我们不能写入超过8个数据,但是通过合理的程序编程我们可以实现跨页写入,从而一次写入多个数据。

    一、CubeMX配置

    1.新建工程,常规配置调试模式、时钟树、项目环境;
    2.选择PB0、PB1、PB5配置为GPIO_Out,初始为高电平,用于测试程序的指示;
    3.配置I2C模式、传输速度,需要中断时开中断。

    4.GENERATE CODE

    二、代码编写

    1.函数介绍
    HAL库部署I2C通信主要使用HAL_I2C_Mem_Write函数和HAL_I2C_Mem_Read函数。

    HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout)
    HAL_StatusTypeDef HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout)
    

    *hi2c是指向设备特定I2C配置信息的结构体的指针;
    DevAddress是从机设备地址;
    MemAddress是写入或者读出数据的地址;
    MemAddSize是MemAddress的地址长度,注意hal库I2C函数中地址长度是以字节为单位的
    *pData是指向待写入的数据内存或者是存放读出数据的内存的指针;
    Size是待写入的数据或者是待读出数据的数据大小;
    Timeout是超时时间,超过该时间函数返回错误值。

    2.单字节和页写入
    这两种写入方式没有特别大差异,之间调用HAL_I2C_Mem_Write函数就可以实现。EEPROM 的单字节时序规定,向它写入数据的时候,第一个字节为内存地址,第二个字节是要写入的数据内容。
    单字节写入一次写一个字节,把Size设为1就可以;页写入一次最多写入8个字节,在MemAddress模8为0时Size最大为8,参考代码如下:

    #define EEPROM_ADDRESS          0xA0
    #define I2C_MEMADD_SIZE_8BIT    0x00000001U
    
    I2C_HandleTypeDef  I2C_Handle;
    
    uint32_t I2C_EE_ByteWrite(uint8_t* pBuffer, uint8_t WriteAddr)
    {
    	HAL_StatusTypeDef status = HAL_OK;
    
    	status = HAL_I2C_Mem_Write(&I2C_Handle, EEPROM_ADDRESS, (uint16_t)WriteAddr, I2C_MEMADD_SIZE_8BIT, pBuffer, 1, 100); 
    
    	/* Check the communication status */
    	if(status != HAL_OK)
    	{
    	/* Execute user timeout callback */
    	//I2Cx_Error(Addr);
    	}
    	while (HAL_I2C_GetState(&I2C_Handle) != HAL_I2C_STATE_READY)
    	{
    		
    	}
    
    	/* Check if the EEPROM is ready for a new operation */
    	while (HAL_I2C_IsDeviceReady(&I2C_Handle, EEPROM_ADDRESS, EEPROM_MAX_TRIALS, I2Cx_TIMEOUT_MAX) == HAL_TIMEOUT);
    
    	/* Wait for the end of the transfer */
    	while (HAL_I2C_GetState(&I2C_Handle) != HAL_I2C_STATE_READY)
    	{
    		
    	}
    	return status;
    }
    
    uint32_t I2C_EE_PageWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint8_t NumByteToWrite)
    {
    	HAL_StatusTypeDef status = HAL_OK;
    	/* Write EEPROM_PAGESIZE */
    	status=HAL_I2C_Mem_Write(&I2C_Handle, EEPROM_ADDRESS,WriteAddr, I2C_MEMADD_SIZE_8BIT, (uint8_t*)(pBuffer),NumByteToWrite, 100);
    
    	while (HAL_I2C_GetState(&I2C_Handle) != HAL_I2C_STATE_READY)
    	{
    		
    	}
    
    	/* Check if the EEPROM is ready for a new operation */
    	while (HAL_I2C_IsDeviceReady(&I2C_Handle, EEPROM_ADDRESS, EEPROM_MAX_TRIALS, I2Cx_TIMEOUT_MAX) == HAL_TIMEOUT);
    
    	/* Wait for the end of the transfer */
    	while (HAL_I2C_GetState(&I2C_Handle) != HAL_I2C_STATE_READY)
    	{
    		
    	}
    	return status;
    }
    

    3.多字节写入
    多字节写入主要针对多页写入设计,引入变量Addr(写入地址模8) count(第一页写入数据长度) NumOfPage(需要页数) NumOfSingle (去除整页剩余数据长度),先把写入第一页的起始地址后的部分写完再写入后面的数据。

    void I2C_EE_BufferWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t NumByteToWrite)
    {
      uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;
    
      Addr = WriteAddr % EEPROM_PAGESIZE;
      count = EEPROM_PAGESIZE - Addr;
      NumOfPage =  NumByteToWrite / EEPROM_PAGESIZE;
      NumOfSingle = NumByteToWrite % EEPROM_PAGESIZE;
     
      /* 写入起始地址是8的整数倍  */
      if(Addr == 0) 
      {
        /* If NumByteToWrite < I2C_PageSize */
        if(NumOfPage == 0) 
        {
          I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
        }
        /* If NumByteToWrite > I2C_PageSize */
        else  
        {
          while(NumOfPage--)
          {
            I2C_EE_PageWrite(pBuffer, WriteAddr, EEPROM_PAGESIZE); 
            WriteAddr +=  EEPROM_PAGESIZE;
            pBuffer += EEPROM_PAGESIZE;
          }
    
          if(NumOfSingle!=0)
          {
            I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
          }
        }
      }
      /* 写入起始地址非8的整数倍  */
      else 
      {
        /* If NumByteToWrite < I2C_PageSize */
        if(NumOfPage== 0) 
        {
          I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
        }
        /* If NumByteToWrite > I2C_PageSize */
        else
        {
          NumByteToWrite -= count;     //去掉第一页要写入的字节数
          NumOfPage =  NumByteToWrite / EEPROM_PAGESIZE;		
          NumOfSingle = NumByteToWrite % EEPROM_PAGESIZE;	
          
          /*如果起始地址与页不对齐,需要先写满当页剩下的字节*/
          if(count != 0)
          {  
            I2C_EE_PageWrite(pBuffer, WriteAddr, count);
            WriteAddr += count;
            pBuffer += count;
          } 
          
          while(NumOfPage--)
          {
            I2C_EE_PageWrite(pBuffer, WriteAddr, EEPROM_PAGESIZE);
            WriteAddr +=  EEPROM_PAGESIZE;
            pBuffer += EEPROM_PAGESIZE;  
          }
          if(NumOfSingle != 0)
          {
            I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle); 
          }
        }
      }  
    }
    
    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32HAL库学习笔记:I2C通信实战

    发表评论