Любой более быстрый расчет значения 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
todouble
(что нормально на машине общего назначения/рабочий стол) Еслиdouble
is очень дорого на вашем микрокомпьютере вы могли бы изменить их обратно. - я также добавил
static
, потому что я не знал, был ли код из функции или из цикла. - я сделал функцию
static
чтобы заставить его быть встроен. Если компилятор не выполняет встроенные статические функции, его следует встроить вручную.
Надеюсь, ваш проект предназначен для измерения "больших" напряжений переменного тока ( а не что-то вроде управления двигателем 9v. ) Если это так, то вы можете обмануть, потому что ваша ошибка может быть в допустимых пределах..
сделайте все математики в integer и используйте простую карту поиска для операции sqrt. ( который вы можете предварительно рассчитать при запуске, вы бы только действительно нужно около ~ 600 нечетных значений, если вы делаете 3 фазы..
также напрашивается вопрос у вас на самом деле нужен ВПТ РМС или некоторая другая мера силы ? (например, можете ли вы уйти с помощью простого алгоритма box mean? )
чтобы найти квадратный корень, используйте приведенное ниже Примечание приложения от микрочипа для 8-битных контроллеров
Быстрый Целочисленный Квадратный Корень
что очень быстро и может найти квадратный корень всего за 9 петель
-
деления / умножения на мощность 2
можно сделать, изменив показатель только через операции битовой маски и
+,-
Итак, маска / извлечь показатель tointeger
значение, а затем применить смещение. После этогоadd/sub
значениеlog2(operand)
и закодировать обратно вdouble
стоимостью -
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 двойной точности с плавающей запятой формат