STM32Cube_FW_F0_V1.10.0 官方库的I2C 调试

   又来写I2C通信了......真是换一个新库就要花时间重新调试一下,这次用的是最新的官方库STM32Cube_FW_F0_V1.10.0 ,开发平台用的也是新的STM32CubeIDE。

(一)

    需要完成的任务是模拟一个电池包被动发送信息给充电器,调试阶段我用STM32F030R8板子上的I2C2做主模拟充电器,I2C1做从模拟电池包。充电器读取电池包信息时通信时序是:先发送从机地址(模拟的电池包地址)+ Write标志,将需要读取信息的寄存器值发送给从机,发送完成后再发送一次从机地址+ Read标志,等待从机发送相关的信息回来。

    主机我用的的读取函数是

    HAL_I2C_Mem_Read(&hi2c2, (uint16_t)BAT_IC_ADDRESS, BAT_REG_TEMP, I2C_MEMADD_SIZE_8BIT, Tempe, 2, 500);

    因为我需要根据不同的寄存器值来发送不同的信息,所以我在第二次地址匹配时应该先判断上次读取的寄存器值,再选择发送哪种类型的数据 。因此,从机采用中断接收模式。

    研究了下官方库,总的来说,I2C如果要实现中断,有一个基本的函数调用顺序:

    1、配置I2C初始化设置,使能I2C中断

    2、当发生事件中断时会调用EV_IRQHandler,而如果发生错误会调用ER_IRQHandler。EV_IRQHandler中,会判断中断ISR以及中断源类型,并根据I2C是主机还是从机来决定调用I2C_Matser_IT还是I2C_Slave_IT

    3、在I2C_Matser/Slave_IT函数中,有具体的区分不同的中断类型:地址匹配中断(ADDR/ADDRIT)、发送中断(TXIS/TXI)、接收中断(RXNE/RXI)、AF/NACKI、STOPF/STOPI,不同的中断类型有不同的中断处理回调函数

   

   然而,实际调试中发现,如果需要在地址匹配时自己做另外处理,则需要重写HAL_I2C_AddrCallback函数,而这个函数只有在满足 ((hi2c->State & HAL_I2C_STATE_LISTEN) == HAL_I2C_STATE_LISTEN)条件时才会调用,找到源头确定满足该条件即需要使用HAL_I2C_Slave_Sequential_Receive_IT函数进行中断接收。而如果需要该函数能正常发生中断,在初始化时还需使能ListenIT即HAL_I2C_EnableListen_IT(&hi2c1);

   在完成这些配置后即可重写地址匹配回调函数HAL_I2C_AddrCallback,如下,我在地址匹配中断发生后判断主机的读写方向,只有当主机发送的是读时,从机才开始发送数据。

void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode)
{
	uint8_t Direction = TransferDirection;
	uint8_t Temp[2] = {0xFE,0x03};

	__HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_ADDR);


	if(hi2c == &hi2c1)
	{
	  if(Direction == 1)//Read
	  {
		 HAL_I2C_Slave_Sequential_Transmit_IT(&hi2c1, Temp, 2, I2C_FIRST_FRAME);
	  }
	}
}

   实际调试过程中发现由有问题,定位到调用HAL_I2C_AddrCallback()的函数I2C_ITAddrCplt(),有一段可以看到当7位地址匹配时,会先Disable_IRQ(hi2c, I2C_XFER_LISTEN_IT),这样会导致第一地址匹配后,第二次再发地址就接收不到中断了。一开始我的解决办法是在后面重新使能中断,即调用I2C_Enable_IRQ(hi2c, I2C_XFER_LISTEN_IT);这样操作后,我发现如果我发两个数据(比如0xFE、0x03),但实际接收到的是0xB6、0x00,DEBUG后发现I2C1中的hi2c->pBuffPtr 的数据在调用I2C_Enable_IRQ(hi2c, I2C_XFER_LISTEN_IT)后会发生变化(具体为什么还未知),这样导致TXDR中的数据变化,发出去的数据也就是错的。最后,我将I2C_Disable_IRQ(hi2c, I2C_XFER_LISTEN_IT);和I2C_Enable_IRQ(hi2c, I2C_XFER_LISTEN_IT)都注释掉,即解决了问题。

    else
    {
      /* Disable ADDR Interrupts */
      I2C_Disable_IRQ(hi2c, I2C_XFER_LISTEN_IT);

      /* Process Unlocked */
      __HAL_UNLOCK(hi2c);

      /* Call Slave Addr callback */
      HAL_I2C_AddrCallback(hi2c, transferdirection, slaveaddrcode);

    }
  }
  /* Else clear address flag only */
  else
  {
    /* Clear ADDR flag */
    __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_ADDR);

    /* Process Unlocked */
    __HAL_UNLOCK(hi2c);
  }

 

   (二)

在调通一次传输后,我想实现I2C的循环传输,然后发现用以上的方式I2C只通讯一次就停止传输了,具体的现象表现为主机的TXDR内的数据没有被清除,TXIS标志一直不置位,导致出现等待TimeOut,继续查找问题,花了蛮久时间搞懂这个库。

   主机Master读取

   1、HAL_I2C_Mem_Read

      该函数采用的是blocking的传输方式,将devAddress设备地址和MemAddress寄存器地址正确写入即可,其中寄存器地址可根据实际选择8 bit或16bit。

   2、HAL_I2C_Master_Sequential_Transmit_IT

      该函数采用中断的方式传输,且每次传输一个序列,如果需要读取寄存器内的值,则该函数的调用顺序应该如下

      HAL_I2C_Master_Sequential_Transmit_IT(&hi2c2, devAddress, &reg_address, 1, I2C_FIRST_FRAME);使用I2C_FIRST_FRAME目的是为了产生restart信号,以继续后面的传输

      然后重写发送完成函数HAL_I2C_MasterTxCpltCallback,在函数内开启Master读取

void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c)
{
	if(hi2c == &hi2c2)
	{
		HAL_I2C_Master_Sequential_Receive_IT(&hi2c2, devAddress, pData, Size, I2C_LAST_FRAME);
	}
}

从机Slave发送

     前面说了我为了在地址匹配中断时能进行自己的操作所以使用了HAL_I2C_Slave_Sequential_Receive_IT函数接收,但是在主机循环发送时用逻辑分析仪捕捉发现只有一次传输的信息,调试后发现当一次传输完成后,在I2C_Slave_ISR_IT中,会调用I2C_ITSlaveCplt(hi2c, ITFlags);在该函数内会清除ADDR、STOPF FLAG以及失能所有中断。

  /* Check if STOPF is set */
  if (((ITFlags & I2C_FLAG_STOPF) != RESET) && ((ITSources & I2C_IT_STOPI) != RESET))
  {
    /* Call I2C Slave complete process */
    I2C_ITSlaveCplt(hi2c, ITFlags);
  }

  /* Process Unlocked */
  __HAL_UNLOCK(hi2c);

  return HAL_OK;

 所以如果要重新发送数据需要重新配置,在I2C_ITSlaveCplt(hi2c, ITFlags)函数最后,如果传输没有发生错误会调用I2C_ITListenCplt(hi2c, ITFlags);Listen传输完成回调函数HAL_I2C_ListenCpltCallback也在里面,因此我们可以重写回调函数HAL_I2C_ListenCpltCallback,在里面重新配置开启Slave发送,记得这时也要重新使能ListenIT

void HAL_I2C_ListenCpltCallback(I2C_HandleTypeDef *hi2c)
{
	if(hi2c == &hi2c1)
		{
		   HAL_I2C_EnableListen_IT(&hi2c1);
		   HAL_I2C_Slave_Sequential_Receive_IT(&hi2c1, Command_Type, 1, I2C_FIRST_AND_NEXT_FRAME);
		}
}

   

总结来说,Sequential_Transmit/Receive函数应该只是用于一次传输(序列传输),如果要实现循环传输,必须要重新配置重新使能中断,至于配置和使能的位置则需要仔细查找对应的回调函数,在正确的位置调用。

  • 13
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值