**TP1 Codage du calcul de l'équation de récurrence** Projet solution pour profs, mis à jour automatiquement par le script **script-genere-projet-etu-et-update-etu-solution.sh**: https://bvdp.inetdoc.net/files/iut/tp_tns/TP_ARCHI_TNS.zip -------------------------------------------------------------------------------------------------------- {{https://bvdp.inetdoc.net/files/iut/tp_pic/warning.jpeg}} Polycopié du cours architecture pour le TNS {{https://bvdp.inetdoc.net/files/iut/tp_tns/cours_tns.pdf}} A FAIRE A LA FIN DE CHAQUE SEANCE POUR COPIER VOS FICHIERS SUR UN DISQUE PARTAGE, ET PERMETTRE LA RECUPERATION DES FICHIERS SI UN MEMBRE DU BINOME EST ABSENT, SINON TANT PIS POUR VOUS ! \\ \\ copier coller dans une console: rsync -av --delete ~/TP_ARCHI_TNS_ETU /mnt/etu/s4 et auto-compléter la ligne jusque votre dossier personnel en appuyant 3 fois sur la touche Tabulation, puis entrée. -------------------------------------------------------------------------------------------------------- =====Principes===== La démarche de développement est composée de deux grandes étapes. La première étape consiste en l'implémentation d'un filtre logiciel qui permet de vérifier si le filtre possède bien les propriétés temporelles et/ou fréquentielles souhaitées. Cette étape réalisée lors des séances de TNS est composée de la façon suivante: - définition du filtre (coefficients stockés dans des tableaux, initialisation d'un sFiltre avec initFiltre() ) - définition des signaux (création de tableaux pour l'impulsion, l'échelon, sinus...) pour une durée fixée - calcul de la réponse du filtre pour tous les échantillons du signal d'entrée (avec **calculReponseFiltre()** ) - affichage des signaux d'entrée et de sortie - affichage éventuel de la fonction de transfert - fin du programme La seconde étape consiste en la réalisation concrète du filtre en tenant compte du matériel utilisé, le programme devant assurer les tâches suivantes: - définition du filtre - acquisition d'un échantillon ek en effectuant une conversion analogique->numérique - calcul de sk en appliquant l'équation de récurrence sur cet échantillon - restituer sk en effectuant une conversion numérique->analogique - recommencer indéfiniment en 2. jusqu'à l'arrêt du système ! Cette seconde étape est l'objectif des TP du module Architecture pour le TNS. Le filtre sera réalisé sur une carte Nucléo intégrant un microcontrôleur de la famille STM32 de STMicroelectronics. =====Récupération du projet Qt de départ et Initialisation du système de gestion de version en local===== {{https://bvdp.inetdoc.net/files/iut/tp_tns/TODO.jpg}} POUR LA PREMIERE SEANCE UNIQUEMENT!, pour récupérez le fichier projet la première fois, ouvrir une console (alt+F2 et taper lxterm) puis copier coller (sélectionner le code qui suit puis clic sur le bouton central de la souris dans le terminal) : echo "commence ici" cd ~/ rm -f TP_ARCHI_TNS_ETU.zip wget https://bvdp.inetdoc.net/files/iut/tp_tns/TP_ARCHI_TNS_ETU.zip unzip TP_ARCHI_TNS_ETU.zip rm -f TP_ARCHI_TNS_ETU.zip cd TP_ARCHI_TNS_ETU git init git add * git commit -m'initial version' qtcreator TP_ARCHI_TNS.pro & echo "C est bon !" à l'ouverture de qtcreator, cliquer sur "Configurer le projet" =====Description de la structure du projet===== - filtre.h et filtre.cpp: contient la classe Filtre permettant de réaliser le filtrage "en ligne". Les méthodes de cette classe devront être implémentées par vous. Ces fichiers seront les seuls utilisés sur le microcontroleur et c'est principalement le fichier filtre.cpp que vous devrez compléter. - filtretrace.h et filtretrace.cpp: contient une classe dérivée de Filtre dans laquelle nous avons ajouté diverses méthodes pour le tracé et la comparaison des réponses (avec une méthode de filtrage "hors ligne"). Ces fichiers ne seront pas utilisés sur le microcontroleur et vous n'aurez pas à le modifier. - tparchi1.cpp: contient la fonction de test void code_tparchi1(void). Elle permet de créer des échantillons pour les signaux d'entrée et de tracer les réponses impulsionnelle, indicielle et le module de la réponse fréquentielle en invoquant les méthodes implémentées dans Filtre et FiltreTrace. Vous aurez à modifier ce fichier. - prototype_tp.h: contient les prototypes des fonctions de test - main.cpp: contient le programme principal, qui appelle la fonction de test. - mainwindow.h et mainwindow.cpp: contiennent la classe qui permet l'affichage de la fenêtre principale de l'application - qcustomplot.h et qcustomplot.cpp: contiennent les classes qui permettent l'affichage de tracé graphique ====Nouveautés par rapport aux TP TNS==== ===Définition de types pour les données=== Afin de pouvoir facilement changer certains types et de pouvoir répercuter les changements partout dans le code, nous avons définis 2 types personnalisés pour: - le stockage des coefficients du échantillons en entrée/sortie/résultats intermédiaires: type **echantillon_t**. - le stockage des coefficients du filtre: type **coefficient_t**. Pour le projet QT, ces définitions seront les suivantes et vous utiliserez ces types au lieu de **double** dans tout votre code: //définition du type utilisé pour les échantillons et les coefficients sous QT typedef double echantillon_t; typedef double coefficient_t; ====Approche objet==== Polycopié du cours de P.O.O. : {{https://bvdp.inetdoc.net/files/iut/tp_qt/cours_POO_intro9_2019.pdf}} ===Classe Filtre=== Le code et les données pour le filtre sont rangés dans une classe Filtre. Les **membres** de la classe Filtre sont déclarés dans le fichier **filtre.h** et l'implémentation des méthodes est réalisée dans le fichier **filtre.cpp**. Les **attributs** de Filtre sont déclarés en **protected** afin de réaliser l'encapsulation et ne seront donc accessibles que depuis les méthodes de Filtre (et dans la classe dérivée FiltreTrace). Les **méthodes** de Filtre sont déclarées en **public** afin de pouvoir les invoquer depuis l'extérieur de la classe Filtre. Pour vous, les attributs de la classe Filtre seront utilisables comme des variables globales accessibles dans les différentes méthodes de filtre. La définition de la classe Filtre est donnée ici: class Filtre { public: Filtre(int nbInitB, coefficient_t *pBInit, int nbInitA, coefficient_t *pAInit,int nbMemoireVkInit=0, echantillon_t *memoireVkInit=NULL, int offsetEntreeInit=0, int offsetSortieInit=0, int valeurSortieMaxInit=10000, int valeurSortieMinInit=-10000); void ResetMemoireVk(); echantillon_t TraiteUnEchantillon(echantillon_t ek); void setADCDAC( int offsetEntreeInit, int offsetSortieInit, int valeurSortieMaxInit, int valeurSortieMinInit){ offsetEntree=offsetEntreeInit; offsetSortie=offsetSortieInit; valeurSortieMax=valeurSortieMaxInit; valeurSortieMin=valeurSortieMinInit; } protected: //attributs accessibles dans les classes dérivées int nbCoeffB; //nombre de coefficients du numérateur de la fonction de transfert du filtre coefficient_t * pCoeffB; //Coefficients du numérateur de la fonction de transfert du filtre int nbCoeffA; //nombre de coefficients du dénominateur de la fonction de transfert du filtre coefficient_t * pCoeffA; //Coefficients du dénominateur de la fonction de transfert du filtre //le premier élément est a1 //le tableau est de taille 1 pour un RIF int nbMemoireVk; //nombre de cases du buffer circulaire pour stocker les valeurs de vk echantillon_t * memoireVk;//buffer circulaire pour stocker les valeurs de vk int indice_ecr; //indice d'écriture dans le buffer rotatif memoireVk //paramètres ADC/DAC int offsetEntree; //offset retranché aux échantillons en entrée int offsetSortie; //offset ajouté aux échantillons en sortie int valeurSortieMax; //valeur maximale pour les échantillons de sortie avant ajout de offsetSortie int valeurSortieMin; //valeur minimale pour les échantillons de sortie avant ajout de offsetSortie }; ===Classe FiltreTrace=== La classe FiltreTrace est définie comme classe fille de la classe Filtre. Elle peut donc faire tout ce que Filtre peut faire mais contient également du code de test, d'affichage etc... Alors que la classe Filtre contient du code ayant vocation a être compilé pour la cible microcontrôleur et est donc limité en termes d'utilisation de librairie et de ressources matérielles, il n'y a pas de telles limitations pour la classe FiltreTrace. Les **membres** de la classe FiltreTrace sont déclarés dans le fichier **filtretrace.h** et l'implémentation des méthodes est réalisée dans le fichier **filtretrace.cpp** ===Paramètres par défaut=== Pour rappel, il est possible en C++ de définir des valeurs par défaut pour les paramètres des méthodes. La valeur de ces paramètres est définie dans la définition de la méthode (fichier .h). Dans l'implémentation des méthodes (fichier .cpp), ces valeurs par défaut n'apparaissent pas. Lors de l’invocation, si la valeur effective d'un paramètre n'est pas fournie, le paramètre est affecté à sa valeur par défaut. ===Allocation dynamique=== Le pointeur **memoireVk** dans la classe Filtre peut être alloué dynamiquement par le constructeur de la classe et sera donc utilisé comme un tableau de taille **nbMemoireVk** dans votre code. -------------------------------------------------------------------------------------------------------- =====Exercice 1: Implémentation pour un exemple simple===== Jusqu'à ce TP, vous avez utilisé la méthode **void FiltreTrace::Reponse(double * ek, double * sk, int nbPts)** pour appliquer le calcul de la réponse d'un filtre. Pour réaliser un filtre capable de traiter des échantillons en ligne, il faut que le processeur effectue le calcul de l'équation de récurrence échantillon par échantillon. La première partie du TP consiste donc à implémenter la méthode **echantillon_t Filtre::TraiteUnEchantillon(echantillon_t ek)** qui réalise le calcul de cette équation de récurrence. Afin de pouvoir tester cette méthode, la méthode **void FiltreTrace::ReponseEnLigne(echantillon_t * e, echantillon_t * s, int nbPts)** doit : - invoquer la méthode **void Filtre::ResetMemoireVk()** - traiter tous les échantillons du tableau e (pour $k\in[0,nbPts-1]$) à l'aide de la méthode **echantillon_t Filtre::TraiteUnEchantillon(echantillon_t ek)** et ranger le résultat dans le tableau s Le code d'une version simplifiée de la méthode **void FiltreTrace::ReponseEnLigne(echantillon_t * e, echantillon_t * s, int nbPts)** est montré ci dessous (Vous n'avez pas à changer le code présent dans le fichier filtretrace.cpp): void FiltreTrace::ReponseEnLigne(echantillon_t * e, echantillon_t * s, int nbPts) { ResetMemoireVk(); for (int k=0; k < nbPts; k++) { s[k] = TraiteUnEchantillon(e[k]); } } {{https://bvdp.inetdoc.net/files/iut/tp_tns/TODO.jpg}} Lire le code de la méthode **void FiltreTrace::ReponseEnLigne** et répondez aux questions suivantes sur le compte rendu : - que représentent e et s ? (indice : la même chose que pour la méthode **calculReponseFiltre()** ) - que représentent e[0] et s[0] ? - que faut-il ranger dans la case s[k] ? {{https://bvdp.inetdoc.net/files/iut/tp_tns/TODO.jpg}} Implémenter la méthode **echantillon_t Filtre::TraiteUnEchantillon** pour qu'elle retourne la valeur de l'échantillon e_k multiplié par 3. Compiler et exécuter le programme. Justifier sur le compte-rendu les réponses affichées à l'écran. Quelle est l'équation correspondant à ce filtre ? {{https://bvdp.inetdoc.net/files/iut/tp_pic/validation.png}} Une fois les réponses validées, mettre à jour le suivi de version en saisissant dans une console: echo commence cd ~/TP_ARCHI_TNS_ETU git commit -a -m'first filter working' gitk & echo fini -------------------------------------------------------------------------------------------------------- =====Exercice 2: Codage de l'équation de récurrence pour un filtre à réponse impulsionnelle finie (RIF)===== On cherche maintenant à implémenter l'équation: $s_k=\sum_{i=0}^{N}b_i.e_{k-i}$. Afin de faciliter la transition vers la réalisation des filtres RII, on codera la forme canonique $s_k=\sum_{i=0}^{N}b_i.v_{k-i}$ avec $v_k=e_k-\sum_{j=1}^{M}a_j.v_{k-j}$. Mais comme pour les filtres RIF $a_j=0\; \forall j\geq1$ on obtient simplement dans ce cas $v_k=e_k$ Pour calculer $s_k$, il est nécessaire de disposer des valeurs des coefficients du filtres $b_i$ et des échantillons $v_k$ courant et précédents, stockés dans un buffer circulaire tel que présenté dans le cours. La figure suivante illustre le fonctionnement d'un tel buffer de taille 6: {{https://bvdp.inetdoc.net/files/iut/tp_tns/figure_vk_3.png?700}} Le tableau **memoireVk** est utilisé pour stocker les échantillons. L'attribut d'instance **indice_ecr** est utilisé pour indiquer le numéro (indice d'écriture) de la case dans laquelle ranger l'échantillon le plus récent. Dans le codage, vous tiendrez compte du fait que le tableau **memoireVk** est circulaire et de taille **nbMemoireVk** éléments pour la mise à jour des indices de lecture et d'écriture. {{https://bvdp.inetdoc.net/files/iut/tp_tns/TODO.jpg}} Compléter la méthode **echantillon_t Filtre::TraiteUnEchantillon(echantillon_t ek)** pour qu'elle: - calcule $v_k$ à partir de $e_k$ - range l'échantillon $v_k$ au bon endroit dans le buffer circulaire. - calcule la valeur de $s_k$, conformément à la formule rappelée en début d'exercice, en remontant les échantillons à partir de $v_k$. Pour cela, vous utiliserez une variable locale servant d'indice de lecture (revoir le TD1 d'architecture...). Les coefficients du filtre sont au nombre de **nbCoeffB** et leurs valeurs se trouvent dans le tableau **pCoeffB**. - prépare l'itération suivante en mettant à jour l'indice d'écriture. {{https://bvdp.inetdoc.net/files/iut/tp_tns/TODO.jpg}} Tester votre méthode à l'aide du filtre dont les coefficients sont définis dans le tableau **coeffB** du fichier **tparchi1.cpp**. {{https://bvdp.inetdoc.net/files/iut/tp_tns/TODO.jpg}} Vérifier l'exactitude des réponses impulsionnelles et indicielles pour le filtre fourni jusque $k=7$ en comparant les échantillons en sortie du filtre avec les valeurs que vous calculerez sur votre compte-rendu. Conclure. {{https://bvdp.inetdoc.net/files/iut/tp_tns/TODO.jpg}} Vérifier l'exactitude des réponses impulsionnelles et indicielles pour le filtre fourni jusque $k=7$ à l'aide d'un test automatique en invoquant cette ligne après **filtre_echo_RIF.ReponseEnLigne(...)** dans la fonction **code_tparchi1()** : double errmax=filtre_echo_RIF.CompareReponses(impulsion,NB_ECH); La variable errmax fournit alors l'erreur maximale entre l'implémentation de référence (fournie par les enseignants) et la votre. Elle doit être (quasi) nulle pour valider votre code. Cette valeur est visible dans la console, (onglet Sortie de l'application, en bas de la fenêtre de QtCreator). {{https://bvdp.inetdoc.net/files/iut/tp_tns/BONUS.jpg}} Appliquer ce filtre à deux sinusoïdes de fréquence 50 Hz et 500 Hz (en considérant la fréquence d'échantillonage à 5Khz). Pour cela, vous devrez ajouter ces signaux à coté de l'impulsion et de l'échelon. Expliquez le résultat en vous aidant du module de la fonction de transfert. {{https://bvdp.inetdoc.net/files/iut/tp_pic/validation.png}} Une fois les réponses validées, mettre à jour le suivi de version en saisissant dans une console: echo commence cd ~/TP_ARCHI_TNS_ETU git commit -a -m'RIF working' gitk & echo fini -------------------------------------------------------------------------------------------------------- =====Exercice 3: Codage de l'équation de récurrence pour un filtre à réponse impulsionnelle infinie (RII)===== On cherche maintenant à implémenter l'équation: $s_k=\sum_{i=0}^{N}b_i.e_{k-i}-\sum_{j=1}^{M}a_j.s_{k-j}$. Pour cela, nous utiliserons la forme canonique vue en cours: $s_k=\sum_{i=0}^{N}b_i.v_{k-i}$ faisant intervenir les termes $v_k$. Pour l'échantillon $k$, il est nécessaire d'évaluer la valeur de $v_k=e_k-\sum_{j=1}^{M}a_j.v_{k-j}$ qui utilise les valeurs des $v_k$ précédemment calculés. {{https://bvdp.inetdoc.net/files/iut/tp_tns/TODO.jpg}} Compléter la méthode **echantillon_t Filtre::TraiteUnEchantillon(echantillon_t ek)** pour qu'elle permette de gérer ce type de filtre. Pour cela, vous devez modifier le calcul de $v_k$ réalisé à l'étape 1. de l'exercice 2 ainsi : - calculer $v_k$ à partir des valeurs précédentes de $v_k$ stockées dans le tableau **memoireVk**. Vous utiliserez le même principe que pour le calcul de $s_k$ réalisé pour le filtre RIF. Les coefficients du filtre à utiliser sont au nombre de **nbCoeffA** et sont situés dans le tableau **pCoeffA**. {{https://bvdp.inetdoc.net/files/iut/tp_tns/TODO.jpg}} Tester l'implémentation de la méthode de calcul sur le filtre graĉe à un filtre simple de votre choix (voir les TP de TNS si vous êtes en panne d'inspiration) {{https://bvdp.inetdoc.net/files/iut/tp_tns/TODO.jpg}} Déclarer les variables nécessaires pour ajouter un second filtre d'équation de récurrence: $ s_k=e_k+0.3s_{k-7} $ {{https://bvdp.inetdoc.net/files/iut/tp_tns/TODO.jpg}} Calculer les réponses impulsionnelles et indicielles en utilisant la méthode **Filtre::ReponseEnLigne(echantillon_t * ek, echantillon_t * sk, int nbPts)** et ajouter le code permettant de les afficher. Activer l'affichage du module de la fonction de transfert de ce filtre en supprimant dans le fichier tparchi1.cpp la ligne **if(0)** avant le commentaire: //SUPPRIMER LA LIGNE SUIVANTE POUR POUVOIR OBSERVER LES RÉPONSES FRÉQUENTIELLES À PARTIR DE L'EXERCICE 2 Vérifier l'exactitude des réponses obtenues grâce à une des deux méthodes expliquées à l'exercice 2 jusqu'à l'échantillon 15. {{https://bvdp.inetdoc.net/files/iut/tp_pic/validation.png}} Une fois les réponses validées, mettre à jour le suivi de version en saisissant dans une console: echo commence cd ~/TP_ARCHI_TNS_ETU git commit -a -m'RII working' gitk & echo fini -------------------------------------------------------------------------------------------------------- Bravo, vous pouvez maintenant passer au TP2: [[tptns2018_2]] ===Structure Directe 1 pour filtre RII=== $s_k=\sum_{i=0}^{N}b_i.e_{k-i}-\sum_{j=1}^{M}a_j.s_{k-j}$ ===Structure Directe 2 (forme canonique) pour filtre RII=== $s_k=\sum_{i=0}^{N}b_i.v_{k-i}$ avec $v_k=e_k-\sum_{j=1}^{M}a_j.v_{k-j}$ ===Equivalence entre les 2 formes=== Application Numérique pour un filtre M=2 et N=2: $s_k=\sum_{i=0}^{N}b_i.v_{k-i}$ $= b_0 .v_k + b_1 .v_{k-1} + b_2 .v_{k-2}$ $= b_0 . (e_k - a_1 . v_{k-1} - a_2 . v_{k-2}) + b_1 . (e_{k-1} - a_1 . v_{k-2} - a_2 . v_{k-3}) + b_2 . (e_{k-2} - a_1 . v_{k-3} - a_2 . v_{k-4})$ $= b_0 . e_k + b_1 . e_{k-1} + b_2 . e_{k-2} - a_1 . ( b_0 . v_{k-1} + b_1 . v_{k-2} + b_2 . v_{k-3} ) - a_2 . ( b_0 . v_{k-2} + b_1 . v_{k-3} + b_2 . v_{k-4} )$ On retrouve bien la Structure Directe 1: $s_k= b_0 . e_k + b_1 . e_{k-1} + b_2 . e_{k-2} - a_1 . s_{k-1} - a_2 . s_{k-2}$ echantillon_t Filtre::TraiteUnEchantillon(echantillon_t ek) { echantillon_t sortie=0; //valeur par défaut //Début code étudiant echantillon_t vktemp; // calcul intermediaire pour vk int i; //indice de lecture pour les valeurs des coefficients du filtre int j; //indice de lecture pour les valeurs des coefficients du filtre int indice_lec; //indice de lecture pour les échantillons d'entrée echantillon_t skout; //valeur pour la sortie calculée //suppression offset numérique vktemp=ek - offsetEntree; //pour un RIF, NB_COEFF_A=1 donc vktemp sera inchangé indice_lec = indice_ecr; // le premier element utile du tableau denCoeffs est a1 for (j=1;j=nbMemoireVk) indice_ecr=0; if (skout > valeurSortieMax ) // gestion des saturations du résultat skout = valeurSortieMax ; else if (skout < valeurSortieMin ) skout = valeurSortieMin ; //ajout offset numérique sortie= skout + offsetSortie; //Fin code étudiant return sortie; }