Cours architecture pour le TNS {{https://bvdp.inetdoc.net/files/iut/tp_tns/cours_Archi_TNS_intro5_6ppf.pdf}} ===== TP1 Codage du calcul de l'équation de récurrence ===== ====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 (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 ! ====Récupération du projet Qt de départ==== {{https://bvdp.inetdoc.net/files/iut/tp_tns/TODO.jpg}} Pour récupérez le fichier zip du projet la première fois, ouvrir une console (alt+F2 et taper lxterm) puis taper dedans: cd /mnt/etu/s4en/..... à compléter pour aller dans votre dossier étudiant, en pressant 2 fois la touche TAB puis ENSUITE SEULEMENT copier coller (sélectionner le code qui suit puis clic sur le bouton central de la souris dans le terminal) : wget https://bvdp.inetdoc.net/files/iut/tp_tns/TP_ARCHI_TNS_2017.zip unzip TP_ARCHI_TNS_2017.zip cd TP_ARCHI_TNS 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: contient les définitions de la structure de filtres (coefficients...), les prototypes des fonctions de filtrage et d'évaluation du filtre. - filtre.cpp: contient l'implémentation des fonctions de filtrage et d'évaluation du filtre. C'est principalement ce fichier que vous devrez compléter. - tparchi1.cpp: contient la fonction de test void code_tparchi1(void) qui appelle vos fonctions après avoir créé des échantillons pour les signaux d'entrée et trace les réponses impulsionnelle, indicielle et le module de la réponse fréquentielle. - 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 ====Implémentation des fonctions pour un exemple simple==== Jusqu'à ce TP, vous avez utilisé la fonction **void calculReponseFiltre(sFiltre * pFiltre, 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 une fonction **double filtreUnEchantillon(double ek)** qui réalise le calcul de cette équation de récurrence. Afin de pouvoir tester cette fonction, la fonction **void calculReponseFiltreEnLigne(sFiltre * pFiltre, double * ek, double * sk, int nbPts)**: - appelle la fonction **initFiltreUnEchantillon(pFiltre)** - traite tous les échantillons du tableau ek (pour $k\in[0,nbEch-1]$) à l'aide de la fonction **double filtreUnEchantillon(double ek)** et range le résultat dans le tableau sk void calculReponseFiltreEnLigne(sFiltre * pFiltre, double * ek, double * sk, int nbPts) { initFiltreUnEchantillon(pFiltre); for (int k=0; k < nbPts; k++) { sk[k] = filtreUnEchantillon(ek[k]); } } {{https://bvdp.inetdoc.net/files/iut/tp_tns/TODO.jpg}} Lire le code de la fonction **initFiltreUnEchantillon** et indiquer son rôle sur le compte rendu. Implémenter la fonction **filtreUnEchantillon** pour qu'elle retourne la valeur de l'échantillon e_k multiplié par 3. Compiler et exécuter le programme. Interpréter les réponses affichées. Quelle est l'équation correspondant à ce filtre ? ====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 les filtres RII, on supposera que les $e_k$ sont équivalents aux $v_k$ et l'équation à coder sera donc: $s_k=\sum_{i=0}^{N}b_i.v_{k-i}$. Pour calculer $s_k$, il est nécessaire de disposer des valeurs des coefficients du filtres $b_i$ et des échantillons d'entrée 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/buff.png?250}} Le tableau **memoireVk** est utilisé pour stocker les échantillons. La variable globale **indice_ecr** est utilisée pour indiquer le numéro (indice d'écriture) de la case dans laquelle ranger l'échantillon le plus récent. {{https://bvdp.inetdoc.net/files/iut/tp_tns/TODO.jpg}} Compléter la fonction **double filtreUnEchantillon(double ek)** pour qu'elle: - range l'échantillon $e_k$ au bon endroit dans le buffer circulaire. - calcule la valeur de $s_k$ en remontant les échantillons à partir de $e_k$. Pour cela, vous utiliserez une variable locale servant d'indice de lecture. Les coefficients du filtre sont au nombre de **NB_COEFF_B** et leurs valeurs se trouvent dans le tableau **b**. - prépare l'itération suivante en mettant à jour l'indice d'écriture. Dans le codage, vous tiendrez compte du fait que le tableau memoireVk est circulaire et de taille **MEMORYSIZE** éléments pour la mise à jour des indices de lecture et d'écriture. {{https://bvdp.inetdoc.net/files/iut/tp_tns/TODO.jpg}} Tester votre fonction à 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=6$. {{https://bvdp.inetdoc.net/files/iut/tp_tns/BONUS.jpg}} Appliquer ce filtre à deux sinusoïdes de fréquence 50 Hz et 500 Hz. Expliquez le résultat en vous aidant du module de la fonction de transfert. ====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 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 fonction **double filtreUnEchantillon(double ek)** pour qu'elle permette de gérer ce type de filtre. Pour cela, vous y ajouterez avant le calcul de $s_k$: - le calcul de $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 **NB_COEFF_A** et sont situés dans le tableau **a**. - l'enregistrement du $v_k$ calculé dans le tableau **memoireVk**, qui remplacera l'enregistrement de $e_k$. {{https://bvdp.inetdoc.net/files/iut/tp_tns/TODO.jpg}} Tester l'implémentation de la fonction de calcul sur le filtre. {{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 fonction calculReponseFiltreEnLigne et ajouter le code permettant de les afficher. Superposer l'affichage du module de la fonction de transfert de ce filtre à l'affichage précédent. =====TP2 Simulation des convertisseurs===== ===Fonctions simulant les convertisseurs=== Dans la démarche qui consiste à se rapprocher des conditions réelles dans lesquelles la fonction FiltreUnEchantillon sera utilisée, nous souhaitons maintenant intégrer les convertisseurs dans la chaîne de traitement en les simulant par des fonctions: - **simuADC** dont le rôle est de convertir un nombre représentant une tension en une valeur numérique quantifiée. - **simuDAC** dont le rôle est de convertir une valeur numérique quantifiée en un nombre représentant une tension. Le schéma suivant représente l'agencement de ces deux fonctions relativement à **FiltreUnEchantillon**. {{https://bvdp.inetdoc.net/files/iut/tp_tns/quantif1_simplifie.png}} Les fonctions **simuADC** et **simuDAC** sont définies dans les fichiers **convertisseurs.cpp et .h ** que vous devez créer dans votre projet ​(Nouveau Fichier -> C++ -> source ou entête) et dans lesquels vous copier/collerez le contenu suivant: double SimuADC ( double Ve, double Vmin, double Vmax, int nbBits); double SimuDAC ( double M, double Vmin, double Vmax, int nbBits); #include /*! * \brief SimuADC simule un ADC avec sortie non signée */ double SimuADC ( double Ve, double Vmin, double Vmax, int nbBits) { double q = (Vmax-Vmin)/(pow(2,nbBits)-1); double ekQuant; if (VeVmax) ekQuant = pow(2,nbBits)-1; else ekQuant = round((Ve-Vmin)/q); return ekQuant; } /*! * \brief SimuDAC simule un DAC avec entrée non signée */ double SimuDAC ( double M, double Vmin, double Vmax, int nbBits) { double q = (Vmax-Vmin)/(pow(2,nbBits)-1); return (Vmin + q*( (unsigned int)M % (1< void test_conv(void) { const double vmin = -3.3; const double vmax = 3.3; const int nbbits = 4; const int nbpts = 1000; double ve[nbpts], caractADC[nbpts] ; double M[(int) pow(2,nbbits)], caractDAC[(int) pow(2,nbbits)]; for(int i=0;iplot(dataADC,style2); w1->setLegend(0,"Ve","M"); w1->setWindowTitle("caractéristique E/S ADC"); w1->show(); w2->plot(dataDAC,style1); w2->setLegend(0,"M","Vs"); w2->setWindowTitle("caractéristique E/S DAC"); w2->show(); } === Visualisation de signaux quantifiés (45' max) === - On souhaite visualiser sur une même fenêtre mais sur 2 axes différents (car les échelles sont différentes) l'impulsion et l'impulsion quantifiée pour 7 échantillons. Vous devez donc utiliser **simuADC()** pour quantifier échantillon par échantillon tout le signal impulsion et stocker le résultat dans un tableau impQuant. Simulez un CAN ayant une plage de conversion de 0/3,3V sur 4 bits (déclarez vmin, vmax, nbbits comme constantes globales). - Créez une nouvelle fonction dans le fichier tparchi1.cpp. - Afficher alors les deux signaux demandés. - Superposer ensuite l'échelon et l'échelon quantifié. - Vous pouvez constater que l'impulsion et l'échelon sont à la limite de la dynamique du CAN. Comme lors de l'utilisation de montage à AOp (souvenez-vous du télémètre !), il est préférable de centrer les signaux d'entrée sur la tension $(vmax+vmin)/2$ plutôt que sur 0V. Modifiez les signaux impulsion et echelon en conséquence. Quelle est la nouvelle valeur de repos des signaux quantifiés ? - Générez un signal sinusoïdal d'amplitude 1,5V, de fréquence 1 kHz correctement centré. Dans une nouvelle fenêtre, visualisez ce signal, ce signal quantifié sur 4 bits et ce signal quantifié sur 8 bits (dynamique 0/3,3V). - Faites valider votre travail. === Ajout des convertisseurs dans la fonction calculReponseFiltreEnLigne() === Comme cela a été dit en introduction de ce TP (relisez là si vous avez oublié...) il faut maintenant inclure les convertisseurs analogique<->numérique dans **calculReponseFiltreEnLigne()**. Déclarez vmin, vmax, nbbits comme constantes globales. - Reprenez le fichier filtre.cpp et intégrez dans la fonction **calculReponseFiltreEnLigne** l'appel de ces 2 fonctions (dynamique 0/3,3V , 8 bits) pour les utiliser avec la fonction **filtreUnEchantillon**: - appeler **simuADC** et stocker sa valeur de retour dans une variable du bon type. - appeler **filtreUnEchantillon** en lui fournissant en paramètre la variable précédemment calculée. - appeler **simuDAC** en lui fournissant la valeur fournie par **filtreUnEchantillon** et ranger la valeur de sortie de **simuDAC** dans le tableau **sk**. - Vérifier le bon fonctionnement des 2 filtres du TP1. Expliquez l'amplitude de la réponse impulsionnelle du filtre RIF. === Prise en compte de l'offset numérique des échantillons=== - Tester à nouveau le fonctionnement de **calculReponseFiltreEnLigne()** sur le filtre passe-bande de paramètres H0=2, f0=1000, Q=0,7. Il a été numérisé par transformée bilinéaire avec Fech=48000 : b[3]={0.1703 , 0.0000 , -0.1703}; a[3]={1.0000 , -1.8140 , 0.8297}; - Que constatez-vous ? - Ce phénomène est provoqué par l'offset numérique de ek_quant (sa valeur de repos est différente de 0). Modifiez **filtreUnEchantillon()** pour respecter le schéma de la P11 du cours. - Comme lors de l'utilisation de montage à AOp (souvenez-vous du télémètre !), il est préférable de centrer les signaux d'entrée sur la tension $(vmax+vmin)/2$ plutôt que sur 0V. Modifiez les signaux impulsion et echelon en conséquence. Vérifiez que le fonctionnement du filtre est maintenant correct. - Vérifiez que les deux premiers filtres fournissent les mêmes réponses impulsionnelles et indicielles que précédemment. === Saturation du calcul de l'équation de récurrence=== - Définissez le nouveau filtre $s_k=2e_k$ - Tracez sa réponse indicielle. Quel problème constatez-vous ? - Pour corriger ce problème, vous devez saturer le résultat du calcul de l'équation de récurrence avant de le retourner (donc avant la fin de **filtreUnEchantillon()**) - Testez le bon fonctionnement de l'ensemble sur la sinusoïde Proposition Bvdp: pour pouvoir utiliser plusieurs filtres, tout en gardant les variables globales, qui permettent gain en perf : il faut ajouter le pointeur d'écriture dans la structure filtre et utiliser une fonction choixFiltre, qui met à jour les variables globales avec les valeurs de la structure passée en arguments ===BAZAR=== =Travail à effectuer= Compiler et exécuter le projet. - Donner l'équation de récurrence du filtre décrit dans la fonction void code_tparchi1(void) du fichier tparchi1.cpp. Indiquer le type de filtre (RII ou RIF). - Dans le fichier **filtre.cpp**, implémenter la fonction filtreUnEchantillon qui applique l'équation de récurrence pour un échantillon du signal comme décrit en TD. ===BAZAR=== Pour l'instant, vous simulez le filtre grâce aux étapes suivantes: Si on veut pouvoir réaliser des filtres différents et les chainer (egaliseurs, visu puissance) il ne faut pas utiliser des variables globales pour le calcul du filtre. On crée une structure buffer à côté ? Vous pouvez constater en lisant le code que le tableau sk est rempli echantillon par échantillon. Pour l'instant ce code simule l'écoulement du temps en appelant filtreUnEchantillon qui calcule l'équation de récurence (je préfèrerais calcEquationRecurrence() qui est plus explicite) pour tous les échantillons $k\in[0,nbEch-1]$. Il vous reste à coder filtreUnEchantillon(double ek, sFiltre *pFiltre,sBuffer *pbuffer) qui est le coeur du filtre numérique.(On doit sans doute ajouter une explication détaillée de la structure buffer en début de séance ou au début du TP ?) Vous devez coder la forme canonique du filtre utilisant un seul buffer. TODO 2018: - faire le rif d'exemple sur 4 coeffs au lieu de 6 pour éviter qu'ils implémentent directement l'exemple du cours avec des constantes nommer les filtres et y faire référence dans le texte