使用STM32 HAL库实现硬件IIC通信

文章目录

  • 一. 前言
  • 二. 关于IIC通信
  • 三. IIC通信过程
  • 四. STM32实现硬件IIC通信
  • 五. 关于硬件IIC的Bug
  • 一. 前言

    最近正在DIY一款智能电池,需要使用STM32F030F4P6和TI的电池管理芯片BQ40Z50进行SMBUS通信。SMBUS本质上就是IIC通信,项目用到STM32CubeMX+HAL库,使用硬件IIC完成MCU对芯片的寄存器数据的读取和改写。

    整篇博客主要是梳理一下IIC的通信流程,并记录一下项目实现的过程,如有错的地方,欢迎各位读者批评指正。

    二. 关于IIC通信

    总线又两条信号线构成一条是数据线SDA ,用于数据传输。另一条则是时钟线SCL ,用于传输CLK信号,始终由主设备控制,主机(Master) 通过控制时钟信号可以实现对从机(Slave) 的控制与读写操作。各设备通过SDA、SCL接入总线,每个连接到IIC总线上的器件都有一个唯一的地址,如上图所示。

    SDA和SCL需通过上拉电阻 接至VCC。由于连接到总线上的器件输出级必须是漏极开路或者集电极开路的,因此只要有一个器件任意时刻输出低电平,都将总线上的信号变低;当IIC总线空闲时,SDA和SCL两条线均为高电平,即各器件的SDA和SCL都是线与的关系。

    三. IIC通信过程

    IIC通信过程由一系列的操作组成:

    1. 起始信号 :通信开始时,主设备发送一个低电平的SCL时钟脉冲,然后再发送一个低电平的SDA数据线脉冲。这个SDA的下降沿表示I2C总线上的一个开始信号。

    2. 从设备地址 :主设备发送从设备地址到I2C总线。I2C地址由7Bit或10Bit组成,取决于使用的设备。

    3. 读/写位传输 :读写位占据 1bit 数据,指定了数据传输的方向:

      a. 主设备发送数据,从设备接受数据,为 0 
      b. 主设备接收数据,从设备发送数据,为 1 
      
    4. 仲裁机制和应答 :应答位由1个Bit的数据组成。主设备通过检测应答位,以确定从设备是否存在。若主设备收到的是NACK,这会让主设备发生重启或停止流程:

       a. 如果从设备存在并正确收到地址,从设备将SDA下拉,产生应答信号 0 (ACK)。
       b. 如果从设备不存在或未正确收到地址,导致SDA空闲,产生非应答信号1(NACK)。
      
    5. 数据传输 :在确认通信目标后,主设备将发送或接收数据。数据传输是在主机产生的每个时钟周期的上升沿或下降沿进行的。

      a. 主设备发送数据: 上图所示,主设备 将要发送的数据位(8位或更多)依次发送到SDA线上,并在每个时钟周期上升沿时更新数据。从设备在每个时钟周期下降沿时接收数据,并在接收之后发送应答位来确认是否接收正确。

      b. 主设备接收数据: 上图所示,当读/写传输位为1时,到了数据传输时,主机从发送变成接收,从机从接收变成发送。从设备 将要发送的数据位(8位或更多)依次发送到SDA线上,并在每个时钟周期上升沿时更新数据。主设备在每个CLK下降沿时接收数据,并在接收之后发送应答位来确认是否接收正确。

    6. 停止信号(Stop Signal) :通信完成后,主设备发送一个停止信号,由一个高电平的SCL时钟脉冲和一个高电平的SDA数据线脉冲组成。这个SDA的上升沿表示I2C总线上的一个停止信号。

    四. STM32实现硬件IIC通信

    本项目用的是STM32F030F4P6通过Smbus通信和TI的电池管理芯片BQ40Z50通信,读取电池内部的电压、电流及电量信息,并且通过改写电池内部寄存器控制输入输出的Mos管。Smbus本质上就是IIC通信,下面通过访问BQ40Z50内部的电压寄存器的例子来说明模拟IIC的实现过程:

    STM32CubeMX配置如下:


    1. 主设备: STM32F030F4P6
    2. 从设备: BQ40Z50
    3. 通信频率: 10-100KHz
    4. 从设备地址: 0x16
    5. 电压寄存器地址: 0x09

    读取寄存器数据:

    1. 主机(STM32)首先产生Start信号。
    2. 然后紧跟着发送从机设备地址(0x16),此时读写位为0,表明是向从机写命令;
    3. 这时候主机等待从机(BQ40Z50)的应答信号(ACK)。
    4. 当主机收到应答信号时,发送要访问的寄存器地址(0x09),继续等待从机的应答信号;
    5. 当主机收到应答信号后,主机要改变通信模式,主机将由发送变为接收,从机将由接收变为发送。接着主机重新发送一个开始Start信号,然后紧跟着发送从机地址(0x16),注意此时读写位为1,表明将主机设置成接收模式开始读取数据。
    6. 这时候主机等待从机的应答信号,当主机收到应答信号时,就可以接收从机发送来的寄存器数据。
    7. 主机产生停止信号,结束传送过程。

    虽然通信过程比较复杂,但是实现非常简单。因为大部分步骤HAL库已经封装好。直接调用现成的接口即可:

    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)
                                       /**
    * @brief  Read an amount of data in blocking mode from a specific memory address
    * @param  hi2c Pointer to a I2C_HandleTypeDef structure that contains
    *                the configuration information for the specified I2C.
    * @param  DevAddress Target device address: The device 7 bits address value
    *         in datasheet must be shifted to the left before calling the interface
    * @param  MemAddress Internal memory address
    * @param  MemAddSize Size of internal memory address
    * @param  pData Pointer to data buffer
    * @param  Size Amount of data to be sent
    * @param  Timeout Timeout duration
    * @retval HAL status
    */
    

    比如,读取BQ40Z50的电压:

    Uint8_t Rxbuffer[2];//用于接受数据的数组
    			 
    HAL_I2C_Mem_Read(hi2cx,0x16,0x09,I2C_MEMADD_SIZE_8BIT,Rxbuffer,2,OxFF);//IIC接受从机数据
    
    voltage = Rxbuffer[1]<<8|Rxbuffer[0];//对接受数据进行解析
    

    同样的,写入寄存器数据:

    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)
    /**
      * @brief  Write an amount of data in blocking mode to a specific memory address
      * @param  hi2c Pointer to a I2C_HandleTypeDef structure that contains
      *                the configuration information for the specified I2C.
      * @param  DevAddress Target device address: The device 7 bits address value
      *         in datasheet must be shifted to the left before calling the interface
      * @param  MemAddress Internal memory address
      * @param  MemAddSize Size of internal memory address
      * @param  pData Pointer to data buffer
      * @param  Size Amount of data to be sent
      * @param  Timeout Timeout duration
      * @retval HAL status
      */
    

    比如:向0x00写入0x270C和0x430D可以关闭BQ40Z50的Mos管:

    uint8_t Mos_off [2];
    Mos_off [0] = 0x0C;
    Mos_off [1] = 0x27;
    if(HAL_I2C_Mem_Write(hi2cx,0x16,0x00,I2C_MEMADD_SIZE_8BIT,Mos_off ,2,0xFF)==HAL_OK)
    {
    	Mos_off [0] = 0x3D;
    	Mos_off [1] = 0x04;
    	HAL_I2C_Mem_Write(hi2cx,0x16,0x00,I2C_MEMADD_SIZE_8BIT,Mos_off ,2,0xFF);
    }
    

    五. 关于硬件IIC的Bug

    在开发过程中,IIC经常会遇到很多莫名其妙的Bug,比如反复超时,陷入I2C_WaitOnFlagUntilTimeout()死循环中,或者是一直处于HAL_I2C_STATE_BUSY_RX直接卡死,看了网络上很多的帖子,但是都没有找到好的解决办法。

    这里的解决方式非常简单粗暴,就在卡死的地方直接对硬件IIC进行重置,重新初始化,亲测有效。

    各位有什么更好的方法,欢迎在评论区留言,一起探讨。

    void User_I2C_ErrorInit(I2C_HandleTypeDef *hi2c)
    {
    	HAL_I2C_DeInit(hi2c);
    	
    	hi2c->Instance = I2C1;
    	hi2c->Init.Timing = 0x2000090E;
    	hi2c->Init.OwnAddress1 = 0;
    	hi2c->Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
    	hi2c->Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
    	hi2c->Init.OwnAddress2 = 0;
    	hi2c->Init.OwnAddress2Masks = I2C_OA2_NOMASK;
    	hi2c->Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
    	hi2c->Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
    
        hi2c->Instance->CR1 |= I2C_CR1_SWRST;
        hi2c->Instance->CR1 &= ~I2C_CR1_SWRST;
    
    	if (HAL_I2C_Init(hi2c) != HAL_OK)
    	{
    		User_Error_Handler();
    	}
    
    	/** Configure Analogue filter
    	*/
    	if (HAL_I2CEx_ConfigAnalogFilter(hi2c, I2C_ANALOGFILTER_ENABLE) != HAL_OK)
    	{
    		User_Error_Handler();
    	}
    
    	/** Configure Digital filter
    	*/
    	if (HAL_I2CEx_ConfigDigitalFilter(hi2c, 0) != HAL_OK)
    	{
    		User_Error_Handler();
    	}
    }
    
    static void User_Error_Handler(void)
    {
      /* USER CODE BEGIN Error_Handler_Debug */
      /* User can add his own implementation to report the HAL error return state */
      __disable_irq();
      while (1)
      {
      }
      /* USER CODE END Error_Handler_Debug */
    }
    

    各位有什么更好的方法,欢迎在评论区留言,一起探讨。

    作者:冬瓜~

    物联沃分享整理
    物联沃-IOTWORD物联网 » 使用STM32 HAL库实现硬件IIC通信

    发表回复