Algoritmo / struttura dati efficienti per calcolare le medie mobili


9

Attualmente sto sviluppando un sistema LCD grafico per visualizzare temperature, flussi, tensioni, potenza ed energia in un sistema a pompa di calore. L'uso di un LCD grafico significa che metà del mio SRAM e ~ 75% del mio flash sono stati utilizzati da un buffer dello schermo e da stringhe.

Attualmente sto visualizzando i dati min / max / medi per l'energia A mezzanotte quando la cifra giornaliera viene ripristinata, il sistema controlla se il consumo per il giorno è superiore o inferiore al minimo o massimo precedente e memorizza il valore. La media viene calcolata dividendo il consumo cumulativo di energia per il numero di giorni.

Vorrei visualizzare la media giornaliera nell'ultima settimana e mese (4 settimane per semplicità), ovvero una media mobile. Attualmente ciò comporta il mantenimento di un array di valori per gli ultimi 28 giorni e il calcolo di una media sull'intero array per mensili e gli ultimi 7 giorni per settimanali.

Inizialmente stavo facendo questo usando una matrice di float (dato che l'energia è nella forma "12.12kWh"), ma questo stava usando 28 * 4 byte = 112 byte (5,4% di SRAM). Non mi dispiace avere un solo punto decimale di risoluzione, quindi ho cambiato usando uint16_t e moltiplicando la cifra per 100. Ciò significa che 12.12 è rappresentato come 1212 e divido per 100 a scopo di visualizzazione.

La dimensione dell'array è ora ridotta a 56 byte (molto meglio!).

Non c'è modo banale per ridurre la cifra a uint8_t che posso vedere. Potrei tollerare la perdita di un decimale ("12,1 kWh" invece di "12,12 kWh"), ma il consumo è spesso superiore a 25,5 kWh (255 è il valore più alto rappresentato da un numero intero senza segno a 8 bit). Il consumo non è mai stato inferiore a 10,0 kWh o superiore a 35,0 kWh, quindi è possibile che io possa sottrarre 10 dalle cifre memorizzate, ma so che un giorno supereremo questi limiti.

Ho quindi testato il codice per comprimere i valori a 9 bit in un array. Ciò fornisce un intervallo compreso tra 0 e 51,2 kWh e utilizza 32 byte in totale. Tuttavia, l'accesso a un array come questo è piuttosto lento, specialmente quando devi calcolare tutti i valori per calcolare una media.

Quindi la mia domanda è: esiste un modo più efficiente di calcolare una media mobile con tre finestre: durata, 28 giorni e 7 giorni? Efficienza significa minore in termini di utilizzo di SRAM, ma senza la penalità di un codice enorme. Posso evitare di memorizzare tutti i valori?


Vuoi calcolare una media mobile su finestre specifiche o farebbe una stima / approssimazione della media?
asheeshr,

Voglio una media mobile su una finestra di 7 giorni e 28 giorni.
Cybergibbons

potresti usare una risoluzione di 0.2kWh (dividere e moltiplicare per il fattore 5) e ottenere comunque un intervallo 0-51.2kWh in 8 bit
maniaco del cricchetto

Potresti finire per mettere stringhe e altre costanti nella RAM esterna o nella Flash esterna - vedi "Cosa posso fare se esaurisco la memoria Flash o SRAM?" .
David Cary,

Risposte:


2

Se i tuoi dati hanno una deviazione standard bassa, un metodo sarebbe quello di sommare i valori sulla finestra e quindi continuare a sottrarre la media dalla somma, aggiungendo il nuovo valore.

Funzionerebbe bene se non ci sono valori anomali , portando così all'errore aggregato che tende a zero nel tempo.

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

puoi usare un metodo diverso, mantenere la media corrente e poi fare

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

non è una vera media mobile e ha una semantica diversa, ma può comunque soddisfare le tue esigenze

per un metodo di calcolo più efficiente per la soluzione a 9 bit per valore, è possibile mantenere gli 8 bit più alti dei valori in un array e separare i bit meno significativi:

uint8_t[28] highbits;
uint32_t lowbits;

per impostare un valore è necessario dividerlo

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

con conseguente 2 turni un AND e un OR e un no

per calcolare la media puoi usare vari trucchi per accelerarla:

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

è possibile utilizzare un bitcount parallelo efficiente perbitcount()


1
Puoi spiegarci di più come questo mi consentirebbe di calcolare la media di 7 e 28 giorni?
Cybergibbons

Ho usato questo approccio per appianare i rumorosi valori analogici in precedenza ed è stato sicuramente abbastanza efficace. Non avevo bisogno di molta precisione, poiché i valori risultanti venivano sottoposti a un quantizzatore molto approssimativo. Inoltre non avevo bisogno di medie storiche.
Peter Bloomfield,

Ciò non consente di calcolare la media per una finestra specifica.
asheeshr

@Cybergibbons puoi usare pesi diversi per approssimare la finestra in modo che i vecchi valori diventino insignificanti prima o poi, oppure mantieni 7 giorni per la finestra di 7 giorni e questa media mobile per la media di 28 giorni
maniaco del cricchetto

1

Che ne dici di memorizzare solo la differenza rispetto al valore precedente? Nell'elettronica esiste un concetto simile chiamato convertitore Delta Sigma, che viene utilizzato per i convertitori DA / AD. Si basa sul fatto che la misurazione precedente è ragionevolmente vicina a quella attuale.


Un'altra idea interessante. Sfortunatamente non sono sicuro che il consumo di energia sarà sempre così, dato che si tratta di un sistema a pompa di calore e un giorno potrebbe richiedere 30kWh, il prossimo 10kWh. Ho davvero bisogno di raccogliere dati e vedere.
Cybergibbons

0

Perché non puoi semplicemente sommare i valori non appena li ottieni. Quindi voglio dire che ottieni il valore per il giorno 1, lo dividi per 1 e lo memorizzi e 1 da qualche parte. Quindi moltiplichi 1 per il valore e lo aggiungi al valore successivo e li dividi entrambi per 2.

Fare questo metodo creerebbe una media mobile con due o tre variabili come posso pensare. Scriverei un po 'di codice ma sono nuovo su stackexchange, quindi per favore abbi pazienza.


Non capisco come si occupa della finestra di 7 e 28 giorni?
Cybergibbons

Tieni traccia dei valori precedenti e successivi e continua ad aggiungere e sottrarli dalla tua media corrente ...
Aditya Somani

1
Quindi sono tornato nello stato di bisogno di ricordare 27 giorni di storia, sicuramente?
Cybergibbons

Ho pensato e hai ragione. In modo che tecnicamente rende la mia risposta errata. Sto investendo un po 'più di tempo e pazienza in esso. Forse qualcosa fuori dagli schemi. Ti farò sapere se mi viene in mente qualcosa. Facciamo qualcosa del genere molto sul mio posto di lavoro. Fammi chiedere in giro. Mi dispiace per la confusione.
Aditya Somani,

0

esiste un modo più efficiente per calcolare una media mobile con ... 28 giorni e 7 giorni? ... bisogno di ricordare 27 giorni di storia ...?

Potresti avvicinarti abbastanza memorizzando 11 valori anziché 28 valori, forse qualcosa del tipo:

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

In altre parole, piuttosto che memorizzare ogni dettaglio di ogni giorno negli ultimi 27 giorni, (a) memorizzare 7 o più valori di informazioni giornaliere dettagliate per gli ultimi 7 giorni circa, e anche (b) memorizzare 4 o così "riassunti" valori delle informazioni totali o medie per ciascuna delle ultime 4 settimane circa.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.