Filtre numerique RII, RIF….digital filter...FFT...atmega328, ESP32

Et le tuto exploitable pour le public de ce forum il arrive quand ?

Car pour le moment vous êtes toujours en phase de R&D.

Pour ce genre de discussion " entre vous de l'IUT ", car personne du forum ne participe, le bar serait plus approprié, ici excusez moi mais c'est de la pollution.

Le but de ce post est d’aider les programmateurs Arduino à comprendre et utiliser des filtres numériques.
Mais, lorsque je vois la difficulté de mes étudiants d’en réaliser,
Pas sûr que la vulgarisation soit possible car il faut de bonne base mathématique.
mais les programmes mis en ligne calculs les coefficients donc il suffit juste de mettre la periode et la frequence de coupure
L’objectif est aussi d’observer les limites possibles des processeurs

Voici un premier jet d’un article qui est prévue pour aider à la vulgarisation et faire un tuto




Il y a tellement de possibilité et de méthodes sur les filtres numériques qu’on les essaye …..pour l’instant
Mais, on veut bien un coup de main et on espere que des personnes sur le forum aide....
personnellement, c'est bizarre qu'il n y a pas ou peu de question

1 Like

Mais, on veut bien un coup de main et on espere que des personnes sur le forum aide…
personnellement, c’est bizarre qu’il n y a pas ou peu de question

Lisez les différentes questions posées et vous verrez que votre travail, sans nul doute intéressant et de qualité, ne correspond pas aux besoins des utilisateurs de ce forum.

Le public de ce forum n’est pas celui d’un IUT.
Pour faire une règle de trois avec décalage des bornes il faut utiliser la fonction map() !
Le public n’a souvent aucune formation en programmation, quant au traitement du signal même pas en rêve.
Le sujet le plus traité est celui de portes de poulailler automatiques suivant la luminosité et non pas suivant l’analyse du chant des poules.

Aidez nous à conserver ce forum propre. Ce sous forum est destiné à recevoir des tutos directement applicables et si possible des bibliothèques également directement applicables.
Votre étude toujours en perpétuel développement trouvera sa place dans le bar que, par ailleurs, vous avez déjà investi pour des exercices destinés aux étudiants de l’IUT.

on va essayer d'etre un peu plus explicite avec des filtres qui n'ont pas été etudiés encore dans ce post
avec des premieres année d'IUT avec des fonctions de bases

On va continuer avec des filtres qui n’ont pas encore été étudiés au préalable de ce poste à cause du lien suivant :
ECG AD8232 nano programme du monitoring, BPM heart rate

Il nous faut un dérivateur pour détecter le BPM du signal. Pour bien comprendre cette fonction numérique, un test dans Excel a été effectué. D’ailleurs, il existe de nombreuses possibilités mathématiques de la fonction dérivateur que l’on peut observer sur ce lien dont le principal et le plus simple est appelé derivée d’Euler…)
http://www.tangentex.com/MethodeEuler.htm

Formule Excel pour le calcul de la dérivée relative
derivéee relative =C4/(2*PI()*5)+512 avec C4 = (B4-B3)/(A4-A3)= (B4-B3)/0.01
Voici la dérivée d’un signal sinusoïdale d’amplitude 1024, de période 0.2 seconde (fréquence 5Hz) avec une période d’échantillonnage de 0.01 s (donc 20 échantillons dans Excel) :

[/img][/url[/iurl]]


voici le Programme Arduino qui permet de faire la meme chose

#include <LiquidCrystal.h>
#include <TimerOne.h>

int entree;                 // Déclaration des variables
float derivee, entree1;


      // Setup de l'affichage LCD
LiquidCrystal lcd(9, 8, 4, 5, 6, 7);   // LiquidCrystal lcd(rs, en, d4, d5, d6, d7);





void setup() {
  
pinMode(13,OUTPUT); // Définit la pin 13 comme sortie
pinMode(A0,INPUT);


// Configure le port série pour l'exemple
Serial.begin(57600);        // definition des baudes
  lcd.begin(16, 2);         // Mise affichage LCD 



  Timer1.initialize(10000);           //   pour 0.001s  1000   0.002s 2000     0.01s 10000
  Timer1.attachInterrupt(callback);   // attaches callback() as a timer overflow interrupt

}


void callback()  { // Boucle qui s'execute toute les 0.01 secondes
  entree1=entree;             // définition des valeurs au rang n et n-1
  entree=analogRead(A0);   //1024

derivee=(entree- entree1); // /0.01         Calcul de la dérivée (différence)

 /* 
  lcd.setCursor(0,0);   //colonne, ligne
lcd.print("input");
lcd.print(entree);
lcd.print("  ");
*/

  Serial.print(entree);Serial.print(";");Serial.print(derivee,0);Serial.println(";");       // Affichage sur le terminal des valeurs.

}



void loop() 
{


}

voici la capture d’ecran dans ISIS qui permet d’avoir le schema electrique, et recuperer sur le terminal serie le signal, la derivée tous les 0.01s avec la routine d’interruption du timer 1

les fonctions récurrentes permettent de faire du traitement du signal

2 Likes

Ce poste ne parle pas de filtre numérique réjecteur de signal ou coupe bande ou à encoche….
Car parfois, il faut atténuer une bande fréquence ou éliminer une fréquence qui perturbe le signal désirée
Tel que le 50Hz à éliminer pour la mesure de l’ECG sur le post suivant

Mais entre la théorie et la pratique quelles sont les différences ?
Quel doit être le rapport entre la fréquence d’échantillonnage et la fréquence à éliminer ?
Est-ce que le filtre sera impacté par la précision des 7 chiffres significatifs du compilateur ?

Dans un premier temps, la fréquence d’échantillonnage est de 500Hz pour éliminer le 50Hz
Voici l’étude mathématique théorique pour un coupe bande du second degré.

On peut observer que plus le coefficient a est proche de 1 est plus la bande de coupage est étroite, mais demande une précision des coefficients A et B plus important.
On peut observer que la différence des gains statiques avec ou sans la troncature des coefficients A et B est négligeable.
mais après la fréquence à éliminer, le gain statique n’est pas égal à 1.

Pour fe=500Hz et une fréquence de 50hz à éliminer, sur les figures suivantes en simulation, on peut observer qu’il y a bien l’atténuation pour la fréquence de 50Hz. Notre programme affiche l’atténuation sur l’afficheur LCD et les coefficients A et B.


Voici le code utilisés qui donne directement les gains en fonction du choix de la période d’échantillonnage

D’ailleurs, voilà les résultats en pratiques qui sont identiques aux résultats de la simulation
Capture4

#include <Wire.h>
#include <LiquidCrystal.h>
#include <SoftwareSerial.h>
#include <TimerOne.h>


#define PWM5   5      //   timer2   
#define LED13    13     

LiquidCrystal lcd(9, 8, 4, 3, 6, 7);   // LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
// Configuration des variables


unsigned    int    temps=0;

            float  entree=0;
            float  entree1=0;
            float  entree2=0;
            float  entree3=0;
            float  entree4=0;
       
            float  sortie=0;
            float  sortie1=0;
            float  sortie2=0;
            float  sortie3=0;
            float  sortie4=0;
           
            float  out=0;
            float A,B;

           
            float Gain=1.5;

float entreeMax;
float outMax;
float attenuation;


const float fo=50;
const float fe=500;
const float a=0.9;
                   

void setup() {
  pinMode(LED13, OUTPUT);
  pinMode(PWM5,OUTPUT);


A=2*cos(2*PI*fo/fe);
B=2*a*cos(2*PI*fo/fe);
Gain=(2-A)/(1-B+a*a);

  Timer1.initialize(1000000/fe);            // initialize timer1, and set a 0,1 second period =>  100 000  pour 0.01s  10 000 
  Timer1.attachInterrupt(callback);   // attaches callback() as a timer overflow interrupt   500=>2KHz    5000=>200Hz
  lcd.begin(20, 4);                   //modifier pour un afficheur 20x4

  Serial.begin(9600);
/*  
 Serial.print("A=");Serial.println(A); 
 Serial.print("B=");Serial.println(B);
 Serial.print("G=");Serial.println(Gain);
*/
  
//  TCCR2B = (TCCR2B & 0b11111000) | 0x01;      //pin 3  32khz    http://playground.arduino.cc/Main/TimerPWMCheatsheet
TCCR0B = (TCCR0B & 0b11111000) | 0x01;         //pin 5  64khz 

}


// Interruptions   par le timer1    ***********************************
void callback()  {
digitalWrite(LED13,HIGH);  //permet de mesurer à l'oscillo, le temps du calcul du filtre et le temps de la routine d'interruption

entree2=entree1;      //entree(n-2)
entree1=entree;       //entree(n-1)
entree=analogRead(A0); //convertisseur 10 bits sous 5V
entree=entree/4;       //PWM 8 bits mais calcul en float

sortie2=sortie1;      //sortie(n-2)
sortie1=sortie;       //sortie(n-1)
// ---- filtre coupe-bande 50Hz------   
sortie=entree-A*entree1+entree2+B*sortie1-(a*a*sortie2);
out=(sortie/(Gain));
        
if (out<0) {out=0;}
if (out>255) {out=255;}           
analogWrite(PWM5,out); 

 temps++;  
if (temps>=300)   {entreeMax=127;outMax=127;temps=0;}
if (entree> entreeMax) {entreeMax = entree; }  // Enregistrer la valeur maximale du capteur
if (out> outMax) {outMax = out; }
digitalWrite(LED13,LOW);

}//fin routine

///////////////////////////////////////////// Boucle correspondant à la fonction main
void loop() {
   lcd.setCursor(0,0);
   lcd.setCursor(0,0);   
  lcd.print("CoupeBande50Hz a=");
  lcd.print(a,1);
  lcd.setCursor(0,1);
  attenuation=((outMax-127)/(entreeMax-127));   
  lcd.print("A=");
  lcd.print(attenuation,2);
  lcd.print("  ");
  lcd.setCursor(0,2);
  lcd.print("A=");lcd.print(A,4); 
  lcd.print(" B=");lcd.print(B,4);
  lcd.setCursor(0,3);
 lcd.print("G=");lcd.print(Gain,4);
} // fin loop

Mathématiquement, si on prend une fréquence d’échantillonnage de 4 fois plus important que la fréquence à éliminer. Les coefficients A et B sont nuls. Donc, c’est intéressant car le processeur aura peu de calculs mais il n’y aura que 4 échantillons.
Est-ce que cela sera pertinent ?

Les résultats du coupe bande avec la fréquence de 4 fois, la fréquence à éliminer donne d’assez bon résultat mathématiquement.

Malgré le peu d’échantillon, on peut apercevoir le signal de sortie sur la figure suivante

En pratique, l’atténuation correspond à la figure suivante

Capture4

Si la fréquence d’échantillon est de 1KHz, le signal de sinusoïdale même à 100Hz avec 10 échantillons et cela élimine bien la fréquence de 50Hz

En analogique, il est possible de faire des filtres d’ordre relativement grand à partir de premier ordre en cascade.
Peut-on transposer, ces filtres analogiques en numérique ?
En numerique, l'avantage de la fonction de transfert du 1er ordre est d’avoir un seul coefficient et de multiplier les fonctions de transfert entre elle.
Donc de n’avoir qu’une seule entrée en mémoire mais doit-on expand le dénominateur en programmation ?

Sur la figure suivante, on peut observer cette fonction de transfert pour un filtre du premier ordre et 2 filtres 1er ordre en cascade, mais la fréquence de coupure n’est pas la même à cause de la convolution.


Voici l’équation empirique qui donne approximativement le coefficient en fonction de l’ordre désirée par rapport à la fréquence de coupure et de la fréquence d’échantillonnage.
Remarque : Plus le rapport en fc/fe est petit et plus le coefficient devra être précis.


Sur les courbes suivantes, on peut observer que l’atténuation vérifie bien la fréquence de coupure désirée malgré l’ordre qui augmente, ainsi que l’augmentation de l’atténuation en fonction de l’ordre à partir de l’équation précédente

En codage, il faut développer la fonction de transfert en Z en fonction de l’ordre désirée, il faudra choisir l’équation et mettre les autres en commentaire.

#include <Wire.h>
#include <LiquidCrystal.h>
#include <SoftwareSerial.h>
#include <TimerOne.h>


#define PWM5   5      //   timer2   
#define LED13    13     

LiquidCrystal lcd(9, 8, 4, 3, 6, 7);   // LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
// Configuration des variables


unsigned    int    temps=0;

float  entree=0;
       
float  sortie=0;
float  sortie1=0;
float  sortie2=0;
float  sortie3=0,sortie4=0;
        
float  out=0;


float entreeMax;
float outMax;
float attenuation;


const float fc=10;
const float fe=1000;
float a;  //coeficient du filtre passe bas
float N=4;  //ordre du fitre
float coef;
                   

void setup() {
  pinMode(LED13, OUTPUT);
  pinMode(PWM5,OUTPUT);


//a=(fe-fc*PI)/(fe+fc*PI);
a=1-(2*fc*PI/fe)+(2*fc*fc*PI*PI/(fe*fe));
coef=pow(N,-0.08);
a=a*coef;

  Timer1.initialize(1000000/fe);            // initialize timer1, and set a 0,1 second period =>  100 000  pour 0.01s  10 000 
  Timer1.attachInterrupt(callback);   // attaches callback() as a timer overflow interrupt   500=>2KHz    5000=>200Hz
  lcd.begin(20, 4);                   //modifier pour un afficheur 20x4

  Serial.begin(57600); 

TCCR0B = (TCCR0B & 0b11111000) | 0x01;         //pin 5  64khz 

}


// Interruptions   par le timer1    ***********************************
void callback()  {
digitalWrite(LED13,HIGH);  //permet de mesurer à l'oscillo, le temps du calcul du filtre et le temps de la routine d'interruption

entree=analogRead(A0); //convertisseur 10 bits sous 5V
entree=(entree)/4;       //PWM en 8 bits, calcul unilaterale

sortie4=sortie3;
sortie3=sortie2;
sortie2=sortie1;
sortie1=sortie;       //sortie(n-1)
// ---- filtre passe bas ---   
//sortie=entree*(1-a)+sortie1*a;    // order=1

//sortie=(entree*(1-a)*(1-a))+(2*sortie1*a)-(sortie2*a*a);  // order=2 ok
//sortie=(entree*(1-a)*(1-a))+(sortie1*sortie1*a*a);      // order=2 no ok
//sortie=(entree*pow((1-a),3))+(3*sortie1*a)-(3*sortie2*a*a)+(sortie3*a*a*a);      // order=3 ok
sortie=(entree*pow((1-a),4))+(4*sortie1*a)-(6*sortie2*a*a)+(4*sortie3*pow(a,3))-sortie4*pow(a,4);    // order=4 ok
out=sortie;
        
if (out<0) {out=0;}
if (out>255) {out=255;}           
analogWrite(PWM5,(out)); 

 temps++;  
if (temps>=3000)   {entreeMax=127;outMax=127;temps=0;}
if (entree> entreeMax) {entreeMax = entree; }  // Enregistrer la valeur maximale du capteur
if (out> outMax) {outMax = out; }
digitalWrite(LED13,LOW);


}//fin routine

///////////////////////////////////////////// Boucle correspondant à la fonction main
void loop() {
Serial.print(entree,2);Serial.print(";"); Serial.println(sortie,2);
  lcd.setCursor(0,0);   
  lcd.print("passe bas ordre 1");
  lcd.setCursor(0,1); 
  lcd.print("cascade N=");
  lcd.print(N,0);
  lcd.setCursor(0,2);
  lcd.print("a=");
  lcd.print(a,3);
  
  lcd.setCursor(0,3);
  attenuation=((outMax-127)/(entreeMax-127));   
  lcd.print("Attenuat=");
  lcd.print(attenuation,3);


} // fin loop

Voici la simulation qui permet de vérifier le code, en utilisant une fréquence d’échantillonnage de 1kHz.
L’ordre 3 demande un temps de calcul de 0.5ms et l’ordre 4 demande un temps de calcul de 1ms donc la fréquence d’échantillonnage doit être diminué pour cet ordre.


Voici les résultats des atténuations pour différents ordres en fréquentiel avec une fréquence d’échantillonnage de 1kHz et une fréquence de coupure désirée de 10Hz.

Avec l’ordre 4, la fréquence d’échantillonnage passe de à 500Hz, car le calcul est trop long pour l’ATmega 328.

Pour les autres courbes, la pratique est identique à la théorie.


Quelles sont les différences dans les résultats entre le filtre précèdent et un filtre numérique un Butter Worth ou un Cheby ?

Pour connaitre les avantages et les inconvénients de chacun des filtres numeriques :
rien de mieux que de les tester avec les limites possibles d’un petit processeur avec son codage

D’ailleurs, il y a un article très intéressant qui compare différents filtres numériques mais qui ne dit pas comment il les codes…….

« An optimal filter for short photoplethysmogram signals »

https://www.nature.com/articles/sdata201876

pas facile de partager son savoir, de le démocratiser et d’optimiser un filtrage.