C中任何更快的RMS值计算?

时间:2015-03-02 10:29:42

标签: c performance algorithm microcontroller

我正在为C中的小型8位微控制器编写软件。部分代码是读取电流互感器(ZCT)的ADC值,然后计算RMS值。流过ZCT的电流是正弦曲线但可能会失真。我的代码如下:

float       adc_value, inst_current;
float       acc_load_current;           // accumulator = (I1*I1 + I2*I2 + ... + In*In)
double      rms_current;

// Calculate the real instantanous value from the ADC reading
inst_current = (adc_value/1024)*2.5;    // 10bit ADC, Voltage ref. 2.5V, so formula is: x=(adc/1024)*2.5V                           

// Update the RMS value with the new instananous value:
// Substract 1 sample from the accumulator (sample size is 512, so divide accumulator by 512 and substract it from the accumulator)
acc_load_current -= (acc_load_current / 512);       
inst_current *= inst_current;          // square the instantanous current
acc_load_current += inst_current;      // Add it to the accumulator

rms_current = (acc_load_current / 512);  // Get the mean square value. (sample size is 512)
rms_current = sqrt(rms_current);         // Get RMS value

// Now the < rms_current > is the real RMS current

但是,它有许多浮点计算。这给我的小型MCU增加了很大的负担。我发现sqrt()函数在我的编译器中不起作用。

是否有任何可以更快运行的代码?

5 个答案:

答案 0 :(得分:6)

由于您的平方和值acc_load_current在迭代之间变化不大,因此其平方根几乎不变。 Newton-Raphson sqrt()函数通常仅在几次迭代中收敛。通过每步使用一次迭代,计算被抹掉。

static double one_step_newton_raphson_sqrt(double val, double hint)
{
double probe;
if (hint <= 0) return val /2;
probe = val / hint;
return (probe+hint) /2;
}

static double      acc_load_current = 0.0;           // accumulator = (I1*I1 + I2*I2 + ... + In*In)
static double      rms_current = 1.0;
float       adc_value, inst_current;
double      tmp_rms_current;

// Calculate the real instantanous value from the ADC reading
inst_current = (adc_value/1024)*2.5;    // 10bit ADC, Voltage ref. 2.5V, so formula is: x=(adc/1024)*2.5V                           

// Update the RMS value with the new instananous value:
// Substract 1 sample from the accumulator (sample size is 512, so divide accumulator by 512 and substract it from the accumulator)
acc_load_current -= (acc_load_current / 512);
inst_current *= inst_current;          // square the instantanous current
acc_load_current += inst_current;      // Add it to the accumulator

tmp_rms_current = (acc_load_current / 512);
rms_current = one_step_newton_raphson_sqrt(tmp_rms_current, rms_current);         // Polish RMS value

// Now the <rms_current> is the APPROXIMATE RMS current

注意:

  • 我将部分数据类型从float更改为double(这在通用机器/桌面上是正常的)如果double在您的微型计算机上非常昂贵,您可以更改它们回来。
  • 我还添加了static,因为我不知道代码是来自函数还是来自循环。
  • 我创建了函数static以强制它内联。如果编译器没有内联静态函数,则应手动内联。

答案 1 :(得分:4)

如果您需要在缺少FPU的处理器上加快速度,那么最好 赌注是用固定点替换浮点计算。结合 这与joop的建议(一个Newton-Raphson sqrt)你得到了 像这样的东西:

#define INITIAL 512  /* Initial value of the filter memory. */
#define SAMPLES 512

uint16_t rms_filter(uint16_t sample)
{
    static uint16_t rms = INITIAL;
    static uint32_t sum_squares = 1UL * SAMPLES * INITIAL * INITIAL;

    sum_squares -= sum_squares / SAMPLES;
    sum_squares += (uint32_t) sample * sample;
    if (rms == 0) rms = 1;    /* do not divide by zero */
    rms = (rms + sum_squares / SAMPLES / rms) / 2;
    return rms;
}

只需通过此过滤器运行原始ADC样本。你可以添加一些 在这里和那里进行位移以获得更高的分辨率,但你必须这样做 小心不要溢出你的变量。我怀疑你真的需要 额外的决议。

滤波器的输出与其输入位于同一单位。在这种情况下, 它是你的ADC的单位: 2.5 V / 1024≈2.44毫伏。如果你能保持 在后续计算中,您将通过避免来节省周期 不必要的转换。如果你真的需要以伏特为单位的值(它 可能是I / O要求),那么你将不得不转换为浮动 点。如果你想要毫伏,你可以保持整数领域:

uint16_t rms_in_mV = rms_filter(raw_sample) * 160000UL >> 16;

答案 2 :(得分:0)

希望您的项目用于测量&#34; Big&#34;交流电压(而不是9v电机控制。)如果情况确实如此,那么你可以作弊,因为你的错误可以在可接受的范围内。

以整数表示所有数学,并使用简单的查找映射进行sqrt操作。 (您可以在启动时预先计算,如果您正在进行3阶段,那么 REALLY 只需要约600个奇数值。

这也引出了一个问题,你 ACTUALLY 需要VAC RMS还是其他一些功率测量? (例如,你可以用一个简单的盒子来逃避algorythm?)

答案 3 :(得分:0)

  1. 除法/乘以2的幂

    可以通过仅通过位掩码操作和+,-更改指数来完成,因此屏蔽/提取指数integer值然后应用的偏置即可。之后add/sublog2(operand)并编码回double

  2. <强> SQRT

    应该多快和准确?您可以在固定点上使用二进制搜索或使用sqrt(x)=pow(x,0.5)=exp2(0.5*log2(x))。再次在固定点上,它很容易实现。您可以通过采用尾数并将其移位到您使用的值周围的某个已知指数来暂时使其成为固定点的两倍+处理偏移量,如果有足够的位,则可以2^0 ...

    计算sqrt,然后存储回double。如果您不需要太大的精度,那么您可以保持操作数指数并直接在尾数上进行二进制搜索。

  3. [edit1] C ++中的二进制搜索

    //---------------------------------------------------------------------------
    double f64_sqrt(double x)
        {
        const int h=1;      // may be platform dependent MSB/LSB order
        const int l=0;
        DWORD b;            // bit mask
        int e;              // exponent
        union               // semi result
            {
            double f;       // 64bit floating point
            DWORD u[2];     // 2x32 bit uint
            } y;
        // fabs
        y.f=x;
        y.u[h]&=0x7FFFFFFF; x=y.f;
        // set safe exponent (~ abs half)
        e=((y.u[h]>>20)&0x07FF)-1023;
        e/=2;               // here can use bit shift with copying MSB ...
        y.u[h]=((e+1023)&0x07FF)<<20;
        // mantisa=0
        y.u[l] =0x00000000;
        y.u[h]&=0xFFF00000;
        // correct exponent
        if (y.f*y.f>x) { e--; y.u[h]=((e+1023)&0x07FF)<<20; }
        // binary search
        for (b =0x00080000;b;b>>=1) { y.u[h]|=b; if (y.f*y.f>x) y.u[h]^=b; }
        for (b =0x80000000;b;b>>=1) { y.u[l]|=b; if (y.f*y.f>x) y.u[l]^=b; }
        return y.f;
        }
    //---------------------------------------------------------------------------
    

    它返回sqrt(abs(x))结果匹配&#34; math.h&#34;我的实现 C ++ IDE(BDS2006 Turbo C ++)。指数从x值的一半开始,如果需要,则对值x>1.0更正为1。其余的很明显

    代码在 C ++ 中,但仍然没有经过优化,可以肯定地改进...如果您的平台不知道DWORD使用unsigned int。如果您的平台不支持32位整数类型,则将其块化为4 x 16位值或8 x 8位值。如果您有64位,则使用单个值来加速该过程

    不要忘记将指数也处理为11位....因此对于8位寄存器仅使用2 ...如果将乘法和mantissas作为整数进行乘法运算,则可以避免 FPU 运算。乘法本身也是累积的,因此您可以使用先前的子结果。

    <强> [注释]

    对于位位置,请参阅wiki double precision floating point format

答案 4 :(得分:0)

要查找平方根,请使用微芯片中的以下应用说明获取8位控制器

Fast Integer Square Root

速度非常快,只能在9个循环中找到平方根