最小化 PLL 频率计算中的整数舍入误差

Minimize integer round-off error in PLL frequency calculation

在特定的 STM32 微控制器上,系统时钟由 PLL 驱动,其频率 F 由以下公式给出:

F := (S/M * (N + K/8192)) / P

S 是 PLL 输入源频率(1 - 64000000,或 64 MHz)。

其他因素MNKP是用户可以修改以校准频率的参数。从我使用的 SDK 中的位掩码来看,每个位掩码的值最多可以限制为 M < 64N < 512K < 8192P < 128

不幸的是,我的目标固件不支持 FPU,因此无法进行浮点运算。相反,我需要使用纯整数算法来计算 F

我已尝试重新排列给定的公式,并牢记 3 个目标:

  1. 展开并分配所有乘数
  2. 最小化每个分母中的因子数
  3. 最小化总执行次数
  4. 如果两个表达式的除法数相同,请选择分母的最大值最小的那个(在前面的段落中确定)

然而,我每次尝试扩展和重新排列表达式都会产生比最初逐字表达的原始公式更大的错误。

为了测试公式的不同排列方式并比较误差,我写了a small Go program you can run online here

是否可以改进此公式,以便在使用整数运算时将错误降至最低?另外,我上面列出的任何目标是否不正确或无用?

我拿了你的程序(你的第一个括号是多余的,所以我去掉了):

 S            K
--- * ( N + ------ )
 M           8192
--------------------
        P

和 运行 通过 QuickMath [1],我得到了这个:

S * (8192 * N + K)
------------------
   8192 * M * P

或者在 Go 代码中:

S * (8192 * N + K) / (8192 * M * P)

所以它确实减少了划分的数量。您可以通过以下方式进一步改进它 拉出较低的常数:

S * (8192 * N + K) / (M * P) >> 13
  1. https://quickmath.com

看着@StevenPerry 的回答,我意识到大部分错误是由我们必须表示的有限精度引起的K/8192。然后,此错误会传播到其他因素和股息中。

但是,推迟该除法通常会导致整数溢出。因此,不幸的是,我找到的解决方案取决于将这些操作数扩展到 64 位。

结果与另一个答案的形式相同,但必须强调将操作数扩展到 64 位是必不可少的。在 Go 源代码中,这看起来像:

var S, N, M, P, K uint32
...
F := uint32(uint64(S) * uint64(8192*N+K) / uint64(8192*M*P))

要查看所有这三个表达式的准确性,run the code yourself on the Go Playground