提问人:SRobertJames 提问时间:9/4/2023 最后编辑:SRobertJames 更新时间:9/11/2023 访问量:99
从时钟“freq”中确定“ticks”的正确方法是什么,同时避免微控制器上的C溢出?
What is the correct way to determine `ticks` from clock `freq` while avoiding overflow in C on a microcontroller?
问:
在 C 中乘除整数以避免溢出的正确方法是什么?我想确定以(Hz)运行的计时器需要多少个计时器才能产生(以毫秒为单位)。这应该是 .ticks
freq
delay
ticks = freq * delay / 1000
但是,这句话对我来说看起来很危险。如果我们写,我们就会冒着溢出的风险。相反,如果我们编写 ,我们将进入浮点数 - 这是不必要的且容易出错的,尤其是在微控制器上。(freq * delay) / 1000
freq * (delay / 1000)
正确的方法是什么?
代码在 Cortex M4 ARM 微控制器上运行。滴答来自 SysTick 计时器。所有变量均为 或 。uint32_t
volatile uint32_t
答:
简单地说,想想你可以有多长时间的最大延迟。
如果使用 32 位无符号数字(延迟的负数不是很实用,因为我不知道如何有一个负延迟,这会让我们回到过去)。最大uint32_t为 4,294,967,295,足以延迟超过一个小时。
如果您将计时器设置为每 1/1000 秒递增一次,那么uint32_t就足以延迟几天49.7102696181
如果需要更多,请在计算中使用更大的无符号整数:((uint64_t)freq * delay) / 1000
评论
您的建议:
freq * (delay / 1000)
不是浮点运算。这需要:
freq * (delay / 1000.0)
但你是对的,没有必要。更好的是:
(freq / 1000) * delay
只要是 1000 的倍数,就不会造成精度损失,并且至少避免“过早”溢出(即由于操作顺序选择不当而在较低值下发生的溢出)。freq
当然,溢出仍然是可能的,但是此表达式在不诉诸较大类型的情况下给出了可能的最大范围。例如,如果表达式的类型为 ,则最多可以是 2 32/1000 或近72 分钟。delay
uint32_t
delay
至关重要的是,重新排序使范围在任何系统中都具有确定性。它不再依赖于 的值 - 它将始终是 72 分钟。delay
freq
如果 72 分钟不够长,您甚至可以考虑较低的分辨率延迟(例如整秒),然后再采用更大的数据类型(这将效率较低,并且可能会增加原子性问题)。长延迟很少需要毫秒级精度,而且在任何情况下,您的时钟在这段时间内都可能不是毫秒级的精度 - 即使是 TCXO 的精度通常为 2ppm,在 500 秒后可能会产生 1ms 的漂移。
确定以频率(以 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);
}
评论
freq
delay
freq
delay
freq
unsigned int ticks = (unsigned int)((unsigned long long)freq * (unsigned long long)delay / 1000ULL)
freq/1000*delay + freq%1000*delay/1000