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

问题描述

在特定的 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