chivalry-315158 发表于 2016-3-30 23:38:22

关于HAL库中超时函数中的tick溢出后产生的问题

本帖最后由 chivalry-315158 于 2016-3-31 00:36 编辑

HAL库中有部分函数具有TimeOut参数,例如I2C的部分函数

/******* Blocking mode: Polling */
HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_I2C_Slave_Transmit(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_I2C_Slave_Receive(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t Timeout);
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);
HAL_StatusTypeDef HAL_I2C_IsDeviceReady(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint32_t Trials, uint32_t Timeout);

static HAL_StatusTypeDef I2C_WaitOnFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Flag, FlagStatus Status, uint32_t Timeout);
static HAL_StatusTypeDef I2C_WaitOnMasterAddressFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Flag, uint32_t Timeout);
static HAL_StatusTypeDef I2C_WaitOnTXEFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Timeout);
static HAL_StatusTypeDef I2C_WaitOnRXNEFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Timeout);
static HAL_StatusTypeDef I2C_WaitOnSTOPFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Timeout);

其源码中关于TimeOut的检测方式如下:

static HAL_StatusTypeDef I2C_WaitOnFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Flag, FlagStatus Status, uint32_t Timeout)
{
uint32_t tickstart = 0;

/* Get tick */
tickstart = HAL_GetTick();

/* Wait until flag is set */
if(Status == RESET)
{
    while(__HAL_I2C_GET_FLAG(hi2c, Flag) == RESET)
    {
      /* Check for the Timeout */
      if(Timeout != HAL_MAX_DELAY)
      {
      if((Timeout == 0)||((HAL_GetTick() - tickstart ) > Timeout))
      {
          hi2c->State= HAL_I2C_STATE_READY;

          /* Process Unlocked */
          __HAL_UNLOCK(hi2c);

          return HAL_TIMEOUT;
      }
      }
    }
}
else其中第16行代码:
HAL_GetTick的函数声明为:unsigned int HAL_GetTick(void)
其实现是系统Tick中断后一个unsigned int类型的变量自增,既然为unsigned int自增,那么当自增到0xFFFFFFFF后就会溢出,从0开始自增。
那么问题来了,上面那段代码,当Tick溢出为0后,变为:(0 - tickstart ) > Timeout ,一个负数会大于一个正数么? 系统Tick频率设置为1毫秒中断一次,那么0xFFFFFFFF毫秒大约为49.71026961805556天,也就是49天以后会产生第一次溢出,然后,就没有然后了。

Linux内核的jiffies变量与此类似,Linux内核用了几个简单的宏定义就完美的解决的问题,建议参考一下。

与此相同HAL层HAL_Delay函数也从在同样的问题,如果使用Delay只要溢出就要Delay上49天了:__weak void HAL_Delay(__IO uint32_t Delay)
{
uint32_t tickstart = 0;
tickstart = HAL_GetTick();
while((HAL_GetTick() - tickstart) < Delay)
{
}
}重定义的HAL_Delay函数,参考Linux内核的jiffies变量,解决Tick溢出问题void HAL_Delay(__IO unsigned int Delay)
{
      uint32_t timeout_tick = 0;
      uint32_t current_tick = 0;
      timeout_tick = HAL_GetTick()+Delay;
      do
      {
                current_tick = HAL_GetTick();
      }while((int32_t)current_tick - (int32_t)timeout_tick <0);
}
代码很简单,就是把unsigned int 类型强制转换为signed int做比较,详细原理请参考度娘。

希望ST能在下个版本的HAL库中修复这个问题。


为什么宏time_after(a,b)可以解决溢出回绕的问题





adlu 发表于 2016-3-31 10:04:41

LZ多虑了。
事实上,当tick溢出时,HAL_Delay()函数依然准确。
在HAL库的架构中__IO uint32_t uwTick;用于实现HAL_Delay()函数。
每1ms产生一次Tick中断,在Tick中断中uwTick++;
uint32_tHAL_GetTick()函数返回的是当前uwTick的值。

假设调用HAL_Delay(10);而执行tickstart = HAL_GetTick();后tickstart = 65530,
然后执行while((HAL_GetTick() - tickstart) < Delay){}

分析,当HAL_GetTick()返回0时的情况:
(HAL_GetTick() - tickstart) =(0-65530)
LZ可能认为这是个负数,此时会导致((HAL_GetTick() - tickstart) < Delay) = ((0 - 65530) < 10)=FALSE
事实并非如此,因为HAL_GetTick()的返回值、tickstart和Delay都是uint32_t 型数据,这样HAL_GetTick() - tickstart的结果不可能是负数!

正确的分析应该是这样((HAL_GetTick() - tickstart) < Delay) = ((0 - 65530) < 10)
=((65536 - 65530) < 10) = (6 < 10) = TRUE

adlu 发表于 2016-3-31 10:09:51

本帖最后由 adlu 于 2016-3-31 10:50 编辑

LZ可以修改HAL_Delay()函数内容为
__weak void HAL_Delay(__IO uint32_t Delay)
{
uint32_t tickstart = 0;
uwTick = 65530;   // 此处强制uwTick为65530
tickstart = HAL_GetTick();
while((HAL_GetTick() - tickstart) < Delay)
{
}
}

然后调用HAL_Delay(10);该函数会导致uwTick变量溢出,如果如LZ所想,HAL_Delay(10);的执行时间应该是49天,LZ可以亲自验证一下是不是。

wu1169668869 发表于 2017-1-12 01:50:08

来学习一下,楼上分析的在理

zlfxia 发表于 2020-3-7 20:56:44

adlu 发表于 2016-3-31 10:09
LZ可以修改HAL_Delay()函数内容为
__weak void HAL_Delay(__IO uint32_t Delay)
{


虽然是很久以前的贴子,但是这个会误导人,65535=2^16离2^32次方远着那
页: [1]
查看完整版本: 关于HAL库中超时函数中的tick溢出后产生的问题