Любой более быстрый расчет значения RMS в C?

Я пишу программное обеспечение для небольшого 8-битного микроконтроллера в C. часть кода состоит в том, чтобы прочитать значение АЦП трансформатора тока (ZCT), а затем вычислить значение 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 ответов


когда вам нужно получить более быстро на процессоре которому не хватает FPU, ваше самое лучшее ставка заключается в замене расчетов с плавающей запятой на фиксированную точку. Объединять это с предложением joop (один ньютон-Рафсон sqrt), и вы получите что-то вроде этого:--3-->

#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;
}

просто запустите необработанные образцы АЦП через этот фильтр. Вы можете добавить несколько бит-сдвиги здесь и там, чтобы получить больше разрешения, но вы должны быть осторожно, не переполняйте переменные. И я сомневаюсь, что тебе действительно нужно ... дополнительный разрешение.

выход фильтра находится в том же блоке, что и его вход. В этом деле, это блок вашего АЦП: 2.5 в / 1024 ≈ 2.44 МВ. Если вы можете держать этот блок в последующих расчетах, вы сэкономите время, избегая ненужные преобразования. Если вам действительно нужно значение, чтобы быть в вольтах (это может быть требование ввода/вывода), то вам придется преобразовать в плавающий точка. Если вы хотите милливольты, вы можете остаться в целочисленной области:

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

так как ваше значение суммы квадратов acc_load_current не сильно меняется между итерациями, его квадратный корень будет почти постоянным. Функция 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 to double (что нормально на машине общего назначения/рабочий стол) Если double is очень дорого на вашем микрокомпьютере вы могли бы изменить их обратно.
  • я также добавил static, потому что я не знал, был ли код из функции или из цикла.
  • я сделал функцию static чтобы заставить его быть встроен. Если компилятор не выполняет встроенные статические функции, его следует встроить вручную.

Надеюсь, ваш проект предназначен для измерения "больших" напряжений переменного тока ( а не что-то вроде управления двигателем 9v. ) Если это так, то вы можете обмануть, потому что ваша ошибка может быть в допустимых пределах..

сделайте все математики в integer и используйте простую карту поиска для операции sqrt. ( который вы можете предварительно рассчитать при запуске, вы бы только действительно нужно около ~ 600 нечетных значений, если вы делаете 3 фазы..

также напрашивается вопрос у вас на самом деле нужен ВПТ РМС или некоторая другая мера силы ? (например, можете ли вы уйти с помощью простого алгоритма box mean? )


чтобы найти квадратный корень, используйте приведенное ниже Примечание приложения от микрочипа для 8-битных контроллеров

Быстрый Целочисленный Квадратный Корень

что очень быстро и может найти квадратный корень всего за 9 петель


  1. деления / умножения на мощность 2

    можно сделать, изменив показатель только через операции битовой маски и +,- Итак, маска / извлечь показатель to integer значение, а затем применить смещение. После этого add/sub значение log2(operand) и закодировать обратно в double стоимостью

  2. sqrt

    как быстро и точно она должна быть? Вы можете использовать двоичный поиск на фиксированной точке или использовать sqrt(x)=pow(x,0.5)=exp2(0.5*log2 (x)). Опять же, на фиксированной точке это довольно легко реализовать. Вы можете временно сделать двойную фиксированную точку, взяв мантиссу и сдвинув ее на некоторый известный показатель вокруг ваших используемых значений + обработайте смещение или 2^0 если у вас достаточно биты ...

    вычислить sqrt а затем хранить обратно в double. Если вам не нужна слишком большая точность, вы можете остаться на экспоненте операнда и выполняйте бинарный поиск только на мантиссе.

[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)) результаты матча "математика.h " реализация из шахты C++ IDE (BDS2006 Turbo C++). Экспонента начинается с половины x значение и корректируется на 1 для значений x>1.0 при необходимости. Остальное довольно очевидно

код C++ но он все еще не оптимизирован можно, конечно, улучшить ... Если ваша платформа не знает DWORD использовать unsigned int вместо. Если ваша платформа не поддерживает 32-разрядные целочисленные типы, то разделите ее на 4 x 16-разрядные значения или 8 x 8-разрядные значения. Если у вас есть 64 бит, используйте одно значение для ускорения процесса

не забудьте обработать показатель также как 11 бит .... поэтому для 8-битных регистров используйте только 2 ... The FPU операции можно избежать, если вы умножаете и сравниваете только мантиссы как целые числа. Также умножение само по себе является кумулятивным, поэтому вы можете использовать предыдущий суб-результат.

[Примечания]

для битовых позиций см. wiki двойной точности с плавающей запятой формат