Lisser / smooth les données d'un capteur.

Bonjour !

Je réalise un projet où j'ai lié l'intensité du volume sonore avec la distance d'un capteur à Ultrason.

Plus on s'éloigne du capteur plus le volume sonore d'un lecteur mp3 augmente.

Cependant, lorsque l'on s'éloigne rapidement du capteur, le passage d'un faible volume à un plus fort se fait sentir trop brutalement.

Y'aurait-il un moyen de lisser ces données pour pouvoir passer progressivement d'un extrème à un autre ?

Voici une partie du code où je pense doit être implémenter cette nouvelle fonction :

lecturecho = pulseIn(echo,HIGH);

cm = lecturecho /58; //distance calculé par le capteur US

V_capteur = cm / 7; //Le volume du mp3 évolue entre 0 et 30, les données du capteur US entre 0 et 400

myDFPlayer.volume(V_capteur); //fonction permettant de changer la valeur du mp3

delay(500);

Voici le code complet pour ceux que ça intéresse :

#include "Arduino.h"
#include "SoftwareSerial.h"
#include "DFRobotDFPlayerMini.h"

SoftwareSerial mySoftwareSerial(10, 11);
DFRobotDFPlayerMini myDFPlayer;
void printDetail(uint8_t type, int value);

int trig = 2;
int echo = 3;
int V_capteur;
long lecturecho;
long cm;
int mindist = 10; //distance à partir de laquelle le volume est de zéro

void setup() {
  
mySoftwareSerial.begin(9600);
Serial.begin(9600);
 

Serial.println();
  Serial.println(F("DFRobot DFPlayer Mini Demo"));
  Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)"));

  if (!myDFPlayer.begin(mySoftwareSerial)) {  //Use softwareSerial to communicate with mp3.
    Serial.println(F("Unable to begin:"));
    Serial.println(F("1.Please recheck the connection!"));
    Serial.println(F("2.Please insert the SD card!"));
    while(true);
  }
 

 myDFPlayer.setTimeOut(500); //Set serial communictaion time out 500ms
 myDFPlayer.volume(20);
 myDFPlayer.loop(1);

pinMode(trig, OUTPUT);
pinMode(echo, INPUT);
 
}

void loop() {

digitalWrite(trig, LOW);
delayMicroseconds(2);
digitalWrite(trig, HIGH);
delayMicroseconds(10);
digitalWrite(trig, LOW);

lecturecho = pulseIn(echo,HIGH);

cm = lecturecho /58;

V_capteur = cm / 7; //V_capteur = map(analogRead(sensor0Pin), 0, 1023, 0, 127);
Serial.print(V_capteur);
Serial.print("et");
Serial.println(cm);
myDFPlayer.volume(V_capteur);

if (cm < mindist) // on mette le volume à zéro ou pause
  {  
     myDFPlayer.pause();
    
    }
  
else {   
    myDFPlayer.start();
   }

 delay(500) ;




 }

Ne montez pas le son d’un coup... définissez une variable volume_a_atteindre et toutes les t millisecondes quand la loop tourne vous regardez si le volume_en_cours est égal au volume à atteindre et si non vous faites une petite étape dans le bon sens

la méthode classique est de faire passer ta variable volume à travers un filtre numérique passe-bas (c-à-d qui coupe les hautes fréquences).
Cependant, la réalisation du filtre demande quelques connaissances...

A l'arrache, tu peux essayer :

V(n) = a*Vcible + (1-a)*V(n-1)
où:
V(n) est le Volume à appliquer à l'instant n,
V(n-1) est le Volume qui a été appliqué à l'instant n-1
Vcible est le Volume désiré au final,
a est un coefficient à fixer : commence par a=0.5, fais des essais et corrige le coefficient si besoin.

Bien sûr, tous tes calculs sont faits avec des floats, pas des ints !

biggil:
la méthode classique est de faire passer ta variable volume à travers un filtre numérique passe-bas (c-à-d qui coupe les hautes fréquences).
Cependant, la réalisation du filtre demande quelques connaissances...

A l'arrache, tu peux essayer :

V(n) = a*Vcible + (1-a)*V(n-1)
où:
V(n) est le Volume à appliquer à l'instant n,
V(n-1) est le Volume qui a été appliqué à l'instant n-1
Vcible est le Volume désiré au final,
a est un coefficient à fixer : commence par a=0.5, fais des essais et corrige le coefficient si besoin.

Bien sûr, tous tes calculs sont faits avec des floats, pas des ints !

Oui c’est la méthode classique de traitement du signal numérique .

J’avais une petite idée derrière la tête sur ma proposition :slight_smile:

La raison pour laquelle je n’ai pas proposé cela c’est que vous perdez l’information de distance cible exacte et il faut ajuster cela dans le domaine temps mais aussi puissance car la perception du volume sonore n’est pas linéaire - la plus petite variation de niveau que l’oreille humaine peut percevoir est d’environ 1 dB et c’est une échelle logarithmique sur l’axe d’émission.

Donc je voulais amener l’OP à séparer la gestion du niveau sonore dans le temps tout en conservant la vraie distance lue de façon à ce que dans un second temps il puisse appliquer une seconde formule ou le temps t entre 2 pas pourrait diminuer (indépendant de la fréquence d’échantillonnage) et la puissance à cet instant (volume) sera lui aussi calculé avec la vraie distance de l’utilisateur

je pensais avoir fait un post un peu compliqué, mais là je suis largement battu :slight_smile:

merci pour vos réponses effectivement ça devient complexe !
Dans l'idée j'aimerais bien commencer par la formule de biggil : V(n) = a*Vcible + (1-a)*V(n-1)
et voir ce que ça donne.

Cependant, l'idée d'utiliser des instants (n, n-1...) implique qu'il faut que je stock des états dans des variables non ? Or actuellement, chaque instant correspond au cycle d'une loop.

Dans la mis en oeuvre, comme je débute en programmation, comment dois-je m'y prendre ?
En vous remerciant,

Non, la formule de bigggil implique une mémoire de 0 (vous pouvez vous permettre d'ignorer Vn -1.... Vn-beaucoup.

A chaque instant (régulièrement réparti dans le temps par vos soins -c'est valable pour les deux formulations- ), "vous "faites
V = (Vcible * alpha + (1.0 - alpha) * V

Nota : vous pouvez accèlerer les calculs, et vous passer de flottants en faisant
alpha = 1- beta, et en restreignant beta à être de la forme 1/2, 1/4, 1/8 (transforme les divisions en décalages binaires; les optimiseurs savent vous le faire depuis une quarantaine d'années. Vous n'aurez plus "qu"'à vous préoccuper des erreurs d'arrondi..... ( edité ou an forçant alpha à etre une puissance negative de 2)
ex :
V = (Vcible + V) / 2 pour beta = 1/2
ou
V = (Vcible *3 + V) / 4 pour beta = 1/4 (qui se rapproche beaucoup plus vite de Vcible)

Super ça marche, je sus resté sur le rapport 1/2.
Merci beaucoup pour l'aide.

Petite question sur le montage. J'aimerais utiliser un vibreur acoustique en le branchant directement sur la sortie HP d'un DFPlayer Mini :

Il est indiqué qu'il est possible d'y connecter un HP de maximum 3W. Qu'en est-il de l'impédance ? J'ai le choix entre deux produits de 4Ohms et 8Ohms. J'imagine que le 4Ohms sera plus fort que le 8Ohms. Qu'en pensez-vous ?

Aussi, une carte Arduino NANO se suffira-elle à elle même pour alimenter le player, le capteur US ainsi que le HP ?
J'aimerais la laisser brancher en continu pendant une journée entière. Cela irait si je la branche à un adaptateur de téléphone USB de 1A ?

Enfin je m'aperçois que le capteur ultrason à un angle trop faible (15°) en connaissez vous d'autres bon marchés qui soient plus larges ? Je suis tombé sur ce modèle qu'en pensez vous ?

En vous remerciant,
Voici aussi une photo du montage avec une arduino uno.

Ce qui me gène, c'est que vous vous soyez arrêté à "ça marche" . JLM avait une idée moins classique, mais qui peut être plus brillante

@dbrion06 Bien sur et j'y reviendrai avec le temps et l'expérience. En tout cas, le dispositif que j'aimerais mettre en place et la perception du son (car il s'agit avant tout d'un effet : je ne cherche pas à être dans l'exactitude), fonctionnent déjà bien !

Bonjour,

Après plusieurs tests, je note que les capteurs que j'utilise ne sont pas très fiables.
Des sauts répétés dans le relevé de distance ("cm" dans le code )indiquent sans raison, une valeur max, récurrente.

Afin d'éviter ce saut brutal de distance (et donc de volume dans le project) j'aimerais exclure certaines valeurs précises, comme la donnée 284 ou un ensemble de valeurs extrèmes.

Comment, en language, pourrais-je procéder pour indiquer à ma boucle d'omettre une valeur si la valeur relevée est = ou > à 284, et de recommencer le début de la boucle ou d'utiliser la valeur du relevé précédent? sachant que je n'ai pas créé dans mon code d'état n-1.

En vous remerciant,

Voici la loop que j'aimerais modifier. Le relevé des distances présentant des anomalies correspondent à l'int "cm".

void loop() {

digitalWrite(trig, LOW);
delayMicroseconds(2);
digitalWrite(trig, HIGH);
delayMicroseconds(10);
digitalWrite(trig, LOW);

lecturecho = pulseIn(echo,HIGH);

cm = lecturecho /58;

if (cm > 283) // ne pas prendre en compte la valeur, ou prendre la valeur cm précédente...
  {  
 
   }

V_capteur = cm / 7; //V_capteur = map(analogRead(sensor0Pin), 0, 1023, 0, 127);
Serial.println(cm);
V_smooth = (V_capteur+V_smooth)/2;
myDFPlayer.volume(V_smooth);



 delay(1100) ;

}

cm.png

Si ma memore est bonne, vous utilisez un filtre de la forme V=alphaMesure + (1-alpha) V (dans votre cas, alpha vaut 1/2)

Si votre mesure est aberrante, prendre la valeur précédente consiste ... à ne pas mettre à jour V;
dire que la mesure est égale à la mesure précédente implique de stocker systematiquement la mesure si elle n'est pas aberrante
if (newMesure < 284) mesure = newMesure
retire (ou cache) une valeur aberrante et met à jour V.

Merci, je comprends,

j'ai néanmoins des difficultés à l'écrire en code, pour écrire l'équivalent de :

Si la mesure en cm est inférieur à 284 alors on la stocke dans NEWcm et on utilise NEWcm pour la suite des opérations, cela marcherait il ? Dois-je également annoncer NEWcm dès le départ en mettant :

int NEWcm = 0; ou long NEWcm; ?

Cela peut-il s'écrire comme suit dans le code :

cm = lecturecho /58;


if (NEWcm<284)cm = NEWcm; // Si la mesure en cm est inférieur à 284 alors on la stocke dans NEWcm

V_capteur = NEWcm / 7; //mapping


V_smooth = (V_capteur+V_smooth)/2;

myDFPlayer.volume(V_smooth);


delay(1100) ;

}
cm = lectureecho/58;
if (cm < 284) V_capteur=cm/7;

suffirait, au vu de la lecture de votre code (incomplet: il manque, scusez du peu, les déclarations) à condition que V_capteur soit déclaré en variable globale...

Bonjour,

Poue affecter la valeur précédente si cm>283, l faut mémoriser la valeur acquise au cycle précédent

 static int cmPrecedent;
  cm = lecturecho / 58;

  if (cm > 283) // ne pas prendre en compte la valeur, ou prendre la valeur cm précédente...
  {
    cm=cmPrecedent;
  }
  cmPrecedent=cm;

En fait, non: il utilise une valeur V_capteur qui est la base de tous ses calculs par filtre recursif.... c'est cette valeur qui est memorisée de fait (à condition d'être globale, ce qui est une grande inconnue) et initialisée à zeo (pourquoi pas) au démarrage.... Il n'est pas nécessaire de faire prolifèrer des tas de variables globales, dans ce cas...

Super, effectivement la solution de dbrion est plus synthétique et marche bien! merci