你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

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

[复制链接]
chivalry-315158 提问时间:2016-3-30 23:38 /
本帖最后由 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)可以解决溢出回绕的问题





收藏 评论4 发布时间:2016-3-30 23:38

举报

4个回答
adlu 回答时间: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
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次方远着那

所属标签

相似问题

关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版