Les programmes du DSP

Introduction sur le DSP de Texas Instrument – TMS 320C54x

Texas Instrument a fait des efforts particuliers pour faciliter la tâche aux programmeurs. En effet, l’assembleur n’a jamais été un langage très " proche de l’homme " et sa syntaxe en a déjà repoussé plus d’un… TI a conçu une syntaxe nommée AIS (Algebraic Instruction Set) dans le but de simplifier la lecture et la programmation d’applications DSP.

Ce n’est pas une évolution du langage mais uniquement de la syntaxe : Ce qui veut dire qu’une instruction AIS sera toujours équivalente à une instruction en syntaxe standard.

Prenons un exemple très simple. Voici un tableau comparatif :
 
 

Syntaxe standard Syntaxe AIS
SETC INTM INTM = 1
LDP #1 DP = #1
OPL #1,PMST PMST = #1
SPLK #1,IMR IMR = #1
SPM 1 PM = 1


 

La syntaxe standard exigeait l’utilisation de dizaines d’instructions différentes pour une seule et même opération : Assigner à un registre la valeur 1. La syntaxe AIS laisse rêveur… Bien entendu, nous remarquons qu’il reste encore des vestiges de cette syntaxe : le # en est un très bon exemple. Quand le mettre ? Quand ne pas le mettre ? Il existe une règle mais également des exceptions qui confirment cette règle !
 
 

Notre but ici n’est pas de reprendre point par point les caractéristiques du DSP telles que ses registres, ses types d’adressage, ses caractéristiques… Nous voudrions uniquement attirer l’attention du lecteur sur certains points qui nous ont posés problème lors de notre première prise en main du DSP. Pour de plus amples informations, les documentations des DSP sont disponibles à l’IUT.
 
 
 
 

Les symboles # et @
 
 

Un point important à noter est l’utilisation des symboles # et @.

Un certain nombre de règles doivent être appliquées afin de ne pas se tromper.
 
 

Le symbole # doit être utilisé :

Ø Devant une valeur immédiate. Par exemple : #1 #0Ch

(sauf dans certains cas, comme on le voit dans le tableau plus haut)

Ø Devant une constante. En effet, les constantes sont remplacées, lors de la compilation, par la valeur immédiate qu’elle représente. Par exemple, si on définit une constante " bonjour " à 1, lorsque le compilateur verra " #bonjour ", il le remplacera par " #1 ".

Ø Devant un nom de variable si on souhaite accéder à l’adresse de celle-ci. Par exemple, le code " A = #variable " assignera à A l’adresse de la variable en mémoire.
 
 

Le symbole @ doit être utilisé :

Ø Devant un nom de variable si on souhaite accéder au contenu de la variable.
 
 

Configuration de la mémoire
 
 

Les premières instructions d’un programme doivent comporter l’initialisation de la mémoire du DSP. Ceci se fait par l’assignation d’une valeur correcte au registre PMST (Processor Mode Status Register). Les différentes possibilités d’assignation sont répertoriées dans les sources " envoi.asm " et " recep.asm ". D’autres registres peuvent (et devraient) être définis afin de s’assurer que le DSP agira selon nos besoins. Les principaux registres (ASM, OVM, …) ont été définis et commentés dans ces mêmes sources.
 
 
 
 

Les interruptions
 
 

Le traitement d’informations en temps réel exige que le programmeur s’adapte à une logique d’interruptions. En effet, il est difficile à un programme " séquentiel " de réagir en fonction d’un événement. Pour cela, toutes nos fonctions d’émission et de réception sont stockées dans une routine d’interruption nommée : Receive. Une autre routine (Transmit) existe et elle joue le même rôle que la précédente. Malgré nos tentatives, nous n’avons trouvé aucune différence entre ces deux routines.
 
 
 
 

Les vecteurs d’interruption
 
 

La table des vecteurs d’interruptions est une zone mémoire où l’on peut définir le comportement du DSP face à différents événements. Par exemple, c’est là que l’on définira si le DSP doit réagir quand le PC veut faire appel à lui (interruption HPI INT), ce qu’il doit faire lorsque l’AIC aura terminé une conversion… C’est une table primordiale lorsque l’on souhaite utiliser le DSP d’une manière intéressante.
 
 
 
 

Les pages
 
 

Le DSP divise sa mémoire un plusieurs blocs de 128 mots (qu’on appelle une page), afin de diminuer la taille du code en mémoire. Pour mieux comprendre ce principe, on peut reprendre l’exemple déjà utilisé d’une ville.

Plutôt que de numéroter chaque maison de 1 à 8192, on divise la ville en 64 rues de 128 maisons.

Ainsi, lorsque l’on se trouve dans la rue n°60, on ne parlera plus de la maison numéro 7681, mais de la maison numéro 1, et ainsi de suite. C’est plus économique en place et en temps.

DP est le registre qui contient le numéro de la rue, de la page courante.
 
 
 
 

Les instructions de test
 
 

L’une des manières les plus simples pour réaliser un test est l’utilisation de l’instruction " if ", combinée à un accumulateur " a " ou " b ".
 
 

Par exemple :
 
 

if (aeq) goto A_est_egal_a_zero Si A = 0, on saute à l’endroit indiqué.
 
 

if (alt) execute(1) Si A < 0, on lui ajoute 10.

A += #10
 
 

" aeq " signifie " a equal " ou A = 0

" agt " signifie " a greater than " ou A > 0

" alt " signifie " a lower than " ou A < 0

" aleq " signifie " a lower or equal than " ou A <= 0

Et ainsi de suite…
 
 

C’est une syntaxe facile à retenir et également facile à comprendre.
 
 
 
 
 
 

Convivialité
 
 

Beaucoup d’instructions ont été développées pour ressembler au C, ou plutôt pour en approcher la convivialité. Par exemple :
 
 

A = B >> 1 A sera égal à B décalé de 1 vers la gauche (divisé par 2).

A += #1 A sera incrémenté de 1.

B = ~B B est complémenté.

B &= #1 B est masqué par 1.
 
 
 
 

Les pointeurs
 
 

Il existe également des pointeurs dont la gestion est remarquablement bien faite. Ils se nomment AR0, AR1, AR2, …, AR7.
 
 

Si l’on a un tableau nommé " tab ", voici les possibilités offertes par les pointeurs :
 
 

AR0 = #tab AR0 va pointer sur le tableau.

A = *AR0 A va contenir le premier élément du tableau.

B = *AR0+ B va également contenir le premier élément du tableau.

Le " + " indique que AR0 va pointer sur l’élément suivant.

A = *AR0- A va contenir le deuxième élément.

Le " - " indique que AR0 va pointeur sur l’élément précédent.
 
 
 
 

Bien d’autres possibilités sont proposées, comme par exemple définir le pas d’avancement dans la table, ou alors définir un buffer circulaire (c’est à dire que lorsqu’on aura dépassé un élément bien défini, le pointeur se repositionnera automatiquement sur le premier élément, et vice versa).
 
 
 
 

  • Le programme d’émission
  •  

    Organisation de la mémoire
     
     

    La mémoire est organisée de la façon suivante :
     
     

    Adresse Limite Contenu
    0180h 01FFh Vecteurs d’interruption
    0200h 02C3h Table des sinus (195 valeurs)
    0400h Variable Fichier à envoyer
    1080h Variable Données
    1800h Variable Programme


     

    Nous n’allons pas détailler la description des variables et des constantes que nous avons utilisées du fait qu’elles sont bien documentées dans les fichiers source. (voir annexe)
     
     
     
     

    Emploi fréquent de l’instruction " nop "
     
     

    L’emploi de l’instruction " nop " (No Opération) pourrait sembler inutile. Pourtant, celle-ci est primordial. En effet, le DSP utilisé utilise une technologie dite " pipeline ". En fait, pour augmenter la vitesse d’exécution des programmes, les instructions sont décomposées en plusieurs opérations de base. Afin de ne pas trop rentrer dans des détails qui remplissent à eux seuls plus d’un chapitre de la documentation du DSP, nous allons donner un exemple pratique afin de mieux cerner le problème.
     
     
     
     

    Voici deux instructions :
     
     

    DP = #ma_variable

    A = @ma_variable
     
     

    Notre but est de placer dans A la valeur contenue dans " ma_variable ".

    DP est ce qu’on appelle un registre de page. Pour en comprendre son principe, prenons l’exemple d’un village. Une personne habite 3 rue des Pommiers, une autre, 3 rue des Poiriers. DP, c’est le nom de la rue. Le symbole @ serait alors le numéro de maison.
     
     

    Ici, on définit DP (rue) comme il faut, puis on tente d’accéder à la bonne case mémoire (numéro de maison). Mais le problème, c’est que quand la deuxième instruction commencera à s’exécuter, la première ne sera pas achevée ! Ce qui va donner qu’au lieu d’accéder à la rue des Poiriers, on sera à la rue des Pommiers ! Le résultat sera donc faux.
     
     

    Pour palier à cela, il faut insérer deux " nop " entre ces deux instructions afin de laisser au DSP le temps de se positionner sur la bonne rue et seulement là, on recherchera la maison voulue.
     
     

    Ce problème est loin d’être mineur du fait qu’il peut fausser totalement des résultats et est entièrement transparent à l’utilisateur.
     
     
     
     

    Fréquence de fonctionnement
     
     

    Lors de l’initialisation de l’AIC (circuit qui permet de convertir un une valeur analogique [d’un micro, …] en numérique), il nous est possible de définir le temps que celui-ci devra mettre pour effectuer les conversions.

    La formule est la suivante :
     
     

    Fréquence = 5.000.000 / (Ta * Tb)
     
     

    Ta et Tb sont deux registres que nous définissons à l’initialisation. Ceux-ci ont tous deux la valeur 16. Ainsi, nous savons que nous pourrons réaliser 19531,25 conversions à la seconde. Selon le théorème de Shannon, la fréquence maximale que nous pourrons générer sera de 9765Hz, ce qui est bien au-dessus de nos 1300 et 2100Hz.
     
     

    Lorsque l’AIC achève une conversion, il génère une interruption. Pour être plus clair, le programme principal s’interrompt et une routine à part est exécutée.

    Du fait que notre DSP fonctionne à 40MHz, nous pouvons déterminer le nombre maximal de cycles d’horloge pouvant être utilisés pour notre routine :
     
     

    40.000.000 / 19.531 = 2.048
     
     

    Sachant que beaucoup d’instructions du DSP ne font qu’un seul cycle d’horloge, il est facile de voir que la quantité d’instructions possibles est énorme.
     
     
     
     

    Fonctionnement
     
     

    Ce programme ne doit pas uniquement générer une simple sinusoïde. Mais en plus de réaliser une modulation de fréquence (FSK), il doit s’adapter à un protocole qui a été décrit plus haut.

    Pour cela, le meilleur moyen est de requérir à une logique d’automate. Le principe en est simple : Associer des actions à un certain nombre d’étapes. Ces étapes sont divisées de la même manière que le protocole lui-même.
     
     

    Etape 0 : Envoi d’une fréquence de 1300Hz pendant 20.000 cycles horloge (env. 1 seconde).

    Etape 1 : Envoi de 128 " 1 " modulés pour permettre au récepteur de déterminer la vitesse d’émission.

    Etape 2 : Envoi de 16 " 0 " modulés pour achever la synchronisation.

    Etape 3 : Envoi du fichier

    Etape 4 : Envoi de 16 " 0 " pour clore la transmission.
     
     
     
     

    Extraction du bit voulu
     
     

    Nous avons vu que chaque mot en mémoire comportait 16 bits. Le problème est donc le suivant : Comment extraire le bit désiré, et passer au mot suivant au moment opportun ?

    Par chance, 16 est un multiple de deux. Il y a donc certainement un moyen d’extraire les informations désirées grâce à des masques.

    La première chose à savoir est que nous employons une variable " position " qui indique où on en est dans le fichier.
     
     

    Ce mot est constitué comme suit : 11111111 11112222

    Les 2, ici, représentent l’indice du bit désiré alors que les 1 nous informent du mot courant.

    Par exemple :
     
     

    Position = 131 = 83h = 00000000 10000011b
     
     

    En décomposant, la partie en gras nous indique que nous désirons le bit n°3 alors que la partie non grasse nous informe que nous accédons au huitième mot.
     
     

    Ce principe très simple permet d’éviter beaucoup de complications.
     
     
     
     

    Génération de la sinusoïde
     
     

    Une variable " pas " contient le pas utilisé pour l’échantillonnage dans notre table. A nouveau, pour simplifier nos calculs, nous n’avons pas adapté le pas à la taille de la table, mais nous avons fait l’inverse.

    Voulant une fréquence de 1300Hz et une autre de 2100Hz, nous nous sommes fixés des pas respectifs de 13 et de 21. De là, d’après la fréquence d’échantillonnage, nous avons déterminés la taille requise pour la table. Voici en bref cette logique en calcul, qui a déjà été abordée dans la partie théorique :
     
     

    Fréquence d’échantillonnage : 19531.25 Hz
     
     

    (19531.25 / 1300) * 13 = 195.3125 valeurs

    (19531.25 / 2100) * 21 = 195.3125 valeurs
     
     

    Par cette simple logique, nous avons défini nos valeurs.

    Avec une table de 195 valeurs, nous tombons sur des fréquences de :
     
     

    Ø 19500 * 1300 / 19531.25 = 1297.92Hz

    Ø 19500 * 2100 / 19531.25 = 2096.64Hz
     
     

    Ce qui est très proche des fréquences désirées. Cette table est stockée en Q15, ce qui signifie que la plage de variation est de :
     
     

    -1 <= nombre Q15 <= 0.9999694
     
     

    Et que la précision est de 1/32768 = 0.0000305.
     
     
     
     

    Conclusion sur l’émetteur
     
     

    A présent, il ne reste plus qu’au lecteur à passer en revue les sources de l’émetteur qui sont détaillées au mieux.
     
     

  • Le programme de réception
     
  • Organisation de la mémoire
     
  • La mémoire est organisée de la façon suivante :
     
     

    Adresse Limite Contenu
    0180h 01FFh Vecteurs d’interruption
    0300h 030Fh Buffer utilisé pour le retard
    1010h Variable Données
    1200h 15FFh Fichier reçu
    1800h Variable Programme


     

    Nous n’allons pas détailler la description des variables et des constantes que nous avons utilisées du fait qu’elles sont bien documentées dans les fichiers source. (voir a
     
     
     
     

    Fréquence de fonctionnement
     
     

    L’AIC est configuré en réception de la même manière qu’en émission. Nous avons la même fréquence d’échantillonnage de 19531Hz.
     
     
     
     

    Les étapes
     
     

    Les différentes étapes de la démodulation, représentées dans l’algorithme ci-dessus, sont les suivantes :
     
     

    Etape 0 : Attente d’une sinusoïde à 1300Hz sur la ligne.

    Etape 1 : Attente d’un " 1 " modulé sur la ligne.

    Etape 2 : Comptage jusqu’à ce qu’il y ait un " 0 " modulé sur la ligne. Détermination du débit.

    Etape 3 : Attente de la fin de la synchronisation.

    Etape 4 : Démodulation et stockage jusqu’à la fin de l’émission.
     
     
     
     

    Extraction du bit voulu
     
     

    Pour le stockage du bit en mémoire, le même principe que pour la modulation est utilisé.
     
     
     
     

    Conclusion sur l’émetteur
     
     

    Une fois que le principe de fonctionnement est compris, il est aisé de parcourir les fichiers source et d’en retirer les informations nécessaires.
     
     

  • Le programme " Gestion "
     
     
     
  • Ce programme n’a pas été prévu dès le commencement de l’année, mais son besoin s’est fait sentir. En effet, comment savoir si la transmission a bien eu lieu sans avoir un retour d’information aussi complet que possible ?

    Pour sa réalisation, nous sommes partis des fichiers sources du programme " loadapp " fourni par Texas Instrument.

    Bien que ce " squelette " soit assez mal conçu d’un point de vue purement informatique (beaucoup de variables globales, mauvaise organisation des routines, gestion dangereuse des pointeurs, …), nous avons décidé de laisser la base telle quelle et de rajouter les fonctions dont nous avions besoin.

    Nous ne ressentions pas le besoin de tout reprendre du fait que ce programme a un rôle mineur et ne fait pas parti du projet que nous avions dès le départ.
     
     

    A ce même titre, nous n’allons pas développer son fonctionnement du fait d’un nombre trop important de routines à commenter ainsi que du protocole HPI qui n’est pas des plus aisés. Toute personne ayant un niveau de base en C pourra tirer beaucoup de profit des commentaires du fichier source.

    Conception des modules audio

    Entrée/Sortie Auxiliaires des DSP C54x












    Nous avons voulu ajouter au DSP Starter Kit une possibilité de pouvoir brancher un micro et un haut parleur avec réglage du gain et du volume, permettant ainsi de faire des applications audio.
     
     

    Pour cela, les DSP C54x sont équipés d'une sortie (out+) et d'une entrée auxiliaire (aux+) analogique permettant d'obtenir un accès direct sur l'AIC. La configuration de ces accès externes à l'AIC se fait par programmation dans le registre 5, ce qui permet d'obtenir soit les entrées/sorties auxiliaires soit les entrées/sorties de la carte DSP, soit la somme des deux. En général, les bits 1 et 2 sont laissés à 1 et 1, pour avoir les 2 accès validés.
     
     

    Les sorties sont utilisées en mode commun avec la sortie out- et l'entrée aux- reliées à la masse. La plage de tension des signaux entrants et sortants dépend de la configuration des bits A0 et A1 du registre 4 de l'AIC.