Wydajny algorytm / struktura danych do obliczania średnich kroczących


9

Obecnie opracowuję graficzny system LCD do wyświetlania temperatur, przepływów, napięć, mocy i energii w systemie pompy ciepła. Zastosowanie graficznego wyświetlacza LCD oznacza, że ​​połowa mojej pamięci SRAM i ~ 75% mojej pamięci flash została wykorzystana przez bufor ekranu i łańcuchy.

Obecnie wyświetlam min / maks / średnie wartości energii O północy, kiedy dzienna liczba resetuje się, system sprawdza, czy zużycie w danym dniu jest wyższe lub niższe od poprzedniego minimum lub maksimum i zapisuje wartość. Średnia jest obliczana poprzez podzielenie skumulowanego zużycia energii przez liczbę dni.

Chciałbym wyświetlić średnią dzienną z ostatniego tygodnia i miesiąca (dla uproszczenia 4 tygodnie), tj. Średnią kroczącą. Obecnie obejmuje to utrzymywanie tablicy wartości z ostatnich 28 dni i obliczanie średniej dla całej tablicy dla miesiąca i ostatnich 7 dni dla tygodnia.

Początkowo robiłem to przy użyciu tablicy pływaków (ponieważ energia ma postać „12,12 kWh”), ale wykorzystywało to 28 * 4 bajtów = 112 bajtów (5,4% SRAM). Nie mam nic przeciwko posiadaniu tylko jednego miejsca po przecinku, więc zmieniłem na użycie uint16_t i pomnożenie liczby przez 100. Oznacza to, że 12.12 jest reprezentowane jako 1212 i dzielę przez 100 dla celów wyświetlania.

Rozmiar tablicy spadł do 56 bajtów (znacznie lepiej!).

Nie ma żadnego trywialnego sposobu na zmniejszenie liczby do uint8_t, który widzę. Mogłem tolerować utratę miejsca po przecinku („12,1 kWh” zamiast „12,12 kWh”), ale zużycie jest często wyższe niż 25,5 kWh (255 to najwyższa wartość reprezentowana przez 8-bitową liczbę całkowitą bez znaku). Zużycie nigdy nie było niższe niż 10,0 kWh lub wyższe niż 35,0 kWh, więc możliwe, że mógłbym odjąć 10 od przechowywanych liczb, ale wiem, że pewnego dnia przekroczymy te limity.

Następnie przetestowałem kod, aby spakować wartości 9-bitowe do tablicy. Daje to zakres 0-51,2 kWh i zużywa łącznie 32 bajty. Jednak dostęp do takiej tablicy jest dość powolny, szczególnie gdy trzeba iterować wszystkie wartości, aby obliczyć średnią.

Moje pytanie brzmi zatem - czy istnieje bardziej skuteczny sposób obliczania średniej ruchomej z trzema oknami - czas życia, 28 dni i 7 dni? Wydajność oznacza mniejsze wykorzystanie SRAM, ale bez kary za ogromny kod. Czy mogę uniknąć przechowywania wszystkich wartości?


Czy chcesz obliczyć średnią ruchomą dla określonych okien, czy oszacowanie / przybliżenie średniej byłoby wystarczające?
asheeshr

Chcę średnią ruchomą w oknie 7-dniowym i 28-dniowym.
Cybergibbons,

możesz użyć rozdzielczości 0,2kWh (podziel i pomnóż przez współczynnik 5), a nadal uzyskasz zakres 0-51,2 kWh w 8 bitach
maniak ratchet

Może się okazać, że umieścisz ciągi znaków i inne stałe w zewnętrznej pamięci RAM lub zewnętrznej pamięci Flash - patrz „Co mogę zrobić, jeśli zabraknie mi pamięci Flash lub SRAM?” .
David Cary,

Odpowiedzi:


2

Jeśli dane mają niskie odchylenie standardowe, jedną z metod byłoby zsumowanie wartości w oknie, a następnie odejmowanie średniej od sumy, dodając nową wartość.

Działałoby to dobrze, gdyby nie było wartości odstających , prowadząc w ten sposób do błędu agregacji zmierzającego do zera w czasie.

//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

możesz użyć innej metody, utrzymasz bieżącą średnią, a następnie zrobisz to

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

nie jest to prawdziwa średnia krocząca i ma inną semantykę, ale może jednak pasować do twoich potrzeb

dla bardziej wydajnej metody obliczania dla rozwiązania 9 bitów na wartość możesz przechowywać 8 najwyższych bitów wartości w tablicy i oddzielić najmniej znaczące bity:

uint8_t[28] highbits;
uint32_t lowbits;

aby ustawić wartość, musisz ją podzielić

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

powodując 2 przesunięcia AND i OR i nie

aby obliczyć średnią, możesz użyć różnych sztuczek bitowych, aby ją przyspieszyć:

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

możesz użyć wydajnej równoległej liczby bitów dlabitcount()


1
Czy możesz wyjaśnić więcej, w jaki sposób pozwoliłoby mi to obliczyć średnią z 7 i 28 dni?
Cybergibbons,

Wcześniej stosowałem to podejście do wygładzania hałaśliwych wartości analogowych i było to z pewnością dość skuteczne. Nie potrzebowałem jednak dużej precyzji, ponieważ wartości wynikowe były poddawane bardzo zgrubnemu kwantyzatorowi. Nie potrzebowałem też średnich historycznych.
Peter Bloomfield,

Nie pozwala to na obliczenie średniej dla określonego okna.
asheeshr

@Cybergibbons można użyć różnych wag, aby przybliżyć okno, więc stare wartości stają się nieznaczne wcześniej lub później, lub zachować 7 dni dla okna 7-dniowego i tej średniej ruchomej dla średniej 28-dniowej
maniak zapadkowy

1

A może zachowasz tylko różnicę od poprzedniej wartości? W elektronice istnieje podobna koncepcja zwana konwerterem Delta Sigma, która jest stosowana w przetwornikach DA / AD. Opiera się na fakcie, że poprzedni pomiar jest dość zbliżony do bieżącego.


Kolejny ciekawy pomysł. Niestety nie jestem pewien, czy zużycie energii będzie zawsze takie, ponieważ jest to system pompy ciepła i jeden dzień może zająć 30 kWh, a kolejne 10 kWh. Naprawdę muszę zebrać dane i zobaczyć.
Cybergibbons,

0

Dlaczego nie możesz po prostu dodać wartości razem, jak tylko je uzyskasz. Mam na myśli to, że otrzymujesz wartość z pierwszego dnia, dzielisz ją przez 1 i przechowujesz gdzieś. Następnie mnożymy 1 przez wartość i dodajesz ją do następnej wartości i dzielisz je oba przez 2.

Wykonanie tej metody stworzyłoby średnią kroczącą z dwiema lub trzema zmiennymi, o ile mogę myśleć. Napisałbym trochę kodu, ale jestem nowy w stosie wymiany, więc proszę o wyrozumiałość.


Nie rozumiem, jak to się ma do okna 7-dniowego i 28-dniowego?
Cybergibbons,

Śledź poprzednie i następne wartości oraz dodawaj i odejmuj je od średniej bieżącej ...
Aditya Somani

1
Więc wróciłem do stanu, w którym muszę pamiętać 27 dni historii?
Cybergibbons,

Myślałem i masz rację. To technicznie czyni moją odpowiedź niepoprawną. Inwestuję w to więcej czasu i cierpliwości. Może coś nieszablonowego. Dam ci znać, jeśli coś wymyślę. Często robimy coś takiego w moim miejscu pracy. Pozwól mi zapytać. Przepraszam za zamieszanie.
Aditya Somani

0

czy istnieje bardziej skuteczny sposób obliczania średniej ruchomej z ... 28 dni i 7 dni? ... musisz pamiętać 27 dni historii ...?

Może być wystarczająco blisko, przechowując 11 wartości zamiast 28 wartości, być może coś w stylu:

// 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 );
}

Innymi słowy, zamiast przechowywać każdy szczegół każdego dnia przez ostatnie 27 dni, (a) przechowuj około 7 wartości szczegółowych informacji dziennych z ostatnich 7 dni, a także (b) przechowuj około 4 „podsumowane” wartości całkowitej lub średniej informacji dla każdego z ostatnich 4 lub kilku tygodni.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.