Moyenne glissante

Bonjour à tous,

Je cherche à réaliser une moyenne glissante d’une accélération sur 10 mesures. Pour cela, je crée un tableau de 10 valeurs que j’initialise dans mon setup. Ensuite, à chaque appel de ma fonction moyenne_glissante, je “décale” les éléments du tableau vers le bas, et je rempli la dernière case par la dernière accélération mesurée (voir code ci-dessous).

Problème : j’obtiens des résultats faux. Mon accélération moyenne reste autour de 1, même si les dernières valeurs de l’accélération non filtrée sont à plus de 8. Ou est l’erreur ?

Merci d’avance.

// Importation des librairies utiles :

#include "Wire.h" // Librairie Wire Arduino
#include "I2Cdev.h" // Librairie permettant les échanges par protocole I2C
#include "MPU6050.h" // Librairie propre à l'accéléromètre
#include "math.h" // Contient des fonctions mathématiques



// Definition des variables :

MPU6050 accel; // nom de l'accéléromètre
int16_t ax, ay, az, gx, gy, gz; // mesure brute des accélérations (sur 16bits)
int buzzer = 2; // Borne de sortie = borne 2
float alpha; // angle (x,g)
float beta; // angle (y,g)
float gamma; // angle (z,g)

float dt = 0.05; // intervalle de temps (en secondes) pour l'intégration
float accelx, accely, accelz; // accélérations calculées
float accelVerticale; // accélération verticale à t
float vitesseVerticale = 0; // vitesse verticale (obtenue par intégration)

float fc = 10; // fréquence d'échantillonnage en Hz (passe-bas)
float Tc = 0.1; // période d'échantillonage
float af = 0; // accélération filtrée

float ai[10] ; // tableau des valeurs de az pour la moyenne glissante



//=========================================================== Initialisation ==============================================================



void setup() {
  Wire.begin(); // débute la communication I2C
  pinMode(buzzer, OUTPUT); // le buzzer est la sortie du montage
  
  Serial.begin(9600); // permet l'affichage des données lues
  Serial.println("Initialisation I2C...");
  accel.initialize(); // initialisation I2C
  Serial.println("Test de la connexion du dispositif...");
  Serial.println(accel.testConnection() ? "Echec de la connexion" : "Connexion réussie"); // test de la connexion à l'accéléromètre
  
  Serial.println("CLEARDATA");
  Serial.println("LABEL,time,az,azf");

    // L'objectif est ici de tester si le variomètre est vertical, cela avec une erreur de 4%.
    // On utilise pour cela la mesure de la gravité terrestre : si l'accélération suivant
    // l'axe z est supérieure à 0.96*9,81, on peut initialiser l'angle gamma à 0, les deux
    // autres à pi.
  accel.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
  while (abs(az) < 0.96*16384) {
    Serial.println("Calibrage...");
    accel.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
  }
  alpha=beta=PI;
  gamma=0;

  for (int i=0; i<=9; i++){
    ai[i] = acceleration();
    delay(50);
  }
}



//=================================================== Orientation + accélération brute =================================================


void integrationAngles(float *angles){
    // Ici, on va mesurer les angles des axes x,y,z de l'accéléromètre avec l'axe vertical.
    // Pour cela, on intègre les vitesses angulaires délivrées par le gyroscope.
    // Les angles renvoyés sont en rad.
  accel.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
  alpha = alpha + (float(gx)*dt/131)*2*PI/360;
  beta = beta + (float(gy)*dt/131)*2*PI/360;
  gamma = gamma + (float(gy)*dt/131)*2*PI/360;
  angles[0] = alpha;
  angles[1] = beta;
  angles[2] = gamma;
}


float acceleration(){
    // Renvoie l'accélération à un instant t
  float angles[3];
  accel.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
  integrationAngles(angles);
  accelx = float(ax)*cos(angles[0])*9.81/16384;
  accely = float(ay)*cos(angles[1])*9.81/16384;
  accelz = float(az)*cos(angles[2])*9.81/16384;
  accelVerticale = sqrt(pow(accelx,2) + pow(accely,2) + pow(accelz,2)) - 9.81;
  return(accelVerticale);
}

//=========================================================== Filtrage de l'accélération =======================================================

// ===== Filtre passe-bas :

float passe_bas(float a){
  af = (dt/Tc)*(a-af)+af;
  return(af);
}

// ===== Moyenne glissante :

float moyenne_glissante(float a){
    // moyenne glissante sur 10 valeurs, sans pondération
  float somme = 0;
  for (int i=1; i<=9; i++){
    ai[i-1] = ai[i];
    somme += ai[i-1];
  }
  ai[10] = a;
  somme += ai[10];
  return(somme/10);
}



//=================================================================== Intégration ==============================================================

// ===== Méthode des trapèzes :

float trapeze(){
    //Renvoie la vitesse verticale par intégration de l'accélération (méthode des trapèzes)
  float accel1 = acceleration();
  vitesseVerticale = vitesseVerticale + accel1*dt;
  delay(50); // attente de 0.05 s
  return(vitesseVerticale);
}

// ===== 

void loop(){
  float af = acceleration();
  Serial.print("DATA,TIME,");
  Serial.print(af);
  Serial.print(",");
  Serial.println(moyenne_glissante(af));
  //delay(50);
}

Dans ta moyenne glissante :

float moyenne_glissante(float a){
    // moyenne glissante sur 10 valeurs, sans pondération
  float somme = 0;
  for (int i=1; i<=9; i++){
    ai[i-1] = ai[i];
    somme += ai[i-1];
  }
  ai[10] = a;
  somme += ai[10];
  return(somme/10);
}

tu fais

  • ai[0] ← ai[1]

  • ai[1] ← ai[2]

  • ai[8] ← ai[9]
    et enfin:

  • ai[10] ← a
    La ai[9] ne change jamais, donc au bout d’un moment il est dupliqué dans tout ton tableau et la moyenne se fait sur 9 copies de cette valeur et la 10ème valeur.

Il faut changer :

  ai[10] = a;
  somme += ai[10];

en

  ai[9] = a;
  somme += ai[9];

et ça devrait marcher !

J'aurais dû réfléchir un peu plus longtemps... merci :slight_smile:

Et puis au lieu de refaire la somme de tous les éléments à chaque fois, il est plus économique de soustraire de la somme la valeur sortante et d'y ajouter la valeur entrante. On ne fait plus qu'une somme et une soustraction au lieu de 10 sommes.

En prime, ça permet d'économiser un tableau et des décalages.

Effectivement, c'est plus économique. J'implémente ça tout de suite.
Merci pour vos conseils.

Bonjour

Une solution alternative qui donne souvent des résultats similaires (ce qui doit être le cas ici sur un accéléromètre) et qui économise le tableau en ram :

float filtre_glissant(float a){
  static float valeur = 0.0;
  valeur = 0.9 * valeur + 0.1 * a;
  return valeur;
}

Bonjour,

La solution de Bricoleau est en fait un filtre passe-bas du 1er ordre de la forme:

s=K*s+(1-K)*e avec K<=1 (s = sortie, e= entrée).
Initialisé avec s=e

la fréquence de coupure de ce filtre dépend de K et de la période d’échantillonnage des valeurs.

Christian_R:
En prime, ça permet d’économiser un tableau et des décalages.

bin non !
pour pouvoir soustraire l’élément le plus ancien, il fait garder les 10 éléments … et faire le décalage à, chaque fois. On n’y coupe pas.
Par contre on remplace 9 additions par 1 addition et 1 soustraction.

Le tableau est bien indispensable.

En revanche les décalages ne sont pas nécessaires si on utilise astucieusement un index de tableau tournant, en laissant à sa place chaque mesure (principe du Premier Rentré, Premier Sorti (First In First Out = FIFO)

Les mesures n°1 à n°10 sont rangées successivement sur les cases mémoire de tableau T1 à T10 (moyenne de 1 à 10).

La mesure n°11 remplace T1 , la moyenne se calcule donc avec n°11 et de n°2 à n°10 (donc 2 à 11)

La mesure n°12 remplace ensuite T2 , la moyenne se fait avec n°11, n°12 et de n°3 à n°10 (donc 3 à 12)
etc

les décalages ne sont pas nécessaires si on utilise astucieusement un index de tableau tournant

ben oui, c'est d'ailleurs comme ça qu'on fait un buffer

Il y a une bibliothèque bien faite pour faire des moyennes glissantes:
https://playground.arduino.cc/Main/RunningAverage

A noter aussi que dans le premier code l’usage de

  ai[10] = a;
  somme += ai[10];

était un bug puisque l’entrée 10 du tableau correspondrait à son 11ème élément et que le tableau n’a que 10 cases (de 0 à 9)

La bonne solution est effectivement un buffer circulaire (on ne déplace pas les valeurs) et la gestion de la somme totale par une soustraction de la valeur sortante et addition de la valeur entrante pour un calcul rapide de la moyenne.

Si on ne veut pas s’embetter à gérer la phase initiale pendant laquelle on remplit le tableau, dans le setup on peut éventuellement mettre 10 fois la même valeur (sinon il faut une variable de plus qui va mémoriser si le tableau a été rempli en entier au moins une fois)

Merci à tous pour votre aide.
J'ai donc réalisé (je crois !) un buffer circulaire à peu près proprement.

Sujet résolu !