STM32 32 位 ARM 架构上的系统节拍翻转
System Tick rollover on STM32 32bit ARM architecture
无法理解当 STM32 MCU 上的 32 位系统节拍使用 ST 提供的 HAL 平台翻转时会发生什么。
如果 MCU 运行ning 直到 HAL_GetTick() returns 其最大值为 2^32 -1 =0xFFFFFFFF 即 4,294,967,295 / 1000 / 60 / 60 / 24 = 大约 49 天(计算 1 毫秒滴答到可测量的最大持续时间时)。
如果你有一个计时器 运行 跨越翻转点会怎样?
在翻转事件上创建 100 毫秒延迟的示例代码:
uint32_t start = HAL_GetTick() // start = 0xFFFF FFFF (in this example)
--> Interrupt increments systick which rolls it over to 0 at this point
while ((HAL_GetTick() - start) < 100);
所以当第一次计算循环中的表达式时 HAL_GetTick() = 0x0000 0000 并且开始 = 0xFFFF FFFF。因此 0x0000 00000 - 0xFFFF FFFF =? (这个数字不存在,因为它是负数,我们正在做无符号算术)
然而,当我 运行 在我的 STM32 上使用 GCC ARM 编译的以下代码时:
uint32_t a = 0xFFFFFFFFUL;
uint32_t b = 0x00000000UL;
uint32_t c = b - a;
printf("a =%lu b=%lu c=%lu\r\n", a, b, c);
输出为:
a =4294967295 b=0 c=1
事实上,从代码在溢出时正常运行的角度来看,c=1 是好的,但我不明白这里在低级别上实际发生了什么。 0 - 4294967295 = 1 如何实现?遇到这种情况,单片机内部的算术逻辑单元在做什么,纸上怎么算?
不会有什么不好的事情发生。它的工作方式与环绕之前相同。
int main(void)
{
uint32_t start = UINT32_MAX - 20;
uint32_t current = start;
for(uint32_t x = 0; x < 100; x++)
{
printf("start = 0x%08"PRIx32" current = 0x%08"PRIx32 " current - start = %"PRIu32"\n", start, current, current-start);
current++;
}
}
你可以在这里看到:
https://godbolt.org/z/jx4T4fhsW
0x00000000 - 0xffffffff 将为 1,因为需要将 1 添加到 0xffffffff 以获得 0x00000000。其他号码也一样。
顺便说一句,如果您使用十六进制数而不是在编程中使用非常有限的十进制数,则更容易理解。
这是modular arithmetic. Or modulo wrapping is what happens when an unsigned integer overflows的特点。
当使用固定数量的 digits/bits 时,算术运算可能会溢出固定数量的数字。但是溢出的部分不能用固定的digits/bits个数表示,基本被mask掉了。溢出部分可以认为是取模,固定数digits/bits内的部分是余数或取模。给定模数,模数值在导致溢出的操作后保持 correct/congruent。
最好的理解方式就是在纸上用笔做一些操作。选择一个基地。十六进制很棒,但它适用于十进制、二进制和所有基数。选择固定数量的digits/bits。对于 uint32_t 你有 8 个十六进制数字或 32 位。选择两个相加时会溢出固定位数的值。在纸上做数学运算,并将任何溢出包括到一个额外的数字中。现在通过用手覆盖溢出来执行模运算。您的 CPU 凭借固定位数(即 uint32_t)自动执行此模运算。用不同的数字重复此操作并用 subtraction/underflow 重复。最终你会开始相信它是有效的。
您做设置此操作时必须小心。使用无符号类型并从当前刻度值中减去起始刻度值,就像在示例代码中所做的那样。 (例如,不要添加开始滴答的延迟并与当前滴答作比较。)Raymond Chen 的文章,Using modular arithmetic to avoid timing overflow problems 有更多信息。
How does 0 - 4294967295 = 1 ?? How would I calculate this on paper to
show what the arithmetic logic unit inside the MCU is doing when this
situation is encountered?
先用十六进制写成这样:
0000_0000
- FFFF_FFFF
_____________
然后意识到在第一个值(被减数)上可以有一个取模值0x1_0000_0000。 (因为根据模运算,“0x0_0000_0000 和 0x1_0000_0000 是全等模 0x1_0000_0000”)。那么应该很明显,差异是 1.
1_0000_0000
- 0_FFFF_FFFF
_____________
0_0000_0001
无法理解当 STM32 MCU 上的 32 位系统节拍使用 ST 提供的 HAL 平台翻转时会发生什么。
如果 MCU 运行ning 直到 HAL_GetTick() returns 其最大值为 2^32 -1 =0xFFFFFFFF 即 4,294,967,295 / 1000 / 60 / 60 / 24 = 大约 49 天(计算 1 毫秒滴答到可测量的最大持续时间时)。 如果你有一个计时器 运行 跨越翻转点会怎样?
在翻转事件上创建 100 毫秒延迟的示例代码:
uint32_t start = HAL_GetTick() // start = 0xFFFF FFFF (in this example)
--> Interrupt increments systick which rolls it over to 0 at this point
while ((HAL_GetTick() - start) < 100);
所以当第一次计算循环中的表达式时 HAL_GetTick() = 0x0000 0000 并且开始 = 0xFFFF FFFF。因此 0x0000 00000 - 0xFFFF FFFF =? (这个数字不存在,因为它是负数,我们正在做无符号算术)
然而,当我 运行 在我的 STM32 上使用 GCC ARM 编译的以下代码时:
uint32_t a = 0xFFFFFFFFUL;
uint32_t b = 0x00000000UL;
uint32_t c = b - a;
printf("a =%lu b=%lu c=%lu\r\n", a, b, c);
输出为: a =4294967295 b=0 c=1
事实上,从代码在溢出时正常运行的角度来看,c=1 是好的,但我不明白这里在低级别上实际发生了什么。 0 - 4294967295 = 1 如何实现?遇到这种情况,单片机内部的算术逻辑单元在做什么,纸上怎么算?
不会有什么不好的事情发生。它的工作方式与环绕之前相同。
int main(void)
{
uint32_t start = UINT32_MAX - 20;
uint32_t current = start;
for(uint32_t x = 0; x < 100; x++)
{
printf("start = 0x%08"PRIx32" current = 0x%08"PRIx32 " current - start = %"PRIu32"\n", start, current, current-start);
current++;
}
}
你可以在这里看到: https://godbolt.org/z/jx4T4fhsW
0x00000000 - 0xffffffff 将为 1,因为需要将 1 添加到 0xffffffff 以获得 0x00000000。其他号码也一样。
顺便说一句,如果您使用十六进制数而不是在编程中使用非常有限的十进制数,则更容易理解。
这是modular arithmetic. Or modulo wrapping is what happens when an unsigned integer overflows的特点。
当使用固定数量的 digits/bits 时,算术运算可能会溢出固定数量的数字。但是溢出的部分不能用固定的digits/bits个数表示,基本被mask掉了。溢出部分可以认为是取模,固定数digits/bits内的部分是余数或取模。给定模数,模数值在导致溢出的操作后保持 correct/congruent。
最好的理解方式就是在纸上用笔做一些操作。选择一个基地。十六进制很棒,但它适用于十进制、二进制和所有基数。选择固定数量的digits/bits。对于 uint32_t 你有 8 个十六进制数字或 32 位。选择两个相加时会溢出固定位数的值。在纸上做数学运算,并将任何溢出包括到一个额外的数字中。现在通过用手覆盖溢出来执行模运算。您的 CPU 凭借固定位数(即 uint32_t)自动执行此模运算。用不同的数字重复此操作并用 subtraction/underflow 重复。最终你会开始相信它是有效的。
您做设置此操作时必须小心。使用无符号类型并从当前刻度值中减去起始刻度值,就像在示例代码中所做的那样。 (例如,不要添加开始滴答的延迟并与当前滴答作比较。)Raymond Chen 的文章,Using modular arithmetic to avoid timing overflow problems 有更多信息。
How does 0 - 4294967295 = 1 ?? How would I calculate this on paper to show what the arithmetic logic unit inside the MCU is doing when this situation is encountered?
先用十六进制写成这样:
0000_0000
- FFFF_FFFF
_____________
然后意识到在第一个值(被减数)上可以有一个取模值0x1_0000_0000。 (因为根据模运算,“0x0_0000_0000 和 0x1_0000_0000 是全等模 0x1_0000_0000”)。那么应该很明显,差异是 1.
1_0000_0000
- 0_FFFF_FFFF
_____________
0_0000_0001