Ефективний алгоритм / структура даних для обчислення ковзних середніх


9

В даний час я розробляю графічну РК-систему для відображення температури, потоків, напруг, потужності та енергії в системі теплового насоса. Використання графічного РК-екрана означає, що половина моєї SRAM та ~ 75% моєї спалаху використовуються буферним екраном та рядками.

На даний момент я показую мінімальні / середні / середні показники енергії Опівночі, коли добова цифра скидається, система перевіряє, чи споживання за день вище або нижче попереднього мінімуму чи максимуму, і зберігає значення. Середнє значення обчислюється шляхом ділення кумулятивного споживання енергії на кількість днів.

Я хотів би відобразити середньоденні показники за останній тиждень і місяць (4 тижні для простоти), тобто середнє середнє значення. В даний час це включає підтримку масиву значень за останні 28 днів та обчислення середнього значення для всього масиву за місяць та останні 7 днів за тиждень.

Спочатку я робив це за допомогою масиву поплавків (оскільки енергія у формі "12,12 кВт-год"), але для цього було використано 28 * 4 байти = 112 байт (5,4% SRAM). Я не проти мати лише одну десяткову точку роздільної здатності, тому я перейшов на використання uint16_t і помножив цифру на 100. Це означає, що 12.12 представлений як 1212, і я ділю на 100 для відображення.

Розмір масиву тепер знизився до 56 байт (набагато краще!).

Немає тривіального способу зменшити фігуру до uint8_t, яку я бачу. Я міг би терпіти втрату десяткового знаку ("12,1 кВт-год" замість "12,12 кВт-год"), але споживання часто перевищує 25,5 кВт-год (255 - це найвище значення, представлене 8-бітним цілим числом, не підписаним). Споживання ніколи не було нижче 10,0 кВт · год або вище 35,0 кВт · год, тому, можливо, я міг би відняти 10 із збережених цифр, але я знаю, що одного разу ми перевищимо ці межі.

Потім я перевірив код, щоб запакувати 9-бітові значення в масив. Це дає діапазон 0-51,2 кВт-год і загалом використовує 32 байти. Однак отримати доступ до такого масиву досить повільно, особливо коли вам доведеться переглядати всі значення, щоб обчислити середнє значення.

Отже, моє запитання - чи є більш ефективний спосіб розрахунку ковзної середньої з трьома вікнами - термін служби, 28 днів і 7 днів? Ефективність означає менший показник використання SRAM, але без штрафного коду. Чи можу я уникати зберігання всіх значень?


Чи хочете ви обчислити ковзну середню за певними вікнами чи зробила б оцінку / наближення середнього?
asheeshr

Я хочу ковзну середню за вікном 7 та 28 днів.
Кібергіббони

ви можете використовувати роздільну здатність 0,2 кВт * год (ділити та помножувати на коефіцієнт 5), і ви все одно отримаєте діапазон 0-51,2 кВт / год у 8 біт
храповик виродка

Ви можете вставити рядки та інші константи у зовнішню оперативну пам'ять або зовнішній Flash - див. "Що я можу зробити, якщо у мене закінчиться флеш-пам'ять або SRAM?" .
Девід Кері

Відповіді:


2

Якщо ваші дані мають низьке стандартне відхилення, то одним із методів було б підсумовувати значення через вікно, а потім продовжувати віднімати середнє значення від суми, додаючи при цьому нове значення.

Це було б добре, якщо не залишилося людей , що призводить до того, що сукупна помилка має тенденцію до нуля з часом.

//Pseudocode

count=0
while new_reading and count<7:
    sum += new_reading        //Calculate the sum of first 7 values
    count++

while new_reading:            //Loop till new readings available
    avg = sum / 7             //Calculate average
    sum -= avg                //Subtract average from sum
    sum += new_reading        //Add next reading to sum
    print avg

2

ви можете скористатися іншим методом, ви збережете поточне середнє і потім зробите

average = (weight1*average+weight2*new_value)/(weight1+weight2);

це не справжнє ковзаюче середнє значення та має різну семантику, але все ж може відповідати вашим потребам

для більш ефективного методу обчислення для ваших розрядів 9 біт на значення ви можете зберегти 8 найвищих бітів значень у масиві та відокремити найменш значущі біти:

uint8_t[28] highbits;
uint32_t lowbits;

щоб встановити значення, потрібно розділити його

void getvalue(uint8_t index, uint16_t value){
    highbits[index] = value>>1;
    uint32_t flag = (value & 1)<<index;
    highbits|=flag;
    highbits&=~flag;
}

в результаті відбувається 2 зсуви І і АБО, і ні

для обчислення середнього значення ви можете використовувати різні бітові трюки, щоб прискорити його:

uint16_t getAverage(){
    uint16_t sum=0;
    for(uint8_t i=0;i<28;i++){
        sum+=highbits[i];
    }
    sum<<=1;//multiply by 2 after the loop
    sum+=bitcount(lowbits);
    return sum/28;
}

ви можете використовувати ефективний паралельний бітовий рахунок дляbitcount()


1
Чи можете ви пояснити більше, як це дозволило б мені обчислити середній показник за 7 та 28 днів?
Кібергіббони

Раніше я використовував цей підхід для згладжування галасливих аналогових значень, і він, безумовно, був досить ефективним. Мені не потрібно було великої точності, оскільки отримані значення переносилися через дуже грубу кількість. Мені також не потрібні середні історичні показники.
Пітер Блумфілд

Це не дозволяє обчислити середнє значення для конкретного вікна.
asheeshr

@Cybergibbons ви можете використовувати різні ваги, щоб наблизити вікно, щоб старі значення стали незначними раніше чи пізніше, або зберегти 7 днів для 7-денного вікна, а ця ковзна середня для 28-денного середнього
грохот фрик

1

Як щодо зберігання різниці від попереднього значення? В електроніці існує аналогічне поняття під назвою Delta Sigma converter, яке використовується для перетворювачів DA / AD. Він спирається на той факт, що попереднє вимірювання досить близьке до поточного.


Ще одна цікава ідея. На жаль, я не впевнений, що споживання енергії завжди буде таким, оскільки це система теплового насоса, і один день може зайняти 30 кВт-год, наступний 10 кВт-год. Мені справді потрібно зібрати дані та подивитися.
Кібергіббони

0

Чому ви не могли просто додати значення разом, як тільки ви їх отримали. Отже, я маю на увазі, що ти отримуєш значення за 1 день, ділиш його на 1 і зберігаєш його, а 1 - кудись. Потім ви помножите 1 на значення і додаєте його до наступного значення і ділите їх обидва на 2.

Цей спосіб дозволить створити середнє значення з двома або трьома змінними, як я можу придумати. Я б написав якийсь код, але я новачок у stackexchange, тому, будь ласка, мати мене.


Я не розумію, як це стосується 7-денного та 28-денного вікна?
Кібергібони

Слідкуйте за попередніми та наступними значеннями та продовжуйте їх додавати та віднімати від середнього бігу ...
Aditya Somani

1
Тож я знову в стані, коли потрібно пам'ятати 27 днів історії, напевно?
Кібергіббони

Я думав, і ти маєш рацію. Тож технічно моя відповідь є неправильною. Я вкладаю в це ще трохи часу і терпіння. Можливо, щось не вийшло. Я дам вам знати, якщо я щось придумаю. Ми щось подібне робимо на моєму робочому місці. Дозвольте розпитати. Вибачте за плутанину.
Адітя Сомані

0

чи існує більш ефективний спосіб розрахунку ковзної середньої з ... 28 днів і 7 днів? ... потрібно пам'ятати 27 днів історії ...?

Ви можете наблизитись до зберігання 11 значень, а не 28 значень, можливо щось на кшталт:

// untested code
// static variables
uint16_t daily_energy[7]; // perhaps in units of 0.01 kWh ?
uint16_t weekly_energy[4]; // perhaps in units of 0.1 kWh ?

void print_week_status(){
    Serial.print( F("last week's total energy :") );
    Serial.println( weekly_energy[0] );
    int sum = 0;
    for( int i=0; i<4; i++ ){
        sum += weekly_energy[i];
    };
    Serial.print( F("Total energy over last 4 complete weeks :") );
    Serial.println( sum );
    int average_weekly_energy = sum/4;
    int average_daily_energy = average_weekly_energy/7;
    Serial.print( F("Average daily energy over last 4 weeks :") );
    Serial.println( average_daily_energy );
}
void print_day_status(){
    Serial.print( F("Yesterday's energy :") );
    Serial.println( daily_energy[0] );
    Serial.print( F("average daily energy over the last 7 complete days: ") );
    int sum = 0;
    for( int i=0; i<7; i++ ){
        sum += daily_energy[i];
    };
    int average = sum/7;
    Serial.println( average );
}

Іншими словами, замість того, щоб зберігати кожну деталь кожного дня протягом останніх 27 днів, (a) зберігайте 7 або більше значень детальної щоденної інформації за останні 7 або більше днів, а також (b) зберігайте 4 або так "підсумовані" значення загальної або середньої інформації за кожен останній 4 або більше тижнів.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.