从时钟“freq”中确定“ticks”的正确方法是什么,同时避免微控制器上的C溢出?

What is the correct way to determine `ticks` from clock `freq` while avoiding overflow in C on a microcontroller?

提问人:SRobertJames 提问时间:9/4/2023 最后编辑:SRobertJames 更新时间:9/11/2023 访问量:99

问:

在 C 中乘除整数以避免溢出的正确方法是什么?我想确定以(Hz)运行的计时器需要多少个计时器才能产生(以毫秒为单位)。这应该是 .ticksfreqdelayticks = freq * delay / 1000

但是,这句话对我来说看起来很危险。如果我们写,我们就会冒着溢出的风险。相反,如果我们编写 ,我们将进入浮点数 - 这是不必要的且容易出错的,尤其是在微控制器上。(freq * delay) / 1000freq * (delay / 1000)

正确的方法是什么?


代码在 Cortex M4 ARM 微控制器上运行。滴答来自 SysTick 计时器。所有变量均为 或 。uint32_tvolatile uint32_t

C 浮点 嵌入式 实时 整数溢出

评论

0赞 Tyler 9/4/2023
在 uC 上,为什么不创建一个以 100 Hz 运行的滴答计时器,使用该中断来增加滴答计数?还是我误会你了?
0赞 Roman Hocke 9/4/2023
您需要考虑最大范围,并使用适当的 int 大小进行乘法。例如,如果 和 都是 16 位,则将它们转换为 32 位整数以进行乘法。如果你能够有常数,你可以利用它来简化计算。freqdelayfreqdelayfreq
1赞 Frankie_C 9/4/2023
为假定的最大计数选择更合适的整数范围,然后使用强制转换进行强制计算。即.此示例应符合您的计算。unsigned int ticks = (unsigned int)((unsigned long long)freq * (unsigned long long)delay / 1000ULL)
0赞 Eric Postpischil 9/4/2023
freq/1000*delay + freq%1000*delay/1000
0赞 Luis Colorado 9/4/2023
您能否至少,如果没有显示实际代码,请指定您如何获得这些滴答声或一些上下文。您是否处于嵌入式上下文中?您正在运行一些高级操作系统(一个具有基于中断的时钟的操作系统),或者您正在运行一个带有系统调用来获取时间的操作系统?您使用什么类型来保存这些时间戳?

答:

1赞 0___________ 9/4/2023 #1

简单地说,想想你可以有多长时间的最大延迟。

如果使用 32 位无符号数字(延迟的负数不是很实用,因为我不知道如何有一个负延迟,这会让我们回到过去)。最大uint32_t为 4,294,967,295,足以延迟超过一个小时。

如果您将计时器设置为每 1/1000 秒递增一次,那么uint32_t就足以延迟几天49.7102696181

如果需要更多,请在计算中使用更大的无符号整数:((uint64_t)freq * delay) / 1000

评论

0赞 SRobertJames 9/4/2023
这可能会给出不正确的结果,因为您可能会在中间计算中溢出。
0赞 0___________ 9/4/2023
@SRobertJames在哪里?顺便说一句,无符号数字不会溢出
1赞 Clifford 9/4/2023
诚然,2^32 毫秒是 >49 天,但这不是问题所在。中间乘法是问题所在。重新排序有助于使其具有确定性(即e. 不依赖于变量),但在 ~72 分钟时仍会发生溢出。在大多数情况下,这可能没问题。
2赞 Clifford 9/4/2023
“无符号数字不溢出”在某种程度上是语义上的诡辩。我称之为溢出,它在很大程度上被理解了。即使你正确地将其称为模n误差,它仍然是一个误差。
3赞 Clifford 9/4/2023 #2

您的建议:

freq * (delay / 1000)

不是浮点运算。这需要:

freq * (delay / 1000.0)

但你是对的,没有必要。更好的是:

(freq / 1000) * delay

只要是 1000 的倍数,就不会造成精度损失,并且至少避免“过早”溢出(即由于操作顺序选择不当而在较低值下发生的溢出)。freq

当然,溢出仍然是可能的,但是此表达式在不诉诸较大类型的情况下给出了可能的最大范围。例如,如果表达式的类型为 ,则最多可以是 2 32/1000 或近72 分钟。delayuint32_tdelay

至关重要的是,重新排序使范围在任何系统中都具有确定性。它不再依赖于 的值 - 它将始终是 72 分钟。delayfreq

如果 72 分钟不够长,您甚至可以考虑较低的分辨率延迟(例如整秒),然后再采用更大的数据类型(这将效率较低,并且可能会增加原子性问题)。长延迟很少需要毫秒级精度,而且在任何情况下,您的时钟在这段时间内都可能不是毫秒级的精度 - 即使是 TCXO 的精度通常为 2ppm,在 500 秒后可能会产生 1ms 的漂移。

0赞 chux - Reinstate Monica 9/11/2023 #3

确定以频率(以 Hz 为单位)运行的计时器需要多少个滴答声才能产生延迟(以毫秒为单位)。这应该是 .ticks = freq * delay / 1000

  • 使用足够宽的数学运算,也许可以防止乘法溢出。uint64_t

  • 在这种情况下,请考虑使用无符号数学以简化舍入。

  • 物理量通常需要一个圆润的答案。四舍五入,在除法前将分母加 1/2。

  • 清楚地识别物理单位。

  • 避免赤裸裸的魔术数字。

uint32_t ticks(uint32_t freq /* Hz */, uint32_t delay /* ms */) {
  #define ms_PER_s 1000
  return (uint32_t) (( (uint64_t)freq*delay + ms_PER_s/2) / ms_PER_s);
}