请选择 进入手机版 | 继续访问电脑版
搜索
查看: 1928|回复: 3

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

[复制链接]

1

主题

5

帖子

0

好友

Rank: 1

新手上路

注册时间
2011-8-28
发表于 2016-3-30 23:38:22 | 显示全部楼层 |阅读模式
本帖最后由 chivalry-315158 于 2016-3-31 00:36 编辑

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

  1. /******* Blocking mode: Polling */
  2. HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
  3. HAL_StatusTypeDef HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
  4. HAL_StatusTypeDef HAL_I2C_Slave_Transmit(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t Timeout);
  5. HAL_StatusTypeDef HAL_I2C_Slave_Receive(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t Timeout);
  6. 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);
  7. 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);
  8. HAL_StatusTypeDef HAL_I2C_IsDeviceReady(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint32_t Trials, uint32_t Timeout);
复制代码

  1. static HAL_StatusTypeDef I2C_WaitOnFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Flag, FlagStatus Status, uint32_t Timeout);
  2. static HAL_StatusTypeDef I2C_WaitOnMasterAddressFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Flag, uint32_t Timeout);
  3. static HAL_StatusTypeDef I2C_WaitOnTXEFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Timeout);
  4. static HAL_StatusTypeDef I2C_WaitOnRXNEFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Timeout);
  5. static HAL_StatusTypeDef I2C_WaitOnSTOPFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Timeout);
复制代码


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

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

  4.   /* Get tick */
  5.   tickstart = HAL_GetTick();

  6.   /* Wait until flag is set */
  7.   if(Status == RESET)
  8.   {
  9.     while(__HAL_I2C_GET_FLAG(hi2c, Flag) == RESET)
  10.     {
  11.       /* Check for the Timeout */
  12.       if(Timeout != HAL_MAX_DELAY)
  13.       {
  14.         if((Timeout == 0)||((HAL_GetTick() - tickstart ) > Timeout))
  15.         {
  16.           hi2c->State= HAL_I2C_STATE_READY;

  17.           /* Process Unlocked */
  18.           __HAL_UNLOCK(hi2c);

  19.           return HAL_TIMEOUT;
  20.         }
  21.       }
  22.     }
  23.   }
  24.   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天了:
  1. __weak void HAL_Delay(__IO uint32_t Delay)
  2. {
  3.   uint32_t tickstart = 0;
  4.   tickstart = HAL_GetTick();
  5.   while((HAL_GetTick() - tickstart) < Delay)
  6.   {
  7.   }
  8. }
复制代码
重定义的HAL_Delay函数,参考Linux内核的jiffies变量,解决Tick溢出问题
  1. void HAL_Delay(__IO unsigned int Delay)
  2. {
  3.         uint32_t timeout_tick = 0;
  4.         uint32_t current_tick = 0;
  5.         timeout_tick = HAL_GetTick()+Delay;
  6.         do
  7.         {
  8.                 current_tick = HAL_GetTick();
  9.         }while((int32_t)current_tick - (int32_t)timeout_tick <0);
  10. }
复制代码

代码很简单,就是把unsigned int 类型强制转换为signed int做比较,详细原理请参考度娘。

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


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





回复

使用道具 举报

21

主题

274

帖子

0

好友

Rank: 6Rank: 6

金牌会员

注册时间
2015-8-21
发表于 2016-3-31 10:04:41 | 显示全部楼层
LZ多虑了。
事实上,当tick溢出时,HAL_Delay()函数依然准确。
在HAL库的架构中__IO uint32_t uwTick;用于实现HAL_Delay()函数。
每1ms产生一次Tick中断,在Tick中断中uwTick++;
uint32_t  HAL_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
回复 支持 反对

使用道具 举报

21

主题

274

帖子

0

好友

Rank: 6Rank: 6

金牌会员

注册时间
2015-8-21
发表于 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可以亲自验证一下是不是。
回复 支持 反对

使用道具 举报

13

主题

1074

帖子

5

好友

Rank: 6Rank: 6

金牌会员

注册时间
2015-1-19
发表于 2017-1-12 01:50:08 | 显示全部楼层
来学习一下,楼上分析的在理
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

站长推荐上一条 /2 下一条

Archiver|手机版|小黑屋|意法半导体STM32/STM8技术社区    

GMT+8, 2017-7-28 00:38 , Processed in 0.081579 second(s), 10 queries , Memcache On.

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表