【STM32】HAL库HAL_I2C_Receive详解

STM32 Master Receive diagram

Diagram中,上方为硬件自动执行,下方为软件控制的事件(EVx)
需要确保一定能执行的操作,都需要关中断运行,防止中断打断操作

N=1 ⇒ 1 byte

1.In the ADDR event, clear the ACK bit.
2. Clear ADDR
3. Program the STOP/START bit.
4. Read the data after the RxNE flag is set.

  • EV6_3 事件,对应为此时从机地址匹配成功,ADDR=1,但我们需要在完整接收到Data1之前,置I2C_CR1→Bit10 ACK为0,这样子在接收到Data1后主机不会回传ACK,对应上图的NA
  • 顺序为:置I2C_CR1→Bit10 ACK为0 ⇒ 清空ADDR ⇒ 发送Stop
  • EV7 事件,对应此时原本存在于Data shift register的数据已被移入Data register,此时I2C_SR1→Bit6 RXNE为1
  • N=2 ⇒ 2 byte

    1. Set POS and ACK
    2. Wait for the ADDR flag to be set
    3. Clear ADDR
    4. Clear ACK
    5. Wait for BTF to be set
    6. Program STOP
    7. Read DR twice
  • EV6_1 事件,紧跟在EV6事件中,故需要注意的是此处和N=1的情况不同,是先清空ADDR,再置I2C_CR1→Bit10 ACK为0。此外,为何此处是在完整接收Data1之前disable ACK?具体可见代码,此处用到了I2C_CR1→Bit11 POS

    当在接收数据前,使能该标志位,并在清空ADDR后置I2C_CR1→Bit10 ACK为0,则会在Data2(第二个byte)被移入Data shift register后给出一个NACK

  • EV7_3 事件,此处我们采用的方法是连续读两次Data register的方式在最后一起读出两个I2C的byte,故会出现Data register和Data shift register同时存在数据的情况,此时I2C_SR1→Bit6 RXNEI2C_SR1→Bit2 BTF均为1

  • N>2 ⇒ >2 byte

    When 3 bytes remain to be read:

    1. RxNE = 1 ⇒ Nothing (DataN-2 not read).
    2. DataN-1 received
    3. BTF = 1 because both shift and data registers are full: DataN-2 in DR and DataN-1 in
      the shift register ⇒ SCL tied low: no other data will be received on the bus.
    4. Clear ACK bit
    5. Read DataN-2 in DR ⇒ This will launch the DataN reception in the shift register
    6. DataN received (with a NACK)
    7. Program START/STOP
    8. Read DataN-1
    9. RxNE = 1
    10. Read DataN

    当通过I2C接收的字节数大于2的情况,实际上需要注意的是其最后三个字节的接收情况,对于非最后三个字节:

  • EV7 事件,当非最后三个字节的时候每当Data shift register(DSR)中的数据被送入Data register,此时I2C_SR1→Bit6 RXNE 拉高为1,此时从DR(Data register)中读出数据清空该标志位
  • 对于最后三个字节,其大致逻辑可参考step 1~10,简要概括为:当DataN-2被移入DR,此时I2C_SR1→Bit6 RXNE 拉高为1,但我们不去读DR,无EV7;继续接收DataN-1,当DataN-1被移入DSR,由于DR和DSR均有数据,此时I2C_SR1→Bit2 BTF被拉高为1,此时SCL会被拉低,不再接收数据;由于下一个字节DataN为最后一个字节,我们需要在接收DataN之前,置I2C_CR1→Bit10 ACK为0,以便接收到DATAN后回传NA,与此同时我们读DR中的DataN-2,则SCL会重新工作,DataN-1移入DR,DataN移入DSR,并且发送Stop信号;

  • EV7_2 事件描述的就是在接收DataN之前,置I2C_CR1→Bit10 ACK为0的事件,直至发送Stop信号这一段
  • 发送Stop信号后,我们读取DR中的DataN-1,此时DataN会从DSR移入DR,则由于DR中有数据而DSR中无数据,对应为I2C_SR1→Bit6 RXNE 拉高为1,I2C_SR1→Bit2 BTF被拉低为0;此时我们再读取DataN,这里对应又为EV7 事件。

    HAL_I2C_Master_Receive

    REF: 6.HAL_I2C_Master_Receive函数解析

    代码解析

    HAL_I2C_Master_Transmitter类似,无论是接收多少个字节,开始的步骤都为发送START → EV5事件 → 发送从机7bit地址,这三个步骤执行的代码和HAL_I2C_Master_Transmitter中是类似的;

    当完成从机7bit地址的发送,会进入一个if~else if的判断,主要根据传入的参数hi2c->XferCount=Size判断待接收的字节数量。

  • hi2c->XferCount==0U ,对应接收0 byte。清零ADDR,并发送Stop信号。

    	if (hi2c->XferSize == 0U)   // transfer size=0 byte,就清空ADDR并且发送停止信号
        {
          /* Clear ADDR flag */
          __HAL_I2C_CLEAR_ADDRFLAG(hi2c);
    
          /* Generate Stop */
          SET_BIT(hi2c->Instance->CR1, I2C_CR1_STOP);
        }
    
  • hi2c->XferCount==1U ,对应接收1 byte。置I2C_CR1→Bit10 ACK为0,清ADDR,并发送Stop信号。(需要注意的是在清空ADDR和发送Stop的时候要关闭中断,确保正确完成上述步骤)

    	else if (hi2c->XferSize == 1U)   // transfer size=1 byte
        {
          /* Disable Acknowledge */
          CLEAR_BIT(hi2c->Instance->CR1, I2C_CR1_ACK);  // ACK=0, 对应为发送NACK,ACK置零需要发生在接收Data1之前
    
          /* Disable all active IRQs around ADDR clearing and STOP programming because the EV6_3
          software sequence must complete before the current byte end of transfer */
          __disable_irq();
    
          /* Clear ADDR flag */
          __HAL_I2C_CLEAR_ADDRFLAG(hi2c);  
    
          /* Generate Stop */
          SET_BIT(hi2c->Instance->CR1, I2C_CR1_STOP);   // STOP信号生成,需要发生在接收Data1之前
    
          /* Re-enable IRQs */
          __enable_irq();
        }
    
  • hi2c->XferCount==2U ,对应接收2 byte。置位I2C_CR1→Bit11 POS,清ADDR,置I2C_CR1→Bit10 ACK为0。此处不需要发送Stop信号,因为Stop是在EV7_3事件中发送,此处仅包含EV6和EV6_1事件。

        else if (hi2c->XferSize == 2U)
        {
          /* Enable Pos */
          SET_BIT(hi2c->Instance->CR1, I2C_CR1_POS);  // POS对应的为接收二个字节时,在接收DATA1之前拉低ACK,则在接收DATA2后会发送一个NACK
    
          /* Disable all active IRQs around ADDR clearing and STOP programming because the EV6_3
          software sequence must complete before the current byte end of transfer */
          __disable_irq();
    
          /* Clear ADDR flag */
          __HAL_I2C_CLEAR_ADDRFLAG(hi2c);
    
          /* Disable Acknowledge */
          CLEAR_BIT(hi2c->Instance->CR1, I2C_CR1_ACK);
    
          /* Re-enable IRQs */
          __enable_irq();
        }
    
  • hi2c->XferCount>2U,对应为接收大于2个byte的情况,此时无论是接收3个byte或是大于3个byte,首先都是EV6 事件,即清空ADDR,并且成功接收下一个字节时回传ACK。

        else
        {
          /* Enable Acknowledge */
          SET_BIT(hi2c->Instance->CR1, I2C_CR1_ACK);
    
          /* Clear ADDR flag */
          __HAL_I2C_CLEAR_ADDRFLAG(hi2c);
        }
    
  • 后续继续通过一个while循环持续地判断hi2c->XferCount的个数,当hi2c->XferCount大于零,则一直运行该循环,在循环内首先判断:

  • hi2c->XferCount <= 3U
  • hi2c->XferCount == 1U ,则继续N=1 ⇒ 1 byte的后续流程,即EV7,读DR寄存器获得接收的1个byte
            /* One byte */
    		if (hi2c->XferSize == 1U)
            {
              /* Wait until RXNE flag is set */
              if (I2C_WaitOnRXNEFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK)
              {
                return HAL_ERROR;
              }
    
              /* Read data from DR */
              *hi2c->pBuffPtr = (uint8_t)hi2c->Instance->DR;
    
              /* Increment Buffer pointer */
              hi2c->pBuffPtr++;
    
              /* Update counter */
              hi2c->XferSize--;
              hi2c->XferCount--;
            }
    
  • hi2c->XferCount == 2U ,则继续N=2 ⇒ 2 byte的后续流程,即EV7_3,故在该部分代码中,首先判断I2C_SR1→Bit2 BTF是否拉高为1,当拉高为1,代表此时DR和DSR分别存有Data1和Data2;则首先发送Stop信号,然后连续读取两次DR,读出所有的数据。(此处关中断其实只需要确保Stop信号能在读取两次DR之前正确发送,否则若发送Stop信号被中断打断,导致未能正确发送Stop信号,可能会导致出错)
           /* Two bytes */
            else if (hi2c->XferSize == 2U)
            {
              /* Wait until BTF flag is set */
              if (I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BTF, RESET, Timeout, tickstart) != HAL_OK)
              {
                return HAL_ERROR;
              }
    
              /* Disable all active IRQs around ADDR clearing and STOP programming because the EV6_3
                 software sequence must complete before the current byte end of transfer */
              __disable_irq();
    
              /* Generate Stop */
              SET_BIT(hi2c->Instance->CR1, I2C_CR1_STOP);
    
              /* Read data from DR */
              *hi2c->pBuffPtr = (uint8_t)hi2c->Instance->DR;  // 第一次读取
    
              /* Increment Buffer pointer */
              hi2c->pBuffPtr++;
    
              /* Update counter */
              hi2c->XferSize--;
              hi2c->XferCount--;
    
              /* Re-enable IRQs */
              __enable_irq();
    
              /* Read data from DR */
              *hi2c->pBuffPtr = (uint8_t)hi2c->Instance->DR;  // 第二次读取
    
              /* Increment Buffer pointer */
              hi2c->pBuffPtr++;
    
              /* Update counter */
              hi2c->XferSize--;
              hi2c->XferCount--;
            }
    
  • hi2c->XferCount == 3U ,则继续N>2 ⇒ >2 byte中读取最后3个byte的后续流程;首先不读取DR,等待I2C_SR1→Bit2 BTF拉高为1,此时DataN-2位于DR,DataN-1位于DSR;当I2C_SR1→Bit2 BTF拉高为1,进入EV7_2 事件,此时首先置I2C_CR1→Bit10 ACK为0,因为后续接收DataN后需要回传NACK,然后读DR获得DataN-2,然后代码中还经过一个等待I2C_SR1→Bit2 BTF拉高为1(?没看懂),发送停止信号,从DR中读取DataN-1;从DR中读取DataN
            /* 3 Last bytes */
            else
            {
              /* Wait until BTF flag is set */
              if (I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BTF, RESET, Timeout, tickstart) != HAL_OK)
              {
                return HAL_ERROR;
              }
    
              /* Disable Acknowledge */
              CLEAR_BIT(hi2c->Instance->CR1, I2C_CR1_ACK);
    
              /* Disable all active IRQs around ADDR clearing and STOP programming because the EV6_3
                 software sequence must complete before the current byte end of transfer */
              __disable_irq();
    
              /* Read data from DR */
              *hi2c->pBuffPtr = (uint8_t)hi2c->Instance->DR;  // Read DataN
    
              /* Increment Buffer pointer */
              hi2c->pBuffPtr++;
    
              /* Update counter */
              hi2c->XferSize--;
              hi2c->XferCount--;
    
              /* Wait until BTF flag is set */ // 此处没看懂?
              count = I2C_TIMEOUT_FLAG * (SystemCoreClock / 25U / 1000U);
              do
              {
                count--;
                if (count == 0U)
                {
                  hi2c->PreviousState       = I2C_STATE_NONE;
                  hi2c->State               = HAL_I2C_STATE_READY;
                  hi2c->Mode                = HAL_I2C_MODE_NONE;
                  hi2c->ErrorCode           |= HAL_I2C_ERROR_TIMEOUT;
    
                  /* Re-enable IRQs */
                  __enable_irq();
    
                  /* Process Unlocked */
                  __HAL_UNLOCK(hi2c);
    
                  return HAL_ERROR;
                }
              }
              while (__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BTF) == RESET);
    
              /* Generate Stop */
              SET_BIT(hi2c->Instance->CR1, I2C_CR1_STOP);
    
              /* Read data from DR */
              *hi2c->pBuffPtr = (uint8_t)hi2c->Instance->DR;  // Read DataN-1
    
              /* Increment Buffer pointer */
              hi2c->pBuffPtr++;
    
              /* Update counter */
              hi2c->XferSize--;
              hi2c->XferCount--;
    
              /* Re-enable IRQs */
              __enable_irq();
    
              /* Read data from DR */
              *hi2c->pBuffPtr = (uint8_t)hi2c->Instance->DR;  // Read DataN
    
              /* Increment Buffer pointer */
              hi2c->pBuffPtr++;
    
              /* Update counter */
              hi2c->XferSize--;
              hi2c->XferCount--;
            }
    
  • hi2c->XferCount > 3U
  • 假若无中断打断I2C的读取过程,则对应正常的EV7 事件,即判断I2C_SR1→Bit6 RXNE是否拉高为1,若是,代表有数据被移入DR,则读取DR;
           /* Wait until RXNE flag is set */
            if (I2C_WaitOnRXNEFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK)
            {
              return HAL_ERROR;
            }
    
            /* Read data from DR */
            *hi2c->pBuffPtr = (uint8_t)hi2c->Instance->DR;
    
            /* Increment Buffer pointer */
            hi2c->pBuffPtr++;
    
            /* Update counter */
            hi2c->XferSize--;
            hi2c->XferCount--;
    
  • 假若有中断打断了I2C,则可能出现DR和DSR均有数据I2C_SR1→Bit2 BTF拉高为1,此时SCL被拉低,暂停从从机接收数据;接着会判断此时hi2c->XferCount
  • hi2c->XferCount == 3U则对应回到了前文中N>2 ⇒ >2 byte且只剩最后3个byte待读取的情况,则会首先置I2C_CR1→Bit10 ACK为0,并读取DR获得DataN-2;当下一次重新进入循环,则会进入前文while循环中的hi2c->XferCoun == 2U,由于此时当读取DataN-2后,DataN-1会进入DR,并且释放SCL,DataN自动进入DSR,故此时I2C_SR1→Bit2 BTF仍为1,故对应为EV7_3事件,首先发送Stop信号,然后读取两次DR获得DataN-1和DataN,完成读取。
  • hi2c->XferCount != 3U,实际就为hi2c->XferCount > 3U;此时非最后3个byte的情景,就正常的读取DR中的数据获得Data;后续理论上就是DR和DSR一直有数据,故需要一直判断I2C_SR1→Bit2 BTF ,然后读取DR,直到剩余3个byte。
       	  else  // > 3bytes
          {
            /* Wait until RXNE flag is set */
            if (I2C_WaitOnRXNEFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK)
            {
              return HAL_ERROR;
            }
    
            /* Read data from DR */
            *hi2c->pBuffPtr = (uint8_t)hi2c->Instance->DR;
    
            /* Increment Buffer pointer */
            hi2c->pBuffPtr++;
    
            /* Update counter */
            hi2c->XferSize--;
            hi2c->XferCount--;
    
            if (__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BTF) == SET)  
            {
    
              if (hi2c->XferSize == 3U)  // 此处if走完,就相当于N=2的情景
              {
                /* Disable Acknowledge */
                CLEAR_BIT(hi2c->Instance->CR1, I2C_CR1_ACK);
              }
    
              /* Read data from DR */
              *hi2c->pBuffPtr = (uint8_t)hi2c->Instance->DR;
    
              /* Increment Buffer pointer */
              hi2c->pBuffPtr++;
    
              /* Update counter */
              hi2c->XferSize--;
              hi2c->XferCount--;
            }
          }
    
  • 作者:康

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【STM32】HAL库HAL_I2C_Receive详解

    发表回复