Structure d'algorithme / de données efficace pour calculer les moyennes mobiles


9

Actuellement, je développe un système LCD graphique pour afficher les températures, les débits, les tensions, la puissance et l'énergie dans un système de pompe à chaleur. L'utilisation d'un écran LCD graphique signifie que la moitié de ma SRAM et ~ 75% de mon flash ont été utilisés par un tampon d'écran et des chaînes.

J'affiche actuellement des chiffres d'énergie min / max / moyenne À minuit, lorsque le chiffre journalier est réinitialisé, le système vérifie si la consommation pour la journée est supérieure ou inférieure au minimum ou au maximum précédent, et enregistre la valeur. La moyenne est calculée en divisant la consommation d'énergie cumulée par le nombre de jours.

Je voudrais afficher la moyenne quotidienne de la dernière semaine et du dernier mois (4 semaines pour plus de simplicité), c'est-à-dire une moyenne mobile. Actuellement, cela implique de maintenir un tableau de valeurs pour les 28 derniers jours et de calculer une moyenne sur l'ensemble du tableau pour les mois et les 7 derniers jours pour les semaines.

Au départ, je faisais cela en utilisant un tableau de flotteurs (car l'énergie est sous la forme "12.12kWh"), mais cela utilisait 28 * 4 octets = 112 octets (5,4% de SRAM). Cela ne me dérange pas d'avoir une seule décimale de résolution, j'ai donc changé en utilisant uint16_t et en multipliant le chiffre par 100. Cela signifie que 12,12 est représenté par 1212 et que je divise par 100 à des fins d'affichage.

La taille du tableau est désormais réduite à 56 octets (bien mieux!).

Il n'y a aucun moyen trivial de réduire le chiffre à un uint8_t que je puisse voir. Je pouvais tolérer la perte d'une décimale ("12,1 kWh" au lieu de "12,12 kWh"), mais la consommation est souvent supérieure à 25,5 kWh (255 étant la valeur la plus élevée représentée par un entier non signé 8 bits). La consommation n'a jamais été inférieure à 10,0 kWh ou supérieure à 35,0 kWh, donc je pense que je pourrais soustraire 10 des chiffres stockés, mais je sais qu'un jour nous dépasserons ces limites.

J'ai ensuite testé du code pour emballer des valeurs 9 bits dans un tableau. Cela donne une plage de 0 à 51,2 kWh et utilise au total 32 octets. Cependant, l'accès à un tableau comme celui-ci est assez lent, surtout lorsque vous devez parcourir toutes les valeurs pour calculer une moyenne.

Ma question est donc - existe-t-il un moyen plus efficace de calculer une moyenne mobile avec trois fenêtres - durée de vie, 28 jours et 7 jours? L'efficacité signifie moins en termes d'utilisation de SRAM, mais sans la pénalité d'un code énorme. Puis-je éviter de stocker toutes les valeurs?


Voulez-vous calculer une moyenne mobile sur des fenêtres spécifiques ou une estimation / approximation de la moyenne suffirait?
asheeshr

Je veux une moyenne mobile sur une fenêtre de 7 jours et 28 jours.
Cybergibbons

vous pouvez utiliser une résolution de 0,2 kWh (diviser et multiplier avec le facteur 5) et vous obtenez toujours une plage de 0 à 51,2 kWh en 8 bits
cliquet freak

Vous pourriez finir par mettre des chaînes et d'autres constantes dans la RAM externe ou Flash externe - voir "Que puis-je faire si je manque de mémoire Flash ou de SRAM?" .
David Cary

Réponses:


2

Si vos données ont un faible écart-type, une méthode consiste à additionner les valeurs sur la fenêtre, puis à soustraire la moyenne de la somme, tout en ajoutant la nouvelle valeur.

Cela fonctionnerait bien s'il n'y avait pas de valeurs aberrantes , ce qui conduirait à l'erreur agrégée tendant à zéro au fil du temps.

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

vous pouvez utiliser une méthode différente, vous gardez la moyenne actuelle et ensuite

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

ce n'est pas une vraie moyenne mobile et a une sémantique différente, mais cela peut néanmoins correspondre à vos besoins

pour une méthode de calcul plus efficace pour votre solution à 9 bits par valeur, vous pouvez conserver les 8 bits les plus élevés des valeurs dans un tableau et séparer les bits les moins significatifs:

uint8_t[28] highbits;
uint32_t lowbits;

pour définir une valeur dont vous avez besoin pour le diviser

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

résultant en 2 décalages un ET et un OU et un non

pour calculer la moyenne, vous pouvez utiliser différentes astuces pour l'accélérer:

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

vous pouvez utiliser un nombre de bits parallèle efficace pour lebitcount()


1
Pouvez-vous expliquer davantage comment cela me permettrait de calculer la moyenne des 7 et 28 jours?
Cybergibbons

J'ai déjà utilisé cette approche pour lisser les valeurs analogiques bruyantes, et c'était certainement assez efficace. Je n'avais cependant pas besoin de beaucoup de précision, car les valeurs résultantes étaient passées par un quantificateur très grossier. Je n'avais pas non plus besoin de moyennes historiques.
Peter Bloomfield

Cela ne permet pas de calculer la moyenne d'une fenêtre spécifique.
asheeshr

@Cybergibbons vous pouvez utiliser différents poids pour approximer la fenêtre afin que les anciennes valeurs deviennent insignifiantes plus tôt ou plus tard, ou garder 7 jours pour la fenêtre de 7 jours et cette moyenne mobile pour la moyenne de 28 jours
ratchet freak

1

Que diriez-vous de ne stocker que la différence par rapport à la valeur précédente? En électronique, il existe un concept similaire appelé convertisseur Delta Sigma, qui est utilisé pour les convertisseurs DA / AD. Elle repose sur le fait que la mesure précédente est raisonnablement proche de la mesure actuelle.


Une autre idée intéressante. Malheureusement, je ne suis pas sûr que la consommation d'énergie sera toujours comme ça, car il s'agit d'un système de pompe à chaleur et un jour pourrait prendre 30 kWh, les 10 kWh suivants. J'ai vraiment besoin de recueillir des données et de voir.
Cybergibbons

0

Pourquoi ne pourriez-vous pas simplement additionner les valeurs dès que vous les avez obtenues. Donc, ce que je veux dire, c'est que vous obtenez la valeur pour le jour 1, vous la divisez par 1 et la stockez et le 1 quelque part. Ensuite, vous multipliez le 1 par la valeur et l'ajoutez à la valeur suivante et les divisez tous les deux par 2.

Faire cette méthode créerait une moyenne mobile avec deux ou trois variables comme je peux le penser. J'écrirais du code mais je suis nouveau sur stackexchange, alors soyez indulgent avec moi.


Je ne comprends pas comment cela fonctionne avec la fenêtre de 7 jours et 28 jours?
Cybergibbons

Gardez une trace des valeurs précédentes et suivantes et continuez à les ajouter et à les soustraire de votre moyenne courante ...
Aditya Somani

1
Alors je suis de retour dans l'état de devoir me souvenir de 27 jours d'histoire, sûrement?
Cybergibbons

J'ai réfléchi et tu as raison. Donc, techniquement, ma réponse est incorrecte. J'y investis un peu plus de temps et de patience. Peut-être quelque chose hors de la boîte. Je vous ferai savoir si je trouve quelque chose. Nous faisons quelque chose comme ça sur mon lieu de travail. Permettez-moi de demander autour. Désolé pour la confusion.
Aditya Somani

0

existe-t-il un moyen plus efficace de calculer une moyenne mobile avec ... 28 jours et 7 jours? ... besoin de se souvenir de 27 jours d'histoire ...?

Vous pouvez vous rapprocher suffisamment en stockant 11 valeurs plutôt que 28 valeurs, peut-être quelque chose comme:

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

En d'autres termes, plutôt que de stocker chaque détail de chaque jour au cours des 27 derniers jours, (a) stocker 7 ou plus de valeurs d'informations quotidiennes détaillées pour les 7 derniers jours, et (b) stocker 4 ou plus "résumées" valeurs des informations totales ou moyennes pour chacune des 4 dernières semaines environ.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.