ELECTRONIQUE ELE119 DSP -...

76
tp_dsp.doc a ELECTRONIQUE ELE119 DSP ----- PARTIE PRATIQUE ETAPE 1 1. PROGRAMME DEMO.C 2 1.1. Fichier demo.C fourni 2 1.2. Etude préalable 2 1.3. Travail pratique 3 2. ET SI LE DSP ETAIT UN VIRGULE FIXE ? 5 2.1. Théorie 5 2.2. Essai 5 ETAPE 2 3. CALCUL DE « VEFF » PAR BLOC « FAUSSES ET VRAIES » FONCTIONS 8 3.1. Algorithme 8 3.2. Programmation tout en C 8 3.3. Optimisation par une « fausse fonction » en assembleur 9 3.4. Optimisation par une vraie fonction C, en assembleur 11 ETAPE 2' 4. FFT PAR BLOC, SIMPLE ET DOUBLE BUFFER 14 4.1. Rappel Simple et Double Buffer 14 4.2. Programme en C et assembleur: calcul de FFT 16 ETAPE 3 5. LES OUTILS DE COMPILATION 25 5.1. Le programme de test test.c 25 6. OPTIMISATION PAR LE CHOIX DE L’EMPLACEMENT EN MEMOIRE DES OPERANDES 30 6.1. Cas optimum 31 6.2. Cas plus défavorable 31

Transcript of ELECTRONIQUE ELE119 DSP -...

tp_dsp.doc a

ELECTRONIQUE ELE119

DSP ----- PARTIE PRATIQUE

ETAPE 1 1. PROGRAMME DEMO.C 2

1.1. Fichier demo.C fourni 2 1.2. Etude préalable 2 1.3. Travail pratique 3

2. ET SI LE DSP ETAIT UN VIRGULE FIXE ? 5

2.1. Théorie 5 2.2. Essai 5

ETAPE 2

3. CALCUL DE « VEFF » PAR BLOC « FAUSSES ET VRAIES » FONCTIONS 8

3.1. Algorithme 8 3.2. Programmation tout en C 8 3.3. Optimisation par une « fausse fonction » en assembleur 9 3.4. Optimisation par une vraie fonction C, en assembleur 11

ETAPE 2' 4. FFT PAR BLOC, SIMPLE ET DOUBLE BUFFER 14

4.1. Rappel Simple et Double Buffer 14 4.2. Programme en C et assembleur: calcul de FFT 16

ETAPE 3 5. LES OUTILS DE COMPILATION 25

5.1. Le programme de test test.c 25

6. OPTIMISATION PAR LE CHOIX DE L’EMPLACEMENT EN MEMOIRE DES OPERANDES 30

6.1. Cas optimum 31 6.2. Cas plus défavorable 31

tp_dsp.doc b

ETAPE 4 7. FILTRAGE NUMERIQUE, SIMPLE REFLEXION SUR LA DYNAMIQUE 33

8. PROGRAMMATION EN C D'UNE CONVOLUTION PAR "PILE GLISSANTE" 34

8.1. Principe 34 8.2. Application simple : moyenne mobile d’un signal 34

9. OPTIMISATION D’UNE CONVOLUTION PAR UNE FONCTION EN ASSEMBLEUR. 39

9.1. Deux techniques possibles 39 9.2. Fonction C de convolution rapide par Buffer circulaire, sur Texas C31 40 9.3. Travail pratique 42

ETAPE 5 PROJET 10. SYNTHESE DE FREQUENCE 45

10.1. Principe 45 10.2. Travail pratique, programme en C 47

11. USAGE SIMPLE D’UNE LIGNE D’INTERRUPTION 51

11.1. Programme d’interruption en assembleur 51 11.2. Programme d’interruption en C 51

12. RECHERCHE DE LA RAPIDITE DE CALCUL PARTIE EN ASSEMBLEUR 54

12.1. Ecriture de la boucle en assembleur 54 12.2. Manipulation 56 12.3. Amélioration, encore ! 56 12.4. Question subsidiaire à se poser 56

13. DEMODULATION D’AMPLITUDE TOUT NUMERIQUE 57

13.1. Théorie 57 13.2. Etude pratique sans sous échantillonnage de la porteuse 59 13.3. Programme sur DSP 61 13.4. Et si on sous échantillonnait la porteuse ? 68 13.5. Et pour centrer la sortie toujours sur zéro ? 68

Suppléments Facultatifs 14. FILTRES IIR 70

14.1. Passe bas du premier ordre 70 14.2. Passe bas du second ordre 73

tp_dsp.doc 1

ETAPE 1

Sur carte Starter Kit But:

• Découverte de la chaîne de traitement formée par CAN, DSP, CNA filtre. • Utilisation du langage C (avec usage de quelques fonctions utilitaires nécessaires fournies

en assembleur). • Programme demo.c (produit simple par facteur = 1,75). • Modifications diverses. Temps d’exécution. • Et si on ne possédait qu’un DSP virgule fixe ? produit par facteur = 1,75.

tp_dsp.doc 2

1. PROGRAMME DEMO.C

1.1. Fichier demo.C fourni

Il effectue simplement en temps réel la multiplication par l'entier "facteur" de l'amplitude d'un signal.

/************************************************** *************************/ /* NE PAS CHANGER CETTE ENTETE !!!!! */

int flag_simu=0 ; /* drapeau pour simulation, mis à 1 automatiquement en simulation*/ /************************************************** *************************/ /***************** PROGRAMME DEMO *******************/ extern initialisation_aic(int TA, int RA, int TB, int RB, int CONTROL); extern int attente_echantillon(); extern sortie_echantillon(int ech); extern anti_overrun();

int x,y; void main(void) float f = 1.75; initialisation_aic(5,5,33,33,0x10); /* Fck AIC = 6.25MHz,, donc Fcad = 625 KHz */

/* Fech = 18,93939 kHz */ anti_overrun(); while(1)

x = attente_echantillon(); y = f*x; sortie_echantillon(y);

1.2. Etude préalable

1) On repère : - Les échantillons x et y déclaré en entier int . (Dynamique théorique 32 bits) - Le facteur déclaré en flottant float. - la boucle parcourue indéfiniment. - des fonctions utilitaires, les prototypes étant dans extern.h

void anti_overrun(void) ; /* déblocage du DRR de l’AIC */ void initialisation_aic(int TA, int RA, int TB, int RB, int CONTROL) ;

(Permettant d’initialiser facilement l’AIC, cette fonction sera examinée plus tard) int attente-echantillon(void) ;

(Pour attendre un instant d’échantillonnage et acquérir la valeur en provenance du CAN. En, simulation elle renvoi toujours 0.25 = 0x4000 )

void sortie-echantillon(int) ; (Pour envoyer une valeur au CNA) ;

Rappel: les échantillons sont interprétables en : Entiers de dynamique pratique16 bits signés (-32768 à +32767), cas le plus fréquent Nombres Q15(16) de module < 1 pour des calculs en virgule fixe.

tp_dsp.doc 3

2) On effectue le produit y = facteur*x Ecrire cette même ligne en écrivant tous les cast

qui sont implicites. 3) D'après les valeurs de TA, TB, RA, RB, et CONTROL, calculez: - La fréquence d'échantillonnage exacte. - La fréquence de coupure à 0,5dB du filtre de sortie, et celle donnant une bonne

atténuation (de -58dB). 4) Commenter ces valeurs en dessinant l’allure du filtre de lissage obtenu et en y plaçant

Fech/2. As-t-on privilégié un bon lissage ou une bonne bande passante, ou les deux ? 5) Le filtre d'entrée est-il validé ?

1.3. Travail pratique

1.3.1. Essai du programme On prendra un Signal d'entrée Ve sinusoïdal, au départ d'environ 1V crête, dont on fera

varier la fréquence ou l'amplitude. Le filtre anti-repliement étant absent, on observera des repliements de spectres successifs

(tous les Fech). 1) Compiler, charger dans le starter kit. 2) Avec la variable initiale facteur = 1.75, vérifier expérimentalement en expliquant ce

que vous faites, et en comparant avec la théorie : - La fréquence d'échantillonnage (rechercher un battement très basse fréquence, ce

battement peut évidemment s’observer pour toutes fréquences voisines de k.Fech ). - La fréquence approchée à laquelle le filtre de sortie commence à couper rapidement.

Est-elle bien placée ? Remarque : en augmentant la fréquence, on observe au début une atténuation assez lente, elle est due à la fonction de transfert en sinx/x (inévitable sauf correction spécifique) de l’échantillonneur bloqueur (cf partie du cours traitement du signal). Puis l’amplitude chute brusquement au moment ou le filtre de lissage de sortie commence à intervenir. On ne cherchera donc pas une mesure exacte de la bande passante à 0,5dB du filtre !

3) A une fréquence voisine de 100Hz (la fréquence exacte importe peu !), vérifier la

tension crête admissible en entrée. Quand on dépasse, dessiner le signal de sortie et expliquer: le phénomène observé est-il dû à un écrêtage ou à un débordement, et a quel endroit ?.

Le phénomène est identique mais on pourra vérifier que sa visualisation est totalement différente pour des fréquences plus élevées (quelques KHz). En effet, le filtre de lissage subissant des discontinuités et ayant une réponse impulsionnelle donc indicielle oscillante, les oscillations deviennent de plus en plus visibles à ces fréquences et masquent le phénomène d’écrêtage.

Si le matériel est installé, on pourra écouter un son subissant un tel phénomène. 4) Vers 100 Hz et pour une tension Ve d'environ 50mv crête, observer le signal de sortie,

expliquer les déformations, en les comparant au bruit de quantification du CAN. Conclure sur la qualité de l’AIC !

Si le matériel est installé, on pourra écouter un son ayant cette allure.

tp_dsp.doc 4

1.3.2. Essai sur simulateur. Nombre de cycles d’exécution On cherche ici juste à découvrir le simulateur … (Se reporter à l’annexe fin de ce poly) Rappel, le simulateur travaille sans pipe line. Le nombre de cycle à la base est de 4 pour

toute instruction. En entrant dans le clock setup une compensation de pipe line de 4, il affiche 1 Tck pour

toute instruction standard. Il ne fournit donc qu’un temps approché, supposant aucun débranchement de pipe

line. On ne peut donc s’en servir pour vérifier les gains obtenus par des modifications d’implantation mémoire des opérandes et du code.

Très important : Bien vérifier donc cette compensation de pipe line de 4, volontairement choisie par le programmeur.

1) Le faire tourner. Visualisez x, y, et facteur, explication de la non visibilité de facteur

(Le compilateur a l’option de d’optimisation maximale). 2) Observer le nombre approché de cycles de Clock d’exécution, à chaque ligne de C, et

entre deux acquisitions. (Vérifier bien la compensation de 4 cycles dans le Clock Set up !)

3) Quelle serait la Fech max possible (si on utilisait un CAN et CNA pouvant travailler à ces fréquences).

1.3.3. Modification du programme demo.c 1) Toujours en virgule flottante, modifier le programme pour multiplier par 0.7 et

vérifier le fonctionnement avec Ve environ 100Hz et 1v crête.

2) Si on augmente trop la tension d'entrée, un phénomène apparaît, différent du débordement précédent. Dessiner alors le signal observé et l'expliquer. Si le matériel est installé, on pourra écouter un son ayant cette allure.

- Remarque : si on augmente encore d’avantage la tension d’entrée, un phénomène un peu « paranormal » peut se manifester ensuite: on pourrait l’interpréter comme un débordement mais c’est impossible car on fait le produit par un nombre de module inférieur à 1. Le pauvre DSP n’y est pour rien, ni le calcul effectué. On en verra l’explication lors de ce TP, le demander à l’enseignant, mais chercher tout de même …. (il y a un ampli op à l’entrée analogique, et au delà d’un certain niveau l’étage d’entrée peut saturer ….).

tp_dsp.doc 5

2. ET SI LE DSP ETAIT UN VIRGULE FIXE ? Il faudrait connaître alors la technique particulière : travail sur des entiers tout en

raisonnant sur des nombres fractionnaires. On supposera donc ici que l’on travaille avec un DSP virgule fixe, et avec les types de

données du C standard : int nombre entiers signés sur 16 bits long nombres entiers signés sur 32 bits (Ce serait le cas du TMS320C5x)

2.1. Théorie

On raisonne ici avec des échantillons en Q15(16) supposés par la pensée de module < 1 On veut effectuer le calcul de y = 1,75x avec x de module < 1 On suppose que y aura aussi une dynamique pratique < 1 On veut obtenir y dans le même format

que x, le petit dessin ci-contre montre ce qu’il faut faire.

On déclarera donc int x,y ; /* sous

entendu interprété en Q15(16) */ int facteur = 1.75*16384 ; /* facteur en Q14(16) */ 1) Donner la dynamique des trois formats Q15(16) Q14(16) et Q13(16) 2) Le format Q14(16) pour facteur est donc sur 16 bits le plus précis, pourquoi ? 3) En s’inspirant du cours, écrire donc la ligne du calcul de y en Q15(16) y = ?

2.2. Essai

On peut utiliser le TMS320C31, le faire travailler en entier ou virgule fixe, et en raisonnant comme si le compilateur travaillait comme le C standard : avec des int sur 16 bits et des long de 32 bits.

On écrira donc les cast en long 1) Modifier demo.c 2) Vérifier le fonctionnement. Evidemment sur le C31 qui est par nature ‘virgule flottante’, cette méthode de calcul est

absolument inutile et totalement stupide ! Le temps du calcul est bien plus long qu’un simple produit virgule flottante ! Mais ce serait

l’inverse si on avait utilisé un DSP virgule fixe (comme le Texas C50).

-,------- -------- x * --,------ -------- 1,75 ________________________ ---,----- -------- -------- -------- y

tp_dsp.doc 6

tp_dsp.doc 7

ETAPE 2

Sur carte "Starter Kit" Buts :

EN VIRGULE FLOTTANTE LE PLUS POSSIBLE

• Programme de calcul de valeur efficace par bloc. Vitesse de calcul de la somme des x2 • Optimisation de la vitesse d’exécution par un calcul en assembleur dans le C. • Optimisation par une fonction C écrite en assembleur.

tp_dsp.doc 8

3. CALCUL DE « VEFF » PAR BLOC « FAUSSES ET VRAIES » FONCTIONS

3.1. Algorithme

On pourrait bien évidemment tout faire dans une seule boucle ! Mais comme l’idée de la manipulation est ensuite d’optimiser un calcul de somme de carrés, on décide ici d’effectuer deux boucles :

∑−

=

≈1

0

21 N

kkx

NVeff

Acquisition de N = 200 valeurs (à la cadence d'échantillonnage)

Calcul de la somme des carrés des 200 valeurs

Calcul de la valeur efficace

initialisations

Modification pour le Starter Kit et l’AIC Il faut des entrées et sortie régulières sur le port analogue pour éviter un blocage (Overrun

de l’AIC), le plus simple est donc de sortie un signal Veff à chaque nouvelle acquisition, Veff n’étant réactualisée évidemment que tous les 200 échantillons !

D’ou l’organigramme à respecter :

Calcul de la somme des carrés des 200 valeurs

Calcul de la valeur efficace, mise en Veff

InitialisationsAnti_overrun();

Lire échantillon à la cadence Fech et mise en tableauSortie vers DXR (CNA) de la variable Veff

Boucled’acquisation

BoucleDe calcul

3.2. Programmation tout en C, fonction en C

On regroupe ici tous les prototypes des fonctions de bases écrites en assembleur dans un ficher .h, d’ou la directive #include ‘extern.h’

On doit calculer une Racine Carrée. Deux choix possibles selon le type de DSP. - Sur un DSP virgule fixe : En virgule flottante, facile, mais des milliers de Tck d’exécution et un code plus long.

En virgule fixe : on utiliserait des nombres supposés de module < 1 et la méthode d’accès à une table, avec interpolation linéaire éventuellement.

- Ici sur notre DSP virgule flottante, comme le compilateur C possède une routine flottante de racine carrée assez rapide, c’est celle que nous utiliserons/

1) Compléter le fichier fourni Veff.c suivant : /* NE PAS CHANGER CETTE ENTETE !!!!! */

tp_dsp.doc 9

int flag_simu=0 ; /* drapeau simulation, mis automatiquement à 1 par le simulateur */

/************************************************** ********************/

/***************** PROGRAMME VEFF en BL OC *******************/ #include ‘extern.h’ /* dans le répertoire de travail */ #include <math.h> /* nécessaire pour utiliser sqrt en C, pour ce compilateur C */ #define taille 200 float tab[taille]; float veff ; float s2 ; void main(void) initialisation_aic(5,5,33,33,0x10); /* Fech = 18,93939 kHz */ anti_overrun(); // déblocage port AIC si besoin est. while(1) anti_overrun(); s2 = 0; for( ? ? ? ? ?) ? ? ? ? ?? ? /* acquisition tableau, avec sortie de l’ancien Veff à chaque échantillon*/ ???? /* Appel à calcul_s2 pour le calcul de la somme des carrés */ veff = ? La fonction calcul_s2 est déjà

fournie dans le fichier pour gagner du temps :

float calcul_s2(float *tab, int n) int k ;float som2 = 0 ; for(k=0 ; k<n ;k++) som2 = som2 + tab[k]*tab[k] ; return(som2) ;

2) Vérifier le fonctionnement (avec générateur autour de 1000 Hz), les deux voies de

l’oscillo réglées sur même gain même décalage. Revoir le cours sur les bonnes conditions de mesure de valeurs moyennes et donc aussi de valeurs efficaces. On rappelle ici les grandes lignes : Soit un signal périodique de période du fondamental T. On effectue une mesure sur N échantillons pris à la cadence Fech = 1/Te, il vient :

Cas particulier : Prendre un nombre entier de périodes T du fondamental : Donc durée de mesure NTe = kT Cas général : Avoir une durée de mesure grande par rapport à T. Donc durée de mesure NTe >> T 3) Mesurer le nombre de cycles d’exécution pour la calcul de la somme de produit (la

seconde boucle for), en nombre de Tck. (Attention au réglage de la compensation de pipe line de 4 !)

4) Observer (dans Veff.asm) le code généré par le compilateur pour cette boucle. D’après vous que peut-on fortement optimiser en écrivant en assembleur ? (voir si le compilateur a généré des instructions RPTS ou RPTB de répétition d’instructions ou de blocs, ou bien des instructions en parallèle).

3.3. Optimisation par une « fausse fonction » en assembleur

On veut créer la fausse fonction (fonction sans paramètres) : void calcul_s2(void) Travaillant sur :

tp_dsp.doc 10

- le tableau global tab[200] (donc label assembleur _tab) - Calculant en s2 (variable globale) la somme des carrés des 200 termes (label _s2) - La taille est donc figée. Ne travaillant que sur des variables globales, il est donc inutile d’étudier la pile. On rappelle que pour ce compilateur, certains registres doivent être préservés dans une

fonction, donc sauvés et restitués si ion les utilise (Voir doc du compilateur) A priori on utilisera donc librement les registres R0, R1, R2, ainsi que AR0, AR1, AR2

sans nécessiter de les sauvegarder. Attention : ceci est vrai dans le sous programme d’une fonction seulement , et non pas dans

des lignes d’assembleur placées au hasard dans du C, car à priori le C peut utiliser tous les registres !

3.3.1. Etude du sous programme _calcul_s2

Il est fourni tout écrit dans un fichier sp.asm Encore une fois, insistons sur le fait que nous ne sauvegardons pas les registres utilisés

car nous travaillons avec un compilateur le permettant dans un sous programme de fonction. .global _calcul_s2,_s2,_tab .text TAB .word _tab ; astuce pour calculer adresse en 32 bits ! _calcul_s2: ldi @TAB ,AR2 ldi @TAB ,AR1 ldf 0,R2 ; raz (En flottant) R2 registre d’accumulation mpyf3 *AR1++,*AR2++,R0 ; Premier produit dans R0 rpts 198 mpyf3 *AR1++,*AR2++,R0 ;instructions // Produit R0 || addf3 R0,R2,R2 ; Accumulation précédent R0 ADDF3 R0,R2,R2 ; Accumulation dernier produit STF R2,@_s2 rets

Comprendre ce sous programme, et retenir les points importants suivants, qui vous serviront lors de l’écriture d’autres fonctions assembleur : • Utilisation du label TAB, constante (ici en mémoire programme) contenant l’adresse sur

32 bits du tableau tab. Nécessaire pour initialiser un pointeur (variable globale) en début de tableau (car l’adressage immédiat n’existe pas sur 32 bits).

• L’instruction RPTS pour répéter (ici 198 pour répéter 199 fois) la ligne suivante. • Multiplication_Accumulation : le produit donnant son résultat dans R0, l’addition

simultanée ayant aussi R0 comme opérande prendra donc l’ancien R0. L’accumulation se fait sur R2.

- D’ou obligation la première fois d’initialiser (par un premier calcul) R0 par l ligne : mpyf3 *AR1++,*AR2++,R0 avant la boucle RPTS

- D’ou l’obligation de la ligne finale ADDF3 R0,R2,R2 accumulant le dernier produit.

3.3.2. Partie pratique Faire plutot la partie pratique de la vraie fonction, décrite page suivante, et celle-ci

seulement après si vous avez du temps.

tp_dsp.doc 11

3.4. Optimisation par une vraie fonction C, en assembleur

3.4.1. Ecriture de la fonction Soit le prototype : float calcul_s2(float *tab, int taille) ;

Lors de l’entrée dans le sous programme _ calcul_s2 la pile contient :

Adresse tableautaille

031

SP PC

Après positionnement du pointeur de trame :

Adresse tableautaille

031

SPPC

Ancien AR3AR3

-2-3

Le sous programme est fourni tout écrit dans le fichier. sp_vraie.asm

La partie en italique est identique au sous programme précédent de la fauuse fionction. .global _calcul_s2 .text _calcul_s2 push AR3 ldi SP,AR3 ; positionnement du pointeur de Trame AR3 ldi *-AR3(2),AR1 ;AR1 sur début tableau ldi AR1,AR2 ;AR2 aussi ldi *-AR3(3),R1 ;taille du tableau dans R1 subi 2,R1 ;taille-2 dans R1 ldf 0,R2 mpyf3 *AR1++,*AR2++,R0 rpts R1 ;repetition taille-1 fois (R1+1 fois) mpyf3 *AR1++,*AR2++,R0 || addf3 R0,R2,R2 ADDF3 R0,R2,R2 LDF R2,R0 ; mise du résultat dans R0 pour retour pop AR3 rets

3.4.2. Essais 1) Vérifier le bon fonctionnement en temps réel (bien penser à assembler sp_vraie.asm,

et à prendre le code object correspondant de ce fichier au niveau de l ’édition de lien. 2) Estimer au simulateur le nouveau nombre de cycles du calcul de la somme de

produit. Et comparer. 3) Bien comprendre et préciser l’intérêt de cette vraie fonction par rapport à l’autre.

tp_dsp.doc 12

tp_dsp.doc 13

ETAPE 2’

Sur carte "Starter Kit" Buts :

• Programme de calcul de FFT par bloc. • Technique Simple et Double Buffer pour temps réel.

tp_dsp.doc 14

4. FFT PAR BLOC, SIMPLE ET DOUBLE BUFFER

4.1. Rappel Simple et Double Buffer

4.1.1. Simple Buffer

Bloc n

tc

Bloc n+1 Echantillons perdus Echantillons perdus

Sortie calcul du bloc n +1 Cadence Fech

Sortie calcul du bloc n Cadence Fech

Echantillons calculés

tc

On perd un grand nombre d’échantillons. Pas de temps réel.

4.1.2. Double Buffer et technique du « Pipe Line » Cette méthode s’applique que l’on ait un échantillon à calculer par bloc ou plusieurs. Exemple d’acquisition et sortie sur N échantillons (N = 6) : ce serait le cas d’une

transformée de Fourier. 4.1.2.1. Principe

Sortie Calcul du Bloc n-2

Calcul bloc n

tc Calcul bloc n-1

Acquisition Bloc n

Acquisition Bloc n+1

Acquisition Bloc n+2

Sortie Calcul du Bloc n-1

Sortie Calcul du Bloc n

Acquisition Bloc n+3

Sortie Calcul du Bloc n+1

Calcul bloc n+2

Calcul bloc n+1

tc tc

Pas de pertes d’échantillons. Cadence d’échantillons identique entrée et sortie. Temps réel respecté ! Les échantillons de sortie correspondant au travail sur un bloc sortent évidemment avec un

retard de 2N échantillons.

tp_dsp.doc 15

4.1.2.2. Mise en œuvre, avec deux buffer d’entrée et deux de sortie, de taille N, et un travail en deux phases.

Phase 1 Phase 2

In1

In2

Out1

Out2

0

N-1

0

N-1

xk yk

Acquisition d’échantillons dans In1 (Fech) Sortie d’échantillons de Out1 (Fech) Calcul de Out2 à partir de In2

In1

In2

Out1

Out2

0

N-1

0

N-1

xk yk

Acquisition d’échantillons dans In2 (Fech) Sortie d’échantillons de Out2 (Fech) Calcul de Out1 à partir de In1

Synchronisation :

Interruption Fech :

Si phase 1 : Entrée d’un xk dans IN1, Sortie d’un yk de OUT1

Si phase 2: Entrée d’un xk dans IN2,

Sortie d’un yk de OUT2 Retour

Cadence Fech

Programme principal sans fin:

Si phase 1 : Calcul bloc IN2OUT2 Si phase 2: Calcul bloc IN1OUT1

Une interruption en tache de fond, cadencée à la fréquence d’échantillonnage Fech

rythme l’acquisition et la sortie des échantillons. Un compteur modulo 2N s’incrémentant à chaque interruption, permet de générer une

variable de phase : valant 1 si le compteur est entre 0 et N-1, et 2 si le compteur est entre N et 2N-1. Deux drapeaux start1 et start2 sont à 1 juste en début de chaque phase.

Le compteur permet aussi lors de la phase 1, d’effectuer l’acquisition dans In1 et de sortir une valeur de Out1, et lors de la phase 2 l’acquisition dans In2 et la sortie d’une valeur de In2. Les buffers In1, In2, Out1 et Out2 doivent débuter à des adresses multiples de 2N (nécessité pour un calcul correct de la FFT), on parle d’alignement à 2N. Phase = 1 Phase = 2 Phase = 1

Start1

Start2

Drapeaux

Variable compteur de synchronisation 0 N-1 N 2N-1 0

Une tache principale sans fin exécute les calculs (sur In2 ou In1 selon la phase), les

débuts de ceux ci devant se synchroniser sur le début des phases. Ceci se réalise au moyen des variables précédentes.

tp_dsp.doc 16

Remarques : Condition de temps réel : La durée du calcul (temps tc) nécessaire pour fournir les valeurs dans un des buffer de

sortie Out1(ou Out2), à partir du bloc de valeurs contenu dans In1 (ou In2 respectivement) doit être inférieure à N.Tech ce qui n’est pas un problème vu la vitesse de calcul des DSP actuels.

Cette technique est similaire à celle utilisée dan les jeux vidéos sur ordinateur, on affiche une image pendant que l’on calcule la suivante.

4.2. Programme en C et assembleur: calcul de FFT

Remarque, il n’y a pas en fait de programmation dans cette partie, vous devrez seulement bien comprendre la structure des programmes et vérifier le fonctionnement.

On suppose que l’on possède une fonction écrite en assembleur : void fft_128(float *IN, float *OUT) ; Entrée : Tableau IN de 128 échantillons du signal Sortie : Tableau OUT de 128 échantillons du module (en log) Cette fonction sera fournie un peu plus loin. Son étude à part les grandes lignes dépasse le

cadre de ce cours.

4.2.1. Méthode par simple Buffer Acquisition de 128 points, traitement, sortie des échantillons. On perd des échantillons /************************************************** ***********************************/ /******** TF SIMPLE 128 points PAR BLOC, Sortie ensuite NON TEMPS REEL ************/ #include "extern.h" #define NN 128 extern float IN1[]; /* Les deux buffer d'entrée et de sortie */ extern float OUT1[]; /* taille 128. Pour la FFT à aligner 128 (ou 256), donc déclarés en assembleur */ extern void fft_128(float *IN, float *OUT); void main(void) unsigned int k; initialisation_aic(5,5,33,33,0x10); /* Fech = 18,939 kHz */ anti_overrun(); do for(k=0;k<NN;k++) IN1[k] = attente_echantillon(); /* ACQUISITION BLOC */ fft_128(IN1,OUT1); /* CALCUL FFT */ pulse(); // impulsion vers oscillo de synchronisation */ anti_overrun(); for(k=0;k<NN;k++) attente_echantillon(); sortie_echantillon(OUT1[k]); /* SORTIE MODULE */ while(1); QUESTIONS : Vérifier le fonctionnement. Indiquer les phases d’acquisition, de traitement et de

restitution. Essayer de mesurer le temps de traitement du bloc. Combien d’échantillons perd-t-on à chaque bloc ?

tp_dsp.doc 17

4.2.2. Méthode Double Buffer /******** TF 128 points PAR BLOC ET DOUBLE BUFFER, TEMPS REEL ************/ #include "extern.h" #define NN 128 #define DEUXNN 2*NN extern float IN1[],IN2[]; /* Les deux buffer d'entrée et de */ extern float OUT1[],OUT2[]; /* taille 128, à aligner 128 (ou 256) à l’édition de lien*/ extern void fft_128(float *IN, float *OUT); /* Pour la synchronisation du traitement par double Buffer: */ unsigned int compteur = 0; int phase = 1, start1=0, start2=0; void calcul_bloc(int phase); extern int lire(void); /*en assembleur, juste lire l'échantillon sans attente de Fech */ void main(void) unsigned int k; initialisation_aic(5,5,33,33,0x10); /* Fech = 18,939 kHz */ anti_overrun(); /* @@@ TACHE PRINCIPALE lancant CALCUL_BLOC en début de chaque phase @@@@@@ */ do if(compteur ==0 && phase ==2) start1 = 1, phase = 1; else if(compteur==NN & phase ==1) phase = 2; start2 = 1; if(start1 == 1) calcul_bloc(phase); start1 = 0; /* SYNCHRONISATION CALCUL BLOC */ if(start2 == 1) calcul_bloc(phase); start2 = 0; /* SYNCHRONISATION CALCUL BLOC */ while(1); /* @@@@@@@@@@ FONCTION CALCUL_BLOC ici TF128 @@@@@@@@@ */ void calcul_bloc(int phase) /* PHASE 1 Calcul out2 à partir de in2 */ /* PHASE 2 Calcul out1 à partir de in1 */ pulse(); if(phase == 1) fft_128(IN2,OUT2); if(phase == 2) fft_128(IN1,OUT1); /* @@@@@ TACHE DE FOND SUR INTERRUPTION I/O Echantillons @@@@@@@@@ */ void c_int02 (void) /* interruption acquisition DRR à chaque Fech, redirigée vers ce label */ int c; c = compteur-NN; /* décalage pour adressage dans OUT2 et IN2 */ if(compteur < NN) /* PHASE 1 LECTURE 1 SORTIE 1 */ IN1[compteur] = lire(); sortie_echantillon(OUT1[compteur]); else IN2[c] = lire (); /* PHASE 2 LECTURE 2 SORTIE 2 */ sortie_echantillon(OUT2[c]); compteur++; if(compteur ==DEUXNN)compteur = 0; /* le modulo DEUXNN*/

tp_dsp.doc 18

QUESTIONS : Vérifier le fonctionnement. Et comprendre le fonctionnement temps réel sans perte

d’échantillon. En étudiant la condition de temps réel, comprendre pourquoi celui-ci est très facile à

obtenir par ce procédé. On observe à chaque fois un pic qui est le pic utile de notre transformée de Fourier,

mais aussi toujours un second pic : as quoi celui-ci est-il du ?

4.2.3. Coup d’œil sur la fonction assembleur

4.2.3.1. Les tables en assembleur

; ************************************************* ******************************* ; Table des "TWIDDLES" ( TR et TI: COS(k*PIN) et -SIN(k*PIN) en Bit Reverse ) ; Peuvent servir pour TF 128 ou 256 ; ************************************************* ******************************* ; Les tableaux d'entrée sortie IN et OUT: déclarés en assembleur pour aligner 256 plus facilement ; Un tableau de travail DI pour la partie imaginaire ; (En fait correspondant à la donnée d'entrée partie Imaginaire (sera mis à zéro en début de TF). ; Ce sont en réalité des flottant : mais comme leur code a été calculé par ailleurs, on introduit ici en entier

(.word) le code flottant hexadécimal 32 bits ! .sect "TABLES" A aligner 256 au lien .global _TR,_TI _TR: ; les COS .word 000000000h .word 0e8222169h .word 0ff3504f4h .word 0ffcafb0eh .word 0ff6c835fh .word 0febc10eeh .word 0fe43ef17h .word 0ff937ca2h .word 0ff7b14bfh .word 0fdb83a44h .word 0ff0e39dah .word 0ffab24d0h ….. etc pour 128 valeurs _TI: ; les SIN .word 080000000h .word 0ff800000h .word 0ffcafb0dh .word 0ffcafb0bh .word 0febc10ebh .word 0ff937ca1h .word 0ff937ca2h .word 0febc10e7h .word 0fdb83a3fh .word 0ff84eb41h .word 0ffab24cfh ….. etc pour 128 valeurs ;@@@@@@@@@@@@ LES BUFFER: @@@@@@@@@@@@@@@@@@@ .global _DI _DI .space 128 ; buffer de calcul, commun à chaque TF

tp_dsp.doc 19

;Pour une première TF .global _IN1,_OUT1 _IN1 .space 128 _OUT1 .space 128 ;Pour une seconde TF éventuelle (pour travail en Double Buffer et Pipe Line) .global _IN2,_OUT2 _IN2 .space 128 _OUT2 .space 128

4.2.3.2. Sous programme de la fonction fft_128

;************************************************** ************************************* ; FFT128.ASM En C: void fft_128(float *IN, float *OUT); VRAIE FONCTION, taille fixe ; Keith Larson, Modif G. Pallot janvier 2005 ; ENTREE 128 échantillons réel, en flottant, en IN. ; SORTIE: 128 échantillond de log(MODULE), en flottant en OUT ;************************************************** ************************************* ;Mantissa Mask ;------------- ; This mask is AND'ed with the mantissa of the butterfly input data and ; twiddle coefficients. You can use this technique to analyze the effects ; of floating point roundoff for various applications ; ;FFT Window ;---------- ; By using a post convolution of the window function after the FFT is ; complete a table for the window function is not required. Furthermore ; the time domain coefficients for a raised cosine window are very simple ; (-0.5,+1.0,-0.5). ; ;Bit Reversal of Twiddle Table ;----------------------------- ; By bit-reversing the twiddle tables the size of an FFT is not dependent ; on the size of the table loaded. In this case simply redefining the ; FFT size will result in a correctly coded FFT. ; .global _TR,_TI,_DI ; Les "twiddles" en bit reverse cos et sin, (128 valeurs chaque) N .set 128 ; ce qui permettrait de faire des TF de 256 ; Voir le Fichier Tables.asm ;================================================== ; Noms internes utilisés ;================================================== ;TR 128 valeurs float de COS(k*PIN), en Bit Reverse ;TI 128 valeurs float de -SIN(k*PIN), en Bit Reverse ;DR (128 Data réelle), DI (128 Data Imaginaire pour le calcul), BF (128 Résultat du module) ;DR,DI,BF flottant ;================================================== .global _fft_128 .text ;================================================== SIZE .word N MASK .word 0FFFFFFFFh ; Mask for mantissa bits FFTSIZE .word N ; 128 point FFT TR_ADDR .word _TR ; Adrresse des 128 SIN values TI_ADDR .word _TI ; Adresse des 128 COS values DR_ADDR .space 1 ; _DR ; Adresse des 128 REAL data point: En fait now paramètre entrée DI_ADDR .word _DI ; Adresse des 128 IMAG data point: intermédiaire de calcul

tp_dsp.doc 20

BF_ADDR .space 1 ; _BF ; Adresse du REAL buffer Résultat: En fait now paramètre de sortie ;========================================================== ; Perform FFT ;------------------------------- _fft_128 push AR3 ; Récupération des paramètres dans la pile LDI SP,AR3 LDI *-AR3(2),R0 ; PARAMETRE IN: adresse tableau d'entrée en DR_ADDR STI R0,@DR_ADDR LDI *-AR3(3),R0 ; PARAMETRE OUT: adresse buffer de sortie en BF_ADDR STI R0,@BF_ADDR pop AR3 sauvegarde contexte compilateur C push R3 push R4 push R5 push R6 push R7 pushf R3 ;pour des flottants, en plus des push, des pushf (spécifique à ce DSP, car les flottant pushf R4 ;sont placés dans les 32 bits de poids forts des registres Ri qui sont en fait de 40 bits, pushf R5 ;les entiers étant évidemment dans les 32 bits LSB. Ne connaissant pas à priori pushf R6 ;le type de données, deux empilements sont donc nécessaires par registre. pushf R7 push AR3 push AR4 push AR5 ; Mise de 0 dans la partie imaginaire du tableau DI ldi @DI_ADDR,AR0 ldi @SIZE,RC rptb encore ldf 0,R0 encore stf R0,*AR0++ ;Début de la FFT ldi @FFTSIZE,IR0 ; ldi @FFTSIZE,IR1 ; lsh -1,IR0 New_Stg ldi @FFTSIZE,RC ; ldi @DR_ADDR,AR0 ; ldi @DI_ADDR,AR1 ; ldi @TR_ADDR,AR2 ; Tw Base never modified ldi @TI_ADDR,AR3 ; lsh -1,RC ; Perform FFTSIZE/2 butterfly loops subi 1,RC ; lsh -1,IR0 ; lsh -1,IR1 ; ldi IR1,R0 ; bz FFT_END ; ;--------------------- Blk_Top rptb B_Fly ; Start by getting all 6 Butterfly inputs ldf *+AR0(IR1) ,R0 ; Bt real || ldf *AR0 ,R1 ; Tp real ldf *+AR1(IR1) ,R2 ; Bt imag || ldf *AR1 ,R3 ; Tp imag ldf *AR2++(IR0)B,R4 ; TW real - R4 || ldf *AR3++(IR0)B,R5 ; TW imag - R5 and @MASK,R0 ; gestion d’arrondi au plus près and @MASK,R1 and @MASK,R2

tp_dsp.doc 21

and @MASK,R3 and @MASK,R4 and @MASK,R5 addf3 R0,R1,R6 ; Top sum REAL ; stf R6,*AR0 ; ; addf3 R2,R3,R7 ; Top sum IMAG addf3 *+AR1(IR1),R3,R7; Top sum IMAG || stf R6,*AR0 subf3 R0,R1,R6 ; R6=d_REAL (R1 free) stf R7,*AR1 ; subf3 R2,R3,R7 ; R7=d_IMAG (R3 free) mpyf3 R6,R4,R1 ; R1 = R*TR = REAL_1 mpyf3 R7,R5,R3 ; R3 = I*TI = REAL_2 subf R3,R1 ; stf R1,*+AR0(IR1) ; Store bottom real nop *++AR0 mpyf3 R6,R5,R1 ; R1 = R*TI = IMAG_1 mpyf3 R7,R4,R3 ; R3 = I*TR = IMAG_2 addf R3,R1 ; stf R1,*+AR1(IR1) ; Store bottom real nop *++AR1 ;---------------------- ; Identify EOB by twiddle wraparound ;---------------------- ident ldi @TR_ADDR,R7 ; At the end of a block the bit-reversed subi AR2,R7 ; addressing of the twiddles will cause ldiz IR1,R7 ; TR_ADDR==AR2. If true, increment the R/I ldinz 0,R7 ; data pointers to start at the next block. addi R7,AR0 ; B_Fly addi R7,AR1 ; Loop till butterflys finished b New_Stg ; When RC=0, set up for new stage ;====================================================================== ; When the FFT is complete a convolution with the response of the desired ; window function is used to clean up (filter) the otherwise non-windowed ; input samples. In this case a raised cosine window is used since the ; filter coefficients are easily calculated as -0.5,1.0,-0.5. ;====================================================================== FFT_END ldi @BF_ADDR,AR0 ; OUTPUT ptr ldi @DR_ADDR,AR1 ; REAL ptr ldi @DI_ADDR,AR4 ; IMAG ptr ldi @SIZE,IR0 ; IR0=SIZE/2 for bit-reverse access lsh -1,IR0 ; ldi IR0,R0 ; subi 1,R0 ; To unroll the response for DC a bit or IR0,R0 ; reversed add of the twos compliment of the addi R0,AR1 ; index is used addi R0,AR4 ; sti R0 ,@BRINDX_N ; The BR twos compliment and normal index sti IR0,@BRINDX_P ; are both kept for easy backrolling ;--------------------- ldi AR1,AR2 ; AR1 = REAL[N-1] (one sample before DC) ldi AR4,AR5 ; AR4 = IMAG[N-1] ;================================================= ldi @SIZE,RC ; Résultats echantillons dans Buffer 256 rptb RESULTATS ; ;--------------------- call Log_Mag ; R^2 + I^2 and log, in integer into 8 MSBs of the 16 LSB of an integer 32 bits.

tp_dsp.doc 22

float R0,R0 ; passage en flottant RESULTATS stf R0,*AR0++ ; store the data in Result Buffer ;--------------------- pop AR5 pop AR4 pop AR3 popf R7 popf R6 popf R5 popf R4 popf R3 pop R7 pop R6 pop R5 pop R4 pop R3 rets ;====================================================================== ; Log_Mag: This function assumes that AR1,AR2,AR3 and IR0 have been ; preinitialized to access the 'post convulationaly windowed' ; FFT array. ;====================================================================== BRINDX_N .word 0 ; variables: BR index values used to move through data BRINDX_P .word 0 ; VU_scale .float 1/(N*128.0) ; scale factor for FFT data growth Log_Mag push R2 ; 26 pixels/10 dB push R4 ; 0.75000 V = 0 dBm push R5 ; pushf R2 ; pushf R4 ; pushf R5 ; ldf -0.500,R5 ; ;- - - - - - - - - - - - mpyf3 R5,*AR1++(IR0)B,R0; addf *AR1++(IR0)B,R0; mpyf3 R5,*AR1 ,R2; addf R0,R2 ; mpyf @VU_scale,R2 ; Unscale data growth and fixed pt ADC mpyf R2,R2 ; REAL^2 ;- - - - - - - - - - - - mpyf3 R5,*AR4++(IR0)B,R0; addf *AR4++(IR0)B,R0; mpyf3 R5,*AR4 ,R4; addf R0,R4 ; mpyf @VU_scale,R4 ; mpyf R4,R4 ; IMAG^2 ;- - - - - - - - - - - - ldi @BRINDX_N,IR0 ; BR roll-back of pointers one sample nop *AR1++(IR0)B ; nop *AR4++(IR0)B ; ldi @BRINDX_P,IR0 ; Normal indexing ;- - - - - - - - - - - - addf R4,R2 ; REAL^2 + IMAG^2 ;- - - - - - - - - - - - lsh 1,R2 ; Quick log using float equivelency pushf R2 ; See Designer Notebook Page DNP-22 pop R0 ; ;- - - - - - - - - - - - ash -21,R0 ; Pack quick log number into 8 MSBs

tp_dsp.doc 23

cmpi -128,R0 ; preserving three mantissa bits ldile -128,R0 ; and clipping the result cmpi 127,R0 ; ldige 127,R0 ; lsh 8,R0 ; résultat is in 8 MSBs des 16 bits LSB d’un 32 bits. ; 0000000000000000 xxxxxxxx00000000 OK for a sample. ;- - - - - - - - - - - - Le max négatif pour le niveau du bruit popf R5 ; popf R4 ; popf R2 ; pop R5 ; pop R4 ; pop R2 ; rets ; ;************************************************** *************************

4.2.3.3. Autres fonctions assembleur, vecteurs it, modif link

_lire ; Simple lecture port DRR ldi @DRR,R0 lsh 16,R0 ash -16,R0 rets ;************************************************** ******** ; Pulse sur sortie extern_IO_W ;************************************************** ******** .global _pulse _pulse ldp 0C00000h ; obligatoire C00000 ! rpts 50 ldi @0,R0 ; 50 pulse en extern USERS_R ldp 800000h

rets

.global _c_int02 .sect "inter" B xint ; XINT0 B _c_int02 ; RINT0 sur C_INT02 pour IO échantillon en tache de fond à Fech Et dans le fichier de commande de l’édition de lien : pour aligner les tables à une

adresse multilple de 256 : TABLES > RAM0 align 256

tp_dsp.doc 24

ETAPE 3

Sur carte "Starter Kit"

But: Rappel des 4 phases de la compilation et le Boot du C.

Etude des outils de compilation. Influence du choix de l’emplacement des opérandes.

tp_dsp.doc 25

5. LES OUTILS DE COMPILATION

5.1. Le programme de test test.c

Il calcule bêtement et sans arrêt la somme des valeurs et des valeurs2 d’un tableau. /************************************************** **********************/ int flag_simu=0 ; /***************** PROGRAMME TEST DIV ERS *******************/ #include "extern.h" #define taille 40 float tab1[taille]; float s1,s2; void main(void) int k ; while(1)

s1=0 ;s2=0 ; for(k=0; k<taille1 ;k++) s1 = s1+tab1[k]; s2 = s2 + tab1[k]*tab1[k]; */ pulse();

La fonction pulse() ; permet de générer une impulsion fine (pour le starter Kit) sur la

sortie validant la mémoire d’extension I/O en écriture. Elle est située dans ini_c31.asm : ;************************************************** ******** ; Pulse sur sortie extern_IO_W ;************************************************** ******** .global _pulse _pulse ldi @_flag_simu,R0 bnz simulateur

ldp 0C00000h ; nouvelle page : C00000 ! rpts 2 ldi @0,R0 ; 1 pulse en extern User_R par simple accès ldp 800000h ; retour à la page du Starter Kit simulateur rets

5.1.1. Etude de test.bat

c:\apw\tmsC31\c\cl30 -ic:\apw\tmsc31\c -s -v31 -q –pw2 -o3 -g -k test.c -z test.cmd pause Remarques cl30.exe est le compilateur -ic:\apw\tmsc31\c le répertoire ou se situent les .h D’après la documentation ci-dessous sur le compilateur, commenter ce fichier de

commande, et les différentes options choisies. Bien remarquer l’option d’optimisation maximale.

tp_dsp.doc 26

tp_dsp.doc 27

tp_dsp.doc 28

5.1.2. Etude de test.cmd /************************************************** **********************/ /* fichier.CMD - v4.70 COMMAND FILE FOR LINKING C30 C PROGRAMS */ /************************************************** *********************/ -a -cr Initialisation au Téléchargement (-c serait pour en exécution) -w -x test.obj ini_c31.obj -o test.out -m test.map -stack 0x200 /* Taille de la pile système * -heap 0x200 /* taille de la zone d’allocation dynamique, sans objet si

non utilisée, donc si pas de malloc */ -l c:\……\tmsc31\c\rts30.lib /* librairie du compilateur C , modèle de base */ MEMORY RAM0: org = 0x00809800 len = 0x00000400 /* RAM BLOCK 0 */ RAM1: org = 0x00809c00 len = 0x00000300 /* RAM BLOCK 1 */ VECTXR: org =0x809FC5 len = 2 /* SPECIFY THE SECTIONS ALLOCATION INTO MEMORY */ SECTIONS .text: > RAM0 /* CODE */ .cinit: > RAM0 /* C INITIALIZATION TABLES */ .const: > RAM0 /* CONSTANTS */ .stack: > RAM0 /* SYSTEM STACK */ .sysmem: > RAM1 /* DYNAMIC MEMORY - DELETE IF NOT USED */ .bss: > RAM1 /* VARIABLES GLOBALES et STATIQUES */ .data: > RAM0 /* DATA - NOT USED FOR C CODE */ inter: > VECTXR /* section .inter utilisateur pour vecteurs XINT et RINT */

En s’inspirant également des options du linker de la doc constructeur en page suivante, on pourra en commenter chaque ligne. On remarque :

- Une première partie avec des options (rechercher sur la doc). - Une seconde partie : les fichiers à « linker ». - Une seconde partie décrivant la mémoire disponible sur la carte Starter Kit. - La troisième partie place les différentes sections dans les différentes zones mémoires

de la carte Starter Kit. - Le code (.text) et les variables globales et statiques (.bss ) sont dans deux blocs

séparés.

tp_dsp.doc 29

5.1.3. Emplacement et valeurs des variables. 1) Au simulateur rechercher visibilité et adresses de k, tab1, s1 et son adresse &s1 2) Dans le fichier test.map rechercher visibilité et adresses de

- tab1, s1 et k. - d’une fonction (sortie_echantillon par exemple). Celle ci n’est pas utilisée mais l’éditeur de lien l’incorpore tout de même).

3) Est ce tout correct par rapport aux indications du fichier test.cmd ?

5.1.4. Le Boot du C, avant le main : 1) Noter dans test.map l’adresse de _c_int0 qui est celle du Reset (adresse 80xxxx) 2) Au simulateur, en faisant Debug et Restart, voir le code à partir de cette adresse. Bien

comprendre les phases d’initialisation avant le main(), à savoir : Initialisation de la pile système, Initialisation des variables statiques et globales initialisées (effective en mode ROM

seulement option –c et non –cr. Ces lignes sont toujours présentes, un CMPI –1 et un BZ adresse permet de tester le modèle et d’agir en conséquence.

Initialisation de la page courante de travail. Appel au main(). Appel éventuel à Exit pour retourner à un système d’exploitation si existe un (la main

doit être une boucle sans fin, sinon plantage possible !) (Se reporter aussi au poly de cours).

tp_dsp.doc 30

5.1.5. Mesure au simulateur de l’effet de l’optimisation 1) Supprimer l’option –O3 de compilation (dans test.bat).

Mesurer au simulateur le nombre N de cycles de Tck de la boucle infinie while(1). 4) Remettre l’option –O3, et refaire la même mesure. Attention, fermer et ré-ouvrir le

simulateur, sinon il n’aime pas et peut donner une fausse mesure, il garde en fait un peu l’ancienne !

On ne gagne peut être pas énormément ici, mais évidemment le code de départ n’est pas trop complexe ! Sur d’autres programmes, d’autres compilateurs ou DSP, on pourrait gagner beaucoup plus. Bien laisser l’option –O3 dans test.bat pour toute la suite ! ! ! !

6. OPTIMISATION PAR LE CHOIX DE L’EMPLACEMENT EN MEMOIRE DES

OPERANDES Un DSP ne travaille de manière optimale que si l’on choisit judicieusement la place des

opérandes en mémoire, ainsi que le choix pour ceux ci du type de mémoire. En effet le pipe line peut se débrancher, avec perte d’un ou 2 cycles, si à un moment donnée le DSP ne peut pas faire deux choses dans le même cycle, comme accéder au code et à une donnée, accéder à deux données, etc …

Pour des DSP avec SARAM (mémoire à 1 accès par cycle) et DARAM (mémoire à deux accès par cycle), il faut évidemment mettre le code en SARAM et les données en DARAM pour optimiser au mieux.

Pour le C31 possédant deux blocs distincts de DARAM, le mieux est à priori

simplement : Code dans un bloc et Données dans l’autre bloc Il peut peut-être y avoir des exceptions dans le cas d’adressages de données multiples sur

un cycle. Si du code ou des données sont extérieures, il faut évidemment utiliser des mémoires les

plus rapides possibles, ou charger des blocs d’instructions ou (et) de code dans le DSP pour les exécuter. Le cache non étudié ici peut améliorer.

Une étude au simulateur pour ce C31 n’est pas possible car justement c’est

l’utilisateur qui estime si son pipe line tourne à fond ! D’ou une méthode de mesure exacte d’un temps d’exécution en temps reél : On peut visualiser sur un oscillo une petite impulsion crée par la fonction judicieusement

placée. Pour notre exemple, on à placé la fonction pulse(); dans la boucle principale. La période du

signal ainsi fourni est égale à N. Tck N étant le nombre de Tck de la boucle.

Tck = 40 ns pour ce C31 (Quartz 50 MHz, donc Fck = 25 MHz).

tp_dsp.doc 31

6.1. Cas optimum

- Si on regarde test.cmd, on peut voir le code (.text) dans un bloc, et les variables globales (.bss) comme tab1 dans l’autre. C’est donc le cas optimum.

- Bien vérifier dans test.bat si on a –O3 ! - Mesurer la période T de l’impulsion fine sur l’oscillo. C’est la durée de la boucle. - Au simulateur, on avait mesuré (cas de l’optimisation maximale –03) un nombre N de

cycles. En déduire la durée de la boucle avec Tck = 40ns. Vérifier si les deux résultats concordent, parfaitement ou avec une toute petite erreur. Conclure.

6.2. Cas plus défavorable

- Dans test.cmd, mettre CODE et VARIABLES GLOBALES dans le même bloc de mémoire.

- Mesurer de nouveau la période T de l’impulsion fine sur l’oscillo. C’est la nouvelle

durée de la boucle. Commenter la différence. Remarque : la différence obtenue pour ce simple test n’est pas énorme peut être, mais tout

de même significatif, elle pourrait être bien plus importante pour des programmes plus complexes, et pour d’autres DSP.

tp_dsp.doc 32

ETAPE 4

Sur carte "Starter Kit" But : Filtrage numérique « en ligne » (par FIR) Application Moyenne mobile d’un signal. Recherche de la vitesse max de traitement par fonction C en assembleur,

instructions spécifiques du DSP et Buffer circulaire. REMARQUE IMPORTANTE : Toute cette partie, à part peut-être la partie assembleur un peu plus étudiée, est identique à

une manipulation de la ½ valeur de TP B au CNAM. Vous pouvez la refaire ! ce n’est pas interdit ! Vous pouvez aussi effectuer une manipulation parmi celles fournies en supplément, par

exemple, sur les filtre récursifs du premier ou du second ordre. Voir alors le texte en fin de ce poly et la théorie dans le poly de cours.

tp_dsp.doc 33

7. FILTRAGE NUMERIQUE, SIMPLE REFLEXION SUR LA DYNAMIQUE

Par exemple pour un filtre numérique FIR, de réponse impulsionnelle finie h (taille N), la

réponse à un signal x se calcule par: y x hn n kk

N

k= −=

∑0

1

.

1) Echantillons d’entrée Ce sont des valeurs entières (de type entier ou flottant ) de –32768 à 32767. Mais il peut être parfois plus simple comme ci-dessous, de raisonner en Q15(16), c’est à

dire avec un bit de partie entière et 15 fractionnaires, et donc avec des nombres en virgule fixe par la pensée de module < 1. Format : -,------- --------

2) Remarques importantes ( raisonnement |xn| <=1 )

Valeur finale de y pour la réponse indicielle, et module du gain pour f = 0: ∑

=

1

0

N

kkh

Valeur max en transitoire : relation moins évidente, il faudrait étudier le transitoire. La formule suivante peut servir, on peut l’utiliser car c’est le pire des cas.

hkk

N

=

∑0

1

Même si le module du gain d’un filtre est toujours < 1, la réponse en transitoire peut osciller et présenter des dépassements au delà de 1.

3) Trois cas principaux Cas N°1, le plus courant : la réponse indicielle (régime transitoire) tout comme la

réponse fréquentielle peut être oscillatoire et donc présenter un certain dépassement, on peut alors prévoir une sortie y de dynamique double, format Q14(16).

Pour ne pas déborder au CNA (qui reçoit du Q15(16)), il faudra donc lui envoyer 1/2 yn soit la moitié de la convolution dans ce cas général.

Cas N°2 : filtres très oscillants, ou très sélectifs, les corrélations également,

demanderont bien plus de dynamique, on sortira alors 1/2k yn. Cas N°3 : la sortie du filtre yn reste toujours (même en transitoire !) de module < 1, on

peut alors garder le même format que les xn, et sortir donc yn. (Ce sera le cas par exemples de filtres réalisant une simple moyenne) 4) Pour un DSP virgule fixe :

Les échantillons sont des entiers en Q15(16). Les coefficients sont des entiers interprétés en Q15(16) si tous de module inférieurs à

1, ou en Q14(16) (tous de module < 2) ou en Q13(16) (tous de module < 4) …. On raisonne donc sur des coefficients 215 ou 214 ou 213 fois plus grand. On fait l’accumulation avec un format (ici 32 bits) permettant juste la dynamique

pratique du résultat (on sait que l’on peut en effet déborder lors de sommes intermédiaires sans problème). On a déjà vu les techniques de travail virgule fixe. On ne l’étudiera pas ici.

5) Pour un DSP virgule flottante (comme notre C31) : Les échantillons sont des flottants Les coefficient sont évidemment aussi en flottant Tous les calculs se font en flottant. On ne peut donc déborder que lors d’une sortie

éventuelle sur un CNA. D’ou l’importance de la dynamique pratique du résultat.

tp_dsp.doc 34

8. PROGRAMMATION EN C D'UNE CONVOLUTION PAR "PILE GLISSANTE"

8.1. Principe

Pour un signal X et une réponse impulsionnelle H de taille N, on veut calculer:

Y X Hn n kk

N

k==== −−−−====

−−−−∑∑∑∑

0

1.

X[0]=XnX[1]=Xn-1X[2]=Xn-2...X[N-2]=Xn-N+2X[N-1]=Xn-N+1

H[0]H[1]H[2]....H[N-2]H[N-1]

entrée des échantillons

pile glissante, taille N pile fixe

réponse impulsionnelletaille N

pointeurs au départ des calculs

libre

La case mémoire après X[N-1] doit être libre !!, car les données se décalent à chaque

fois d'un cran vers le bas et donc elles passent toutes par la dernière case. On peut si on l’oublie détruire une donnée utile d’un programme, et créer un aléas très difficile à debugger par la suite !

Le calcul démarre par la fin du tableau pour pouvoir à chaque boucle décaler la valeur pointée désormais inutile vers le bas. Toute la pile X est ainsi décalée d'un cran à la fin de chaque calcul de Yn+1.

8.2. Application simple : moyenne mobile d’un signal

Ouvrir le projet filtre1 On choisi pour cette application une réponse impulsionnelle de N = 21 échantillons

identiques et égaux à 1/N.

La fréquence d'échantillonnage est égale à 18,939 kHz, on gardera celle ci durant toute la suite.

organigramme:

tp_dsp.doc 35

Initialisation diverses

attente et lectured'unéchantillonXn**

boucle de calcul d'un point Ynavec décalage d'un cran des valeurs Xn

FILTRENUMERIQUE

** a la cadence des demandes d'interruption DRR .

sortie de l'échantilloncalculé

mise en tête de tableau

à faire dansune seule boucle !!

Réalisable par la suite en assembleurpour accélérer la vitesse d’exécution

programme: fichier filtre1.C /************************************************** *************************/ /* NE PAS CHANGER CETTE ENTETE !!!!! */ int flag_simu = 0 ; /************************************************** *************************/ /* *********** CONVOLUTION tout en C, par PI LE GLISSANTE, **************/ #define taille 21 /* taille filtre */ #include ˝extern.h˝ /* prototypes des fonctions utilitaires en assembleur */ float h[taille]; /* Réponse impulsionnelle */ float x[taille+1]; /* le dernier doit rester libre .... */ float y; /* en global pour les voir sûrement au simulateur */ void main() int k; initialisation_aic(5,5,33,33,0x10); /* Fech = 18,939 kHz */ for (k=0;k<taille;k++) h[k] = 1.0/taille; /* 1/taille donnerait 0 ! */ anti_overrun() ; do x[0]=attente_echantillon(); /* lecture échantillon et mise en tete de pile glissante */ y=0; for(k=taille-1;k>=;k--) y+= x[k] * h[k]; /* calcul en flottant */ x[k+1] = x[k]; /* décalage un cran valeur pointée */ y = y/2 ; /* pour sortir ½ convolution, filtres standards */ sortie_echantillon(y ); /* ok sortie de ½ convolution */ while(1);

8.2.1. Etude théorique 1) Examen du programme : - Le calcul démarre à partir de la fin des tableaux, sinon les décalages écraseraient tout.

On examinera la ligne effectuant le décalage. - On introduit 1/N en flottant pat 1.0/taille pour que la division se fasse en flottant, sinon

résultat 0 !

tp_dsp.doc 36

- produits et accumulation se font en flottant. - On sort ensuite la ½ convolution, soit y/2 cast en int implicite par le prototype. - Le gain du filtre peut ainsi atteindre presque 2 sans dépasser. 2) Fonction de transfert théorique : D’après le cours, pour une fréquence d'échantillonnage et une réponse impulsionnelle de

« taille » coefficients égaux à 1/taille, le module du gain G est :

|G| = Fechfxavec

x

xtaille

taille=

ππsin

)(sin1

Il y a repliement du spectre après fech/2, et effet « miroir ». Le bloqueur modifie un peu l’allure du spectre, mais les zéros correspondent toujours à

taille

FechkFzéros .= et le gain pour f voisin de 0, donc pour x voisin de 0, vaut également 1.

D’ou les fréquences des deux premiers zéros (pour Fech = 18,939kHz et taille = 21) : environ 900 Hz et 1800 Hz

Calculer ces fréquences exactes, et tracer rapidement l’allure de |G| pour F entre 0 et Fech/2, marquer le gain en continu et les fréquences des 2 premiers zéros.

8.2.2. Essai du programme.

8.2.2.1. Fonction de transfert pratique

1) En appliquant un signal sinusoïdal, de par exemple 2v crête, vérifier l’allure de la courbe de réponse en fréquence obtenue (de 0 à Fech/2), ainsi que la valeur des deux premiers zéros. Comparer avec la théorie.

2) Vérifier le gain de ½ en BF. Comparer avec la théorie. 3) Que se passe-t-il pour F entre Fech/2 et Fech, expliquer la courbe obtenue. 4) Comment éliminerait-on cette partie dite "miroir" de la fonction de transfert ?

(correspondant à Fech/2 < F < Fech).

8.2.2.2. Utilisation pour la mesure de valeur moyenne

1) Dans le cas particulier du filtre moyenne mobile, quel est le gain maximum. En déduire que l’on peut alors sortir y et non ½y. Modifier le programme en conséquence et vérifier alors le gain de 1 en BF.

2) Se reporter au cours correspondant précisant les conditions de bonne mesure de valeur

moyenne. On vérifiera et on commentera pour les deux cas ci-dessous, la bonne restitution de la

valeur moyenne, avec un signal de fréquence F, sinusoïdal ou triangulaire, et en faisant varier l’offset du signal par rapport à zéro (On réglera les deux voies de l’oscilloscope sur le même gain et le même décalage). On précisera bien selon le cas si la restitution est parfaite ou si il reste un petit résiduel.

a) Cas particulier d’une bonne mesure : N

FeF = ou

N

FeF 2= ou … (Mesure sur

une ou deux périodes, ou un nombre entier de périodes).

b) Cas général : pour une bonne mesure, il faut N

FeF >> (Mesure sur un grand

nombre de périodes).

tp_dsp.doc 37

8.2.3. Vitesse d'exécution

8.2.3.1. Théorie

Le code généré ressemble à ceci : Le nombre total de cycles est égal à

n1 + n2*taille Pour une fréquence Fech= 18,9kHz

d’échantillonnage, et une cadence du DSP de Fck = 25 MHz (Quartz de 50 MHz), il faut que

FechFcktaillenn11)..21( ≤+

donc Fech

Fcktaillenn ≤+ ).21( et

2

1max_

n

nFech

Fck

taille−

=

8.2.3.2. Etude au simulateur

Remarques: Le pas à pas en C, du fait de l’optimisation fait qu’il y a parfois un décalage entre le

curseur et le nombre de cycles lu, on ne sait pas toujours exactement où est placé le point d’arrêt.

A cause du pipe line du DSP qui parfois se bloque ou se vide (conflits de pipe line), le simulateur du C31 ne donne qu’une valeur approchée du nombre de cycle. C’est à l’utilisateur d’estimer si le pipe line tourne à plein régime ou non.

On supposera en fait que le pipe line tourne au maximum, il faudra pour cela

introduire sur le simulateur une valeur de 4 pour le nombre de cycle de compensation de pipe line.

(Si on met 0, toute instruction mettra 5 cycles pour s’exécuter !) On cherche en fait ici la valeur approchée de la taille max possible (taille supposée

grande) pour le filtre, donc il faut veiller à la bonne mesure surtout de n2. Il faut donc placer un point d’arrêt avec précision (dans le source C, mais parfois aussi

dans le code assembleur, comme on le verra plus tard), et aussi garder un œil critique sur la valeur lue pour ne pas faire de grossières erreurs.

Placer un point d’arrêt sur la ligne d’accumulation s+= x[k] * h[k]; avec la

commande Run, on doit trouver n2 voisin de 10 à 15 ( plusieurs lectures successives identiques !)

Enlever ce point d’arrêt et en placer un sur x[0] = attente_echantillon() ; Un pas à pas doit alors fournir n1 (somme de plusieurs valeurs, attention une valeur assez élevée pouvant correspondre à n2*taille n’est évidemment pas à prendre en compte !).

En déduire la taille max possible du filtre pour cette cadence de Fech.

8.2.3.3. Conclusions

Pour de nombreuses applications, ce code source C ne donne pas un code objet assez court. Afin de réaliser des filtres et des corrélations de taille plus importante, et (ou) à des cadences plus élevées, nous allons chercher à réduire le temps d'exécution.

Attente_echantillonInitialisations diverses de la boucle

Boucle d’accumulation des produits n2*taille cycles

Sortie_echantillon

Tout le reste : n1 cycles

tp_dsp.doc 38

Il est clair qu’il est plus efficace surtout pour des filtres de taille importante de réduire

en priorité n2 pour gagner très rapidement en vitesse d’exécution. Les efforts devront donc porter surtout à programmer en assembleur cette petite boucle.

On pourrait certes ici gagner sur n1, au moyen d'un programme écrit entièrement en

assembleur, mais le gain en temps d'exécution serait faible par rapport à la difficulté de programmation !.

Pour des filtres de très petite taille cependant, on pourrai aussi optimiser n1. Une approche rapide et pratique est d’écrire une fonction C de convolution en assembleur,

ou même une fonction de filtrage directement, la plus simple possible, en gardant tout de même si possible une certaine souplesse (passages de paramètres), et exploitant au maximum les instructions spécifiques et puissantes du DSP.

tp_dsp.doc 39

9. OPTIMISATION D’UNE CONVOLUTION PAR UNE FONCTION EN ASSEMBLEUR.

9.1. Deux techniques possibles

9.1.1. Sans Buffer circulaire Possible si le DSP possède des instructions s’exécutant en un cycle de

produit_accumulation et de gestion de pile glissante (décalage automatique d’une case mémoire de la donnée adressée sans perte de temps). C’est le cas sur le TMS320C50 de LTD MADD.

Ce n’est pas le cas du C31, on ne les étudiera donc pas ici.

9.1.2. Principe avec Buffer circulaire Pour un filtre de taille N, on prend un buffer circulaire de taille N

Xn Xn-5

Xn-4

Xn-3Xn-2

Xn Xn-5

Xn-4

Xn-3

Xn Xn+1

Xn-4

Xn-2

Xn-1

Xn-2

Convolution de taille 6 buffer circulaire de taille 6Entréeéchantillon

Introduction de Xn

Calcul de Yn Introduction de Xn+1

Xn-1

Débutcalcul

Xn-1

Xn-3

Fin calcul(après 6acccumulations

Entréenouveléchantillon

Début calcul suivant

Senscalcul

Après chaque calcul de la somme de produits, le point d’entrée d’un nouvel échantillon se

retrouve décalé d'une case. La structure de pile glissante est donc simulée par un décalage du pointeur.

Les DSP possèdent un ou plusieurs pointeurs internes pouvant travailler de cette façon (le modulo du buffer est automatique). Des instructions spécifiques permettent de les initialiser et de les gérer.

Cette technique n’apporte en fait pas d’amélioration sur les convolutions pour des DSP

possédant les instructions avec déplacement automatique de donnée pointée, elle est par contre essentielle pour les autre.

Un tel buffer est très utile également pour des programmes nécessitant des tampons de données, avec lectures et écritures éventuellement à cadence différentes, ou pour gestions de tables modulo quelconque.

Pour modifier la taille , il faut que cela ne se fasse que dans un seul fichier (l’habitude naturelle !). Plus aisé dans le fichier assembleur.

Attention tout de même: si on change la taille du filtre au delà d’un 2k écrit dans le fichier de link, on devra modifier ce dernier !

tp_dsp.doc 40

9.2. Fonction C de convolution rapide par Buffer circulaire, sur Texas C31

9.2.1. Buffer Circulaire sur C31

Attention : Une réponse impulsionelle de taille N demande un Buffer de taille identique N.

Tous registres Ari utilisables, mais de préférence AR0, AR1, ou AR2 que l’on n’a pas besoin de préserver pour le C.

Registre BK (Block Size) 32 bits, contiendra la taille N (16 bits LSB utiles) Adresse de départ du Buffer (sur laquelle on initialisera Ari utilisé): cette adresse doit

être multiple de la plus petite puissance de 2 supérieure à la taille du filtre. Exemple : taille 21 coefficients : 25=32 est la plus petite puissance de deux supérieure à

21, donc l’adresse de base devra avoir les 5 bits LSB à zéro, soit -------- -------- ---00000 Le Buffer devra donc être déclaré en assembleur pour une place précise en mémoire. Exemple de modes d’adressage à utiliser : *Ari++% ou *Ari--%

9.2.2. Prototype de fonction C choisi Comme le Buffer circulaire doit être placé avec précision en mémoire, on ne peut faire

vraiment une fonction polyvalente, avec xn, buffer, tableau H, taille … en paramètres. Adresse de départ :

MULTIPLE de la plus petite puissance 2n > Taille

DébutBuffer

On choisit donc ici de réaliser une fonction spécifique d’un Buffer particulier, et donc

un prototype très simple : float fir1(float xn) ; Entrée un échantillon de signal à filtrer Retourne l’échantillon correspondant filtrée Réponse impulsionnelle h1[ ] spécifique de fir1 en variable globale Taille taille1 spécifique de fir1 en variable globale La taille sera fixée dans un seul fichier, donc ici dans le fichier assembleur

correspondant, et à un seul endroit évidemment, par la ligne N1 .set …. La taille doit être accessible du C, d’ou le label taille1. Ce label sera déclaré

constante par sécurité, on ne peut pas la modifier à partir du C. Le Buffer se situe dans une section ″″″″buffer_1″″″″ (spécifique de fir1) qui devra

démarrer à une adresse multiple de 2k (avec 2k >=Taille filtre) (Intervention nécessaire à l’édition de lien). On parle d’alignement de la section correspondante. Directive Align dans le fichier de link .

9.2.3. Sous programme correspondant, pour un filtre fir1

9.2.3.1. Nécessité d’un pointeur de Buffer circulaire supplémentaire

Une programmation tout assembleur permettrait de dédier définitivement un des registres Ari à un Buffer circulaire.

tp_dsp.doc 41

Le compilateur C en principe génère du code utilisant tous les registres du C31, il faut donc associer à chaque Buffer une variable statique qui sera un pointeur sur ce Buffer. Soit donc pt_buff1 dans le cas présent.

- En entrée dans la fonction de filtrage, on récupérera ce pointeur dans AR0 (par exemple)

- Le modulo du Buffer est automatiquement réalisé par l’adressage circulaire % sur A0 - En fin de fonction, il faudra alors réactualiser le pointeur pt_buff1

9.2.3.2. Disposition des éléments

Xn Xn-N+1

Xn- 1

tableau des XEn buffer circulaire: de taille N

AR0

H0

H1

.

.

.

.HN-2HN-1AR1

réponse Hde taille N

fin_x X[N-1]

Xn-(N-1)

Xn-2

Xn-1

XnPt_buffer

début_x X[0]

Début_hH[0]

Fin_hH[N-1]

Sens calculs

Début Calcul

Introductionde Xn

Début Calcul

9.2.3.3. Code assembleur

Essayer de comprendre les diverses déclarations, ainsi que les calculs effectués une fois pour toute à l’assemblage (et éventuellement édition de lien) de certaines valeurs et certaines adresses. Ne pas confondre :

N1 qui est la taille du filtre définie par une directive .set et qui n’est donc pas une variable ni une constante ni une donnée directement accessible du C, mais seulement le nombre correspondant ! On change la taille du filtre en changeant N1 (avec un coup d’œil ensuite au niveau du fichier d’édition de lien pour régler le problème de l’alignement)

_taille1 qui est la donnée correspondante en mémoire, accessible en C par taille1. On admirera le parallélisme des instructions au niveau de l’accumulation des produits,

l’ensemble s’exécutant en 1 période d’horloge ! ;------------------------------------------ FILTRE FIR 1 ------------------------------------- .global _h1,_taille1, _fir1 ;On doit déclarer le Buffer Circulaire et pour la réponse impulsionnelle ;Attention, intervenir à l'édition de lien pour placer la section correspondante au Buffer

;Taille du filtre

N1 .set ? ; écriture N1 = écriture 21 par exemple (pas en mémoire, invisible en C)

buffer1 .usect "buffer_1",N1 ; Buffer X en section "buffer_1 " , taille N1

.data

pt_buff1 .word buffer1 ; Pointeur (statique) sur Début_X car utile. pt_fin_h1 .word _h1+N1-1 ; Pointeur sur Fin_H car utile.

tp_dsp.doc 42

_taille1 .word N1 ; donnée pour accéder à la taille depuis le C .bss _h1,N1 ; Réponse impulsionnelle, taille N1 ;Sous programme de la fonction .text _fir1

PUSH AR3 LDI SP,AR3 ;pointeur de trame AR3 LDI N1,BK LDI @pt_buff1,AR0 ;pointeur Buffer --> AR0 LDI @pt_fin_h1,AR1 ;AR1 sur H[N-1] (fin_H)

Valeur xn

0 31

SP PC

Ancien AR3 AR3

-2

LDF *-AR3(2),R0 ; prise de l'échantillon nouveau Xn STF R0,*AR0--% ; mise en buffer, pointeur AR0 décalé sur Xn-(N-1),

; pour le début du calcul ldf 0,R2 ; Raz R2 pour accumuler mpyf3 *AR0--%,*AR1--,R0 ; premier produit dans R0 rpts N1-2 ; pour répéter N1-1 fois on doit mettre N1-2 mpyf3 *AR0--%,*AR1--,R0 ; produit nouveau dans R0

|| addf3 R0,R2,R2 ; en parallèle addition R2 + R0(précédent) --> R2 addf3 R0,R2,R0 ; dernière addition R2 + R0 --> R0 STI AR0,@pt_buff1 ; pointeur Buff_Circulaire rangé POP AR3 rets

9.3. Travail pratique

Ouvrir le projet Filtre2.c /************************************************** *********************/ int flag_simu=0; /************************************************** *********************/ /******** FILTRE FIR Filtre en assembleur *************/ #include "extern.h" float x; float ech; extern float h1[]; /* CARACTERISTIQUES */ extern float filtre_fir1(float); /* PROPRES */ extern const int taille1; /* A ce filtre FIR1 */

tp_dsp.doc 43

void main(void) int k; /* Fck AIC = 6.25 MHz */ /* Fcad = 625 KHz */ initialisation_aic(5,5,33,33,0x10); /* Fech = 18,93939 kHz */ for(k=0;k<taille1;k++)h1[k] = 1.0/taille1; /* initialisation de la réponse impulsionnelle */ anti_overrun(); while(1) x = attente_echantillon(); ? ? ? ? ? ? sortie_echantillon((int)(y/2)); 1) Compléter le programme filtre2.c fourni (aux endroits marqués par ? ci dessus) 2) Consulter le fichier pour_fir.asm fourni. Fixer par N1 la taille à 21, il faudra ensuite

évidemment l’incorporer au lien. Comprendre chaque instruction.

3) Dans le fichier d’édition de lien, ajouter la ligne : Buffer_1 > RAM1 align 32

Cette ligne a pour but : - D’implanter en mémoire dans RAM1 la section utilisateur Buffer_1 - De la faire débuter à une adresse multiple de 32 (puissance de 2 immédiatement

supérieure à l la taille du filtre désiré 21) (nommé alignement).

4) Vérifier le fonctionnement identique du programme (allure du module du gain, gain en BF et valeur premier zéro).

5) Etude de la vitesse de calcul. Se reporter à l’étude faite précédemment.

Avec le simulateur, mesurer le nombre de cycles n1 de la grande boucle sauf l’accumulation de la somme de produit.

L’instruction parallèle de multiplication et d’addition faisant 1 Tck ; le nouveau n2 fait 1. En déduire la taille maximale possible pour un tel filtre à cette fréquence

d’échantillonnage (En toute rigueur, comme dans n1 il y a déjà un produit calculé la boucle n2 n’est

parcourue que taille-1 fois, il faudrait majorer de 1 la taille_max calculée …. Mais comme de toute façon le Debugger estime juste le temps de calcul et que quelques conflits de piper line peuvent survenir, ce n’est pas bien grave !)

tp_dsp.doc 44

ETAPE 5

Projet

Sur carte "Starter Kit" Buts : - Synthèse de fréquence en C - Utilisation d’une ligne matérielle d’interruption

- Amélioration de la vitesse de calcul par de l’assembleur

- Application à une démodulation AM tout numérique On utilisera l’interface visual basic fourni pour : Visualiser les variables Globales

Modifier ces variables globales (Attention, tout le programme est alors relancé mais sans les premières initialisations des variables globales et statiques (celles-ci se font seulement au téléchargement, car on est en mode RAM, option –cr au link).

tp_dsp.doc 45

10. SYNTHESE DE FREQUENCE On veut réaliser un générateur sinusoïdal fonctionnant par synthèse directe de fréquence.

10.1. Principe

Soi une période d’un signal quelconque (ici sinusoïdal), on peut le décrire par une succession d’états de phases.

On peut exprimer ces phases par un nombre entier x, variant de 0 à N-1, N étant le nombre maximum choisi d’états possibles, pouvant être à priori très grand.

Une période

A partir d’une phase quelconque, (entre 0 et N-1) on passe à la suivante par xxx ∆+=

(modulo N). On obtient donc x

N

∆ états par période (sur la figure : 16/3 = 5,33)

Si les échantillons sont fournis à la cadence Fech

Tech1= , la période du signal obtenu est

x

NTechT

∆= . (Sur la figure,

x

N

∆= 16).

Le théorème de Shannon doit être respecté. On génère ainsi toute une série de fréquences F, comprises entre 0 et presque Fech/2

N

xFechF

∆= . avec 2

Nx <∆ La résolution est égale à

N

Fech

Plus N est grand, meilleure est la résolution en fréquence (Il n’y a théoriquement pas de limite).

Choix des valeurs :

En fonction de Fmax désirée, on en déduit (en théorie, il faut en effet prendre une marge de sécurité pour filtrer !) Fech = 2.Fmax. La limite de Fech dépend du matériel utilisé: microprocesseur, DSP, structure cablée, circuit logique programmable, performance du CNA, etc … donc de plusieurs dizaines de kHz à peut être 100 MHz.

Pour une bonne résolution, le nombre N doit être grand, ce qui nécessite à priori de

stocker un grand nombre de valeurs du signal à générer. On peut en fait n’utiliser qu’un petit nombre de valeurs de la fonction (par exemple 128 ou 256), moyennant une certaine

tp_dsp.doc 46

distorsion (dite de phase) du signal fourni. Cette distorsion ne modifie nullement la fréquence générée.

L’entier x servant d’accumulateur de phase est codé sur n bits, et nN 2= . Il suffira de n’adresser la mémoire contenant la période du signal à générer que par m bits de poids forts de x, avec m < n (par exemple 7 ou 8 bits pour un tableau de respectivement 128 ou 256 valeurs de la fonction.

- - - - - - - - - - - - - - - - -m bits

Adressage du tableau de valeurs

x sur n bits

Schéma synoptique :

+ AccumulateurDe Phase x

MémoireDe signal

(1 période)

CNA

Deltax

Lissage

Signal Sn bits

n-1bits m bits

d bits

Exemples de réalisation:

• Synthétiseur HP33120A n = 48 bits. Donc 248 états de phase m = 14 bits d = 12 bits

• Pour cette manipulation sur DSP - Accumulateur de phase x sur n =16 bits. donc en non signé, N=216 = 65536 états de

phase L ‘accumulation modulo N est alors automatique. La résolution de fréquence vaudra donc df = Fech/65536 La fréquence min est Fmin = df La fréquence max est Fmax = Fech/2 - On prend une mémoire « sinus » de 128 valeurs couvrant une période complète :

)128

..2sin(.]sin[k

ktab πλ= avec k entier de 0 à 127. On l’adressera par les m = 7 bits hauts

de l’accumulateur de phase x. Pour diminuer très légèrement la distorsion, plutôt que de tronquer la partie basse, on peut

prendre l’arrondi au plus près. Mais une technique apportant une amélioration très nette serait d’effectuer une interpolation linéaire entre deux valeurs proches de la fonction. Cette méthode doit être utilisée si on recherche une meilleure pureté spectrale (à m constant). Nous ne l’appliquerons pas ici. Cette pureté est aussi limitée par le CNA de sortie et les non linéarité de la partie analogique.

tp_dsp.doc 47

10.2. Travail pratique, programme en C

10.2.1. Fichier de valeurs de sinus Une fonction sinus étant comprise mathématiquement entre –1 et +1. Il ne faut

évidemment pas envoyer au CNA des valeurs flottantes de même dynamique, mais des entiers de -32767 à +32767, sinon le résultat est n’importe quoi !

La dynamique mathématique étant de –1 +1, autant raisonner sur des valeurs en Q15(16). mais +1 étant interdit dans ce format, on choisit de fournir ½ sinus au lieu du sinus.

Un fichier pour_sin.asm contenant 128 valeurs d’une sinusoïde de période 128 vous est fourni, ainsi que pour_sin.obj

; TABLE DE SINUS .global _tabsin .global _flag .data

_tabsin: .word 0 .word 804 .word 1606 .word 2404 .word 3196 .word 3981 .word 4756 .word 5520 .word 6270 .word 7005 .word 7723 .word 8423 .word 9102 .word 9760 .word 10394 .word 11003 .word 11585 .word 12140 .word 12665 .word 13160 .word 13623 .word 14053 .word 14449 .word 14811 .word 15137 .word 15426 .word 15679 .word 15893 .word 16069 .word 16207 .word 16305 .word 16364

.word 16384 .word 16364 .word 16305 .word 16207 .word 16069 .word 15893 .word 15679 .word 15426 .word 15137 .word 14811 .word 14449 .word 14053 .word 13623 .word 13160 .word 12665 .word 12140 .word 11585 .word 11003 .word 10394 .word 9760 .word 9102 .word 8423 .word 7723 .word 7005 .word 6270 .word 5520 .word 4756 .word 3981 .word 3196 .word 2404 .word 1606

.word 804

.word 0 .word -804 .word -1606 .word -2404 .word -3196 .word -3981 .word -4756 .word -5520 .word -6270 .word -7005 .word -7723 .word -8423 .word -9102 .word -9760 .word -10394 .word -11003 .word -11585 .word -12140 .word -12665 .word -13160 .word -13623 .word -14053 .word -14449 .word -14811 .word -15137 .word -15426 .word -15679 .word -15893 .word -16069 .word -16207 .word -16305

word -16364

.word -16384 .word -16364 .word -16305 .word -16207 .word -16069 .word -15893 .word -15679 .word -15426 .word -15137 .word -14811 .word -14449 .word -14053 .word -13623 .word -13160 .word -12665 .word -12140 .word -11585 .word -11003 .word -10394 .word -9760 .word -9102 .word -8423 .word -7723 .word -7005 .word -6270 .word -5520 .word -4756 .word -3981 .word -3196 .word -2404 .word -1606

.word -804 Ce tableau a été généré par un tableur type Excel, les valeurs max sont 16384± ce qui

donne sur le CNA : 5,03276816384 ±=±

Donc une fonction )128

..2sin(5,0]sin[k

ktab π= en Q15(16)

ou )128

..2sin(]sin[k

ktab π= en Q14(16)

tp_dsp.doc 48

La sinusoïde en sortie de CNA aura alors comme amplitude ± 1,5 volt crête.

Ce fichier sera pris en compte à l’édition de lien.

10.2.2. Programme en C

Ouvrer le projet sinus1.c Nous utilisons des variables en virgule flottante pour initialiser la boucle. Celle ci par

contre restera en virgule fixe, car le seul travail est d’incrémenter un entier et d’adresser une table.

La boucle tournerait tout aussi bien sans aucun problème de vitesse sur un DSP virgule fixe.

L’échantillon y à fournir au CNA est un entier, travailler avec un flottant ne sert à rien !

Le point de départ est ce fichier fourni sinus1.c

/************************************************** **********************/ /* NE PAS CHANGER CETTE ENTETE !!!!! */ int flag_simu=0 ; /* drapeau pour simulation mis à 1 par le simulateur */ /************************************************** **********************/ /*********** PROGRAMME SYNTHESE DE FREQUENCE ************/ #include ″extern.h ″ /* fonctions externes de base */ extern int tabsin[]; /* tableau de sinus externe */ void gene(float); /* prototype fonction gene() */ int RA,RB,TA,TB; float fck=6250000; /* Fck AIC = 6.25 MHz */ float fechdesir = 18000; /* fech désirée */ float fcad; float fechvraie; /* fech obtenue */ float freq = 1523; /* F à générer */ int y; /* on sort directement sur le CAN, float inutile */ void main(void) fcad=288000*(fechdesir/2)/4400; /* fcadencement pour attenuation > 58db à fech/2 */ RA=(int)( fck/2/fcad);TA=RA; RB=(int)( fcad/fechdesir);TB=RB; fechvraie=fck/(2*RA*RB); /* fech réelle obtenue */ initialisation_aic(TA,RA,TB,RB,0x10); gene(freq) ; /* lancement d'une fonction gene(f) à créer */ void gene(float f) ? ? ? ? deltax =(unsigned int)( ? ? ?); anti_overrun(); ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?

tp_dsp.doc 49

en s’inspirant de l’organigramme suivant, on complétera la fonction void gene(float f);

Certains calculs envirgule flottante

Introduction de fechdesir et de freq à générerCalcul de fech vraie

Initialisation de l’aic

Fonction gene(freq) ;

Dans la boucle :Tout en virgule fixe !

Calcul de deltax (en flottant et conversion en fixe)Anti_overrun

x = x + deltax (sur 16 bits, modulo 65536) Indice = 7 bits de poids forts de x

Attente cadence Fech (par idle) Sortie de tabsin[indice]

Programme principal Fonction void gene(float f) ;

Rezmarques : - On décide dans ce programme de pouvoir modifier Fech. On introduit donc Fechdesir

(en float). Comme RA, RB, TA, TB de l’interface analogique sont des entiers, il faut recalculer en flottant la fréquence exacte fechvraie, la synthèse de fréquence ayant besoin de la valeur précise de celle-ci. On ne retrouve du virgule fixe (des entiers en fait) qu’après avoir calculé deltax (deltax est calculé en flottant puis converti en fixe pour l’accumulation).

- x et deltax et indice sont forcément des non signés (unsigned int). On peut à priori les

déclarer static de la fonction gene(float) car il n’est pas utile de pouvoir y accéder de partout (sauf peut être pour deltax pour la suite).

- Attention : sur le C31, les int ou unsigned int étant sur 32 bits, on devra limiter x à 16 bits par un ET avec 0xFFFF (pour des unsigned int sur 16 bits le modulo se ferait tout seul !)

- La fonction void gene(float f) n’est pas en fait une vraie fonction ! (on n’en revient jamais…).

1) Vérifier le fonctionnement sur le Starter Kit. (Les fichiers sinus.cmd et sinus.bat sont

fournis, il faut peut être ajouter si ce n’est pas déjà écrit la prise en compte de sin.obj). Au simulateur si besoin est on peut facilement voir évoluer correctement x, deltax et indice.

2) Contrôler (au fréquencemètre) l’exactitude (à moins d’un Hz près !) de la fréquence

générée, ici 1523Hz. Vérifier également l’amplitude du signal, est-elle celle attendue ? 3) En exécution sur l’interface Visual Basic, lire la valeur exacte de Fechvraie, et de Fcad. 4) En déduire alors pour la sinusoïde générée.

La fréquence minimale. La résolution en fréquence (en Hz). La fréquence max théorique (due seulement à Shannon théorique : Fmax = Fech/2). La fréquence max pratique en calculant la fréquence de coupure à 0,5dB du filtre de

lissage dans le cas présent, pour cela revoir l’AIC, et le gabarit du filtre).

5) Comment peut-on augmenter la résolution en fréquence en gardant Fech et donc Fmax identiques.

tp_dsp.doc 50

10.2.3. Essais de quelques fréquences 1) Essayer 2000 ou 3000 Hz. (Utilisez l’outil Visual Basic qui vous permet de modifier

aisément les variables globales (et seulement celles-ci). 2) Générer une sinusoïde de fréquence égale à Fmax pratique calculée précédemment à

0.5dB (G ≅ 0,95), observer signal et amplitude. Mesurer l’affaiblissement réel obtenu (rapport G et en dB). On s’attendrait à un affaiblissement de seulement 0,5dB (G ≅ 0,95), et il est en réalité bien plus élevé, expliquer pourquoi, et vérifier si cette valeur d’affaiblissement est finalement normale. (vous avez dû déjà observé ce problème…).

Plusieurs remèdes seraient possibles pour palier à ce phénomène : - Un filtre numérique récursif à 1 seul coefficient, non étudié ici, pourrait améliorer cela

dans de grande proportion, afin d’approcher la Fmax pratique à 0.5dB précédemment calculée.

- On pourrait aussi sur-échantillonner le signal de sortie avant de l’envoyer au CNA, le premier zéro du sinx/x serait alors repoussé plus loin au-delà de Fech.

- On pourrait aussi envoyer au CNA un signal avec un maintient de durée inférieur à Tech, par exemple 1/4 ou 1/8 Tech, cela à pour effet également de repousser le premier zéro du sinx/x au-delà de Fech, mais le niveau du signal obtenu après lissage serait alors inférieur dans toute la bande.

3) Essayer de générer une sinusoïde de fréquence égale à Fmax théorique (Shannon

théorique) calculée précédemment, observer signal et amplitude. Le résultat ne doit pas vous surprendre, pourquoi ?

4) Observer une fréquence basse, 20Hz par exemple, et expliquer la déformation en palier. 5) Que peut-on faire pour améliorer l’allure en BF ? (plusieurs possibilités)

10.2.4. Fréquence maximale possible si autre AIC La limitation provient ici de la cadence d’échantillonnage obtenue par la partie

analogique de la carte Starter Kit. On cherche maintenant la fréquence maximale possible pour le signal généré, qui serait

due seulement au temps de calcul du DSP, et non à la limitation de l’interface analogique, on pourrait en effet sortir la sinusoïde sur un CNA très rapide, câblé directement sur le bus du DSP, et accroître ainsi très fortement la cadence Fech.

Au moyen du simulateur, essayer de calculer cette fréquence max (En cherchant

d’abord la fréquence d’échantillonnage maximale possible).

tp_dsp.doc 51

11. USAGE SIMPLE D’UNE LIGNE D’INTERRUPTION

Signal Wobbulé On veut modifier à chaque interruption deltax d’une petite quantité d(deltax) tel que :

deltax = deltax + d(deltax). On choisit d(deltax) = 10 décimal. On réalise ainsi un signal wobbulé de 0 à presque Fech/2 (en fait il faudrait s’arrêter quand

l’amplitude du signal diminue, mais il s’agit ici d’un simple programme « école »…

On se servira de l’interruption de la ligne 1INT . Attention, cette ligne étant sur niveau et non sur front, on doit théoriquement envoyer des

impulsions courtes d’environ 100ns, à une fréquence de 10 à 1 kHz, et d’amplitude compatible TTL 5 à 0 v).

100ns

Un nouveau bas plus long que la durée du programme d’interruption provoquerait en effet des interruptions successives incontrôlées. Avec une largeur de 100ns on est tranquille.

Cependant, comme un circuit protecteur présent sur la maquette ne passe pas des impulsions aussi étroites, on enverra en pratique des impulsions de environ 1 µµµµs à 2 µµµµs quitte à provoquer peut être deux ou trois interruptions successives, ce n’est pas trop gênant ici ! Attention des impulsions bien plus longues font planter la synthèse de fréquence !!!

Se reporter au cours pour valider ou non l’interruption 1INT (lignes assembleur ou mieux fausses fonctions).

Remarque: deltax doit être obligatoirement global (modifier votre programme si ce n’est pas le cas ) si on veut le modifier par le programme d’interruption.

11.1. Programme d’interruption en assembleur

Possible, mais autant utiliser l’interrupt handler du C qui sauvegarde les registres nécessaires !

11.2. Programme d’interruption en C

Le logiciel du C31 permet d’utiliser et de compléter par exemple la fonction : void c_int01() ; (noms possibles de c_int00 à c_int99 en ne mettant pas de prototypes !)

On peut l’associer (le mnémonique le suggérant ) à l’interruption INT1. Dans cette fonction, sans paramètres, on ne peut accéder qu’à des variables globales. La fonction ainsi nommée est reconnue par le C comme étant une fonction

d’interruption , le compilateur génère ce qu’il faut pour préserver l’environnement du C, et on peut donc l’utiliser presque sans soucis dans un programme en C.

Mais attention, cet interrupt Handler ne programme pas le vecteur d’interruption . (D’autres compilateurs le font automatiquement !)

11.2.1. Première idée INT1 validée avant la boucle 1) Ecrire par exemple en fin du fichier pour_sin.asm deux fausses fonctions : valid_int1() ; et en même temps inhib_int1() qui servira plus loin. Les labels _respectifs doivent être globals. Ne pas oublier de remettre une section .text

tp_dsp.doc 52

2) Modifier sinus.c 3) Ecriture du vecteur d’interruption Dans un fichier assembleur, par exemple en bas de ini_c31.asm Actuellement, une petite section (de 2 mots) nommée "inter" comporte les deux jumps pour les interruption DXR et DRR, on doit lire :

.sect "inter" B xint ; XINT0 B rint ; RINT0

On ne peut créer une section plus grande qui engloberait tous les vecteurs, car il ne faut pas détruire la programmation du vecteur INT2 faite au Boot et qui est nécessaire pour le dialogue avec le PC.

Il faut donc juste ajouter une petite section (nommée par exemple int1), de taille 1, et qui comportera donc seulement le saut pour l’interruption INT1. On ajoutera donc en bas du fichier ini_c31.asm :

.global _c_int01 .sect "int1" B _c_int01 4) Modification du fichier d’édition de lien pour placer la nouvelle section int1 Rappelons tout d’abord le tableau des vecteurs de base (en fait ce sont des pseudos

vecteurs contenant des instructions de saut, il s’agit donc de la table initiale de Jump.

Dans le mode microcomputeur, celui du Starter Kit, cette table de sauts en ROM renvoi à

une table de saut en RAM (sauf pour le Reset), située à partir de 0x809FC1 L’adresse 0x809FC1 est donc l’adresse d’une instruction de saut à _c_INT00

Observation et modification du fichier sinus.cmd

Bien remarquer les deux lignes pour placer à l’adresse adéquate la section inter dans le bloc mémoire de taille 2 ici nommé VECTXR.

tp_dsp.doc 53

S’en inspirer pour ajouter la section int1 avec un bloc mémoire par exemple INT1 de taille 1.

MEMORY RAM0: org = 0x00809800 len = 0x00000400 /* RAM BLOCK 0 */ RAM1: org = 0x00809c00 len = 0x00000300 /* RAM BLOCK 1 */ VECTXR: org =0x809FC5 len = 2 INT1 : ? ? ? ? ? /* SPECIFY THE SECTIONS ALLOCATION INTO MEMORY */ SECTIONS .text: > RAM0 /* CODE */ .cinit: > RAM0 /* C INITIALIZATION TABLES */ .const: > RAM0 /* CONSTANTS */ .stack: > RAM1 /* SYSTEM STACK */ .sysmem: > RAM1 /* DYNAMIC MEMORY - DELETE IF NOT USED */ .bss: > RAM0 /* VARIABLES GLOBALES et STATIQUES */ .data: > RAM0 /* DATA - NOT USED FOR C CODE */ inter: > VECTXR /* section .inter utilisateur pour vecteurs XINT et RINT */ int1: > ? ? ? ? /* vecteur utilisateur INT1 */ 5) Vérifier le fonctionnement. Pour cela régler un générateur d’impulsions pour fournir un signal sur INT1 correct (impulsions en logique négative fines de 1µµµµs et donc plus longues que les 100ns théoriquement nécessaires, pour la raison que nous avons déjà exposée) - Observer la variation de fréquence, pour l’instant avec parasites. - Expliquer l’aléas observé (on doit trouver une cause certaine au niveau de l’instruction

idle, mais aussi une autre cause possible).

11.2.2. Interdiction de départ en interruption aux endroits critiques.

1) En s’aidant de inhib_int1() ; ¨protéger¨ la ligne critique, et seulement celle-ci. 2) Vérifier le fonctionnement. 3) Est-ce suffisant, et en déduire si la fonction void sortie_echantillon(int val) ; est

interromptible à tout moment ?

11.2.3. Danger des interruptions sur niveau Que se passe-t-il si la durée du niveau bas de l’interruption est trop grande ? Remarque : le remède simple et le plus sûr serait la création ou l’utilisation d’entrées

sensibles sur front, avec drapeaux associé sque le programmeur remet lui même à zéro.

tp_dsp.doc 54

12. RECHERCHE DE LA RAPIDITE DE CALCUL PARTIE EN ASSEMBLEUR

Ouvrir le projet : sinus2.c

12.1. Ecriture de la boucle en assembleur

On cherche à écrire une fonction optimisée en assembleur uniquement pour la boucle. Comme on passe en paramètre une valeur en virgule flottante (la fréquence freq), et que l’on a besoin ensuite de calculer deltax au moyen d’un calcul en virgule flottante, ce n’est pas gene(float f); que l’on écrit en assembleur, mais simplement une fausse fonction sans paramètres par exemple synthese() travaillant sur la variable globale deltax.

Les variables x et indice peuvent rester internes à synthese() ; et même encore mieux pourront rester dans des registres, sans leur sonner de nom !

L’organigramme devient :

Certains calculs envirgule flottante

Introduction de fechdesir et de freq à générerCalcul de fech vraie

Initialisation de l’aic

Fonction gene(freq) ;

Dans la boucle :Tout en entier

Sous programme _synthese en assembleur Initialisation d’un pointeur sur _tabsin AR0

Attente cadence Fech (idle)x = x + deltax (sur 16 bits, modulo 65536)Indice = 7 bits de poids forts de x

Lecture tableau à _tabsin + indice masque par $fffc Sortie vers CNA (DXR)

Programme principal

Fonction void gene(float freq)

Calcul de deltax (usage du flottant et conversion)Fonction synthese()

12.1.1. Travail dans sinus2.c fourni int flag_simu=0 ; /*************** SYNTHESE DE FREQUENCE Avec Assem bleur ************/ #include "extern.h" extern int tabsin[]; void gene(float); int RA,RB,TA,TB; float fck=6250000; /* 6,25 MHz */ float fechdesir = 18000; /* fech désirée */ float fcad; float fechvraie; /* fech obtenue */ float freq = 768; /* F à générer */ int y; /* on sort directement sur le CAN, float inutile */

tp_dsp.doc 55

unsigned int deltax; void main(void) fcad=288000*(fechdesir/2)/4400; /* fcadencement pour attenuation > 58db à fech/2 */ RA=(int)( fck/2/fcad);TA=RA; RB=(int)( fcad/fechdesir);TB=RB; fechvraie=fck/(2*RA*RB); /* fech réelle obtenue */ /* Fck AIC = 6.25 MHz */ /* Fcad = 625 Khz */ initialisation_aic(TA,RA,TB,RB,0x10); /* Fech = 18,93939 kHz */ anti_overrun(); gene(freq) ; /* lancement d'une fonction gene(f) à créer */ void gene(float f) deltax =(unsigned int)(65536.0*f/fechvraie); ??????????? 1) Compléter la fonction void gene(freq). Ajouter le prototype extern void synthese() ; 2) Bien remarquer que les labels x et indice n’existent plus. On ne garde que deltax en

variable globale.

12.1.2. Ecriture du sous programme _synthese Le code assembleur démarre à un label à déclarer global _synthese. On le programmera

par exemple dans le fichier pour_sin.asm sur lequel on a déjà travaillé. 5) Remarques et aides diverses - Déclarer global le label _deltax (puisqu’il est défini dans le C !) - On recherche la vitesse maximale, il faut donc utiliser au maximum les registres, et

ceux dont l’utilisation à l’intérieur d’une fonction ne pose aucun soucis de sauvegarde : On prendra R0 registre standard banalisé R1 pour x et donc pour accumuler _deltax AR0 pour pointer _tabsin IR0 comme registre d’index (pour calculer indice), à utiliser ensuite

en déplacement variable dans un adressage indirect. - Avant la boucle, on initialise une fois pour toute AR0 sur _tabsin (attention à

l’adressage immédiat sur 32 bits qui n’existe pas, il faut créer une donnée par exemple TABSIN en mémoire programme (.text) ou donnée (.data) qui contient l’adresse _tabsin.

- Effectuer l’accumulation de _deltax sur R1 modulo 16 bit par :

ADDI @_deltax,R1 suivi de AND 0FFFFh,R1 - On peut mettre alors directement x donc R1, dans IR0 - On décalera alors de 9 à droite IR0, pour obtenir l’indice dans IR0. Ce décalage est

logique ou arithmétique, à vous de bien choisir ! Voir dans le poly l’utilisation des instructions correspondantes LSH ou ASH.

tp_dsp.doc 56

- On peut alors lire l’échantillon de sinusoïde à l’adresse _tabsin + indice simplement par l’adressage :

LDI *+AR0(IR0),R0

AR0 reste ici

IR0

- Ne pas oublier ensuite le masque nécessaire pour mettre à zéro les 2 bits LSB (sinon

plantage de l’AIC) - On sortira ensuite l’échantillon sur DXR. Attention, DXR n’est pas un registre mais un

label correspondant à une adresse. Il s’agit du port série rapide du C31, en sortie. Pour que l’assembleur connaisse ce label, ne pas oublier la ligne :

DXR .set 808048 h (voir si l’on veut dans ini_c31.asm ou est déclaré aussi ce label)

12.2. Manipulation

1) Vérifier le fonctionnement. Remarque si vous vous servez du simulateur en mise au point, supprimer idle qui le fait attendre éternellement …. !

2) Pour estimer la vitesse de calcul, le simulateur est inutile ici. Si on suppose que le

pipe line ne se débranche pas, presque toutes les instructions font 1 Tck.. Sauf BR et IDLE qui ont 4 Tck

Trouver alors la nouvelle Fréquence max possible pour Fech puis pour le signal de sortie (limitation due uniquement au temps de calcul du DSP et non aux possibilités limitées de l’AIC de la carte Starter Kit).

12.3. Amélioration, encore !

Utiliser à la place de BR (qui prend 4 cycles) l’instruction de branchement retardé BRD qui n’en prend qu’un !

Cette instruction permet d’anticipe un saut 3 Tck plus loin. Il faut donc la placer 3 lignes avant ! (Si ces 3 lignes s’exécutent en 3 Tck).

1) Faire cette modification, 2) Contrôler le fonctionnement 3) Que devient alors la Fréquence max possible pour la sinusoïde de sortie ?

12.4. Question subsidiaire à se poser

Un buffer circulaire, par ailleurs très aisé à gérer en assembleur, n’aurait pas pu être utilisé ici pour la table de sinus ? Comprendre pourquoi.

tp_dsp.doc 57

13. DEMODULATION D’AMPLITUDE TOUT NUMERIQUE

13.1. Théorie

1) Signal modulé en amplitude

2vV

Soit le signal sinusoïdal utile s(t) = tv ωcos. modulant une porteuse tV Ωcos.

Le signal complet s’écrit donc : ttV

vVtS Ω+= cos)cos.1()( ω avec l’indice de

modulation 1≤=V

vm

Il vient : tv

tv

tVtSm )cos(2

)cos(2

cos)( ωω +Ω+−Ω+Ω=

Son spectre d’amplitude se compose donc

des trois raies :

F

F-f F+f

V

v/2 v/2

Démoduler signifie retrouver le signal s(t) initial.

2) Démodulation synchrone Faisons le produit du signal modulé Sm(t)

par une sinusoïde d’amplitude k (sans dimension) et de fréquence Ω égale à la porteuse, soit par )cos(. ϕ+Ωtk

Sm(t)

k.cos(Ωt+ϕ)

I(t)

Calculons donc :

)cos(.).()( ϕ+Ω= tktSmtI = )cos(.)cos(2

)cos(2

cos ϕωω +Ω

+Ω+−Ω+Ω ttv

tv

tV

Il vient :

[ ]

[ ])cos()2cos(4

1

)cos()2cos(4

1)cos(.

2

1)2cos(.

2

1)(

ϕωϕω

ϕωϕωϕϕ

−−++−Ω+

−+++Ω+−++Ω=

tttkv

tttkvkVtkVtI

tp_dsp.doc 58

On peut regrouper les deux termes en cosωt, ils fournissent )cos()cos(2

1tkv ωϕ

D’ou le spectre d’amplitude

(en fréquence) :

2F

2F-f 2F+f

kV/2

kv/4 kv/4

f

½ kv cos(ϕϕϕϕ)½ kVcos(ϕϕϕϕ)

0

Signal utile

En filtrant et en éliminant les fréquences voisines de 2F, il reste :

[ ])cos().cos(.2

1tvVkI ωϕ +=

Conclusion : on obtient bien le signal utile initial décalé de V, mais d’amplitude multiplié

par )cos(2

ϕk . Si ϕ n’est pas constant, mais dérive lentement, l’amplitude du signal récupéré

varie. Il est donc pour l’instant nécessaire d’avoir en réception un signal sinusoïdal non

seulement de fréquence exactement F mais aussi parfaitement synchrone de la porteuse initiale(ϕ constant et de préférence voisin de 0 pour une amplitude maximale). C’est utilisé dans de nombreux cas, et il s’agit de la démodulation synchrone.

3) Démodulation AM sur deux voies en quadrature On peut effectuer le même calcul sur une seconde voie dite en quadrature.

Sm(t)

k.cos(Ωt+ϕ)

I(t)

k.sin(Ωt+ϕ)

Q(t)

I2+Q2

I

Q

y

On obtient donc signal :

)sin(.).()( ϕ+Ω= tktSmtQ = )sin(.)cos(2

)cos(2

cos ϕωω +Ω

+Ω+−Ω+Ω ttv

tv

tV

Par un calcul identique, on trouverait aisément son spectre d’amplitude :

2F

2F-f 2F+f

kV/2

kv/4 kv/4

f

½ kv sin(ϕϕϕϕ)½ kVsin(ϕϕϕϕ)

0

En filtrant on peut récupérer alors :

[ ])cos().sin(.2

1tvVkQ ωϕ +=

On peut alors calculer :

)cos(22

)]cos(.[.2

122 tvkkV

tvVkQIy ωω +=+=+=

tp_dsp.doc 59

On obtient alors à un décalage et à un coefficient près le signal utile initial, et ceci même si on ne multiplie pas par un signal de fréquence exactement égale à Ω ni synchrone de la porteuse. Le produit par un signal de fréquence voisine de Ω provoque à chaque instant un déphasage ϕ différent, mais l’astuce du calcul sur les deux voies élimine ce problème.

13.2. Etude pratique sans sous échantillonnage de la porteuse

13.2.1. Cahier des charges On essaye ici, d’exploiter presque au maximum les possibilités fréquentielles de notre

Starter Kit C31 Les coefficient RA, TA, RB et TB de l’AIC sont fixés à une valeur (4 et 33) telle que l’on

ait : Fech = 23674 Hz et donc Fech/2 = 11837 Hz Filtre de lissage : < 0.5 DB jusqu'à 9923 Hz > 58dB pour F > 11936 Hz On choisit une porteuse de F = 8 kHz Et un signal utile de fréquence fmax = 3 kHz

13.2.2. Etude spectrale pour voir si on pourrait faire mieux (avec notre Fech)

Spectre du signal utile

fmax3000

0

Spectre du signal de départ modulé en amplitude, avec une largeur de bande pour le signal utile de 0 à fmax = 3kHz,

Fech = 23674

0

Fech/2 = 11837Filtre de lissage

F-fmax5000

F+fmax11000

F8000

2fmax

9223(-0,5dB)

11936(-58dB)

Le spectre du signal « modulé » respecte donc Shannon. La partie droite du spectre, vers F+fmax si on lissait serait atténuée, mais peu importe car ce n’est pas ce signal qui sera lissé !

tp_dsp.doc 60

Spectre des signaux I(t) et Q(t)

2F16000

Fech = 23674Fech-(2F-fmax)

10674

Fmax3000

0

Fech/2 = 11837

Fech-2F7674

2F-fmax13000

2F+fmax19000

Fech-(2F+fmax)4674

Spectre replié

Filtre Numériqueà programmer

Filtre de lissage

La naissance de fréquences autour de 2F = 16kHz provoque du fait de l’échantillonnage à

seulement 23674Hz le spectre replié indiqué sur la figure (Shannon non respecté). On constate que l’on pourrait un peu augmenter la largeur de bande (fmax), mais que le

filtre numérique devrait être alors de plus en plus raide. On peut choisir le gabarit ci-contre pour le

filtre numérique à réaliser. Avec Matlab et l’outil sptool de « Signal Tool boxes », on peut aisément trouver un filtre non récursif « Equiripple » de 27 coefficients qui donnera ce gabarit (Ce type de filtre, moyennnant une petite ondulation dans la bande passante est très raide)

±0.5dB

Gabarit du Filtre Numériqueà programmer

30004400

-40dB

Filtre réel obtenu :

tp_dsp.doc 61

13.3. Programme sur DSP

13.3.1. Etude de la dynamique pour un DSP virgule flottante

Sur un DSP virgule fixe, une étude serait à faire à chaque étape pour ne pas déborder

tout en choisissant le format Qi(n) optimum pour le maximum de précision.

Sur un DSP virgule flottante, seule une étude de la dynamique finale est utile pour sortir le résultat sur le CNA

Le signal de départ à traiter Sm(t) est sur 16 bits, de –32768 à presque –32767 en

raisonnement entier, ou de –1 à presque +1 en raisonnement Q15(16), peu importe. Cherchons à obtenir un signal BF (à envoyer au CNA) de même amplitude que le signal

BF modulant. Synthèse de kcos(ΩΩΩΩt+ϕϕϕϕ) et ksin(ΩΩΩΩt+ϕϕϕϕ) Les signaux sortant de la table sont en réalité des entiers entre –16384 et +16384 (voir la

synthèse sinusoïdale ), et fournissent en fait ½ cos et ½ sin en Q15(16). En virgule fixe il faudrait garder ce coefficient k= ½ pour ne pas déborder (+1

n’existant pas en Q15(16), Comme on travaille ici en flottant, il faut des valeurs de kcos et ksin évoluant réellement

entre –k et +k. Il faut donc transformer les valeurs sortant des tables en vraies valeurs flottantes valant réellement kcos et ksin. Et il est aussi pratique de choisir directement k = 1 !

Pour le sinus, si tabsin[] est le tableau d’entier, on fera pour une valeur d’indice nommé indice :

float cos,sin ; /* flottants valant réellement sin et cos de –1 à +1 sin = tabsin[indice] /16384.0; /* le .0 force le calcul en flottant */

On aurait pu écrire aussi : sin = (float)tabsin[indice]/16384 ;

Remarques : Pour un bon rapport S/B de la démodulation, il faut une synthèse limitant les

distorsions, on prendra une table de 512 valeurs de sinus et non seulement 128. D’autre part pour limiter la taille du code, la même table de sinus servira pour le

cosinus avec une astuce (simple déphasage de π/2)

Les deux filtres numériques passe bas ont dans la bande un gain de 1 à de petites ondulations près, et de petits dépassement sont possibles en transitoire. Mais pas de soucis de débordement puisque l’on travaille en flottant !

Dans le calcul de I(t) et de Q(t) et d’après la théorie, on voit qu’il apparaît un coefficient

de ½. Conclusion : au total, le gain de la chaîne sera de ½. Pour récupérer notre signal BF de

même amplitude que le modulant, on devra donc multiplier par deux le résultat final avant de le sortir au CNA. On obtiendra donc y = V + vcos(ωt) donc au décalage V près le signal s(t) initial.

tp_dsp.doc 62

⇒ D’ou le schéma de traitement, tous les calculs s’effectuant en virgule flottante :

Sm(t)

cos(Ωt+ϕ)

I(t)

sin(Ωt+ϕ)

Q(t)

I2+Q2

I

Q

y = V + vcos(ωωωωt)*2

½[ V+ vcos(ωt)]

Entiers16 bitsen flottants

Entiers 16 bits

s(t) initial !

13.3.2. Découverte et travail sur les fichiers fournis : Ouvrir le projet demodul.C Il comprend demodul.c demodul.bat et demodul.cmd pour_fir.asm Une fonction fir1 de filtrage en assembleur coeff.asm Les 27 Coefficients du filtre passe bas sin512.asm 512 valeurs de ½ sinus en Q15(16) 1) Etude des fonctions de filtrage, fichiers pour_fir.asm et coeff.asm Ayant en plus de la synthèse sinusoïdale deux voies à filtrer, pour assurer une cadence

d’échantillonnage aussi élevée que 23674 Hz, une convolution toute en C serait trop lente.. On doit utiliser une fonction C de filtrage écrite en assembleur, et travaillant sur Buffer Circulaire.

Nous avons déjà étudiée une telle fonction de prototype :

float fir1(float xn) ; Entrée un échantillon de signal à filtrer Retourne l’échantillon correspondant filtrée. Réponse impulsionnelle h1[ ] spécifique de fir1 en variable globale Taille taille1 spécifique de fir1 en variable globale La taille est fixée dans le fichier assembleur correspondant, à un seul endroit

évidemment (par le label N1) Le Buffer se situe dans une section ″″″″buffer_1″″″″ (spécifique de fir1) qui devra

démarrer à une adresse multiple de 2k (avec 2k >=Taille filtre) (Intervention nécessaire à l’édition de lien). On parle d’alignement de la section correspondante.

On redonne ici le code assembleur correspondant. Il vous est fourni dans le fichier

pour_fir.asm. La taille est pour l’instant remplacée par un ?.

tp_dsp.doc 63

;------------------------------------------ FILTRE FIR 1 ------------------------------------- .global _h1,_taille1, _fir1 ;On doit déclarer le Buffer Circulaire et pour la réponse impulsionnelle ;Attention, intervenir à l'édition de lien pour placer la section correspondante au Buffer

;Taille du filtre

N1 .set ? ; écriture N1 = écriture ? (pas en mémoire, invisible en C)

buffer1 .usect "buffer_1",N1 ; Buffer X en section "buffer_1 " , taille N1

.data pt_buff1 .word buffer1 ; Pointeur (variable statique) sur Début_X car

utile. pt_fin_h1 .word _h1+N1-1 ; Pointeur sur Fin_H car utile. _taille1 .word N1 ;Création d'une donnée pour accéder à la taille depuis

le C .bss _h1,N1 ; Réponse impulsionnelle, taille N1 ;Sous programme de la fonction .text _fir1

PUSH AR3 LDI SP,AR3 ;pointeur de trame AR3 LDI N1,BK LDI @pt_buff1,AR0 ;pointeur Buffer --> AR0 LDI @pt_fin_h1,AR1 ;AR1 sur H[N-1] (fin_H)

valeur

031

SPPC

Ancien AR3AR3

-2

LDF *-AR3(2),R0 ; prise de l'échantillon nouveau Xn STF R0,*AR0--% ; mise en buffer, pointeur AR0 décalé sur Xn-(N-1),

; pour le début du calcul ldf 0,R2 ; Raz R2 pour accumuler mpyf3 *AR0--%,*AR1--,R0 ; premier produit dans R0 rpts N1-2 ; pour répéter N1-1 fois on doit mettre N1-2 mpyf3 *AR0--%,*AR1--,R0 ; produit nouveau dans R0

|| addf3 R0,R2,R2 ; en parallèle addition R2 + R0(précédent) --> R2 addf3 R0,R2,R0 ; dernière addition R2 + R0 --> R0 STI AR0,@pt_buff1 ; pointeur Buff_Circulaire rangé POP AR3 rets

Mais cette fonction float fir1(float) est spécifique d’un filtre particulier, avec son

propre Buffer circulaire, sa propre réponse impulsionnelle et sa taille. On a besoin de filtrer une seconde voie, la réponse impulsionnelle est la même mais il faut

forcément une seconde pile d’échantillons.

tp_dsp.doc 64

⇒ En s’aidant de copier/coller et en changeant les endroit utiles, créer dans ce même fichier pour_fir.asm une nouvelle fonction float fir2(float) ; avec le cahier des charges :

float fir2(float xn) ; Entrée un échantillon de signal à filtrer Retourne l’échantillon correspondant filtrée. Réponse impulsionnelle h2[ ] spécifique de fir2 en variable globale Taille taille2 spécifique de fir2 en variable globale La taille est fixée dans le fichier assembleur correspondant, à un seul endroit

évidemment, par le label N2 Le Buffer se situe dans une section ″″″″buffer_2″″″″ (spécifique de fir2) qui devra

démarrer à une adresse multiple de 2k (avec 2k >=Taille filtre). ⇒ Ne pas oublier de mettre la valeur de la taille1 (N1) et taille2 (N2) ! ⇒ Petit coup d’œil aux coefficients : (fichier coeff.asm) ; FILTRE EQUI RIPPLE Fech = 23674 0,5 dB de 0 à 3000 -40dB > 4400Hz ; 27 Coefficients .global _coeff .data _coeff .float -0.0022,-0.0150,-0.0151,-0.0056,0.0143,0.0279,0.0171 .float -0.0192,-0.0551,-0.0496,0.0227,0.1451,0.2616,0.3095 .float 0.2616,0.1451,0.0227,-0.0496,-0.0551,-0.0192,0.0171 .float 0.0279,0.0143,-0.0056,-0.0151,-0.0150,-0.0022 2) fonction fournie (dans demodul.c) de génération des porteuses On utilisera la fonction (interne) fournie presque entièrement :

void calcul_porteuse (float *cos, float *sin); Elle effectue la synthèse directe des cos(ΩΩΩΩt+ϕϕϕϕ) et sin(ΩΩΩΩt+ϕϕϕϕ)). L’incrément delta réglant la fréquence est en variable globale. Elle travaille avec une table de 512 valeurs de ½ sinus en Q15(16) Elle fournit les vraies valeurs de sin et cos en flottant de –1 à +1 On aurait pu évidemment faire une fonction plus polyvalente, et passer en paramètre F à

générer et Fech, mais on se pénaliserait en temps d’exécution. void calcul_porteuse (float *cos, float *sin) /* génération de cos et sin en flottant */ /* delta est en variable globale */ unsigned int indice, indicecos ; static unsigned int accum=0; accum=accum+delta; accum = accum & 0xffff ; indice=accum >> 7; /* car indice = 9 MSB de Accum pour accéder au tableau de

512 valeurs de ½ sinus en Q15(16) */

indicecos = ????

*sin =tabsin[indice]/ ? ? ? ? ;

*cos = tabsin[indicecos]/ ? ? ? ? ;

tp_dsp.doc 65

⇒ Remarquer ici le décalage de 7 au lieu des 9 nécessaires pour une table de 128 valeurs. Pour 512 valeurs, on doit prendre en effet les 9 bits de poids fort de Accum pour accéder au tableau (au lieu de 7).

⇒ Pour gagner de la place en mémoire, il est inutile de stocker aussi 512 valeurs de

cosinus. Etudier l’astuce simple pour créer à partir de indice un indicecos permettant de passer d’un sinus à un cosinus, (ne pas oublier aussi le modulo 512 de indicecos !).

⇒ Compléter ainsi la fonction aux endroits marqués par des ?.

3) Fonction de calcul de la racine carré On utilisera évidemment la fonction double sqrt (double) ; du C, sans oublier une directive #include <math.h > 4) Fichier demodul.c fourni /************************************************** **********************/ int flag_simu=0 ; /* passe à 1 en simulation */ /************************************************** **********************/ /***** PROGRAMME DEMODULATION TOUT NUMERIQUE ***********/ #include "extern.h" #include <math.h> float Fck = 6.25e6; /* FCk AIC = 6,25 MHz */ float x; float y; int k; extern int tabsin[] ; /* table de 512 valeurs de ½ sinus en Q15(16) ; /* extern float h1[],h2[]; /* Les deux tableaux des réponses impulsionelles*/ extern int taille1,taille2; /* Les deux tailles */ extern float fir1(float); extern float fir2(float); extern float coeff[]; /* Les coefficients du filtre à créer */ void calcul_porteuse(float *cos, float *sin); int TA=4,RA=4,TB=33,RB=33; /* POUR FECH = 23674 Hz */ float Fech; /* Fréquence exacte recalculée */ float F=8000; /* Fréquence porteuse à synthétiser */ unsigned int delta; /* incrément pour le synthétiseur */ void main(void) float I,Q; float cos,sin; Fech = Fck/(2*RA*RB); delta = 65536.0*F/Fech; /* Fck AIC = 6.25 MHz */ initialisation_aic(TA,RA,TB,RB,0x10);

for(k=0;k<taille1;k++)h1[k] = ? ; /* Recopie des coeff dans les deux filtres */

for(k=0;k<taille2;k++)h2[k] = ? ;

tp_dsp.doc 66

anti_overrun(); while(1)

x = attente_echantillon(); /* lecture échantillon */

?????????? sortie_echantillon(y);

void calcul_porteuse (float *cos, float *sin) /* génération de cos et sin en flottant */ unsigned int indice; /* delta est en variable globale */ unsigned int indicecos; static unsigned int accum=0; accum=accum+delta; accum = accum & 0xffff; indice=accum >> 7; /* car indice = 9 MSB de Accum pour tableau de 512 valeurs de sin */

indicecos = ???? *sin = tabsin[indice]/16384.0; *cos = tabsin[indicecos]/16384.0; ⇒ Compléter donc ce programme (pour la fonction la fonction calcul_porteuse, c’est

normalement déjà fait !) 1) Fichier de commande de l’édition de lien : demodul.cmd La répartition en mémoire est légèrement différente des .cmd précédents, le but étant de bien faire rentrer tous les éléments dans le DSP, en équilibrant les zones RAM0 et RAM1. -a -cr -w -x demodul.obj ini_c31.obj pour_fir.obj sin512.obj coeff.obj -o demodul.out -m demodul.map -stack 0x80 /* limité à 120 mots ! */ -heap 0x200 -l c:\apw\tmsc31\c\rts30.lib MEMORY RAM0: org = 0x00809800 len = 0x00000400 /* RAM BLOCK 0 */ RAM1: org = 0x00809c00 len = 0x000003BF /* RAM BLOCK 1, on va jusqu’à la zone réservée aux vecteurs d’interruptions ( 0x80FBF) */

tp_dsp.doc 67

VECTXR: org =0x809FC5 len = 2 /* SPECIFY THE SECTIONS ALLOCATION INTO MEMORY */ SECTIONS .text: > RAM0 /* CODE */ .cinit: > RAM0 /* C INITIALIZATION TABLES */ .const: > RAM0 /* CONSTANTS */ .stack: > RAM0 /* SYSTEM STACK */ .sysmem: > RAM1 /* DYNAMIC MEMORY - DELETE IF NOT USED */ .bss: > RAM1 /* VARIABLES GLOBALES et STATIQUES */ .data: > RAM1 /* DATA - NOT USED FOR C CODE */ inter: > VECTXR /* section .inter utilisateur pour ecteurs XINT et RINT */

buffer_1 > RAM1 align ? buffer_2 > RAM1 align ? ⇒ Compléter.

13.3.3. Vérification du fonctionnement 1) Pour un signal AM (fourni déjà à priori réglé sur chaque table: porteuse F = 8000Hz, signal

utile f = 100Hz , amplitude V = 1volt et v = 0,8 volt, donc indice de modulation m = 0,8) Vérifier la bonne démodulation (ainsi que le gain de 1 désiré, on doit retrouver en sortie une sinusoïde de même amplitude v que l’enveloppe du signal AM, et décalée de V) ⇒ Si vous obtenez n’importe quoi, il est simple de Debugger petit à petit en sortant au

CNA et en visualisant non pas y, mais différentes variables intermédiaires (x déjà !, puis sin et cos. On peut aussi sortir x filtrée par fir1, x filtrée par fir2 …, I et Q filtrés, etc ). Le simulateur permet aussi de voir l’exactitude ou l’ordre de grandeur de certaines valeurs.

Facultatif : Si l’on possède un générateur sur chaque poste, ou en demandant à l’enseignant de modifier le signal fourni, on peut vérifier rapidement la bande passante du cahier des charges.

2) Observation de I ou Q à l’oscilloscope Expliquer le battement observer, et comprendre l’intérêt du calcul sur les deux voies. 3) Questions - Pourquoi dans le cas présent le filtre numérique est nécessaire ? - La présence d’une petite composante continue sur le signal à traiter (en surveillant qu’elle

ne provoque un écrêtage lors de l’acquisition) serait-elle gênante ? - Choisir une valeur de Fech qui permettrait d’éviter le filtre numérique. Mais alors la

présence d’une petite composante continue sur le signal à traiter serait très néfaste. Expliquer pourquoi.

tp_dsp.doc 68

13.4. Et si on sous échantillonnait la porteuse ?

Les porteuses sont souvent de fréquences très élevées (au moins 150kHz, ou plusieurs MHz ou nettement plus).

Un sous échantillonnage de F produit des fréquences fech ± F, 2 fech ± F … K.fech ± F. On peut donc choisir une fréquence F (de la porteuse) telle que K.fech – F redonne

une fréquence égale à 8 kHz. Le sous échantillonnage ramènera alors le spectre à traiter (de largeur de bande 2fmax) en BF autour de 8 KHz.

Fech = 23674

0

Signal à démoduler

Filtre de lissage

F

(K+1).Fech-F (K+2).Fech-F

ETC …..

Signal ramené autour de 10000par le sous échantillonnageK.fech – F= 8000

Faire l’essai avec une porteuse (que l’on calculera) autour de 300 kHz. (Si un générateur

n’est pas sur chaque table, demander à l’enseignant).

13.5. Et pour centrer la sortie toujours sur zéro ?

1) Décrire brièvement une solution analogique 2) Essai d’une solution numérique On peut calculer la valeur moyenne d’un signal par un filtre moyenneur non récursif,

comme nous l’avons déjà vu, mais mieux encore et bien plus simple (surtout si on peut travailler en flottant) par un moyenneur récursif :

Yn = (1-a)Xn + a.Yn-1 avec a = 0.99 ou 0.999 (Une étude de ce filtre montrerait que plus a se rapproche de 1, plus on moyenne sur une

grande durée). Il suffira alors de soustraire la valeur moyenne. a) Faire fonctionner cette solution sur votre maquette, en utilisant les virgules flottantes. c) Même question en supposant que le processeur est un virgule fixe, opérandes sur

16 bits. On rappelle que les processeurs les plus rapides ne travaillent qu’en virgule fixe, (ce qui veut dire, que sur des entiers !). On codera a et 1-a en Q14(16) et non en Q15(16) pour se garder un bit de réserve de dynamique pour le calcul de la somme. Les échantillons seront évidemment en Q0(16).

Remarques : Ce filtre tout simple est souvent utilisé pour calculer la puissance d’un signal, en entrant

Xn2 au lieu de Xn. D’autre part, on ne le fait pas ici, mais toute la démodulation serait relativement aisée à

programmer entriérement en virgule fixe, avec une table (de 256 ou 512 valeurs) pour le calcul de la racine carré.

tp_dsp.doc 69

ETAPES SUPPLEMENTAIRES FACULTATIVES

Pour les élèves ayant déjà fait dans la ½ valeur de TP B la manipulation sur le DSP

Texas C31, portant sur : Filtrage numérique par filtre FIR, convolution en C, mesure de valeur moyenne et optimisation par convolution en assembleur.

Ou si vous avez encore du temps !

Sur carte "Starter Kit" Buts : - Filtre IIR du Premier ordre, moyenneur récursif - Fitre IIR du Second ordre, filtre résonnant. Oscillateur numérique.

tp_dsp.doc 70

14. FILTRES IIR

Pour toute cette partie, et les suivantes, la fréquence d’échantillonnage est fixée à : Fech ≈≈≈≈ 18,939 KHz donc limite Shannon Fech/2 ≈≈≈≈ 9,47 kHz

Tous les calculs en virgule flottante.

14.1.Passe bas du premier ordre yn = (1+a)xn – ayn-1 -1 < a < 0

14.1.1. Travail préalable théorique

1) Retrouver la formule donnant le module du gain : 2)2cos(.21

1)(

axa

axG

+++=

π

2) Pour a = -0,5 calculer : − Le gain continu ou très basse fréquence G(0) − La fréquence de coupure à 3dB (en assimilant cette fonction de transfert à un premier

ordre analogique, voir le rappel de cours correspondant. − G(0,5) − Tracer alors l’allure de G(f), avec donc des fréquences en abscisse!

3) On veut réaliser un voltmètre numérique de cahier des charges identique au TP précédent, à savoir :

Echantillonnage à Fech = 18,939 KHz (comme précédemment). Calcul de la valeur moyenne, ondulations résiduelles ≤≤≤≤ 1% pour F ≥≥≥≥ 50 Hz Il faut trouver a, mais il faut toujours réfléchir pour éviter de s’embarquer bêtement dans

un calcul à priori simple, mais en fait long et délicat à cause de valeurs numériques particulières, et absolument inutile !

On est dans le cas ou x est petit, l’approximation faite dans le rappel du cours (développement limité du cos) est tout a fait justifiée ! (sinon on ne sait pas résoudre analytiquement …. )

D’autre part, comme a est très proche de –1, la résolution exacte de l’équation du second ordre que l’on devrait trouver est délicate (nécessitant des valeurs numériques intermédiaires précises), donc autant l’éviter !

On effectuera donc la démarche suivante : Comme x est petit, on choisit l’approximation de la fonction

classique du premier ordre analogique :

2

2

1

1)(

cx

xxG

+≈

- En déduire la valeur numérique approchée de xc. D’après la formule de xc en fonction de a du rappel de cours, en déduire alors la valeur de a (très proche de –1, donc surtout ne

pas résoudre d’équation du second degré, a− est alors proche de quelque chose … ! ). Bien prendre pour a 6 à 7 chiffres significatifs !

00

0.01

0.05 a = ?

50Hz

Courbe du module du gain

x

tp_dsp.doc 71

14.1.2. Travail pratique

Coup d’œil sur l’organigramme Le coefficient a ainsi que les variables xn et yn sont déclarés en flottant. Les échantillons xn et yn sont à l’acquisition et en restitution des entiers 16 bits (de –32768

à 32767), les cast en flottant dans les deux sens sont implicites.

Initialisation diverses, et y = 0 FILTRENUMERIQUEPREMIER ORDRE IIR

** a la cadence de la fréquence d’échantillonnage

sortie de l'échantilloncalculé

Calcul du nouveau y en fonction de y et de x

Acquisition de x **

Programme fourni: fichier (et projet) iir_1.C Ecrire la ou les lignes de programme permettant de calculer yn.

/* NE PAS CHANGER CETTE ENTETE !!!!! */ int flag_simu = 0 /* *********** Filtre IIR du PREMIER ORDRE *** ***********/ float xn,yn ; void main() float a = ? ; initialisation_aic(5,5,33,33,0x10); /* fech voisine de 18.939 kHz */ /* **************** Acquisition, Filtrage, recon stitution ************************/ anti_overrun() ; yn = 0 ; /* valeur initiale nulle pour y */ do

xn=attente_echantillon(); /* cast en float implicite */

/* Calcul de y à compléter? */

sortie_echantillon(yn ); /* cast en int implicite */ while(1);

tp_dsp.doc 72

Essai Pour une valeur de a de –0,5 - Observer rapidement la courbe de réponse, relever le gain G(0) et la fréquence de

coupure fc à –3dB. - Comparer aux valeurs théoriques calculées. - Pour f de 8000 Hz à 10000Hz environ, le gain chute bien plus vite que la courbe

théorique du filtre. Cela vous étonne-t-il et à quoi cela est-il du ?

Fonctionnement en Voltmètre numérique mesurant la valeur moyenne

- Prendre pour a la valeur calculée dans le travail théorique, assurant une bonne mesure à partir de 50Hz (résiduelles < 1%)

- Faire comme d’habitude varier la composante continue du signal (sinusoïdal, triangulaire ou carré), les deux voies de l’oscilloscope étant réglées sur la même sensibilité et le même décalage.

- Conclusion.

Etude du transitoire, cas du bon moyenneur précédent

Avec la valeur de a précédente, on peut l’observer en faisant varier (à la main) brusquement la composant continue du générateur. - Estimer sa durée - Commenter pertinemment ce résultat (relation bon moyenneur, fréquence de coupure et

durée du transitoire)

tp_dsp.doc 73

14.2.Passe bas du second ordre

yn = (1+a1+a2)xn – a1yn-1 - a2yn-2 0<a2<1 a1 condition résonnance (Cf cours)

14.2.1. Filtre obtenu avec : a1 = -1,2 et a2 = 0,6 et fech = 18939 Hz 1) Etude théorique - Montrer que le régime est « avec résonance ». - Donner G(0) - Calculer la fréquence de résonance x0 (réduite) puis f0 (en Hz) - Calculer le module du gain G(f0) = G0 à la résonance, ainsi que le facteur de résonance

ou de surtension :

)0(

)( 0

==

=fG

ffGSurtensiondeCoeff

2) Mise en œuvre sur la maquette DSP, fichier et projet IIR2_.C Le listing est similaire à l’étude effectuée sur un premier ordre, avec cette fois ci deux

coefficients flottant a1 = -1.2 et a2 = 0.6

Compléter le programme Il contient les variables flottante xn, yn_1, et yn_2 Pour cela (un peu comme sur l’étude Excel) il faut :

calculer un yn = (1+a1+a2)xn –a1yn_1 – a2yn_2 sortir sur le CNA cet yn réactualiser yn_2, puis yn_1

Vérifier le fonctionnement, avec une sinusoïde à l’entrée de environ 1 volt crête. Mesurer rapidement :

- Gain en BF (à comparer à celle théorique) - Fréquence de résonance (à comparer à celle théorique) - Gain à la résonance (à comparer à celle théorique)

(Ce gain étant > 1, si on déborde, limiter le signal sinusoïdal d’entrée !).

14.2.2. Filtre à forte résonance Faire : a2 = 0,99 Pour la même tension d’entrée d’environ 1 volt crête, que constate-t-on sur la sortie

autour de la résonance f0 cela vous étonne-t-il ? (remarque : f0 est un peu différente du cas précédent, mais on ne demande pas de la recalculer).?

Diminuer alors la tension d’entrée pour visualiser la forte résonance autour de la f0, mesurer alors cette fréquence f0.

Mesurer rapidement le nouveau facteur de surtension. Est-ce correct par rapport à la courbe fournie dans le rappel de cours ?

tp_dsp.doc 74

14.2.3. Et si on faisait a2 = 1 ! On est à la limite de la stabilité avec un régime oscillant. Donc sans autre calcul on peut

dire que (un peu comme an analogique) la réponse impulsionnelle est une sinusoïde d’amplitude stable, de fréquence f0.

En analogique, on ne peut rester juste à la limite d’oscillation (en dessous, l’amplitude diminue, au delà elle augmente indéfiniment). Ici en numérique, on peut évidemment coder a2 = 1 sans erreur ! Si a2 diffère de 1 même de très peu, l’amplitude de la sinusoïde diminuera (système limite stable) ou au contraire augmentera (limite instable)

On crée donc un générateur sinusoïdal, c’est totalement mâgique …. ! (Remarque : une autre technique numérique existe pour effectue une synthèse sinusoïdale,

par accumulation de phase et accès à une table, totalement différente et non étudiée ici !) Faire donc a2 = 1 On veut que le programme génère sa réponse impulsionnelle : il faut initialiser xn à une

valeur avant la boucle, pour aussitôt le mettre à zéro dans celle ci. Modifier le programme de la façon suivante :

Initialiser xn avant la boucle à par exemple 16384 (donc la moitié de la valeur max possible).

Ecrire xn = 0 juste après la ligne calculant yn, ainsi au tour suivant xn vaudra 0. Laisser attente_echantillon() qui permet de conserver une sortie à la cadence Fech,

mais surtout ne pas récupérer de valeur xn ! Vérifier le fonctionnement

Calculer théoriquement la nouvelle valeur de f0 de résonance (avec a2 = 1). Mesurer la fréquence générée, est ce correct ? Fixer a2 à 0.99999 puis a2 à 1.00001 et observer et commenter pour chacun des cas le

signal généré. En particulier quelle est la durée du transitoire quand a2 = 1 ?