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

Après avoir su que le problème de l’incohérence des valeurs de l’atténuation sont liés au +127 qu’on doit rajouter sur la sortie , nous allons faire des essais toujours au second ordre avec des fréquences de 30 et 70 Hz.

Nous avons également ajouté le calcul de l’atténuation automatique selon la formule écrite dans le précédent post

le code est alors

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
//#include <LiquidCrystal.h>
#include <SoftwareSerial.h>
#include <TimerOne.h>
#include <math.h>


#define PWM3   3      //   timer2   
#define LED13    13     
LiquidCrystal_I2C lcd(0x27, 16, 2);
//LiquidCrystal_I2C lcd(9, 8, 4, 5, 6, 7);   // LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
// Configuration des variables
//LiquidCrystal_I2C lcd(0x27, 20, 4);     //A0, A1, A2 non shunter 20x4ligne


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;
            const float b1 =0;
            const float b2 =-2;
            const float b3=0;
            const float b4=1;

            float a1= -3.4965116214 ;
            float a2=4.7244111639;
            float a3=-2.9230616667;
            float a4=0.7008967812 ;
          
            float Gain=70;

            float  fe=1000;  //frequence d'echantilonnage 
              
           float entreeMax;
float outMax;
float attenuation;

                   

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

  Timer1.initialize(1000);           // 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
  lcd.begin(20, 4);                   //modifier pour un afficheur 20x4


  Serial.begin(9600);

  TCCR2B = (TCCR2B & 0b11111000) | 0x01;         //pin 3  32khz    http://playground.arduino.cc/Main/TimerPWMCheatsheet

 //gain donné par le soft pour le calcul des coefficients
 
}


// Interruptions  tous les 1ms fait par le timer1    fe=1000Hz***********************************
void callback()  {
if ( digitalRead(13)== 1 ) {digitalWrite(13,LOW);}  else {digitalWrite(13,HIGH);}
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
 
     
// ---- filtre passe bande Butterwoth , fechantillon=1000Hz------       
entree4=entree3;      //entree(n-4)
entree3=entree2;       //entree(n-3)
entree2=entree1;      //entree(n-2)
entree1=entree;       //entree(n-1)

sortie4=sortie3;      //sortie(n-4)
sortie3=sortie2;       //sortie(n-3)
sortie2=sortie1;      //sortie(n-2)
sortie1=sortie;       //sortie(n-1)


  

sortie=(entree4-2*entree2+entree-a4*sortie4-a3*sortie3-a2*sortie2-a1*sortie1);  //filtre passe bande second ordre
//gain unitaire

out=(sortie/Gain);
out=((out/4)+127)  ;       //mise à l'echelle 10 bits en entrée et 8 bits en sortie         
if (out<0) {out=0;}
if (out>255) {out=255;}           
analogWrite(PWM3,out);          //    
if (temps>=200)   {entreeMax=512;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("passe bande 2eme");
  lcd.setCursor(0,1);
  attenuation=((outMax-127)*4/(entreeMax-512));   
  lcd.print("A=");
  lcd.print(attenuation,2);
   lcd.print("  "); 

   



} // fin loop

La valeur du gain a été légèrement modifiée pour avoir une atténuation nulle à la fréquence centrale de 50Hz

Pour une fréquence de 10 Hz

Nous remarquons une très forte atténuation de 0.12

Pour les fréquences de coupures 30 et 70Hz nous obtenons une atténuation théorique de 0.7 correspondant au gain à -3dB

Nous voyons bien que le résultat théorique est conforme avec la simulation

Pour la fréquence centrale 50Hz

A la fréquence centrale nous avons une atténuation nulle comme attendu en théorie donc le signal est parfaitement transmis

Pour une fréquence de 90Hz

Nous avons également une atténuation importante de 0.27

On peut conclure alors que le filtre passe bande joue bien son rôle car il atténue fortement la sortie pour des fréquences non comprises dans la bande passante entre 30 et 70Hz et transmet tout le signal d’entrée en sortie sans atténuation à la fréquence centrale. Les résultats obtenus pour les fréquences de coupure sont également ceux qui étaient attendu en théorie

Dans la conclusion, il faudrait rajouter

  • plus, la fréquence d’échantillonnage est éloignée de la fréquence de résonnance et plus le gain devient important. Donc plus, il faut de précision.
  • Plus la bande passante, est faible est plus, le gain est important.
  • Mais le petit micro Atmel 328 s’en sort quand même.
    Remarque
    Étant donné qu’un float prend toujours 7 chiffres significatifs.
    Pour avoir moins de problème de divergence, à la place de diviser la sortie par 4 tout à la fin du programme, autant, le faire sur l’entrée.
    En plus, il n’y a pas de problème de précision car il y a la virgule.

Avec une bande passante de 2Hz, le coefficient de qualité sera de 12.5, comme on peut le voir sur la photo suivante

La gain est de 7000 et cela fonctionne encore, mais on est limite à la divergence

Voici le nouveau code

#include <Wire.h>
//#include <LiquidCrystal_I2C.h>
#include <LiquidCrystal.h>
#include <SoftwareSerial.h>
#include <TimerOne.h>
//#include <math.h>


#define PWM3   3      //   timer2    
#define LED13    13       
LiquidCrystal lcd(9, 8, 4, 5, 6, 7);   // LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
// Configuration des variables
//LiquidCrystal_I2C lcd(0x27, 20, 4);     //A0, A1, A2 non shunter 20x4ligne


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 entreeMax;
float outMax;
float attenuation;

            
 //        Fc 40hz et 60 Hz  fe=1000Hz  ok  
             float a1=-3.770723;
              float a2=5.519325;
              float a3=-3.704298;
              float a4=0.9650811;
              float Gain=7000;     //
            
 

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

  Timer1.initialize(1000);           //   pour 0.001s  1000   0.002s 2000
  Timer1.attachInterrupt(callback);   // attaches callback() as a timer overflow interrupt
  lcd.begin(20, 4);                   //modifier pour un afficheur 20x4
/*
lcd.init();               //et pas lcd begin comme cetraine biblio
lcd.display();            // activer l'affichage
lcd.backlight();          // allumer retroeclairage  
*/  
  
  Serial.begin(9600); 

  TCCR2B = (TCCR2B & 0b11111000) | 0x01;         //pin 3  32khz    http://playground.arduino.cc/Main/TimerPWMCheatsheet

}



// Interruptions  tous les 1ms fait par le timer1    fe=1000Hz***********************************
void callback()  {
//if ( digitalRead(13)== 1 ) {digitalWrite(13,LOW);}  else {digitalWrite(13,HIGH);}
//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;          //mise à l'echelle 10 bits en entrée et 8 bits en sortie+chiffre significatif    
      
// ---- exemple filtre passe pas Butterwoth   ,  fechantillon=1000Hz------    
entree4=entree3;      //entree(n-4)
entree3=entree2;      //entree(n-3)
entree2=entree1;      //entree(n-2)
entree1=entree;       //entree(n-1)

sortie4=sortie3;
sortie3=sortie2;      //sortie(n-3)
sortie2=sortie1;      //sortie(n-2)
sortie1=sortie;       //sortie(n-1)

sortie=(entree4-2*entree2+entree-a4*sortie4-a3*sortie3-a2*sortie2-a1*sortie1);  //filtre passe bande second ordre
//sortie=(entree+entree1*b1+entree2*b2+entree3*b3+entree4*b4-sortie1*a1-sortie2*a2-sortie3*a3-sortie4*a4)    ;
//sortie=(entree+entree1*b1+entree2*b2+entree3*b3-sortie1*a1-sortie2*a2-sortie3*a3) ;   //filtre passe pas recursif ordre 3
//sortie=(entree+entree1*b1+entree2*b2-sortie1*a1-sortie2*a2) ;                        //filtre passe pas recursif ordre 2
//sortie=(entree*0.19+sortie*0.81);                                 //filtre passe bas    a1=0.992 pour une constante de temps de 10ms  Te=1ms durre algo 150us                                                                                   //filtre passe pas recursif ordre 1
//out= entree  ;                                                                                     //gain unitaire
out=(sortie/Gain+127);     //mise à l'echelle 10 bits en entrée et 8 bits en sortie     
if (out<0) {out=0;}
if (out>255) {out=255;}                  
analogWrite(PWM3,out);          //    
//Serial.println(out);
temps++;
//rafraichissement toutes les 0.2s
if (temps>=200)   {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.print("passe bande 2eme");
  lcd.setCursor(0,1);    
  lcd.print("f1=48");
  lcd.setCursor(6,1);    
  lcd.print("f2=52");
  lcd.setCursor(0,2);    
  lcd.print("Q=12.5");
  lcd.setCursor(0,4);
  attenuation=((outMax-127)/(entreeMax-127));
  lcd.print(attenuation,2);    
  lcd.print("=Attenuat");

} // fin loop

Pour éviter les divergences, les filtre RIF peuvent etre utilisé, il n’ont pas encore été traité….sur ce post

mais, ce sera une autre histoire…

Les filtres RIF (appelé filtre non récursif), utilise que les valeurs antérieures de l’entrée et pas de la sortie et c’est pour cela qu’ils n’ont pas pas de problème de divergence, donc stable.
Par contre, ils vont utiliser beaucoup de variables mais pour une le processeur ATMEGA328, il y a 2Koctets possibles et il y aura plus de calculs.

Des liens pour comprendre la base mais un peu trop théorique sans exemple pratique

https://www.f-legrand.fr/scidoc/docimg/numerique/filtre/rif/rif.html
http://herve.boeglen.free.fr/Tsignal/chapitre3/chapitre3.htm
Ces 2 liens n’expliquent pas simplement pour les signaux périodique si la fréquence d’échantillonnage est de 1000Hz avec une la fréquence de coupure désiré de 100Hz (10 échantillons d’entrés), ce n’est pas la peine de prendre 20 échantillonnons.

Souvent on ne part pas d’un filtre analogique, mais du système récursif pour connaitre la transmitance du filtre. Mais il est possible de faire l’inverse

D’ailleurs, le lien suivant donne les coefficients pour une fréquence d’échantillonnage de 1000Hz et une fréquence de coupure de 250Hz avec 17 variables mais nombreuses sont à 0.
https://www-users.cs.york.ac.uk/~fisher/mkfilter/


Le filtre passe bas RIF, le plus connu est celui du moyenneur

Mais on peut observer sur la figure suivante qu’a 250Hz, il n’y a que 4 échantillons, donc ce n’est pas un filtre qui va permettre de voir l’atténuation sur un signal périodique

Voici le code qui va permettre d’augmenter le nombre d’entrée dans un futur proche

#include <Wire.h>
//#include <LiquidCrystal_I2C.h>
#include <LiquidCrystal.h>
#include <SoftwareSerial.h>
#include <TimerOne.h>
//#include <math.h>


#define PWM3   3      //   timer2    
#define LED13    13       
LiquidCrystal lcd(9, 8, 4, 5, 6, 7);   // LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
// Configuration des variables
//LiquidCrystal_I2C lcd(0x27, 20, 4);     //A0, A1, A2 non shunter 20x4ligne


unsigned    int    temps=0; 


            float  entree=0;
            float  entree1=0;
            float  entree2=0;
            float  entree3=0;
            float  entree4=0;
         
            float  sortie=0;

            
            float  out=0;
float entreeMax;
float outMax;
float attenuation;
            
 //   
              float b0=0.5;
              float b1=0.5;
              float b2=0;
       
 

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

  Timer1.initialize(1000);           //   pour 0.001s  1000   0.002s 2000
  Timer1.attachInterrupt(callback);   // attaches callback() as a timer overflow interrupt
  lcd.begin(20, 4);                   //modifier pour un afficheur 20x4
  
  Serial.begin(9600); 

  TCCR2B = (TCCR2B & 0b11111000) | 0x01;         //pin 3  32khz    http://playground.arduino.cc/Main/TimerPWMCheatsheet

}



// Interruptions  tous les 1ms fait par le timer1    fe=1000Hz***********************************
void callback()  {
//if ( digitalRead(13)== 1 ) {digitalWrite(13,LOW);}  else {digitalWrite(13,HIGH);}
//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;          //mise à l'echelle 10 bits en entrée et 8 bits en sortie+chiffre significatif    
      

entree4=entree3;      //entree(n-4)
entree3=entree2;      //entree(n-3)
entree2=entree1;      //entree(n-2)
entree1=entree;       //entree(n-1)


out=(entree*b0+entree1*b1+entree2*b2);  //filtre moyeneur
                                                                
if (out<0) {out=0;}
if (out>255) {out=255;}                  
analogWrite(PWM3,out);          //    
//Serial.println(out);
temps++;
//rafraichissement toutes les 0.2s
if (temps>=200)   {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.print("filtre RIF moyenneur");
  lcd.setCursor(0,1);    
  lcd.print("B0=0.5");
  lcd.setCursor(8,1);    
  lcd.print("B1=0.5");
  lcd.setCursor(0,2);    
  lcd.print("B2=0");
  lcd.setCursor(0,4);
  attenuation=((outMax-127)/(entreeMax-127));
  lcd.print(attenuation,2);    
  lcd.print("=Attenuat");


} // fin loop

il y a aussi, les 2 libray suivantes à etudier

GitHub - sebnil/FIR-filter-Arduino-Library: FIR filter Arduino Library ne donne que 5 valeurs ???
GitHub - jeroendoggen/Arduino-signal-filtering-library: Arduino library for signal filtering ils n’ont pas poussé le bouchon…

1 Like

Avec 4 valeurs d’entrées, toujours en moyenneur la fréquence de coupure passe à 110 Hz comme on peut l’observer sur la figure suivante. Mais entre 250Hz et 500Hhz, il y a un rebond surnommé « Gibbs » phénomène, mais n’oublions pas qu’il n’y a que 3 à 4 échantillons, donc ce n’est pas trop problématique pour un signal périodique.

voici la fonction de transfert de ce filtre

Donc, pour la fréquence de 50Hz, il n’y a pas d’atténuation

A 150Hz, l’atténuation est bien autour de 0.7

A 250Hz, l’atténuation est bien de 0

Entre de 250Hz et 500Hz, l’atténuation atteint environ 0.22 au maximum comme en théorie, mais il n’y a pas beaucoup d’échantillon pour recréer un signal périodique.

Voici le code qui travaille avec une table et qui permet de gérer un grand nombre d’entrée

#include <Wire.h>
//#include <LiquidCrystal_I2C.h>
#include <LiquidCrystal.h>
#include <SoftwareSerial.h>
#include <TimerOne.h>
//#include <math.h>


#define PWM3   3      //   timer2    
#define LED13    13       
LiquidCrystal lcd(9, 8, 4, 5, 6, 7);   // LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
// Configuration des variables
//LiquidCrystal_I2C lcd(0x27, 20, 4);     //A0, A1, A2 non shunter 20x4ligne


unsigned    int    temps=0; 

            byte M=4;
            float  entree[4];
            float  b[]={0.25, 0.25, 0.25, 0.25};   //b[3], b[2], b[1],  b[0]
            float  out=0;

float entreeMax;
float outMax;
float attenuation;
            
void setup() {
  pinMode(LED13, OUTPUT);
  pinMode(PWM3,OUTPUT);

  Timer1.initialize(1000);           //   pour 0.001s  1000   0.002s 2000
  Timer1.attachInterrupt(callback);   // attaches callback() as a timer overflow interrupt
  lcd.begin(20, 4);                   //modifier pour un afficheur 20x4
  
  Serial.begin(9600); 

  TCCR2B = (TCCR2B & 0b11111000) | 0x01;         //pin 3  32khz    http://playground.arduino.cc/Main/TimerPWMCheatsheet


}



// Interruptions  tous les 1ms fait par le timer1    fe=1000Hz***********************************
void callback()  {
//if ( digitalRead(13)== 1 ) {digitalWrite(13,LOW);}  else {digitalWrite(13,HIGH);}
digitalWrite(LED13,HIGH);  //permet de mesurer à l'oscillo, le temps du calcul du filtre et le temps de la routine d'interruption
entree[M-1]=analogRead(A0); //convertisseur 10 bits sous 5V
entree[M-1]=entree[M-1]/4;          //mise à l'echelle 10 bits en entrée et 8 bits en sortie+chiffre significatif    

out=0;     //remise à zero du calucl du filtre RIF
for (byte i = 0; i < M; i++) {out=(b[i]*entree[i]+out);/*Serial.print(entree[i]);Serial.print(";");*/}   
for (byte i = 0; i < M; i++) {entree[i]=entree[i+1];}        
//Serial.print(out,0); 
//Serial.println(";");                                                              
if (out<0) {out=0;}
if (out>255) {out=255;}                  
analogWrite(PWM3,out);        //    

temps++;
//rafraichissement toutes les 0.2s
if (temps>=200)   {entreeMax=127;outMax=127;temps=0;}
if (entreeMax <entree[0]) {entreeMax = entree[0]; }  // 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.print("filtre RIFmoyenneur4");
  lcd.setCursor(0,1);    
  lcd.print("B0=B1=B2=B3=0.25");
  lcd.setCursor(0,4);
  attenuation=((outMax-127)/(entreeMax-127));
  lcd.print(attenuation,2);    
  lcd.print("=Attenuat");


} // fin loop

La détermination des coefficients peuvent être déduit par la méthode de fourrier comme on peut l’observer sur la figure suivante avec Fc la frequence de coupure desirée, fe la frequence d’échantillonnage
Voici les coefficients pour une fréquence de coupure de 100Hz et une fréquence d’échantillonnage de 1000Hz avec 8 coefficients.
Il y a une symétrie des valeurs de coefficient autour de M
D’ailleurs à cette valeur le coefficient, le coefficient déterminé par mathlab n’est pas correct car sinuscardinal(0) doit donner 2fe/fc et pas 0 donc il faut le recalculer.
La somme des coefficients b donne le gain, mais le Gibbs ainsi que l’imprécision du au nombre de coefficient donne un gain légèrement diffèrent de 1.
Par conséquent, la valeur de la moyenne de la sortie ne sera pas égale à 127.
Par conséquent, la valeur moyenne de la sortie doit être vérifier

Avec seulement 9 coefficients, la fréquence de coupure ne correspond pas à celle désirée, mais plutôt autour de 60Hz comme on peut l’observer sur la courbe en bleu.
Par contre, avec 35 valeurs, la courbe en magenta aura bien la fréquence de coupure désirée


la courbe en bleu correspond à la troncature de hamming de recalucler tous les coefficient qui permet de minimiser le phénomène de Gibbs.

matlab permet de le faire aussi
https://nl.mathworks.com/help/signal/ug/fir-filter-design.html
mais il faut le toolbox Signal Processing
certain compilateur de DSP ont aussi des fonctions pour determiner les valeurs

Donc, ces valeurs du coefficient peuvent être déterminées directement par un soft extérieur ou directement par l’Arduino.
Mais cela sera une autre histoire.

Difficile de trouver des exemples de valeurs des « digital filter FIR », beaucoup de théorie et peu de pratique pour vérifier si nos calculs sont corrects ou pas.

Des exemples ici, quand même pour vérifier la théorie et tres peu de pratique.

Nous allons vérifier l’atténuation pour 9 coefficients et 23 coefficients
Ainsi que la détermination du temps de calcul du filtre avec ce nombre de coefficients.

Voici le code

#include <Wire.h>
//#include <LiquidCrystal_I2C.h>
#include <LiquidCrystal.h>
#include <SoftwareSerial.h>
#include <TimerOne.h>
//#include <math.h>


#define PWM3   3      //   timer2    
#define LED13    13       
LiquidCrystal lcd(9, 8, 4, 5, 6, 7);   // LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
// Configuration des variables
//LiquidCrystal_I2C lcd(0x27, 20, 4);     //A0, A1, A2 non shunter 20x4ligne


unsigned    int    temps=0;

byte M=23;
float  entree[23];
float  b[]={0.017,0,-0.021,-0.038,-0.043,-0.031,0,0.047,0.101,0.151,0.187,0.2,0.187,0.151,0.101,0.047,0,-0.031,-0.043,-0.038,-0.021,0,0.017};
       
/*
            byte M=9;
            float  entree[9];
            float  b[]={0.047, 0.101, 0.151, 0.187, 0.2, 0.187, 0.151, 0.101, 0.047};   //b[3], b[2], b[1],  b[0]
*/            
float  out=0;
float entreeMax;
float outMax;
float attenuation;

float outaverage;
float outaverage1;

            float  fe=1000;  //frequence d'echantilonnage  
            float  fc=100;  //frequence de coupure desiréé  
            
void setup() {
  pinMode(LED13, OUTPUT);
  pinMode(PWM3,OUTPUT);

  Timer1.initialize(1000);           //   pour 0.001s  1000   0.002s 2000
  Timer1.attachInterrupt(callback);   // attaches callback() as a timer overflow interrupt
  lcd.begin(20, 4);                   //modifier pour un afficheur 20x4
  
  Serial.begin(9600); 

  TCCR2B = (TCCR2B & 0b11111000) | 0x01;         //pin 3  32khz    http://playground.arduino.cc/Main/TimerPWMCheatsheet
}



// Interruptions  tous les 1ms fait par le timer1    fe=1000Hz***********************************
void callback()  {
//if ( digitalRead(13)== 1 ) {digitalWrite(13,LOW);}  else {digitalWrite(13,HIGH);}
digitalWrite(LED13,HIGH);  //permet de mesurer à l'oscillo, le temps du calcul du filtre et le temps de la routine d'interruption
entree[M-1]=analogRead(A0);         //convertisseur 10 bits sous 5V   https://www.gammon.com.au/adc
entree[M-1]=entree[M-1]/4;          //mise à l'echelle 10 bits en entrée et 8 bits en sortie+chiffre significatif    

out=0;     //remise à zero du calucl du filtre RIF
for (byte i = 0; i < M; i++) {out=(b[i]*entree[i]+out);/*Serial.print(entree[i]);Serial.print(";");*/}   
for (byte i = 0; i < M; i++) {entree[i]=entree[i+1];}        
//Serial.print(out,0); 
//Serial.println(";");                                                              
if (out<0) {out=0;}
if (out>255) {out=255;}                  
analogWrite(PWM3,out);        //    



temps++;    
outaverage=outaverage+out;          //calcul de la valeur moyenne      
//rafraichissement toutes les 0.2s
if (temps>=200)   {entreeMax=127;outMax=127;temps=0;outaverage1=outaverage/200;outaverage=0;}
if (entreeMax <entree[0]) {entreeMax = entree[0]; }  // 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.print("filtre RIF passe bas");
lcd.setCursor(0,2);
lcd.print(outaverage1,0);   //valeur moyenne sur 0.2seconde
lcd.print(" out_average");  
  lcd.setCursor(0,3);
  attenuation=((outMax-outaverage1)/(entreeMax-127));
  lcd.print(attenuation,2);    
  lcd.print("=Attenuat ");


} // fin loop

On ne va pas faire une capture d’écran à chaque fois pour justifier les résultats du filtre.
On va plutôt faire un tableau des résultats sur Excel qui sera plus lisible, et on pourrait tracer la courbe la fonction de transfert du filtre
avec fréquence de coupure de 100Hz et échantillonnage de 1000hz déterminer précédemment, voici les mesures en réels pour 9 coefficients et 23 coefficients

Comme pour la théorie, la fréquence de coupure est plus poche de celle désirée lorsque le nombre d’échantillons est plus grand.

Pour copier les coefficients de mathcad, il faut enlever les « return » du vecteur
Dont ctrl+h, ^P remplacer les return par des virgules

Avec la conversion analogique 10 bits qui dure 0.11s avec le prescaleur de 128.
Pour 9 coefficients, le temps des instructions est de 0.3ms donc inferieure à notre période d’échantillonnage de 1ms et utilisant 24% de la mémoire dynamique.
Pour 23 coefficients, le temps des instructions est de 0.55ms donc toujours bien inférieure à notre période d’échantillonnage de 1ms et utilisant 30% de la mémoire dynamique.
Donc environ 44 coefficients maximums pourront être utilisés avec la période d’échantillonnage de 1ms

Le calcul des coefficients par l’Arduino est en perspective

Voici le programme qui va calculer les coefficients du filtre FIR par l’arduino
Il suffit juste de rentrer le nombre de coefficient désiré M=9, ainsi que la fréquence d’échantillonnage, et la fréquence de coupure. Attention M=9 correspond à M=4 dans mathcad précèdent

byte M=9;               //nombre impaire de coefficients desirées (ne pas dépasser 43 pour fe=1000hz) 
float  entree[9];       //declaration de la table des entrées
float  b[9];   //b[3], b[2], b[1],  b[0]     //table des coefficients
float  fe=1000;  //frequence d'echantillonnage  
float  fc=100;  //frequence de coupure desiree
// calcul coefficient filtre FIR
for (byte i = 0; i < M; i++) {b[i]=(sin(2*fc*PI*(i-((M-1)/2))/fe)/(((i-((M-1)/2)))*PI));}   
b[((M-1)/2)]=2*fc/fe;            //sinus cardinal(0)
// ajustement des coefficient avec de la troncature hamming
for (byte i = 0; i < M; i++) {b[i]=b[i]*(0.54+0.46*cos(PI*(i-((M-1)/2))/M));}
for (byte i = 0; i < M; i++) {Serial.print(b[i],3);Serial.print(";");}

Etant donné que les coefficients peuvent être nombreux, ils seront envoyés par l’intermédiaire du terminal, comme on peut l’observer sur la figure suivante

Voici le code complet

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

#define PWM3   3      //   timer2    
#define LED13    13       
LiquidCrystal lcd(9, 8, 4, 5, 6, 7);   // LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

#define PI 3.141592
unsigned    int    temps=0;
          
float  out=0;
float entreeMax;
float outMax;
float attenuation;

float outaverage;
float outaverage1;

float  fe=1000;  //frequence d'echantilonnage  
float  fc=100;  //frequence de coupure desiréé  

byte M=9;               //nombre impaire de coefficients desirées (ne pas depasser 43) 
float  entree[9];       //declaration de la table des entrées
float  b[9];   //b[3], b[2], b[1],  b[0]     //table des coefficients

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

  Timer1.initialize(1000);           //   pour 0.001s  1000   0.002s 2000
  Timer1.attachInterrupt(callback);   // attaches callback() as a timer overflow interrupt
  lcd.begin(20, 4);                   //modifier pour un afficheur 20x4
  
  Serial.begin(9600); 

  TCCR2B = (TCCR2B & 0b11111000) | 0x01;         //pin 3  32khz    http://playground.arduino.cc/Main/TimerPWMCheatsheet


// calcul coefficient filtre FIR
for (byte i = 0; i < M; i++) {b[i]=(sin(2*fc*PI*(i-((M-1)/2))/fe)/(((i-((M-1)/2)))*PI));}   
b[((M-1)/2)]=2*fc/fe;            //sinus cardinal(0)
//ajustement des coefficients avec la troncature hamming
for (byte i = 0; i < M; i++) {b[i]=b[i]*(0.54+0.46*cos(PI*(i-((M-1)/2))/M));}
for (byte i = 0; i < M; i++) {Serial.print(b[i],3);Serial.print(";");}  
}



// Interruptions  tous les 1ms fait par le timer1    fe=1000Hz***********************************
void callback()  {
//if ( digitalRead(13)== 1 ) {digitalWrite(13,LOW);}  else {digitalWrite(13,HIGH);}
digitalWrite(LED13,HIGH);  //permet de mesurer à l'oscillo, le temps du calcul du filtre et le temps de la routine d'interruption
entree[M-1]=analogRead(A0);         //convertisseur 10 bits sous 5V   https://www.gammon.com.au/adc
entree[M-1]=entree[M-1]/4;          //mise à l'echelle 10 bits en entrée et 8 bits en sortie+chiffre significatif    

out=0;     //remise à zero du calcul du filtre RIF
for (byte i = 0; i < M; i++) {out=(b[i]*entree[i]+out);/*Serial.print(entree[i]);Serial.print(";");*/}   
for (byte i = 0; i < M; i++) {entree[i]=entree[i+1];}        
//Serial.print(out,0); 
//Serial.println(";");                                                              
if (out<0) {out=0;}
if (out>255) {out=255;}                  
analogWrite(PWM3,out);        //    

temps++;    
outaverage=outaverage+out;          //calcul de la valeur moyenne      
//rafraichissement toutes les 0.2s
if (temps>=200)   {entreeMax=127;outMax=127;temps=0;outaverage1=outaverage/200;outaverage=0;}
if (entreeMax <entree[0]) {entreeMax = entree[0]; }  // 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.print("filtre RIF passe bas");
lcd.setCursor(0,1);    
lcd.print("ordre");
lcd.print(M);
lcd.setCursor(0,2);
lcd.print(outaverage1,0);   //valeur moyenne sur 0.2seconde
lcd.print(" out_average"); 

 
  lcd.setCursor(0,3);
  attenuation=((outMax-outaverage1)/(entreeMax-127));
  lcd.print(attenuation,2);    
  lcd.print("=Attenuat ");

} // fin loop

Il y a une autre méthode pour déterminer sur les filtres FIR, « méthode d’échantillonnage de fréquence »
C’est-à-dire que le nombre d’échantillon est fixe en connaissant la fréquence d’entrée.
D’ailleurs avec 8 valeurs à 10 valeurs, cela est idéal.
Mais ce sera pour une autre histoire

la FFT (fast Fourriers transform discret ) et les filtres sont étroitement liés.

En effet, la FFT (analyse spectrale) permet de connaitre le niveau des signaux indésirables et les filtres de les atténuer.
L’amplitude des fréquences que l’on désire voir est déterminé par le nombre d’échantillon N avec l’équation suivante Fdesirée=Fe/N
Exemple avec une fréquence d’échantillon de 1kHz et N=16, la précision de l’abscisse sera de 62Hz avec une symétrie des amplitudes à fe/2=500Hz
Pour chaque raie Nfe/2, il faut faire faire 16 multiplication et additions de calculs de cosinus et de sinus. Puis en faire le module avec une division donc 202 opérations. Donc environ 2N^2, opérations pour chaque raie de fréquence désiré.
Donc au total, N^3, opérations.
Qui peut être divisé par 2 grâce à la transformée rapide de fourriers, donc N^3/2.
Donc avec 16 échantillons, cela donne 2048 calculs.
Mais avec 32 échantillons, 16384 calculs

Donc avec un nombre d’échantillon désiré, pour une certaine précision voulue :
Quel sera le temps de traitement du programme avec un ATMEGA 328 ?
Pourra-t-on faire une FFT en temps réel ?
Quelle sera la mémoire demandée ?

Evidemment, il existe des Library, sur Arduino (arduinoFFT, fix_FFT, adafruit FFT )

Connait-on les tenants et les aboutissaient de ces Library,
Voici la première Library avec quelques liens

https://www.norwegiancreations.com/2017/08/what-is-fft-and-how-can-you-implement-it-on-an-arduino/
Je n’ai pas trouvé le code source ???
C’est cela open source ???
Tient, il existe une nouvelle Library sur le calcul des complexe, cela pourrait être utile

Par contre, sur le lien suivant, il y a le code de la FFT, il est très très fort ce Fréderic Legrand
https://www.f-legrand.fr/scidoc/docimg/sciphys/arduino/spectrale/spectrale.html
https://pdfprof.com/Cours_Telecharger_Exercices_3.php?q=filtre+passe+bas

Nous allons faire un petit exemple avec 16 valeurs et une fréquence d’échantillon de 1000Hz.
Nous allons utiliser un signal carré car on connait son spectre
La valeur moyenne est de 512
L’harmonique 1 a une amplitude de 4512/PI=651
L’harmonique 2 sera nulle.
L’harmonique 3 a une amplitude de 4
512/(PI*3)=217
L’harmonique 4 a une amplitude de 0
L’harmonique 5 a une amplitude de 651/5=130
etc

Voici le code qui demande 35ms pour faire la FFT de seulement 16 valeurs
Et avec l’envoie des données cela demande 80ms, donc les données sont envoyés tous les 0.2s sur le terminal. Par contre avec 16 valeurs, cela ne prend que 25% des 2K de mémoire de l’ATMEGA 328.

#include <Wire.h>
#include <LiquidCrystal.h>
#include <SoftwareSerial.h>
#include <TimerOne.h>
//#include <math.h>
//# inclure  "Complex.h "
  
#define LED13    13 
      
LiquidCrystal lcd(9, 8, 4, 5, 6, 7);   // LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

#define PI 3.141592
unsigned    int    temps=0;
            byte   N=16;    // nombre d'echantilonn;
            byte k=0;
            bool flagaffichage;
            

          

float entreeMax;
float outMax;
float outaverage;
float outaverage1;
float out;


float  entree[16];          //declaration de la table des entrées
//float  entree[]={10,10,10,10,10,10,10,10,-10,-10,-10,-10,-10,-10,-10,-9};          
float  amplitude[8];       
  int   fe=1000;  //frequence d'echantilonnage  
 
float im;
float re;
float amplitudeRE;
float amplitudeIM;
            
void setup() {
  pinMode(LED13, OUTPUT);

  Timer1.initialize(1000);           //   pour 0.001s  1000   0.002s 2000
  Timer1.attachInterrupt(callback);   // attaches callback() as a timer overflow interrupt
  lcd.begin(20, 4);                   //modifier pour un afficheur 20x4
  
  Serial.begin(9600); 

  TCCR2B = (TCCR2B & 0b11111000) | 0x01;         //pin 3  32khz    http://playground.arduino.cc/Main/TimerPWMCheatsheet
lcd.setCursor(0,0);    
lcd.print("FFT");
lcd.setCursor(0,1);    
lcd.print(N);
lcd.print(" Echantillons");

}



// Interruptions  tous les 1ms fait par le timer1    fe=1000Hz***********************************
void callback()  {
//if ( digitalRead(13)== 1 ) {digitalWrite(13,LOW);}  else {digitalWrite(13,HIGH);}

temps++; 

if (flagaffichage==1)  {    
            entree[k]=(analogRead(A0)-512); k++;        
            if (k>=N) {k=0; }   //convertisseur 10 bits sous 5V   https://www.gammon.com.au/adc
                       }  
                                                                            
}//fin routine interrupt timer



///////////////////////////////////////////// Boucle correspondant à la fonction main 
void loop() {


//rafraichissement du spectre toutes les 0.2s   affichage des 16 echantillons
if (temps>=200)   {flagaffichage=0;         //stop la mesure
                   temps=0;
for (byte i=0; (i<(N));i++) {Serial.print(entree[i],0);Serial.print(";");}                    //affichage des 16 echantillons
Serial.println("");                 
digitalWrite(LED13,HIGH);
for (byte j=0; j <(N); j++) {amplitude[0]=((entree[j])/N);}   //valeur moyenne f=0Hz
Serial.print("0");Serial.print(";");Serial.println(amplitude[0],0);

for (byte i=1; (i<(N/2));i++) {amplitudeRE=0;amplitudeIM=0;             //calcul des 8 premiers raies spectrales
  for (byte i1=0; (i1<N); i1++) {amplitudeRE=entree[i1]*cos(2*PI*i*i1/16)+amplitudeRE;}   //calcul des amplitudes pour chaque raies, ma partie reel                               
  for (byte i1=0; (i1<N); i1++) {amplitudeIM=entree[i1]*sin(2*PI*i*i1/16)+amplitudeIM;}                             
Serial.print(fe*i/N);Serial.print(";");                                                       
amplitude[i]=(sqrt(pow(amplitudeRE,2)+pow(amplitudeIM,2))/(N/2));Serial.println(amplitude[i],0);   
                             }
digitalWrite(LED13,LOW);

}//fin temps 200
flagaffichage=1;


} // fin loop

les resultats sur le post suivant

Sur la figure suivante, pour une fréquence de 62Hz mesurée, le spectre correspond bien à la théorie. Etant donné que le signal est unilatéral autour de 512, on a retiré dans le programme cette valeur comme pour un signal alternatif. Donc la valeur moyenne sera porche de 0 et on de 512.
Sur le Virtual terminal
La première grande ligne correspond aux 16 échantillons mesurés.
La deuxième ligne à la valeur moyenne raie du spectre à 0hz
La troisième ligne la raie de la fréquence de 62Hz=fe/16 et on retrouve la valeur de l’harmonique 1
La quatrième ligne la raie de la fréquence de 125Hz=2.fe/16 correspondant à l’harmonique 2
La cinquième ligne la raie de la fréquence de 187Hz correspondant à l’harmonique 3
etc

Pour une fréquence de 130Hz mesurée, le spectre correspond bien à la théorie
Sur le Virtual terminal :
La première ligne à la valeur moyenne
La deuxième ligne pour 62 Hz il n’y a rien
La troisième ligne est maintenant 2Fe/16 est l’harmonique 1
6fe/15 est devenu l’harmonique 3

Pour une fréquence de 99Hz, étant donné l’imprécision de raies spectrales.
L’amplitude de l’harmonique 1 de 100Hz va se répercuter entre 62Hz et surtout 125 Hz
L’amplitude de l’harmonique 2 de 200Hz qui est nulle sur aucune raie mais il y a des valeurs autour de la raies de 187Hz et 250Hz
L’amplitude de l’harmonique 3 de 300Hz va se répercuter sur la raie de 312 Hz
etc

Par conséquent, on peut mettre les valeurs de la FFT, soit sur un LCD graphique, ou grâce au terminal prendre les données et les mettre sur tableur

Evidemment, on peut enregistrer le signal et faire la FFT par un tableur, matlab et autre

En effet, il est possible de vérifier ces valeurs avec Excel comme on peut l’observer sur la figure suivante
Un lien qui permet de savoir comment les calculs sont faits dans Excel avec le toolpak analys

Conclusion l’imprécision des raie spectrales séparée de 62Hz donne une imprécision importante surtout si l’harmonique 1 du signal traité est loin d’un multiple des raies Fe/16.
Par conséquent, il faudrait choisir une fréquence d’échantillonnage qui soit un multiple de la fréquence du signal traité
De plus, Il faudrait quand même essayer de faire avec 32 échantillons, voir 64 échantillons….

Pour faire une FFT mais pas discrétisé mais numérique
Une autre solution était de faire :

  • 1 filtre passe bas pour avoir avoir la valeur moyenne
  • 7 filtres passe bande de fréquence de N*Fe/16 avec une bande passante de fe/16
    Chaque filtre ayant un calcul de 0.3ms, cela donne 2.1ms donc 16 fois plus petit que la FFT précédente.

Evidement un ESP 32, ou une DUE serait beaucoup mieux pour faire un la FFT par rapport à un ATMEGA 328 qui est un peu obsolète mais on peut observer les limites

Nous allons vous présenter le filtre passe-haut 2nd ordre.
Nous avons utilisé le lien suivant pour trouver les coefficients et le gain pour une fréquence d’échantillonnage de 1000 Hz et une fréquence de coupure de 50Hz.
https://www-users.cs.york.ac.uk/~fisher/mkfilter/

Voici le code :

#include <Wire.h>

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



#define PWM3   3      //   timer2   
#define LED13    13     

LiquidCrystal lcd(9, 8, 4, 5, 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;
            const float b0 =1;
            const float b1 =-2;
            const float b2=1;

            float a2= -0.6413515 ;
            float a1=1.561018 ;
           
         
            float Gain=2.5;

            float  fe=1000;  //frequence d'echantilonnage
            float  fc=50;  //frequence de coupure desiréé   

           float entreeMax;
float outMax;
float attenuation;

                   

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

  Timer1.initialize(1000);           // 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
  lcd.begin(16, 2);                   //modifier pour un afficheur 20x4


  Serial.begin(9600);

  TCCR2B = (TCCR2B & 0b11111000) | 0x01;         //pin 3  32khz    http://playground.arduino.cc/Main/TimerPWMCheatsheet

 //gain donné par le soft pour le calcul des coefficients
 
}


// Interruptions  tous les 1ms fait par le timer1    fe=1000Hz***********************************
void callback()  {
//if ( digitalRead(13)== 1 ) {digitalWrite(13,LOW);}  else {digitalWrite(13,HIGH);}
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; 
     
// ---- filtre passe haut Butterwoth , fechantillon=1000Hz------       

entree2=entree1;      //entree(n-2)
entree1=entree;       //entree(n-1)

sortie2=sortie1;      //sortie(n-2)
sortie1=sortie;       //sortie(n-1)
  
sortie=(b0*entree+entree1*b1+entree2*b2+sortie1*a1+sortie2*a2)    ;


out=(sortie/Gain)+127;
         
if (out<0) {out=0;}
if (out>255) {out=255;}           
analogWrite(PWM3,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("passe haut 2eme");
  lcd.setCursor(0,1);
  attenuation=((outMax-127)/(entreeMax-127));   
  lcd.print("A=");
  lcd.print(attenuation,2);
   lcd.print("  ");

   



} // fin loop

Nous avons effectué quelques simulations sur ISIS pour mesurer l’atténuation en fonction de la fréquence:
Voici quelques valeurs prélevées en simulation:

Nous constatons que plus on augmente la fréquence, plus l’atténuation se rapproche de 1.
Nous avons par la suite fait des testes avec des fréquences plus élevées et à partir de 75Hz, l’atténuation redevient de plus en plus importante. Pour corriger cette anomalie, nous avons modifié la valeur des coefficients en passant de 7 chiffres après la virgule à 3.

Les résultats:

Avec cette modification, nous observons qu’il n’y a pas pratiquement pas de changements, raison pour laquelle nous allons passer à l’ordre 3 pour améliorer notre filtre.

Bonjour monsieur,
Voici le nouveau programme et le schéma ISIS.

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


#define PWM3   3      //   timer2    
#define LED13    13      
LiquidCrystal lcd(9, 8, 4, 5, 6, 7);   // LiquidCrystal lcd(rs, en, d4, d5, d6, d7);


unsigned    int    temps=0;

            float  entree=0;
            float  entree1=0;
            float  entree2=0;
            float  entree3=0;
            float  entree4=0;

            float entreeMax;
            float entreeMin;
            float entreeMoy;
            float phase;
            float attenuation;
            float P;        //pour mesurer le dephasage
            float PMax;
            float PMin;
            float PMoy;
            
            float outMax;
            float Div;
            float factorPower;
//           float outMin;
//           float outMoy;
        
            float  sortie=0;
            float  sortie1=0;
            float  sortie2=0;
            float  sortie3=0;
            float  sortie4=0;
            
            float  out=0;

            const float b1 =4;            
            const float b2 =6;
            const float b3 =4;
            const float b4 =1;
            
            float a1=-3.181;
            float a2=3.86;
            float a3=-2.112;
            float a4=0.438;  
                      
            float denominateur=0;
            float Gain=3200;

            float  fe=1000;  //frequence d'echantilonnage  
            float  fc=50;  //frequence de coupure desiréé    
            

                    

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

  Timer1.initialize(1000);           // 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
  lcd.begin(20, 4);                   //modifier pour un afficheur 20x4
  
  Serial.begin(9600);

  TCCR2B = (TCCR2B & 0b11111000) | 0x01;         //pin 3  32khz    http://playground.arduino.cc/Main/TimerPWMCheatsheet
 
}



// Interruptions  tous les 1ms fait par le timer1    fe=1000Hz***********************************
void callback()  {
digitalWrite(LED13,HIGH);  //permet de mesurer à l'oscillo, le temps du calcul du filtre et le temps de la routine d'interruption
temps++;

entree=analogRead(A0); //convertisseur 10 bits sous 5V      https://www.gammon.com.au/adc
  
      
// ---- exemple filtre passe pas Butterwoth   pour fc=50hz,  fechantillon=1000Hz------    
entree4=entree3;      //entree(n-3)    
entree3=entree2;      //entree(n-3)
entree2=entree1;      //entree(n-2)
entree1=entree;       //entree(n-1)

sortie4=sortie3;      //sortie(n-3)
sortie3=sortie2;      //sortie(n-3)
sortie2=sortie1;      //sortie(n-2)
sortie1=sortie;       //sortie(n-1)

sortie=(entree+entree1*b1+entree2*b2+entree3*b3+entree4*b4-sortie1*a1-sortie2*a2-sortie3*a3-sortie4*a4)    ;
out=(sortie/Gain);
        
//rafraichissement toutes les 1s
if (temps>=1000)   {entreeMax=0;outMax=0;entreeMin=1023;PMin=1023;PMax=0;temps=0;}
if (entree> entreeMax) {entreeMax = entree; }  // Enregistrer la valeur maximale du capteur
if (out> outMax) {outMax = out; }  // Enregistrer la valeur maximale du capteur  
if (entree< entreeMin) {entreeMin = entree; }  // Enregistrer la valeur mini  
entreeMoy=(entreeMax+entreeMin)/2;
P=(entree-entreeMoy)*(out-entreeMoy);               //power instantanée  outmoy=entreemoy
if (P< PMin) {PMin = P; }
if (P> PMax) {PMax = P; }

out=(out/4)  ;       //mise à l'echelle 10 bits en entrée et 8 bits en sortie
if (out<0) {out=0;}
if (out>255) {out=255;}                                    
analogWrite(PWM3,out);
digitalWrite(LED13,LOW);  

}//fin routine



///////////////////////////////////////////// Boucle correspondant à la fonction main
void loop() {  
PMoy=((PMax+PMin)/2);   //valable seulement si
Div=(outMax-entreeMoy)*(entreeMax-entreeMoy);
factorPower=(((PMoy*2)/Div));


lcd.setCursor(0,0);
lcd.print("Facteur");
lcd.print(factorPower,2);

lcd.setCursor(0,1);
lcd.print("atenuation");
  
lcd.print((outMax-entreeMoy)/(entreeMax-entreeMoy));
lcd.print("  ");

phase=(acos(factorPower))*180/3.1416;   //dephasage par la methode des puissance

  lcd.setCursor(0,2);
  lcd.print("phase=");
  lcd.print(phase,1);
  lcd.print((char)223);
  lcd.print("  ");
} // fin loop

Effectivement, cette atténuation à partir de 100Hz est bizarre
De plus, il y a un écart important entre la fréquence de coupure désirée de 50hz et la fréquence de coupure réelle que l’on retrouve aussi en théorie et sur le site qui vous a donné les pôles.


Car à la fréquence de coupure, normalement, il faut une atténuation de 0.707.
Donc pour avoir fréquence de coupure désirée, il faut légèrement tricher en demandant de mettre fc un peu supérieur à celle désiré.
J’ai fait quelques tests pour avoir réellement la fréquence de coupure à 0.707

//calcul pour Fc 50 Hz    fitre passe haut   mais attenuation à 1 pour 50z  mais  d'attenation au dela de 100Hz      
//             float a1=1.561; float a2=-0.641;  float Gain=2.5;   
//calcul pour  Fc 75 Hz    fitre passe haut   mais attenuation à 0.9 pour 50hz plus d'attenation au dela de 100Hz         
//             float a1=1.3489; float a2=-0.514;  float Gain=2;  
//calcul pour  Fc 80 Hz    mais attenuation à 0.84 pour 50hz pas d'attenation au dela de 100Hz  
//calcul pour  Fc 90 Hz    attenuation à 0.7 pour 50hz 
             float a1=1.243; float a2=-0.46;  float Gain=2;        
//calcul pour  Fc 100 Hz   mais attenuation à 0.5 pour 50z pas       
//             float a1=0.942; float a2=-0.3333;  float Gain=1.5;

On peut observer qu’il n’y a pas beaucoup de différence sur les valeurs de coefficients malgré la variation de la fréquence de coupure par contre il n’y a plus l’atténuation au-delà de 100Hz.

En synthèse, il faut souvent faire de nombreux essais pour avoir ce que l’on désire et pouvoir faire une conclusion. Par dichotomie, on arrive à trouver la bonne valeur des coefficients.
De plus, si la fréquence de coupure est trop éloignée de la fréquence de coupure, on n’a pas un passe haut mais un passe bande.

Comme pour le filtre de deuxième ordre passe bas, il est possible de faire le calcul des coefficients par l’Arduino directement.
D’ailleurs, voici le code avec les calculs du coefficient.

#include <Wire.h>
//#include <LiquidCrystal_I2C.h>
#include <LiquidCrystal.h>
#include <SoftwareSerial.h>
#include <TimerOne.h>
//#include <math.h>


#define PWM3   3      //   timer2    
#define LED13    13       
LiquidCrystal lcd(9, 8, 4, 5, 6, 7);   // LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
// Configuration des variables
//LiquidCrystal_I2C lcd(0x27, 20, 4);     //A0, A1, A2 non shunter 20x4ligne


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 entreeMax;
float outMax;
float attenuation;

float fe=1000;
float fc=90;
float denominateur;


//calcul pour Fc 50 Hz    fitre passe haut   mais attenuation à 1 pour 50z  mais  d'attenation au dela de 100Hz      
//             float a1=1.561; float a2=-0.641;  float Gain=2.5;   
//calcul pour  Fc 75 Hz    fitre passe haut   mais attenuation à 0.9 pour 50hz plus d'attenation au dela de 100Hz         
//             float a1=1.3489; float a2=-0.514;  float Gain=2;  
//calcul pour  Fc 80 Hz    mais attenuation à 0.84 pour 50hz pas d'attenation au dela de 100Hz  
//calcul pour  Fc 90 Hz    attenuation à 0.7 pour 50hz 
             float a1=1.243; float a2=-0.46;  float Gain=2;        
//calcul pour  Fc 100 Hz   mais attenuation à 0.5 pour 50z pas       
//             float a1=0.942; float a2=-0.3333;  float Gain=1.5;                
               

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

  Timer1.initialize(1000);           //   pour 0.001s  1000   0.002s 2000
  Timer1.attachInterrupt(callback);   // attaches callback() as a timer overflow interrupt
  lcd.begin(20, 4);                   //modifier pour un afficheur 20x4
  
  Serial.begin(9600); 
  TCCR2B = (TCCR2B & 0b11111000) | 0x01;         //pin 3  32khz    http://playground.arduino.cc/Main/TimerPWMCheatsheet

denominateur=(fe*fe+fe*fc*PI*sqrt(2)+fc*fc*PI*PI);
a1=-(fc*fc*PI*PI-fe*fe)*2/denominateur;
a2=-(fe*fe*fe*fe+(fc*fc*fc*fc*PI*PI*PI*PI))/(denominateur*denominateur)         ;                             //pow(base, exponent)

  lcd.setCursor(0,0);    
  lcd.print("passe haut 2eme");
  lcd.setCursor(0,1);
  lcd.print("fc");
  lcd.print(fc,0);       
  lcd.print("Hz");
  lcd.setCursor(0,2);
  lcd.print("a1:");
  lcd.print(a1,3); 
  lcd.setCursor(10,2);
  lcd.print("a2:");
  lcd.print(a2,3);      
}



// Interruptions  tous les 1ms fait par le timer1    fe=1000Hz***********************************
void callback()  {
//if ( digitalRead(13)== 1 ) {digitalWrite(13,LOW);}  else {digitalWrite(13,HIGH);}
//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;          //mise à l'echelle 10 bits en entrée et 8 bits en sortie+chiffre significatif    
      
// ---- exemple filtre passe pas Butterwoth   ,  fechantillon=1000Hz------    
//entree4=entree3;      //entree(n-4)
//entree3=entree2;      //entree(n-3)
entree2=entree1;      //entree(n-2)
entree1=entree;       //entree(n-1)

//sortie4=sortie3;
//sortie3=sortie2;      //sortie(n-3)
sortie2=sortie1;      //sortie(n-2)
sortie1=sortie;       //sortie(n-1)

sortie=(entree-2*entree1+entree2+sortie1*a1+sortie2*a2);    //filtre passe haut 2éme ordre
//sortie=(entree4-2*entree2+entree-a4*sortie4-a3*sortie3-a2*sortie2-a1*sortie1);  //filtre passe bande second ordre
//sortie=(entree+entree1*b1+entree2*b2+entree3*b3+entree4*b4-sortie1*a1-sortie2*a2-sortie3*a3-sortie4*a4)    ;
//sortie=(entree+entree1*b1+entree2*b2+entree3*b3-sortie1*a1-sortie2*a2-sortie3*a3) ;   //filtre passe pas recursif ordre 3
//sortie=(entree+entree1*b1+entree2*b2-sortie1*a1-sortie2*a2) ;                        //filtre passe pas recursif ordre 2
//sortie=(entree*0.19+sortie*0.81);                                 //filtre passe bas    a1=0.992 pour une constante de temps de 10ms  Te=1ms durre algo 150us                                                                                   //filtre passe pas recursif ordre 1
//out= entree  ;                                                                                     //gain unitaire
out=(sortie/Gain+127);     //mise à l'echelle 10 bits en entrée et 8 bits en sortie     
if (out<0) {out=0;}
if (out>255) {out=255;}                  
analogWrite(PWM3,out);          //    
//Serial.println(out);
temps++;
//rafraichissement toutes les 0.2s
if (temps>=200)   {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,4);
  attenuation=((outMax-127)/(entreeMax-127));
  lcd.print(attenuation,2);    
  lcd.print("=Attenuat");

} // fin loop

Pour Kadhim, lit tout le poste….puis demande à tes camarades, car tu as rien compris à l’arduino et sur le cablage electric

filtre passe-haut 3éme ordre
Notre fréquence de coupure désirée est de 50Hz.




Après simulation, nous avons constaté que la valeur de l’atténuation ne correspond pas à celle attendue(0.7). Par la suite nous avons modifié les valeurs du gain et de la fréquence de coupure(FC) pour avoir une atténuation de 0.7 à la fréquence de coupure.
Voici le code

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


#define PWM3   3      //   timer2   
#define LED13    13     

LiquidCrystal lcd(9, 8, 4, 5, 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;
            const float b0 =-1;
            const float b1 =3;
            const float b2=-3;
            const float b3=1;

            float denominateur=0;
            float a1=2.374 ;
            float a2=-1.929 ;
            float a3=0.532; 
         
            float Gain=8;

            float  fe=1000;  //frequence d'echantilonnage
            float  fc=20;  //frequence de coupure desiréé   

           float entreeMax;
float outMax;
float attenuation;

                   

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

  Timer1.initialize(1000);           // 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
  lcd.begin(16, 2);                   //modifier pour un afficheur 20x4


  Serial.begin(9600);

  TCCR2B = (TCCR2B & 0b11111000) | 0x01;         //pin 3  32khz    http://playground.arduino.cc/Main/TimerPWMCheatsheet

 
}


// Interruptions  tous les 1ms fait par le timer1    fe=1000Hz***********************************
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; 
     
// ---- filtre passe haut Butterwoth , fechantillon=1000Hz------       
entree3=entree2;      //entree(n-3)
entree2=entree1;      //entree(n-2)
entree1=entree;       //entree(n-1)

sortie3=sortie2;      //sortie(n-3)
sortie2=sortie1;      //sortie(n-2)
sortie1=sortie;       //sortie(n-1)
  
sortie=(b0*entree+b1*entree1+b2*entree2+b3*entree3+a1*sortie1+a2*sortie2+a3*sortie3)    ;

out=(sortie/Gain)+127;
         
if (out<0) {out=0;}
if (out>255) {out=255;}           
analogWrite(PWM3,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("passe haut 3eme");
  lcd.setCursor(0,1);
  attenuation=((outMax-127)/(entreeMax-127));   
  lcd.print("A=");
  lcd.print(attenuation,2);
   lcd.print("  ");
} // fin loop

Avec F=20Hz, nous avons ceci:

Seulement à partir de 70 Hz, nous remarquons une nouvelle atténuation du signal alors que celle-ci doit rester constante à 1.
Avec 71Hz:

Avec 96 Hz:

L’atténuation en fonction de la fréquence:

Pour corriger ces erreurs, nous allons utiliser une autre méthode qui nous permet de calculer les valeurs des coefficients.

Suite du filtre passe-haut 3éme ordre

Cette-fois nous allons calculer les valeurs des coefficients et vérifier si l’atténuation restera constante une fois qu’elle soit égale à 1.

a1=0.893; a2=-0.384; a3=0.473;

Voici le nouveau code:

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


#define PWM3   3      //   timer2   
#define LED13    13     

LiquidCrystal lcd(9, 8, 4, 5, 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;
            const float b0 =-1;
            const float b1 =3;
            const float b2=-3;
            const float b3=1;

            float denominateur=0;
            float a1 ;
            float a2 ;
            float a3; 
         
            float Gain=1.1;

            float  fe=1000;  //frequence d'echantilonnage
            float  fc=60;  //frequence de coupure desiréé   

           float entreeMax;
float outMax;
float attenuation;

                   

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

  Timer1.initialize(1000);           // 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
  lcd.begin(16, 2);                   //modifier pour un afficheur 20x4
denominateur=(fe*fe+fe*fc*PI+fc*fc*PI*PI);
a1=((fe*fe-fc*fc*PI*PI)*2/denominateur)-(fe-fc*PI)/(fe+fc*PI);
a2=-(((fe*fe-fc*fc*PI*PI)*2)/denominateur)*((fe-fc*PI)/(fe+fc*PI))+((fe*fe*fe*fe+fc*fc*fe*fe*PI*PI)+(fc*fc*fc*fc*PI*PI*PI*PI))/(denominateur*denominateur);
a3=(((fe*fe*fe*fe+fc*fc*fe*fe*PI*PI)+(fc*fc*fc*fc*PI*PI*PI*PI))/(denominateur*denominateur))*((fe-fc*PI)/(fe+fc*PI));

  Serial.begin(9600);

  TCCR2B = (TCCR2B & 0b11111000) | 0x01;         //pin 3  32khz    http://playground.arduino.cc/Main/TimerPWMCheatsheet

 
}


// Interruptions  tous les 1ms fait par le timer1    fe=1000Hz***********************************
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; 
     
// ---- filtre passe haut Butterwoth , fechantillon=1000Hz------       
entree3=entree2;      //entree(n-3)
entree2=entree1;      //entree(n-2)
entree1=entree;       //entree(n-1)

sortie3=sortie2;      //sortie(n-3)
sortie2=sortie1;      //sortie(n-2)
sortie1=sortie;       //sortie(n-1)
  
sortie=(b0*entree+b1*entree1+b2*entree2+b3*entree3+a1*sortie1+a2*sortie2+a3*sortie3)    ;

out=(sortie/Gain)+127;
         
if (out<0) {out=0;}
if (out>255) {out=255;}           
analogWrite(PWM3,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("passe haut 3eme");
  lcd.setCursor(0,1);
  attenuation=((outMax-127)/(entreeMax-127));   
  lcd.print("A=");
  lcd.print(attenuation,2);
   lcd.print("  ");
} // fin loop

Avec fc=60 Hz, on obtient une atténuation de 0.7 comme vous pouvez le voir ci-dessous:

Avec f=110 Hz:

Avec f=230 Hz:

On voit qu’en utilisant les valeurs théoriques, on n’obtient des résultats correctes:
-une atténuation de 0.7 à la fréquence de coupure.
-A partir de 110 Hz, on obtient plus d’atténuation.

La courbe de l’atténuation en fonction de la fréquence:

1 Like

Attention : il faut d’abord ajuster le gain pour avoir une atténuation de 1 pour une fréquence supérieure à 2 fois les fréquences de coupure.
Puis avec les valeurs des coefficients vérifier l’atténuation du filtre.

Sur la courbe du post précèdent à F=230Hz, on peut observer une saturation de la sortie donc il y a un gain légèrement trop important.
De plus, c’est bizarre que les coefficients soit différents entre le calcul et « filtre design result »
Est qu’il y a un bug pour le filtre passe haut de “filtre design result” ???
Il est aussi possible d’avoir les coefficients avec Matlab ? Et d’évaluer le filtre

Bref,
Le temps pour déterminer la FFT et la mémoire demandée du programme du 22 avril de ce poste est de

  • pour 16 échantillons est de 80ms avec 25% de la mémoire des 2k de l’ATMEGA 328
  • pour 32 échantillons est de 160ms avec 33% de la mémoire
  • pour 64 échantillons est de 650ms avec 39% de la mémoire
    Sachant qu’il y a aussi l’envoie des donnée de 8 raies pour 16 échantillons, ou 16 raies pour 32 échantillons….ce qui prend aussi un peu de temps mais négligeable par rapport au calcul de la FFT.

Donc, il faudrait mieux choisir une fréquence d’échantillonnage qui soit un multiple de la fréquence du signal traité
Exemple si l’on désire 8 raies ce qui est pas mal puisqu’on mesurera l’harmonique 7, donc il faudra 16 échantillons seulement avec une fréquence d’échantillonnage Fe=82fréquence signal

Avec 16 échantillons, donc pour 50Hz de signal d’entrée, fe=800hz, Tinterrup=periodesignal/16=1250.
On a bien l’harmonique 1 à 643 puis toutes les harmoniques impaires sont bien nulles.
Exemple pour 62Hz, toutes les harmoniques impaires sont bien nulles et les amplitudes des harmoniques sont correctes

Par contre, parfois, il y a des loupés en mesure, Exemple pour 50Hz,
c’est comme si, le rapport cyclique n’est pas égale à 0.5.

Pour 100Hz de signal d’entrée, fe=1600 Hz, Tinterrup=periodesignal/16=1600.
Les harmoniques impaires ne sont pas nulles surtout l’harmonique 2.

Pour 32 échantillons, pour 16 raies, la fréquence d’échantillonnage doit être doublée
Donc, on a bougé le prescaler de la mesure analogique de 128 à 16, ce qui a permis de diminuer le temps de la mesure analogique de 1ms à 0.02ms
Mais pour 100Hz, l’amplitude de l’harmonique 1 n’est pas correcte ?
Et il y a une symétrie autour de 800Hz pourtant, fe est bien est bien correcte.

De même pour 50Hz, il y a une symétrie par rapport à l’harmonique 8

Bref, de toutes façon, 16 raies n’est pas viable à cause de la fréquence d’échantillonnage qui devient trop importante.

Voici le nouveau code avec la fréquence d’échantillonnage variable

#include <Wire.h>
#include <LiquidCrystal.h>
//#include <SoftwareSerial.h>
#include <TimerOne.h>
//#include <math.h>
//# inclure  "Complex.h "
  
#define LED13    13 
      
LiquidCrystal lcd(9, 8, 4, 5, 6, 7);   // LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

#define PI 3.141592
unsigned    int    temps=0;
unsigned    int    Time=0;
unsigned    int    TO;
unsigned    int  Tinterupt;
            float fmesure;
            byte k=0;
            byte J=0;
            bool flagaffichage;       

float entreeMax;
float outMax;
float outaverage;
float outaverage1;
float out;

 byte   N=16;    // nombre d'echantilons;
float  entree[16];          //declaration de la table des entrées         
float  amplitude[8];       
  int   fe=500;  //frequence d'echantilonnage  
 
float im;
float re;
float amplitudeRE;
float amplitudeIM;
            
void setup() {
  pinMode(LED13, OUTPUT);

  Timer1.initialize(1000);           //   pour 0.001s  1000   0.002s 2000   donc valeur=10^6/fe
  Timer1.attachInterrupt(callback);   // attaches callback() as a timer overflow interrupt
  lcd.begin(20, 4);                   //modifier pour un afficheur 20x4

attachInterrupt(0, interrup2, FALLING);   // broche2
TO = micros();  
  
  Serial.begin(9600); 

  TCCR2B = (TCCR2B & 0b11111000) | 0x01;         //pin 3  32khz    http://playground.arduino.cc/Main/TimerPWMCheatsheet

  ADCSRA &= ~(bit (ADPS0) | bit (ADPS1) | bit (ADPS2)); // clear prescaler bits  car il est à 128
  ADCSRA |= bit (ADPS2);                           //  16    mesure analogique passe de 110us à 20us

lcd.setCursor(0,0);    
lcd.print("FFT");
lcd.setCursor(0,1);    
lcd.print(N);
lcd.print(" Echantillons");
}



void interrup2() // la fonction appelée par l'interruption externe n°0
{
Time=(micros()-TO);  //mesure du temps
TO = micros();
}



// Interruptions  varailble fait par le timer1  16*Fsignal mesure    ***********************************
void callback()  {
//if ( digitalRead(13)== 1 ) {digitalWrite(13,LOW);}  else {digitalWrite(13,HIGH);}

temps++; 
if (flagaffichage==1)  { //digitalWrite(LED13,HIGH);   
            entree[k]=(analogRead(A0)-512); k++;        
            if (k>=N) {k=0; }   //convertisseur 10 bits sous 5V   https://www.gammon.com.au/adc
                        //digitalWrite(LED13,LOW); 
                        }  
                                                                            
}//fin routine interrupt timer



///////////////////////////////////////////// Boucle correspondant à la fonction main 
void loop() {
//rafraichissement du spectre toutes les 0.2s   affichage du spectre

if (temps>=200)   {flagaffichage=0;         //stop la mesure
                   temps=0;
                  
for (byte i=0; (i<(N));i++) {Serial.print(entree[i],0);Serial.print(";");}                    //affichage des N echantillons
Serial.println("");                 
//digitalWrite(LED13,HIGH);
for (byte j=0; j <(N); j++) {amplitude[0]=((entree[j])/N);}   //valeur moyenne f=0Hz
Serial.print("0");Serial.print(";");Serial.println(amplitude[0],0);

for (unsigned int i=1; (i<(N/2));i++) {amplitudeRE=0;amplitudeIM=0;                               //calcul des N/2 premiers raies spectrales
  for (byte i1=0; (i1<N); i1++) {amplitudeRE=entree[i1]*cos(2*PI*i*i1/16)+amplitudeRE;}   //calcul des amplitudes pour chaque raies, ma partie reel                               
  for (byte i1=0; (i1<N); i1++) {amplitudeIM=entree[i1]*sin(2*PI*i*i1/16)+amplitudeIM;}                             
            Serial.print(fe*i/N);Serial.print(";");                                                       
            amplitude[i]=(sqrt(pow(amplitudeRE,2)+pow(amplitudeIM,2))/(N/2));Serial.println(amplitude[i],0);   
                                }
//digitalWrite(LED13,LOW);
//for (J=0; (J<(N/2));J++) {Serial.print(amplitude[J],0);Serial.print(";");}   //affichage du spectre sur le terminal 

flagaffichage=1;
lcd.setCursor(0,2);
fmesure=1000000/Time;        //mesure de la frequence du signal
lcd.print(fmesure,0);
lcd.print("Hz  ");
Tinterupt=Time/N;           //changement de la frequence d'echantillonage  
if (Tinterupt<250)          // limitation à 250 correspondant à 4KHz
Timer1.initialize(Tinterupt);   
fe=N*fmesure;               // cacul de fe pour l'afichage du spectre

} //fin temps 200



} // fin loop

Il faudrait mieux comparer la FFT avec le spectre fait avec des filtres bande passantes du 2 ordre.

1 Like

Voilà le programme du spectre avec 5 raies spectrales qui demande seulement 0.6ms de calcul avec 28% de la mémoire dynamique. Les valeurs sont sur 8bits avec une tension de référence de 5V
Les variables ainsi que les données ont été déclarés sous forme de tableau pour faciliter la programmation.

On peut observer avec un signal carré à 62Hz d’amplitude de 2.5V avec un rapport cyclique de 0.5, les valeurs sont
Une valeur moyenne autour de 127 aussi à 2.5V
L’amplitude a 62Hz est de 42.5V255/(PI*5)=161 donc correcte
L’amplitude a 128Hz normalement à 0
L’amplitude a 187Hz normalement à 161/3=53 or on a 42
L’amplitude a 250Hz normalement à 0

avec un signal carré à 62Hz d’amplitude de 1.5V avec un rapport cyclique de 0.5, les valeurs sont
Une valeur moyenne autour de 77 aussi à 2.5V
L’amplitude a 62Hz est de 41.5V255/(PI*5)=96
L’amplitude a 128Hz normalement à 0
L’amplitude a 187Hz normalement à 96/3=32
L’amplitude a 250Hz normalement à 0

avec un signal carré à 120Hz d’amplitude de 2.5V avec un rapport cyclique de 0.5, les valeurs sont
Une valeur moyenne autour de 135 donc très proche de 2.5V
L’amplitude à 62Hz devrait être de 0 mais il y a 29
L’amplitude a 125Hz devrait être de 42.5V255/(PI*5)=161 mais on a 109
L’amplitude a 187Hz normalement 0
L’amplitude a 250Hz normalement à 53

Donc cela marche pas si mal,
D’ailleurs, voici la figure pour 190Hz

Evidemment, si la fréquence d’entrée et entre 2 raies, alors l’amplitude est repartie entre ces 2 raies

Donc, comme pour la FFT, il faudrait que la fréquence de chaque raie soit un multiple de la fréquence d’entrée mais dans ce cas, il faut calculer les coefficients des programmes qui dependent de la frequence d’echantillonnage.
mais il serait possible de faire des boucles FOR…
une autre methode serait d’enregistrer pendant un certain temps le signal puis de faire le spectre avec les filtres.

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


#define PWM3   3      //   timer2    
#define LED13    13       
LiquidCrystal lcd(9, 8, 4, 5, 6, 7);   // LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
// Configuration des variables
//LiquidCrystal_I2C lcd(0x27, 20, 4);     //A0, A1, A2 non shunter 20x4ligne


unsigned    int    temps=0; 
unsigned    int    temps1=0; 
            byte   N;

float  entree; float entree1; float entree2;  float entree3; float entree4;
float sortie; float sortie1; float sortie2;                                                         //variable  pour la raie N°0
float sPbas; float s1Pbas; float s2Pbas; float sortie_sPbas;       
float Sbande; float Sbande1 ; float Sbande2;  float Sbande3; float Sbande4; float sortie_sPbande;   //variable  pour la raie N°1 à 62Hz
//float a[5]={3.261031, -4.156910, 2.462596, -0.576610, 33 };   //a1,a2,a3,a4,gain fc1=31Hz  fo=62Hz  fc2=93Hz
float a[5]={3.4, -4.558605, 2.843214,-0.7008967, 74 }; //fc1=42Hz  fo=62Hz  fc2=82Hz

float bandeR2[6];    //variable  pour la raie N°2
float aH2[5]={2.4802654818, -3.0116544500, 1.8641899317,-0.5715251515, 33 }; //fc1=94Hz  fo=125Hz  fc2=157Hz 

float bandeR3[6];    //variable  pour la raie N°3
float aH3[5]={1.379648, -1.941475, 1.041854,-0.576610, 33 }; //fc1=155Hz  fo=186Hz  fc2=217Hz 

float bandeR4[6];    //variable  pour la raie N°5
float aH4[5]={0, -1.4754804436, 0,-0.5879, 36 }; //fc1=219Hz  fo=250Hz  fc2=281Hz 

float spectre[5];       //amplitude sPbas0Hz; Sbande62Hz; Sbande125Hz.......

float out;
float numerateur;

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

  Timer1.initialize(1000);           //  
  Timer1.attachInterrupt(callback);   // attaches callback() as a timer overflow interrupt
  lcd.begin(20, 4);                   //modifier pour un afficheur 20x4
  
  Serial.begin(9600); 
  TCCR2B = (TCCR2B & 0b11111000) | 0x01;         //pin 3  32khz    http://playground.arduino.cc/Main/TimerPWMCheatsheet
 
}



// Interruptions  tous les 1ms fait par le timer1    fe=1000Hz***********************************
void callback()  {
//if ( digitalRead(13)== 1 ) {digitalWrite(13,LOW);}  else {digitalWrite(13,HIGH);}
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;          //mise à l'echelle 10 bits en entrée et 8 bits en sortie+chiffre significatif    
       
entree4=entree3;      //entree(n-4)
entree3=entree2;      //entree(n-3)
entree2=entree1;      //entree(n-2)
entree1=entree;       //entree(n-1)

numerateur=entree4-2*entree2+entree;

//filtre passe bas 2 butteworth  ordre fc=10hz   fechantillon=1000Hz------   
s2Pbas=s1Pbas;
s1Pbas=sPbas;     
sPbas=(entree+entree1*2+entree2-s2Pbas*0.915+s1Pbas*1.91123) ;             //filtre passe pas recursif ordre 2 
sortie_sPbas=(sPbas/1059);

//filtre passe bande second ordre  fo 62Hz
Sbande4=Sbande3; 
Sbande3=Sbande2; 
Sbande2=Sbande1; 
Sbande1=Sbande;
//Sbande=(entree4-2*entree2+entree+a[3]*Sbande4+a[2]*Sbande3+a[1]*Sbande2+a[0]*Sbande1);
Sbande=(numerateur+a[3]*Sbande4+a[2]*Sbande3+a[1]*Sbande2+a[0]*Sbande1); 
sortie_sPbande=(Sbande/a[4]);

//filtre passe bande second ordre  fo 125Hz
bandeR2[4]=bandeR2[3]; 
bandeR2[3]=bandeR2[2]; 
bandeR2[2]=bandeR2[1]; 
bandeR2[1]=bandeR2[0];
bandeR2[0]=(numerateur+aH2[3]*bandeR2[4]+aH2[2]*bandeR2[3]+aH2[1]*bandeR2[2]+aH2[0]*bandeR2[1]);  
bandeR2[5]=(bandeR2[0]/aH2[4]);

//filtre passe bande second ordre  fo 187Hz
bandeR4[4]=bandeR4[3]; 
bandeR4[3]=bandeR4[2]; 
bandeR4[2]=bandeR4[1]; 
bandeR4[1]=bandeR4[0];
bandeR4[0]=(numerateur+aH4[3]*bandeR4[4]+aH4[2]*bandeR4[3]+aH4[1]*bandeR4[2]+aH4[0]*bandeR4[1]);  
bandeR4[5]=(bandeR4[0]/aH4[4]);

//filtre passe bande second ordre  fo 250Hz
bandeR3[4]=bandeR3[3]; 
bandeR3[3]=bandeR3[2]; 
bandeR3[2]=bandeR3[1]; 
bandeR3[1]=bandeR3[0];
bandeR3[0]=(numerateur+aH3[3]*bandeR3[4]+aH3[2]*bandeR3[3]+aH3[1]*bandeR3[2]+aH3[0]*bandeR3[1]);  
bandeR3[5]=(bandeR3[0]/aH3[4]);
                                                                        
out=bandeR3[5]+sortie_sPbas;     //verification des frequences de coupures des filtres sion inutile
if (out<0) {out=0;}
if (out>255) {out=255;}                  
analogWrite(PWM3,out);                                                                                
        

temps++;temps1++;
//rafraichissement toutes les 0.2s  fmini 5Hz
if (temps>=200)   {spectre[0]=0;spectre[1]=0;spectre[2]=0;spectre[3]=0;spectre[4]=0;temps=0;}   //reinitialiasation des valeurs du spectre
if (sortie_sPbas> spectre[0]) {spectre[0] = sortie_sPbas; }               // amplitude raie à 0Hz (valeur moyenne)
if (sortie_sPbande> spectre[1]) {spectre[1] = sortie_sPbande; }             // amplitude raie à 62Hz 
if (bandeR2[5]> spectre[2]) {spectre[2] = bandeR2[5]; } 
if (bandeR3[5]> spectre[3]) {spectre[3] = bandeR3[5]; } 
if (bandeR4[5]> spectre[4]) {spectre[4] = bandeR4[5]; } 

digitalWrite(13,LOW);
}//fin routine


///////////////////////////////////////////// Boucle correspondant à la fonction main 
void loop() {  
/*
if (temps1>=200)  {temps1=0;
Serial.print("0;");Serial.print(spectre[0],0);Serial.println(";");    
Serial.print("62;");Serial.print(spectre[1],0);Serial.println(";");
                   } 
*/
  lcd.setCursor(0,0);    
  lcd.print("0Hz");
  lcd.print(spectre[0],0);
  lcd.print("   ");
    
  lcd.setCursor(9,0);    
  lcd.print("62Hz");
  lcd.print(spectre[1],0);
  lcd.print("   ");
  
  lcd.setCursor(0,1);    
  lcd.print("125Hz");
  lcd.print(spectre[2],0);
  lcd.print("   ");   

    lcd.setCursor(9,1);    
  lcd.print("187Hz");
  lcd.print(spectre[3],0);
  lcd.print("    ");      

  lcd.setCursor(0,2);    
  lcd.print("250Hz");
  lcd.print(spectre[4],0);
  lcd.print("    ");     

  lcd.setCursor(9,2);    
  lcd.print("311Hz");
//  lcd.print(spectre[5],0);
  lcd.print("0   ");     
                
} // fin loop
1 Like

Filtre passe-haut 4éme ordre

Nous utilisons Filter Design Results pour connaitre les coefficients et le gain.


En prenant ces valeurs nous constatons que les résultats ne sont pas cohérents (il n’y a pas d’atténuation) raison pour laquelle nous avons modifié les valeurs des coefficients. Après quelques essais nous sommes parvenus à obtenir des résultats plus satisfaisants.
Voici le code:

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


#define PWM3   3      //   timer2   
#define LED13    13     

LiquidCrystal lcd(9, 8, 4, 5, 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;
            const float b0 =1;
            const float b1 =-4;
            const float b2=6;
            const float b3=-4;
            const float b4=1;
            
            float denominateur;
            float a1=0.929;
            float a2=-0.415;
            float a3=0.493;
            float a4=-0.448;

            float Gain=1.5;

            float  fe=1000;  //frequence d'echantilonnage
            float  fc=50;  //frequence de coupure desiréé


            float entreeMax;
float outMax;
float attenuation;

                   

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

  Timer1.initialize(1000);           // 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
  lcd.begin(20, 4);                   //modifier pour un afficheur 20x4

  Serial.begin(9600);
  TCCR2B = (TCCR2B & 0b11111000) | 0x01;         //pin 3  32khz    http://playground.arduino.cc/Main/TimerPWMCheatsheet
 
}


// Interruptions  tous les 1ms fait par le timer1    fe=1000Hz***********************************
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; 
     
// ---- filtre passe haut Butterwoth , fechantillon=1000Hz------       
entree4=entree3;      //entree(n-4)
entree3=entree2;      //entree(n-3)
entree2=entree1;      //entree(n-2)
entree1=entree;       //entree(n-1)

sortie4=sortie3;      //sortie(n-4)
sortie3=sortie2;      //sortie(n-3)
sortie2=sortie1;      //sortie(n-2)
sortie1=sortie;       //sortie(n-1)
  
sortie=(b0*entree+b1*entree1+b2*entree2+b3*entree3+b4*entree4+a1*sortie1+a2*sortie2+a3*sortie3+a4*sortie4)    ;

out=(sortie/Gain)+127;
         
if (out<0) {out=0;}
if (out>255) {out=255;}           
analogWrite(PWM3,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("passe haut 4eme");
  lcd.setCursor(0,1);
  attenuation=((outMax-127)/(entreeMax-127));   
  lcd.print("A=");
  lcd.print(attenuation,2);
   lcd.print("  ");
} // fin loop

Nous avons effectué quelques essais en simulation:

Avec fc=50Hz on a une atténuation de 0.7

f=120Hz; atténuation est de 1

f=170Hz ; atténuation=1

Les résultats paraissent correctes avec ces essais.

Voici la variation de l’atténuation en fonction de la fréquence:

mais d'ou viennent tes valeurs de coefficients ? ???????? :smiling_imp:

  • du passe bas deja etudié sur ce post ? car les coefficients ont des valeurs opposés. :sunglasses:
  • de matlab, il faudrait mettre les captures d'ecran de matlab de la simulation et faire des explications :fearful:

les coefficients devrait etre mis sur l'afficheur LCD avec 3 chiffres apres la virgule.
pour un quatriéme ordre, quelle devrait etre l'attenuation therorique ?
sur la courbe excel, la courbe pratique et theorique devrait etre tracée.....pour faire une comparaison

Filtre numérique avec ESP32

L’ESP 32 a presque le prix d'un ATMEGA328 mais il bien plus puissant
May 03, 2020, des temps d’instruction ESP 32 ont été mesuré est posté sur ce le lien

Mais quel sont les possibilités des timers, du DAC, du temps de calcul ?
Comment gérer les timers en interruptions avec ESP 32 ?

Nombreux liens sur les interruption timers avec ESP32
https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/intr_alloc.html
https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/esp_timer.html

Mais il y a un libray pour interruption timer
https://www.ardu-badge.com/ESP32TimerInterrupt

Mais avec la library dans la routine d’interruption, impossible d’utiliser le DAC, ni la PWM…..

donc une boucle dans la loop a été réalisé de 100µs (fréquence échantillonnage de 10KHz), donc impossible d’utiliser oled, c’est dommage.
Pourra ton utiliser un afficheur LCD ?
Le traitement du filtre du 2eme ordre avec ESP32 demande un temps 50 µs, donc bien moindre que que l'ATMEGA328 qui est de ?.

Avec le confinement, on ne peut plus accéder aux labos et utiliser un GBF.
Donc, le signal d’entrée a été calculé dans le programme suivant.
La fréquence et l’amplitude peuvent être modifiées par un potentiomètre ou par des entrées numériques.
Le DAC sera utilisé pour vérifier la sortie ou l’entrée.
Mais avec notre carte ESP32 heltec, seule la sortie 25 DAC fonctionne.
Mais une PWM filtré peut etre utilisée, pour avoir la sortie…..

Voici le programme sans routine timers

include "Arduino.h"
#include "heltec.h"
#include "ESP32TimerInterrupt.h"
//#include "ESP32_ISR_Timer.h"

#define TIMER1_INTERVAL_MS     100  //en microseconde

unsigned  int temps;
          int Time;

unsigned  int newTime;
unsigned  int endTime;
          
 float mesure;
 float fm=500;
 float sortie; float sortie1; float sortie2;
 float entree; float entree1;float entree2;float entreeMax;
 float out;float outMax;
 float attenuation;

 float amplitude=116;     //3V*255/(3.3V*2)=116

            const float a1 =-1.56;
            const float a2 =0.64;
            const float gain =50;

 ESP32Timer ITimer1(1); 

void setup() {
  pinMode(33, OUTPUT);
  
  Heltec.begin(true /*DisplayEnable Enable*/, false /*LoRa Disable*/, true /*Serial Enable*/);
//  Serial.begin(9600); 
  Serial.begin(115200);
  Heltec.display->flipScreenVertically();
  Heltec.display->setFont(ArialMT_Plain_10); //22 caracteres possible 
  Heltec.display->setTextAlignment(TEXT_ALIGN_LEFT);


    Heltec.display->clear();  //tres court 
    Heltec.display->drawString(0, 0, "IUT GEII Soissons");          // afficher 17 caractéres 5ms;  1 seul caractere 3.6ms       
    Heltec.display->drawString(0, 11, "GBF gen basse f"); 
    Heltec.display->drawString(0, 21, String(amplitude)+"DEC");              
    Heltec.display->drawString(0, 31, String(fm)+"Hz");   
    Heltec.display->display(); 
    
//    ITimer1.attachInterruptInterval(TIMER1_INTERVAL_MS, TimerHandler1); //

}

//routine d'interruption timer1  2kHz  500microseconde  
void TimerHandler1(void)
{
//if ( digitalRead(33)== 1 ) {digitalWrite(33,LOW);}  else {digitalWrite(33,HIGH);} 
// Time++;                     //125ns
//Serial.print(Time);       //8micro seconde      avec 115200 bauds  ne pas utiliser dans la routine
//dacWrite(25, sortie);    //ne fonctionne pas  dans la routine
//PWM ne fonctionne dans la routine ?

}  //fin routine


void loop() {
  

newTime = micros();
if ((newTime - endTime) >=100 ) {       //doit etre sperieur à la boucle de calucle et du filtre
 endTime = micros();                   //remise à zero
//generateur basse frequence 
digitalWrite(33,HIGH);                  //temps de la boucle 50 microseconde
Time++;
temps++;
entree=(127+(116 * sin(PI*Time*0.0006*fm)));   //  sin(PI * Time*2*Te*fm)  
//entree=analogRead(36);                             // ou analogRead d'un signal
//dacWrite(25, entree);                         
entree2=entree1;      //entree(n-2)           //  le filtre et PWM ou DAC
entree1=entree;       //entree(n-1)

sortie2=sortie1;      //sortie(n-2)
sortie1=sortie;       //sortie(n-1)

sortie=(entree+entree1*2+entree2-sortie1*a1-sortie2*a2);   //filtre passe pas recursif ordre 2

out=sortie/gain;
dacWrite(25, out); 
//PWM  avec filtre analogique à 10KHz
//  ledcAttachPin(27, 0); // broche 18, canal 0.
//  ledcSetup(0, 64000, 10); // canal, fequence, resolution,   PWM resolution 1bit pour 40MHz, 8bits 156KHz, 10bits 39kHz, 12bis 9.6kHz,
//  ledcWrite(0, out);   //  canal = 0, rapport cyclique = 2048    

//deuxieme DAC de l'ESP32 mais chez heltec, il ne fonctionne pas
//     dacWrite(26, 127);      //  broche 26    127  0.3V, 255 0.92V,    256 petite tension , 300 0.4V ne fonctionne pas à cause de la led D5 ????

//ou utilisation du traceur serie mais les instructions dure trop longtemps, il faut changer les bauds pour modifier la base de temps
//Serial.print(entree);   //80micro seconde
//Serial.print(",");      
//Serial.println(out); 

if (temps>=200)   {entreeMax=0;outMax=0;temps=0;}
if (entree> entreeMax) {entreeMax = entree; }  // Enregistrer la valeur maximale du capteur
if (out> outMax) {outMax = out; }
attenuation=outMax/entreeMax;

 digitalWrite(33,LOW);
 }//end if  

}//end loop

2 photos pour démontrer que le programme fonctionne bien.
Ici, le signal crée avec le DAC à 50Hz, avec Fe=10kHz, il y a 200 échantillons, donc la courbe est bien lisse

Le signal avec le DAC à 500Hz, avec Fe=10kHz, a 20 échantillons, donc la courbe est plus saccadée

mais, Il faudrait absolument que le programmer timers en ISR, pour avoir un affichage de l’attenuation.
d'autres problemes
https://riton-duino.blogspot.com/search/label/ESP32

1 Like

des infos sur ISR timers (routine interruption timer) pour ESP32 sur ces liens et cela fonctionne

https://www.dfrobot.com/blog-836.html
Attention les flottants ne fonctionnent pas à l’intérieurs de la routine d’interruption
https://esp32.com/viewtopic.php?t=1292
Il faut juste les déclarer dans DRAM_ATTR, et activer le FPU
Le noyau IDE Arduino devrait le faire directement le faire, cela occasionnerai moins de recherche

Bref,
Voici la sortie du filtre de deuxième ordre avec une fréquence, avec une fréquence d’échantillonnage de 1000Hz d’entre à 50Hz, une fréquence de coupure de 50Hz.
Il y a bien 20 échantillons et l’affichage OLED fonctionne bien en parralléle.

Précédemment, le temps de la routine d’interruption timer dure seulement 25µs pour le deuxième ordre.
Donc, un essai a été effectué avec une routine d’interruption de 50 µs (f échantillonnage de 20kHz), avec une frenquence d'entrée de 100Hz. evidement, les coefficients changent
Voici le résultat, cela fonctionne parfaitement, comme il y a 200 échantillons, les increments de la sortie sur le DAC ne sont plus du tout visibles.

Voici le code

#include "Arduino.h"
#include "heltec.h"


int Te=50  ; //periode d'echantillonnage micro seconde

DRAM_ATTR  int Time;
DRAM_ATTR   int temps;
//volatile  float mesure;
DRAM_ATTR   float fm=50;
DRAM_ATTR   float sortie, sortie1,  sortie2;
DRAM_ATTR   float entree,  entree1, entree2, entreeMax;
DRAM_ATTR   float out, outMax;
DRAM_ATTR   float attenuation;

DRAM_ATTR  float amplitude=116;     //3V*255/(3.3V*2)=116

  DRAM_ATTR    float fe ;
  DRAM_ATTR    float fc=50 ;     //frequence de coupure desirée
  DRAM_ATTR    float a1 =-1.561;  //valeur pour fe=1000Hz
  DRAM_ATTR    float a2 =0.641;
  DRAM_ATTR    float gain =50;
 
hw_timer_t * timer = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
uint32_t cp0_regs[18];


void IRAM_ATTR onTimer() {      //IRAM et DRAM
digitalWrite(33,HIGH); 
  portENTER_CRITICAL_ISR(&timerMux);

xthal_set_cpenable(1);  xthal_save_cp0(cp0_regs);   // // Save FPU registers
//if ( digitalRead(33)== 1 ) {digitalWrite(33,LOW);}  else {digitalWrite(33,HIGH);}

Time++;
temps++;

entree=(127+(116 * sin(PI*Time*2*fm/fe)));   //  sin(PI * Time*2*Te*fm)  
//entree=analogRead(36);                      // ou analogRead d'un signal
                   
entree2=entree1;      //entree(n-2)           //  le filtre et PWM ou DAC
entree1=entree;       //entree(n-1)

sortie2=sortie1;      //sortie(n-2)
sortie1=sortie;       //sortie(n-1)

sortie=(entree+entree1*2+entree2-sortie1*a1-sortie2*a2);   //filtre passe pas recursif ordre 2

out=sortie/(gain);
dacWrite(25, out);

if (temps>=(20000/Te))   {attenuation=outMax/entreeMax;entreeMax=0;outMax=0;temps=0; }
if (entree> entreeMax) {entreeMax = entree; }  // Enregistrer la valeur maximale du capteur
if (out> outMax) {outMax = out; }

  xthal_restore_cp0(cp0_regs);  xthal_set_cpenable(0);  // and turn it back off
  portEXIT_CRITICAL_ISR(&timerMux);
  digitalWrite(33,LOW);
}
 
void setup() {
 
  pinMode(33, OUTPUT);
  
  Heltec.begin(true /*DisplayEnable Enable*/, false /*LoRa Disable*/, true /*Serial Enable*/);
//  Serial.begin(9600); 
  Serial.begin(115200);
  Heltec.display->flipScreenVertically();
  Heltec.display->setFont(ArialMT_Plain_10); //22 caracteres possible 
  Heltec.display->setTextAlignment(TEXT_ALIGN_LEFT);
  
fe=1000000/Te;
//denominateur=(fe*fe+fe*fc*PI*sqrt(2)+fc*fc*PI*PI);
a1=(fc*fc*PI*PI-fe*fe)*2/(fe*fe+fe*fc*PI*sqrt(2)+fc*fc*PI*PI);
a2=(fe*fe*fe*fe+(fc*fc*fc*fc*PI*PI*PI*PI))/((fe*fe+fe*fc*PI*sqrt(2)+fc*fc*PI*PI)*(fe*fe+fe*fc*PI*sqrt(2)+fc*fc*PI*PI));   //pow(base, exponent)
gain=(1+2+1)/(1+a1+a2);

  timer = timerBegin(0, 80, true);
  timerAttachInterrupt(timer, &onTimer, true);
  timerAlarmWrite(timer, Te, true);     //200000 0.2s ; 1000 0.001  te temps d'echantilllon
  timerAlarmEnable(timer);
 
}
 
void loop() {
   
    Heltec.display->clear();   
    Heltec.display->drawString(0, 0, "IUT GEII Soissons");          // afficher 17 caractéres 5ms;  1 seul caractere 3.6ms       
    Heltec.display->drawString(0, 11, "Attenuation "+String(attenuation,3));
    Heltec.display->drawString(0, 21, "fm"+String(fm,0)+"Hz"+"  fe"+String(fe,0)+"Hz");
    Heltec.display->drawString(0, 31, "a1:"+String(a1,3)+"  a2:"+String(a2,3)); 
    Heltec.display->drawString(0, 41, "gain"+String(gain,1)); 
    Heltec.display->display(); 
 
}
1 Like

Je viens de refaire le filtre passe-haut 4éme ordre en prenant les coefficients et l’équation de récurrence de “filter design results” .
Voici les valeurs des coefficients et l’équation de récurrence:

Avec ces valeurs, je n’ai pas réussi à bien filtrer. L’atténuation reste égale à 1.
Voici le code:

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


#define PWM3   3      //   timer2   
#define LED13    13     

LiquidCrystal lcd(9, 8, 4, 5, 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;
            const float b0 =1;
            const float b1 =-4;
            const float b2=6;
            const float b3=-4;
            const float b4=1;
            
            
            float a1=3.1806;
            float a2=-3.8612;
            float a3=2.1122;
            float a4=-0.4383;

            float Gain=1.5;

            float  fe=1000; //frequence d'echantilonnage
            float  fc=50;  //frequence de coupure desiréé
     

            float entreeMax;
float outMax;
float attenuation;

                   

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

  Timer1.initialize(1000);           // 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
  lcd.begin(20, 4);                   //modifier pour un afficheur 20x4

  Serial.begin(9600);
  TCCR2B = (TCCR2B & 0b11111000) | 0x01;         //pin 3  32khz    http://playground.arduino.cc/Main/TimerPWMCheatsheet

 
}


// Interruptions  tous les 1ms fait par le timer1    fe=1000Hz***********************************
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; 
     
// ---- filtre passe haut Butterwoth , fechantillon=1000Hz------       
entree4=entree3;      //entree(n-4)
entree3=entree2;      //entree(n-3)
entree2=entree1;      //entree(n-2)
entree1=entree;       //entree(n-1)

sortie4=sortie3;      //sortie(n-4)
sortie3=sortie2;      //sortie(n-3)
sortie2=sortie1;      //sortie(n-2)
sortie1=sortie;       //sortie(n-1)
  
sortie=(b0*entree+b1*entree1+b2*entree2+b3*entree3+b4*entree4+a1*sortie1+a2*sortie2+a3*sortie3+a4*sortie4)    ;

out=(sortie/Gain)+127;
         
if (out<0) {out=0;}
if (out>255) {out=255;}           
analogWrite(PWM3,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.print("passe haut 4eme");
  lcd.setCursor(0,1);
  attenuation=((outMax-127)/(entreeMax-127));   
  lcd.print("A=");
  lcd.print(attenuation,2);
   lcd.print("  ");
} // fin loop

Voici quelques simulations effectuées.
f=50Hz

f=120Hz

Par la suite, j’ai essayé de le faire avec Matlab.
Je vous présente le programme Matlab et le schéma simulink que j’ai effectué.

Avec f=50Hz:

f=120Hz:

Les résultats semblent correctes sur Matlab. Par contre je ne sais pas où se trouve l’erreur sur Arduino.