【问题标题】:Handling Decimals on Embedded C在嵌入式 C 上处理小数
【发布时间】:2015-08-02 23:49:57
【问题描述】:

我的代码在下面,我想问一下解决小数点后 4 位的数字(除法、乘法、对数、指数)的最佳方法是什么?我使用 PIC16F1789 作为我的设备。

float sensorValue;
float sensorAverage;

void main(){
    //Get an average data by testing 100 times
    for(int x = 0; x < 100; x++){
        // Get the total sum of all 100 data
        sensorValue = (sensorValue + ADC_GetConversion(SENSOR));
    }

    // Get the average
    sensorAverage = sensorValue/100.0;
}

【问题讨论】:

  • 没有浮点硬件的小型嵌入式系统的编译器通常具有处理浮点运算的特殊功能,因此您仍然可以使用正常的算术运算符。虽然性能可能不是很好。我建议您搜索并阅读有关 定点算术 的信息,这在较小的系统上更常见,尤其是在您只需要少量固定小数的情况下。它需要更多代码,但通常比浮点算法更有效。
  • 感谢您的帮助 Joachim Pileborg。我会读一些关于定点算法的资料。
  • 定点的基本是“Q格式”。您可以执行int sensorAverageQ14 = sensorValue*16384/100;,这与您的浮点值相同,但要大 2^14 倍。每次使用时都需要牢记这个 Q=14 值。
  • 还要注意微控制器通常没有硬件乘法器,也没有除法指令。乘法和除法通常在 50 多个时钟周期范围内,加上函数调用开销,因为它们都是运行时库函数。如果您接近实时截止日期,最好现在考虑使用 DSP。
  • 在上面的例子中,四位小数是不必要的,因为它是一百个整数的和,精度只有两位小数。

标签: c embedded microcontroller pic floating


【解决方案1】:

通常,在 MCU 上,浮点 类型的处理成本(时钟、代码)比整数类型更高。虽然对于具有硬件浮点单元的设备来说这通常是正确的,但对于没有硬件浮点单元的设备(如 PIC16/18 控制器)来说,它成为重要信息。这些必须模拟软件中的所有浮点运算。每次加法很容易花费超过 100 个时钟周期(乘法需要更多),并且会使代码膨胀。

因此,最好避免float(更不用说在此类系统上使用double

对于您的示例,ADC 无论如何都会返回整数类型,因此可以纯粹使用整数类型进行求和。您只需要确保求和不会溢出,因此它必须为您的代码保留 ~100 *。

最后,要计算平均值,您可以将整数除以迭代次数(四舍五入到零),或者 - 更好的是 - 应用简单的“四舍五入”:

#define NUMBER_OF_ITERATIONS 100

sensorAverage = (sensorValue + NUMBER_OF_ITERATIONS / 2) / NUMBER_OF_ITERATIONS;

如果您真的想加快代码速度,请将 NUMBER_OF_ITERATIONS 设置为 2 的幂(此处为 64 或 128),前提是您的代码可以容忍这一点。

最后:不仅要得到除法的整数部分,还可以将总和 (sensoreValue) 视为小数值。对于给定的 100 次迭代,您可以将其视为小数:转换为字符串时,只需打印低 2 位左侧的小数点。当你除以 100 时,小数部分的有效数字不会超过两位。如果你真的需要 4 位数字,例如对于其他操作,您可以将总和乘以 100(实际上是 10000,但您已经通过循环将其乘以 100)。

这称为十进制定点。如上所述,更快的处理(用移位代替乘法)将是使用二进制定点。

在 PIC16 上,我强烈建议考虑使用二进制分数,因为在这个平台上乘法和除法非常昂贵。一般来说,它们不太适合信号处理。如果您需要维持一些性能,ARM Cortex-M0 或 M4 将是更好的选择 - 价格相近。

【讨论】:

  • “finally”注释应该是首选解决方案 - 在缺少硬件除法操作的 PIC16 上除法成本很高。简单把sensorAverage的单位看成百分之一;可能最好在名称中注明,但 sensorAveragex100。也就是说,100 的乘数将给出两位小数,而不是三位。
  • @Clifford:当然,您对两位数是正确的 - 已编辑。我所说的“终于”是指最后的处理步骤,而不是“哦,你可能会想到……”。我希望我的回答的第一段足以说明这一点。
  • 100 次迭代,求和后乘以 100 只会添加两个零位,根本不会增加精度。
  • @Clifford:请仔细阅读正文。我已经说过不会再有重要的数字了。但是当 OP 要求更多时,我将其添加为一个选项。因此迭代后的乘法得到小数点的共同位置。如果使用的整数类型允许,使用更多的小数位数并不少见。
  • @elvinguitar:你应该扩展你的问题!这不是讨论论坛,而是一个问答网站 - 没有线程的原因。哦,如果答案有帮助,您可能会接受和/或投票。
【解决方案2】:

在您的示例中,完全避免非整数表示是微不足道的,但是为了更一般地回答您的问题,符合 ISO 标准的编译器将支持浮点算术和库,但出于性能和代码大小的原因,您可能希望避免这种情况.

定点算术是您可能需要的。对于简单的计算,可以使用针对固定点的特殊方法,例如,您将示例中的 sensorAverage 的单位视为百分之 (1/100),并完全避免昂贵的除法。但是,如果您想执行完整的数学库操作,那么更好的方法是使用定点库。 Anthony Williams 在Optimizing Applications with Fixed-Point Arithmetic 中介绍了一个这样的库。代码是 C++ 并且 PIC16 可能缺少一个像样的 C++ 编译器,但是这些方法可以不太优雅地移植到 C 中。它还使用巨大的 64 位定点 36Q28 格式,这在 PIC16 上会很昂贵而且速度很慢;您可能想调整它以使用 16Q16。

【讨论】:

    【解决方案3】:

    如果您真的关心性能,请坚持整数算术,尝试使样本数平均为 2 的幂,以便可以通过位移进行除法,但如果它不是 2 的幂假设为 100(正如 Olaf 指出的定点),您还可以使用位移和加法:How can I multiply and divide using only bit shifting and adding?

    如果您不关心性能并且仍然想使用浮点数(您已经收到警告,这在 PIC16 中可能不是很快,并且可能会使用大量闪存),math.h 具有以下功能:@987654322 @ 包括指数:pow(base,exp) and logarithms* only base 2, base 10 和 base e, 对于任意base使用base logarithmic property的变化

    【讨论】:

    • 如果合理的话,任何最近的编译器都会将与某些常量的乘法转换为内联移位/加法。看看 gcc,你可能会感到惊讶。 (对不起,没有链接,我前段时间读过这个,但找不到链接)
    • 好的,谢谢,很高兴知道我不必再担心了
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-06
    • 1970-01-01
    • 2014-08-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多