Outils pour utilisateurs

Outils du site


tptns

TP 1 Architecture pour le TNS: Mise en oeuvre sur PC avec type double

cours architecture pour le TNS cours_Archi_TNS_intro5_6ppf.pdf

Objectifs

  • Prise en main de l'environnement de développement QT
  • Génération d'échantillons pour différents signaux
  • Utilisation des structure de contrôle et de données pour le filtrage
  • Test sur des filtres RIF simples

Outils

Qtcreator

Qtcreator est un outils de développement présenté durant le cours de Programmation Orienté Objet. Nous allons l'utiliser pour développer des filtres et les tester sur PC. Vous devrez développer une application à partir d'une structure de programme capable de visualiser des signaux temporels.

Récupération du projet

Pour récupérez le fichier zip du projet la première fois, ouvrir une console (alt+F2 et taper lxterm) puis copier coller dedans:

wget https://bvdp.inetdoc.net/files/iut/tp_tns/TNSQT.zip
unzip TNSQT.zip 
cd TNSQT
rm TNSQT.pro.user
qmake TNSQT.pro
make clean
qtcreator TNSQT.pro &
echo "fini"
    

IMPORTANT: A l'ouverture du projet, cocher Debug et décocher Release pour avoir exactement les mêmes réglages que sur l'image suivante, en remplacant bertrand.vandeportae par votre login. Veillez à bien remplir les noms des dossiers!!!

Au cas ou vous vous seriez trompé, fermer qtcreator et tapez dans la console:

rm TNSQT.pro.user
qtcreator TNSQT.pro &

Une fois les réglages effectués, cliquer sur Configurer le projet

Pour ouvrir le projet les fois suivantes , vous ouvrirez ce projet en ouvrant une console et en tapant juste

cd TNSQT
qtcreator TNSQT.pro &

Compilez et exécutez le programme en tapant CTRL+R, vous devez voir s'ouvrir une fenêtre sur laquelle on peut zoomer et se déplacer à l'aide de la souris:

Structure du projet

Le projet est constitué des fichiers sources d'une application graphique sous QT. Votre code sera saisi dans l'implémentation de la fenêtre principale, à l'intérieur du fichier Sources→mainwindow.cpp. C'est le seul fichier que vous aurez à modifier jusqu'à nouvel ordre. Pour information le projet contient également:

TNSQT.pro      fichier décrivant le projet QT, les règles de compilation, les librairies à utiliser etc...
mainwindow.h   fichier décrivant la classe MainWindow utilisée pour la fenêtre principale de l'application
qcustomplot.h et qcustomplot.cpp fichiers décrivant et implémentant les classes permettant le tracés de courbes dans une fenêtre
main.cpp       fichier décrivant le programme principal, qui se contente de lancer la fenêtre principale.
mainwindow.ui  fichier contenant la description de l'interface graphique (minimaliste) de l'application

Notice d'utilisation de l'outils QTCreator

Une fois le projet ouvert, l'utilisateur peut choisir entre plusieurs mode d'affichages, notamment “Editer” et “Debogage” à l'aide des boutons sur la gauche de la fenêtre.

La fenêtre Projets fait apparaître les fichiers du (des) projet(s) ouvert(s), triés selon leur type. Sur la droite, la fenêtre affiche le contenu d'un fichier sélectionné.

CTRL+Click souris gauche sur un mot du programme permet de se déplacer à la déclaration d'une variable, d'un objet, d'une fonction, d'un type ou autre. Cela sera très utile pour connaître par exemple les méthodes utilisables sur un objet d'une certaine classe.

CTRL+B permet de compiler l'application, la fenêtre Sortie de compilation permet alors d'observer les éventuelles erreurs, sur lesquelles vous pourrez cliquer pour aller à la ligne correspondante dans le code.

CTRL+R permet d’exécuter le programme. Le programme devra être fermé en cliquant sur le bouton croix de la fenêtre avant de le recompiler ou de l’exécuter à nouveau.

F5 permet d'exécuter le programme en mode DEBUG. L'utilisateur passe alors en mode Debug, ce qui ajoute à l'affichage une zone à droite dans laquelle l'utilisateur pourra observer les variables et leurs valeurs. L'utilisateur pourra placer ou retirer des points d'arrêt en cliquant à droite du numéro de ligne d'un fichier. Il pourra exécuter en pas à pas sans rentrer dans les fonctions à l'aide de F10 ou bien en entrant dans les fonctions à l'aide de F11. Il pourra ajouter une variable particulière à la liste des variables affichées en Debug en selectionant le nom de la variable dans le fichier programme et en cliquant droit dessus puis clic gauche sur ajouter un évaluateur d'expression.

Travail à réaliser

Écriture des fonctions de génération de signaux pour le test

Dans la démarche de développement proposée, il est nécessaire d'avoir des échantillons d'entrées pour “alimenter le filtre”. Dans cet exercice, vous devrez définir et remplir des tableaux contenant des échantillons de différents signaux. Nous utiliserons le type double, permettant de manipuler des nombres flottants en double précision (64bits) afin de minimiser les problèmes liés au codage numérique.

Les différentes fonctions à implémenter dans la suite du TP doivent remplir le tableau passé en paramètre via le pointeur ptr_tabech avec nbEchantillons échantillons et devront être testées une par une.

Nous utiliserons les déclarations suivantes que vous devez ajouter à votre programme:

#define SIZEDONNEE 1000  
double entree_d[SIZEDONNEE]; //pour stocker les échantillons d'entrée
double sortie_d[SIZEDONNEE]; //pour stocker les échantillons de sortie
double date_d[SIZEDONNEE];   //pour stocker les dates associées à chaque échantillons (en numéro ou en seconde)

Les appels aux différentes fonctions devront être commentés et non remplacés au fur et à mesure du développement pour permettre leur réutilisation facile dans les TP suivants: DONC TOUT GARDER EN COMMENTAIRE!!!!

Signal Zéro

Implémenter la fonction suivante:

void GenereEchantillonsZero(double * ptr_tabech , int nbEchantillons)  

afin de remplir le tableau avec tous les échantillons à 0.

Appeler cette fonction dans la méthode MainWindow::Filtrage à l'emplacement indiqué “génération du signal de sortie” pour remplir le tableau sortie_d. Pour le test, il faut modifier la partie notée “gestion de la recopie des valeurs” en remplaçant la ligne suivante qui donne une valeur par défaut au signal de sortie affiché:

st[i]=-i; 

par

st[i]=sortie_d[i];

Signal Rampe

Implémenter la fonction suivante:

void GenereEchantillonsNumero(double * ptr_tabech , int nbEchantillons, double Increment)  

afin de remplir le tableau avec dans chaque case la valeur correspondant à son indice multiplié par incrément.

Cette fonction sera notamment utilisée pour dater les échantillons, en associant à chacun soit un numéro (incrément = 1) , soit une date en unité de temps (incrément = période d'échantillonnage).

Appeler cette fonction dans la méthode MainWindow::Filtrage à l'emplacement indiqué “génération du signal de datation des échantillons” avec une valeur Increment=1 pour remplir le tableau date_d. Pour le test, il faut modifier l'affectation par défaut du signal utilisé pour légender l'axe x:

t[i]=date_d[i];

Signal Impulsion

Implémenter la fonction suivante:

void GenereEchantillonsImpulsion(double * ptr_tabech , int nbEchantillons, double Amplitude)  

afin de remplir le tableau avec un premier échantillon à la valeur Amplitude et tous les suivants à 0.

Appeler cette fonction dans la méthode MainWindow::Filtrage à l'emplacement indiqué “génération du signal d'entrée” pour remplir le tableau entree_d. Pour le test, il faut modifier l'affectation du signal de sortie affiché:

et[i]=entree_d[i];

Signal Indice

Implémenter la fonction suivante:

void GenereEchantillonsIndice(double * ptr_tabech , int nbEchantillons, double Amplitude)   

afin de remplir le tableau avec tous les échantillons à Amplitude.

Appeler cette fonction dans la méthode MainWindow::Filtrage à l'emplacement indiqué “génération du signal d'entrée” pour remplir le tableau entree_d (donc mettre en commentaire l'appel précédent) et exécuter le programme pour observer le signal temporel généré en vert.

Signal Sinusoïdal

Implémenter la fonction suivante:

void GenereEchantillonsSinus(double * tabech , int nbEchantillons,
                             double Amplitude,  double Offset, double Frequence, 
                             double freqEch)    

afin de remplir le tableau avec des échantillons d'un signal sinusoïdal paramétré par Amplitude et Offset. Ce signal doit être de fréquence Frequence et échantillonné à une fréquence freqEch.

Appeler cette fonction dans la méthode MainWindow::Filtrage à l'emplacement indiqué “génération du signal d'entrée” pour remplir le tableau entree_d et exécuter le programme pour observer le signal temporel généré en vert.

Mise en oeuvre de filtres simples

Structure imposée pour chaque filtre

Chaque filtre sera implémenté tout d'abord en utilisant le type flottant double précision à l'aide de deux fonctions nommées en fonction du filtre:

void initFiltre_d_nomdufiltre();  //initialise les variables nécessaires au bon fonctionnement du filtre si besoin 

et

double execFiltre_d_nomdufiltre(double e); //application de l'équation de récurrence. e est l'échantillon d'entrée et la fonction renvoie l'échantillon de sortie.

La fonction execFiltre_d_nomdufiltre devra gérer en interne les éventuelles structures de données nécessaires à son bon fonctionnement.

TOUTES Les variables globales utilisées pour le fonctionnement du filtre devront être nommées spécifiquement en fonction du nom du filtre et on indiquera avec _d_ si le type est double (pour virgule flottante) ou _i_ si le type est int (pour virgule fixe)

Chaque filtre devra respecter la structure de cet exemple:

Exemple de filtre

exemple.cpp
  ////////////////////////////////////////////////////////////////////////////////////
  //Nom du filtre: filtre_exemple
  //Description du filtre: dire ce que fait le filtre  
  ////////////////////////////////////////////////////////////////////////////////////
  //Partie commune aux implémentations en virgule flottante et fixe:
  ////////////////////////////////////////////////////////////////////////////////////
  #DEFINE SIZENUM_FILTRE_EXEMPLE 5
 
  ////////////////////////////////////////////////////////////////////////////////////
  //Implémentation en virgule flottante double précision:
  ////////////////////////////////////////////////////////////////////////////////////
  //mettre ici toutes les constantes et variables définies en double, en indiquant le type dans le nom avec _d_
  double numCoeff_d_FILTRE_EXEMPLE[SIZENUM_FILTRE_EXEMPLE];  //Coefficients du numérateur de la fonction de transfert du filtre au format double
  double ek_d_filtre_exemple[SIZENUM_FILTRE_EXEMPLE];  //tableau pour stocker les échantillons d'entrée au format double
  int indice_ecr_d_filtre_exemple;
  void initFiltre_d_filtre_exemple()
    {  //à vous de remplir
    }
  double execFiltre_d_filtre_exemple(double e)
    {  //à vous de remplir
    }
  ////////////////////////////////////////////////////////////////////////////////////
  //Implémentation en virgule fixe:
  ////////////////////////////////////////////////////////////////////////////////////  
  //mettre ici toutes les constantes et variables définies en int, en indiquant le type dans le nom avec _i_
  short int numCoeff_i_FILTRE_EXEMPLE[SIZENUM_FILTRE_EXEMPLE];  //Coefficients du numérateur de la fonction de transfert du filtre au format virgule fixe
  short int ek_filtre_i_exemple[SIZENUM_FILTRE_EXEMPLE];  //tableau pour stocker les échantillons d'entrée au format virgule fixe
  int indice_ecr_i_filtre_exemple;
  void initFiltre_i_filtre_exemple()
    {  //à vous de remplir
    }
  short int execFiltre_i_filtre_exemple(short int e)
    {  //à vous de remplir
    }    
  ////////////////////////////////////////////////////////////////////////////////////

Filtres RIF

Commencez par implémenter les filtres RIF simples qui suivent. Observer la réponse impulsionnelle des filtres pour vérifier les résultats.

Filtre recopie

Équation du filtre: s(k)=e(k)

Pour ce filtre, la fonction execFiltre_d_filtre_recopie doit renvoyer la valeur de l'échantillon fournit en argument.

Filtre retard fixe et gain

Équation du filtre: s(k)=A.e(k-r)

La valeur de A est fournie à la compilation à l'aide de la macro:

#define A_d_FILTRE_RETARDETGAIN  0.8

Pour ce filtre on considérera que r est dans un premier temps fixé à 5. Il vous incombe de dimensionner le buffer circulaire de manière adéquate. La mise en œuvre du buffer circulaire devra être réalisée comme indiquée en cours (page 32 de cours architecture pour le TNS:https://bvdp.inetdoc.net/files/iut/tp_tns/cours_Archi_TNS_intro5_6ppf.pdf ), à l'aide d'une variable servant d'indice d'écriture qui est incrémentée à chaque nouvelle écriture, et remise à zéro si la fin du buffer est dépassée. La lecture des valeurs du buffer doit se faire à partir de l'indice d'écriture, en utilisant un indice de lecture que l'on décrémente pour remonter le temps, en gérant également le fait que le buffer est circulaire.

Dans la fonction d'initialisation du filtre, vous veillerez à bien initialiser le contenu du buffer circulaire pour les échantillons d'entrée à zéro afin de pouvoir générer correctement les réponses impulsionnelle et indicielle.

Filtre retard réglable et gain

Équation du filtre: s(k)=A.e(k-r)

Adapter le code précédent pour tenir compte d'une valeur quelconque de r fournie à la compilation via la macro

#define R_d_FILTRE_RETARDETGAIN  500
Filtre écho

Équation du filtre: s(k)=A.e(k) + B.e(k-r)

Les valeurs de A et B et r sont fournies à la compilation à l'aide des macros:

#define A_d_FILTRE_ECHO  0.8
#define B_d_FILTRE_ECHO  0.5
#define R_d_FILTRE_ECHO  500

Filtres RII

Filtre Feedback delai

Équation du filtre: s(k)=e(k) + (A-B).e(k-r) + B.s(k-r)

Implémenter ce filtre pour une valeur quelconque de A,B et r fournie à la compilation via la macro

#define A_d_FILTRE_FEEDBACKDELAY  0.8
#define B_d_FILTRE_FEEDBACKDELAY  0.5
#define R_d_FILTRE_FEEDBACKDELAY  500

Le filtre sera réalisé en utilisant la structure canonique de 2nde forme, et il vous incombe donc de stocker dans le buffer circulaire les valeurs v(k).

Filtre général

Équation générale du filtre: s(k)=….

Les coefficients sont stockés dans 2 tableaux dont la taille sera réglée par les macros suivantes:

#define SIZENUM_d_NOMDUFILTRE 5
#define SIZEDEN_d_NOMDUFILTRE 6
#define MAXSIZE_d_NOMDUFILTRE  (SIZENUM_d_NOMDUFILTRE>SIZEDEN_d_NOMDUFILTRE?  SIZENUM_d_NOMDUFILTRE : SIZEDEN_d_NOMDUFILTRE)
double numCoeff_d_NOMDUFILTRE[SIZENUM_NOMDUFILTRE];  //Coefficients du numérateur de la fonction de transfert du filtre
double denCoeff_d_NOMDUFILTRE[SIZEDEN_NOMDUFILTRE];  //Coefficients du dénominateur de la fonction de transfert du filtre

Coefficients pour un filtre RIF Passe-Bas fc(-3dB)=1300 Hz si Fech=10 kHz :

#define SIZEFILTER 11 
const double num[SIZEFILTER] = {-0.0349249458233189,-0.0370641891927234,0.0209236117413746,0.1368055347416942,0.2562279031324722,0.3071029997932009,0.2562279031324722,0.1368055347416942,0.0209236117413746,-0.0370641891927234,-0.0349249458233189};

TP 2 Architecture pour le TNS: Mise en oeuvre sur PC avec calculs en virgule fixe

Pour cette partie du TP, vous devez réaliser, à coté des implémentations de filtre en virgule flottante, des implémentations en virgule fixe. Pour cela, vous implémenterez les fonctions:

void initFiltre_i_nomdufiltre();  //initialise les variables nécessaires au bon fonctionnement du filtre si besoin 

et

short int execFiltre_i_nomdufiltre(short int e); //application de l'équation de récurrence. e est l'échantillon d'entrée et la fonction renvoie l'échantillon de sortie.

Calcul des échantillons d'entrées

Tout d'abord, il faut générer des échantillons d'entrée quantifiés sur des valeurs entières et simuler la quantification et la saturation effectuées par l'ADC. Pour cela, copiez coller dans votre code la fonction suivante:

QuantifieEchantillons.cpp
void QuantifieEchantillons(double * tabin, short int * tabout,int nbBitsQuantADC, int nbEchantillons )
{
    for (int i=0;i<nbEchantillons;i++)
    {
        //arrondi
        tabout[i]=(short int)round(tabin[i]);
        //gestion des saturations
        if (tabout[i]>(1<<nbBitsQuantADC)-1)
            tabout[i]=(1<<nbBitsQuantADC)-1;
        if (tabout[i]<0)
            tabout[i]=0;
    }
}

Il vous faut ensuite définir des tableaux permettant de gérer les signaux en échantillons codés sur des entiers:

short int entree_i[SIZEDONNEE]; //pour stocker les échantillons d'entrée
short int sortie_i[SIZEDONNEE]; //pour stocker les échantillons de sortie

Le tableau d'entrée entree_i sera initialisé après que le tableau entree_d ait été rempli par une de vos fonctions en appelant:

QuantifieEchantillons(entree_d, entree_i,10, SIZEDONNEE );

Ceci permet de simuler un ADC sur 10 bits et les échantillons seront donc codés par des valeurs entre 0 et 1023. (pensez à générer des valeurs en entrée de QuantifieEchantillons qui soient aussi dans cet intervalle).

Passage en virgule fixe des coefficients du filtre

Dans la fonction initFiltre_i_nomdufiltre(), vous devrez convertir en virgule fixe les coefficients du filtre exprimés en double.

Pour cela, vous commencerez par définir une macro indiquant le nombre de bits servant à coder la partie fractionnaire, en le réglant par défaut à 14:

#define NB_BITS_FRACTIONNAIRE 14

Ensuite, vous devrez, pour chaque coefficient codé en double appliquer la conversion par arrondi. Le facteur multiplicatif (2 puissance NB_BITS_FRACTIONNAIRE) sera obtenu par (1«NB_BITS_FRACTIONNAIRE) et pour réaliser l'arrondi, vous utiliserez la fonction double floor(double) et l'ajout d'une valeur 1/2.

Codage de l'équation de récurrence en virgule fixe

Il faut maintenant implémenter la fonction:

short int execFiltre_i_nomdufiltre(short int e);

Pour cela, vous vous baserez sur la version traitant des doubles. Les opérandes seront stockés sur 16 bits (type short int) et les résultats intermédiaires de multiplication-additions seront quand à eux gérés sur 32 bits (type long int)

Vous veillerez:

  1. à bien retrancher la valeur de la moitié du max en sortie de l'ADC pour avoir les échantillons d'entrée référencés autour de la valeur 0 et passer ainsi du format UQM.0 à Q(M-1).0.
  2. à effectuer tous les calculs sur 32 bits (en “castant” correctement les opérandes en long int lors du calcul de l'échantillon de sortie en virgule fixe)
  3. à interpréter correctement le résultat de l'échantillon de sortie en virgule fixe en QW.Y (pour rappel, les échantillons d'entrées sont considérés en Q(M-1).0 et les coefficients du filtre en QX.Y donc vous devez savoir calculer le résultat de l'application de l'équation de récurrence.)
  4. à réaliser l'arrondi sur les valeurs en sortie de l'équation de récurrence pour passer de QW.Y à QW.0. Pour cela, comme indiqué dans le cours, il faut tester le bit de poids fort de la partie fractionnaire, et s'il est à 1, ajouter 1 au résultat. Ce bit est à la position (NB_BITS_FRACTIONNAIRE-1) dans le résultat.
  5. à gérer les saturations sur les sorties en veillant à ce que les valeurs calculées ne dépassent pas les bornes pour passer du format QW.0 à QN.0.
  6. à adapter la sortie pour utiliser l'intervalle acceptable pour la commande d'un DAC. Ceci se traduit par l'ajout d'une valeur permettant de passer du format QN.0 à UQ(N+1).0.

Comparaison avec le filtre réalisé en virgule flottante

Pour pouvoir visualiser les signaux quantifiés en virgule fixe et les comparer aux signaux en virgules flottantes, il va falloir modifier la méthode void MainWindow::Filtrage. Ajoutez les définitions suivantes:

bool affichageVirguleFixe=TRUE; //mettre à TRUE pour un affichage des entrées et sorties quantifiées en virgule fixe
QVector<double> etvfixe(nbEchantillons),stvfixe(nbEchantillons); 

Ajoutez ensuite dans la boucle de recopie des échantillons l'affectation des valeurs aux cases du tableau.

for (int i=0; i<nbEchantillons; i++)
  {
      if (affichageVirguleFixe)
      {
      etvfixe[i]=(double)entree_i[i]/pow(2,NB_BITS_FRACTIONNAIRE);   //génération des valeurs des échantillons d'entrée
      stvfixe[i]=(double)sortie_i[i]/pow(2,NB_BITS_FRACTIONNAIRE);  //génération des échantillons de sorties
      }
  }
 

Puis vous copiez-collez le code suivant pour remplacer l'ancien code:

Code à récupérer:

mainwindow.cpp
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
// NE PAS MODIFIER JUSQU'A NOUVEL ORDRE!!!!
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
    // create graph and assign data to it:
    customPlot->addGraph();
    customPlot->graph(0)->setPen(QPen(Qt::green));
    customPlot->graph(0)->setData(t, et);
    // give the axes some labels:
    customPlot->xAxis->setLabel("t");
    customPlot->yAxis->setLabel("valeur  ");
    // set axes ranges, so we see all data:
    customPlot->xAxis->setRange(0, t[nbEchantillons-1]);
    customPlot->yAxis->setRange(0,MaxQuantADC);
    customPlot->addGraph();
    customPlot->graph(1)->setPen(QPen(Qt::red));
    customPlot->graph(1)->setData(t, st);
    if (affichageVirguleFixe)
    {
        customPlot->addGraph();
        customPlot->graph(2)->setPen(QPen(Qt::black));
        customPlot->graph(2)->setData(t, etvfixe);
        customPlot->addGraph();
        customPlot->graph(3)->setPen(QPen(Qt::blue));
        customPlot->graph(3)->setData(t, stvfixe);
    }
    QColor c(128,128,128);
    QBrush b(c);
    customPlot->setBackground(b);
 
    if (affichageTemporel==FALSE)
    {
        customPlot->graph(0)->setLineStyle(QCPGraph::lsImpulse);
        customPlot->graph(1)->setLineStyle(QCPGraph::lsImpulse);
        customPlot->graph(0)->setScatterStyle(QCPScatterStyle(QCPScatterStyle:: ssCircle, 5));
        customPlot->graph(1)->setScatterStyle(QCPScatterStyle(QCPScatterStyle:: ssCross, 5));
        if (affichageVirguleFixe)
        {
            customPlot->graph(2)->setLineStyle(QCPGraph::lsImpulse);
            customPlot->graph(3)->setLineStyle(QCPGraph::lsImpulse);
            customPlot->graph(2)->setScatterStyle(QCPScatterStyle(QCPScatterStyle:: ssSquare, 5));
            customPlot->graph(3)->setScatterStyle(QCPScatterStyle(QCPScatterStyle:: ssPlus, 5));
        }
 
    }
    else
    {
        customPlot->graph(0)->setLineStyle(QCPGraph::lsStepLeft);
        customPlot->graph(1)->setLineStyle(QCPGraph::lsStepLeft);
        if (affichageVirguleFixe)
        {
            customPlot->graph(2)->setLineStyle(QCPGraph::lsStepLeft);
            customPlot->graph(3)->setLineStyle(QCPGraph::lsStepLeft);
        }
    }
    customPlot->legend->setVisible(true);
    customPlot->legend->setFont(QFont("Helvetica",9));
    // set locale to english, so we get english decimal separator:
    customPlot->setLocale(QLocale(QLocale::English, QLocale::UnitedKingdom));
    customPlot->graph(0)->setName("e(t)");
    customPlot->graph(1)->setName("s(t)");
    if (affichageVirguleFixe)
    {
        customPlot->graph(2)->setName("e(t) vfixe");
        customPlot->graph(3)->setName("s(t) vfixe");
    }
    // configure right and top axis to show ticks but no labels:
    // (see QCPAxisRect::setupFullAxesBox for a quicker method to do this)
    customPlot->xAxis2->setVisible(true);
    customPlot->xAxis2->setTickLabels(false);
    customPlot->yAxis2->setVisible(true);
    customPlot->yAxis2->setTickLabels(false);
    // make left and bottom axes always transfer their ranges to right and top axes:
    connect(customPlot->xAxis, SIGNAL(rangeChanged(QCPRange)), customPlot->xAxis2, SLOT(setRange(QCPRange)));
    connect(customPlot->yAxis, SIGNAL(rangeChanged(QCPRange)), customPlot->yAxis2, SLOT(setRange(QCPRange)));
    // let the ranges scale themselves so graph 0 fits perfectly in the visible area:
    customPlot->graph(0)->rescaleAxes();
    // same thing for graph 1, but only enlarge ranges (in case graph 1 is smaller than graph 0):
    customPlot->graph(1)->rescaleAxes(true);
    if (affichageVirguleFixe)
    {
        customPlot->graph(2)->rescaleAxes(true);
        customPlot->graph(3)->rescaleAxes(true);
    }
    // Note: we could have also just called customPlot->rescaleAxes(); instead
    // Allow user to drag axis ranges with mouse, zoom with mouse wheel and select graphs by clicking:
    customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables);
}
///////////////////////////////////////////////////////////////////////

TP 4 Architecture pour le TNS: Mise en oeuvre sur ARDUINO

Installer tout d'abord les librairies SPI et Tlv dans ~/Arduino/librairies et lancer la version 1.6.0 d'arduino, en tapant dans une console lancée à l'aide de ALT+F2 lxterm:

cd ~
mkdir Arduino
cd Arduino
mkdir libraries
cd ~/Arduino/libraries/
wget https://bvdp.inetdoc.net/files/iut/tp_dacspi/Tlv5637.zip
wget https://bvdp.inetdoc.net/files/iut/tp_dacspi/SPI.zip
unzip -o Tlv5637.zip
unzip -o SPI.zip
rm Tlv5637.zip SPI.zip
echo "version 1.6.0 installée en /usr/share/arduino-1.6.0/" 
/usr/share/arduino-1.6.0/arduino  
echo "FINI"

Les fois suivantes, vous lancerez juste arduino 1.6.0 en tapant dans une console lancée à l'aide de ALT+F2 lxterm:

/usr/share/arduino-1.6.0/arduino 

Solution pour un RIF:

execFiltre_i_nomdufiltre.cpp
// created 2015 March 29th by Bertrand VANDEPORTAELE
#include <SPI.h>
#include <Tlv5637.h>
const int chipSelectPin = 3;  //connect to the Chip Select of the TLV5637
const int boutonPin = 9;
const int itpin = 8;   //used to monitor the activity of the processor during the interrupt, on arduino it is PORTB bit 0
const int filtrepin = 10;   //used to monitor the activity of the processor during the interrupt, on arduino it is PORTB bit 2
 
TLV5637 DAC(chipSelectPin, REF_2048MV_TLV5637);
////////////////////////////////////////////////////////////////////////////////////
//Nom du filtre: filtre_RIF
//Description du filtre: Un filtre RIF général
////////////////////////////////////////////////////////////////////////////////////
//Partie commune aux implémentations en virgule flottante et fixe:
////////////////////////////////////////////////////////////////////////////////////
//passe ricrac pour un filtre avec 100 coefficients et à 2khz sur l'arduino en Virgule fixe
#define SIZEFILTER_RIF 41
////////////////////////////////////////////////////////////////////////////////////
//Implémentation en virgule flottante double précision:
////////////////////////////////////////////////////////////////////////////////////
//mettre ici toutes les constantes et variables définies en double, en indiquant le type dans le nom avec _d_
const double numCoeff_d_FILTRE_RIF[SIZEFILTER_RIF] = { 0.016244657856333, -0.018864421488768, -0.012238782422342, -0.005085409431928, 0.003969691594410, 0.012267146479049, 0.015030618183398, 0.008966346831124, -0.004682203645285, -0.019357735197421, -0.025875762043837, -0.017435787337996, 0.005471034822059, 0.033037851847265, 0.049218355348873, 0.038520192602532, -0.006112347636894, -0.078041093847378, -0.157847718052422, -0.220135476387537, 0.756344281504610, -0.220135476387537, -0.157847718052422, -0.078041093847378, -0.006112347636894, 0.038520192602532, 0.049218355348873, 0.033037851847265, 0.005471034822059, -0.017435787337996, -0.025875762043837, -0.019357735197421, -0.004682203645285, 0.008966346831124, 0.015030618183398, 0.012267146479049, 0.003969691594410, -0.005085409431928, -0.012238782422342, -0.018864421488768, 0.016244657856333};
/*double numCoeff_d_FILTRE_EXEMPLE[SIZEFILTER_RIF];  //Coefficients du numérateur de la fonction de transfert du filtre au format double
  double ek_d_filtre_exemple[SIZEFILTER_RIF];  //tableau pour stocker les échantillons d'entrée au format double
  int indice_ecr_d_filtre_rif;
  void initFiltre_d_filtre_rif() {}
  double execFiltre_d_filtre_rif(double e) {}    */
////////////////////////////////////////////////////////////////////////////////////
//Implémentation en virgule fixe:
////////////////////////////////////////////////////////////////////////////////////
//mettre ici toutes les constantes et variables définies en int, en indiquant le type dans le nom avec _i_
short int numCoeff_i_FILTRE_RIF[SIZEFILTER_RIF];  //Coefficients du numérateur de la fonction de transfert du filtre au format virgule fixe
short int ek_filtre_i_RIF[SIZEFILTER_RIF];  //tableau pour stocker les échantillons d'entrée au format virgule fixe
int indice_ecr_i_filtre_rif; //pour gestion buffer circulaire
#define NB_BITS_FRACTIONNAIRE 14
////////////////////////////////////////////////////////////////////////////////////
void initFiltre_i_filtre_rif()
{
  int i;
  for (i = 0; i < SIZEFILTER_RIF; i++)
    {
    numCoeff_i_FILTRE_RIF[i] = floor(  (numCoeff_d_FILTRE_RIF[i] * (1<< NB_BITS_FRACTIONNAIRE) )+ 0.5) ;
    ek_filtre_i_RIF[i]=0;
    }
}
////////////////////////////////////////////////////////////////////////////////////
inline  short int execFiltre_i_filtre_rif(short int e)
{
  long int temp; // calcul intermediaire sur 32 bits
  int i; //indice de lecture pour les valeurs des coefficients du filtre
  int indice_lec_i_filtre_rif = indice_ecr_i_filtre_rif; //indice de lecture pour les échantillons d'entrée
  short int skout = 0; //valeur pour la sortie calculée
  ek_filtre_i_RIF[indice_ecr_i_filtre_rif] = e - 512; //rangement de l'echantillon d'entrée dans le buffer circulaire des entrées en référencant par rapport au 0
//  indice_ecr_i_filtre_rif = (indice_ecr_i_filtre_rif + 1) % SIZEFILTER_RIF;
  indice_ecr_i_filtre_rif = (indice_ecr_i_filtre_rif + 1);
  if (indice_ecr_i_filtre_rif>=SIZEFILTER_RIF)
    indice_ecr_i_filtre_rif=0;
  temp = 0; //valeur par défaut pour le résultat
  for (i = 0; i < SIZEFILTER_RIF; i++)
  {
    temp += (long int) numCoeff_i_FILTRE_RIF[i] * (long int)(ek_filtre_i_RIF[indice_lec_i_filtre_rif]);   // calcul sur 32 bits
    indice_lec_i_filtre_rif--;
    if (indice_lec_i_filtre_rif < 0)
      indice_lec_i_filtre_rif = SIZEFILTER_RIF - 1;
  }
  if  (temp & (1<<(NB_BITS_FRACTIONNAIRE-1)) ) // calcul de l'arrondi
    skout = (temp >> NB_BITS_FRACTIONNAIRE ) + 1;	 
  else					 
    skout = (temp >> NB_BITS_FRACTIONNAIRE ) ;
  if (skout >  511 )		               // saturation du résultat sur 10 bits
    skout = 511 ;		 
  else if (skout < -512 )	 
    skout = -512 ;	 
  return  skout + 512;  
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ISR(TIMER0_COMPA_vect)
{ //timer0 interrupt 2kHz toggles pin 8
  static short int skprec = 0;
  static short int eksuiv = 0;
  PORTB |= 1;  // visu du temps passé dans l'interruption
  eksuiv = finishReadADCPolling() ;  // valeur de l'echantillon sans valeur moyenne.  
  startReadADCPolling();   //avec prescaler réglé à 1/128, l'acquisition d'un échantillon prend 120us, mais ce temps est utilisé pour commander le dac et calculer la valeur de l'échantillon en cours
  DAC.writeDACAB(skprec , eksuiv );     //prend 40us
  PORTB |= 4;  // visu du temps passé dans la fonction filtre 
  skprec = execFiltre_i_filtre_rif(eksuiv);
  PORTB &= (0xFF -4); //fin visu
  PORTB &= 0xFE; //fin visu
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void setupADCPolling()
{
  ADCSRB=0; 
  //ADCSRA   = 1 << ADPS2 | 1 << ADPS1 | 1 << ADPS0 | 1 << ADEN; // prescaler 1/128, enabled, in that case, we use the best resolution, as the ADC conversion will be interlaced with the processing, the time needed is not lost, otherwise, we could use:
  //ADCSRA   = 1 << ADPS2 | 1 << ADPS1 | 1 << ADEN; // prescaler 1/64, enabled
  ADCSRA  = 1 << ADPS2 | 1 << ADPS0 | 1 << ADEN; // prescaler 1/32, enabled
  ADMUX    = 1 << 6;  //choix entrée analogique 0 et référence de tension =VCC
 }
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*! \brief ADC Conversion Routine start */
inline void startReadADCPolling(void)
{
  ADCSRA  |= (1 << ADSC);			        // Start ADC Conversion
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*! \brief ADC Conversion Routine wait and read */
inline unsigned int finishReadADCPolling(void)
{
  unsigned int result;
  while ((ADCSRA & (1 << ADIF)) != 0x10);	// Wait till conversion is complete
  result   = ADC;                                              // Read the ADC Result
  ADCSRA  |= (1 << ADIF);			        // Clear ADC Conversion Interrupt Flag
  return result ;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void setup() {
  Serial.begin(9600);
  Serial.println("Bonjour");
  pinMode(itpin, OUTPUT);
  pinMode(filtrepin, OUTPUT);
  pinMode(boutonPin, INPUT);
  DAC.powerOn(); // start the tlv5637 library:
  DAC.speedFast();
  initFiltre_i_filtre_rif();  //initialise le filtre
  cli();//stop interrupts
  //set timer0 interrupt at 2kHz
  TCCR0A = 0;// set entire TCCR2A register to 0
  TCCR0B = 0;// same for TCCR2B
  TCNT0  = 0;//initialize counter value to 0
  // set compare match register for 2khz increments
  OCR0A = 124;// = (16*10^6) / (2000*64) - 1 (must be <256)
  // turn on CTC mode
  TCCR0A |= (1 << WGM01);
  // Set CS01 and CS00 bits for 64 prescaler
  TCCR0B |= (1 << CS01) | (1 << CS00);
  // enable timer compare interrupt
  TIMSK0 |= (1 << OCIE0A);
  setupADCPolling();
  startReadADCPolling(); //lance la demande d'acquisition du premier échantillon pour que la première interruption timer ne soit pas bloquée
  sei();//allows interrupts
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void loop()
{
    Serial.println("Je suis vivant!");
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Connexion

IMPORTANT: Ne jamais connecter de signal sur l'entrée analogique A0 avant de s'être assuré avec l'oscilloscope que le signal soit dans la plage 0 à 5V!!!

Le signal connecté à l'entrée A0 pourra avoir une amplitude crête à crête inférieur à 5V mais devra être centré sur 2.5V.

Sur la sortie A du DAC, la sortie du filtre est visible, avec une amplitude crête à crête maximale de 4.096V.

Sur la sortie B du DAC, une version numérisée et atténuée de l'entrée du filtre est visible, avec une amplitude crête à crête maximale de 4.096V.

Sur la broche 8 de l'Arduino, un signal périodique indique la fréquence de l'interruption Timer 0. La durée à l'état haut indique la durée nécessaire au traitement de l'interruption TIMER.

Sur la broche 10 de l'Arduino, un signal périodique indique la fréquence de l'interruption Timer 0. La durée à l'état haut indique la durée nécessaire au calcul de l'équation de récurence.

Caractérisation du filtre

  1. Identifier le Gain max
  2. Identifier la bande passante à -3db

Mesures

  1. Mesurer le temps de calcul d'une équation de récurence
  2. Mesurer le temps total de l'interruption timer
  1. Déduire la fréquence d'échantillonnage maximale pour un filtre réalisant l'équation s(k)=1/2.( e(k) + e(k-1) ). (pour cela, commenter la ligne Serial.println(“Je suis vivant!”); dans la fonction loop().)
  1. Déduire le nombre de coefficients maximal de l'équation de récurrence pour une fréquence d'échantillonnage de 2Khz. Vérifier avec un exemple pris dans le wiki.

Changement de filtre

Version filtre passe bas, fc=260Hz pour fe=2khz

 #define SIZEFILTER_RIF 11 
 const double numCoeff_d_FILTRE_RIF[SIZEFILTER_RIF] = {-0.0349249458233189,-0.0370641891927234,0.0209236117413746,0.1368055347416942,0.2562279031324722,0.3071029997932009,0.2562279031324722,0.1368055347416942,0.0209236117413746,-0.0370641891927234,-0.0349249458233189};

Passe haut, fe=2khz, fpass (-1dB) =275Hz, fstop (-40dB) =200Hz, equiripple

coeff du filtre RIF 41 termes: num

#define SIZEFILTER_RIF 41 
const double numCoeff_d_FILTRE_RIF[SIZEFILTER_RIF]={ 0.016244657856333,-0.018864421488768,-0.012238782422342,-0.005085409431928,0.003969691594410, 0.012267146479049, 0.015030618183398, 0.008966346831124, -0.004682203645285,-0.019357735197421,-0.025875762043837,-0.017435787337996, 0.005471034822059, 0.033037851847265, 0.049218355348873, 0.038520192602532, -0.006112347636894,-0.078041093847378,-0.157847718052422,-0.220135476387537,0.756344281504610,-0.220135476387537,-0.157847718052422,-0.078041093847378, -0.006112347636894, 0.038520192602532, 0.049218355348873, 0.033037851847265,0.005471034822059,-0.017435787337996,-0.025875762043837,-0.019357735197421, -0.004682203645285, 0.008966346831124, 0.015030618183398, 0.012267146479049,0.003969691594410,-0.005085409431928,-0.012238782422342,-0.018864421488768, 0.016244657856333};

Rejecteur 50Hz, fe=1khz, module de la fonction de transfert:

#define SIZEFILTER_RIF 201 
const double numCoeff_d_FILTRE_RIF[SIZEFILTER_RIF] = {0.0010497657664069,0.0010248032141060,0.0008950989702105,0.0006754260236328,0.0003914824095416,0.0000766934492105,-0.0002321048737491,-0.0004995386970487,-0.0006964600877342,-0.0008039239652070,-0.0008157836216176,-0.0007394830559167,-0.0005948664932619,-0.0004111061172667,-0.0002221300434536,-0.0000611735162575,0.0000447598151640,0.0000796811308439,0.0000418604532548,-0.0000550716758918,-0.0001834867559374,-0.0003055590399293,-0.0003791090967164,-0.0003647560591893,-0.0002333866931914,0.0000271842699248,0.0004074693745616,0.0008744059311998,0.0013728937238789,0.0018309281895384,0.0021679805132536,0.0023057764653131,0.0021801999890144,0.0017527602862087,0.0010199632250395,0.0000190482922570,-0.0011711072134873,-0.0024345821147888,-0.0036284987472194,-0.0045978697208798,-0.0051937725260009,-0.0052928489822895,-0.0048157655121297,-0.0037421830301847,-0.0021200045223263,-0.0000671897708197,0.0022347930844576,0.0045558495491448,0.0066394125108034,0.0082298250869960,0.0091021062349281,0.0090907379538161,0.0081139702738372,0.0061904123965458,0.0034453596342308,0.0001053400669057,-0.0035193504333161,-0.0070629599518906,-0.0101409269984312,-0.0123912150958553,-0.0135159141893970,-0.0133183847721507,-0.0117314514765957,-0.0088329189234603,-0.0048459221009567,-0.0001232158250872,0.0048837149938801,0.0096680778553808,0.0137195341098129,0.0165789638233212,0.0178903118599852,0.0174436522975187,0.0152043335317103,0.0113244013729469,0.0061343135538643,0.0001150678565127,-0.0061469611180756,-0.0120181883656648,-0.0168831175207452,-0.0202099218564704,-0.0216092075118743,-0.0208793906867327,-0.0180333949515915,-0.0133032695303891,-0.0071216486525055,-0.0000814675096998,0.0071222518254061,0.0137626997041129,0.0191563866117297,0.0227351202843601,0.0241071081284681,0.0231004857065963,0.0197843647677042,0.0144648478539781,0.0076561189341496,0.0000294013994965,-0.0076550186151438,-0.0146256412959852,-0.0201792994512465,-0.0237539929269980,0.9750127221960708,-0.0237539929269980,-0.0201792994512465,-0.0146256412959852,-0.0076550186151438,0.0000294013994965,0.0076561189341496,0.0144648478539781,0.0197843647677042,0.0231004857065963,0.0241071081284681,0.0227351202843601,0.0191563866117297,0.0137626997041129,0.0071222518254061,-0.0000814675096998,-0.0071216486525055,-0.0133032695303891,-0.0180333949515915,-0.0208793906867327,-0.0216092075118743,-0.0202099218564704,-0.0168831175207452,-0.0120181883656648,-0.0061469611180756,0.0001150678565127,0.0061343135538643,0.0113244013729469,0.0152043335317103,0.0174436522975187,0.0178903118599852,0.0165789638233212,0.0137195341098129,0.0096680778553808,0.0048837149938801,-0.0001232158250872,-0.0048459221009567,-0.0088329189234603,-0.0117314514765957,-0.0133183847721507,-0.0135159141893970,-0.0123912150958553,-0.0101409269984312,-0.0070629599518906,-0.0035193504333161,0.0001053400669057,0.0034453596342308,0.0061904123965458,0.0081139702738372,0.0090907379538161,0.0091021062349281,0.0082298250869960,0.0066394125108034,0.0045558495491448,0.0022347930844576,-0.0000671897708197,-0.0021200045223263,-0.0037421830301847,-0.0048157655121297,-0.0052928489822895,-0.0051937725260009,-0.0045978697208798,-0.0036284987472194,-0.0024345821147888,-0.0011711072134873,0.0000190482922570,0.0010199632250395,0.0017527602862087,0.0021801999890144,0.0023057764653131,0.0021679805132536,0.0018309281895384,0.0013728937238789,0.0008744059311998,0.0004074693745616,0.0000271842699248,-0.0002333866931914,-0.0003647560591893,-0.0003791090967164,-0.0003055590399293,-0.0001834867559374,-0.0000550716758918,0.0000418604532548,0.0000796811308439,0.0000447598151640,-0.0000611735162575,-0.0002221300434536,-0.0004111061172667,-0.0005948664932619,-0.0007394830559167,-0.0008157836216176,-0.0008039239652070,-0.0006964600877342,-0.0004995386970487,-0.0002321048737491,0.0000766934492105,0.0003914824095416,0.0006754260236328,0.0008950989702105,0.0010248032141060,0.0010497657664069};

analyse de la réponse théorique du filtre:

n=[0.016244657856333, -0.018864421488768, -0.012238782422342, -0.005085409431928, 0.003969691594410, 0.012267146479049, 0.015030618183398, 0.008966346831124, -0.004682203645285, -0.019357735197421, -0.025875762043837, -0.017435787337996, 0.005471034822059, 0.033037851847265, 0.049218355348873, 0.038520192602532, -0.006112347636894, -0.078041093847378, -0.157847718052422, -0.220135476387537, 0.756344281504610, -0.220135476387537, -0.157847718052422, -0.078041093847378, -0.006112347636894, 0.038520192602532, 0.049218355348873, 0.033037851847265, 0.005471034822059, -0.017435787337996, -0.025875762043837, -0.019357735197421, -0.004682203645285, 0.008966346831124, 0.015030618183398, 0.012267146479049, 0.003969691594410, -0.005085409431928, -0.012238782422342, -0.018864421488768, 0.016244657856333] 
fvtool(n,1)

tptns.txt · Dernière modification : 2021/02/19 21:20 de 127.0.0.1