滴答时间溢出问题
千年虫问题一直困扰着所有程序员,嵌入式资源稀缺,滴答时钟最大计数时uint32_t ms,通过简单计算可知最大计时仅仅49天,那么对于动不动就是几个月不关机超长续航的设备来说,数据溢出后,定时任务又如何运行的呢?
这里我们将分析危害以及给出解决方案。
这里先给出一个我们常用但是有些许问题的例子
static void led_loop(void)
{
static uint32_t next_tick = 0;
if(HAL_GetTick() >= next_tick)
{
next_tick = HAL_GetTick() + 100;
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
}
}
int main(void)
{
// 此处省略一万字
while (1)
{
// other loop
led_loop();
}
}
HAL_GetTick 在这里获得了一个uint32_t计数值,最大可计数到UINT32_MAX,不考虑溢出情况下他将运行良好,但是在临界溢出时他将发生什么呢?
HAL_GetTick() 等于(UINT32_MAX -10)时,next_tick将得到结果为(UINT32_MAX+90)由于溢出截断得到结果为89,那么条件if(HAL_GetTick() >= next_tick) 将持续有效,while速度有多快,这个任务将执行多快,如果你这里是控制的继电器或者其它不能高速开关的设备,将导致设备损坏。
那么这个异常情况会持续多久呢?根据上述进一步分析可知,持续到HAL_GetTick()达到溢出条件。
那么如何避免这种问题?
调整判断过程
if((int32_t)(HAL_GetTick() - next_tick) >= 0)
{
next_tick = HAL_GetTick() + 100;
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
}
重复上述分析,HAL_GetTick() 等于(UINT32_MAX -10)时,next_tick将得到结果为(UINT32_MAX+90)由于溢出截断得到结果为89,那么条件if((int32_t)(HAL_GetTick()-next_tick) >= 0) 由于计算结果是一个负数而不满足条件,当HAL_GetTick()达到溢出条件后,由于next_tick仍大于HAL_GetTick()则仍是一个负数而不满足条件。
随着HAL_GetTick()数值的逐渐接近next_tick直到相等条件才会满足而执行这个任务,因此解决了随着数据溢出而导致定时任务出错的问题。
更完整的解决方案
static void led_loop(void)
{
static uint32_t next_tick = 0;
// 使用有符号数比较,正确处理溢出情况
if((int32_t)(HAL_GetTick() - next_tick) >= 0)
{
next_tick = HAL_GetTick() + 100;
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
}
}
原理解释:
- 当
HAL_GetTick() >= next_tick时,差值(HAL_GetTick() - next_tick)为正数或0 - 当发生溢出导致
HAL_GetTick() < next_tick时,差值会是一个很大的正数(接近UINT32_MAX) - 将差值强制转换为
int32_t后,大正数会变成负数 - 因此
(int32_t)(HAL_GetTick() - next_tick) >= 0能正确判断时间是否到达
其它问题
- 为什么使用
(int32_t)(HAL_GetTick() - next_tick) >= 0而不是直接比较?
这个是想每隔一段时间翻转下IO口,所以实际应该是HAL_GetTick()≥next_tick(不考虑溢出)。考虑到溢出,通过将两个uint32_t相减的结果强制转换为int32_t,利用有符号数的特性来判断时间差的正负。当HAL_GetTick() >= next_tick时,差值转换为int32_t后为正数或0;当发生溢出导致HAL_GetTick() < next_tick时,差值转换为int32_t后为负数。 - 为什么不考虑RTC或者uint64_t作为计数?
首先是效率问题,RTC时间戳通常也是32位的秒计时,每次判断需要扩展到uint64_t,在32位内核中判断条件效率比较低,高频使用的逻辑不合适。虽然PC中已经普及了64位,时间戳也逐步扩展64位,但是考虑到我们的硬件环境有的更加低端,很多地方都在使用这个tick,如果它扩展到64位,将增加代码复杂度和内存占用,所以应考虑解决溢出问题而不是增加计数长度。