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


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.