mercredi 9 janvier 2013

introduction aux MCU ATtiny, partie 4

Aujourd'hui je me suis un peut amusé dans l'électronique analogique car j'ai décidé que j'avais besoin d'un petit ampli audio pour la suite de ces chroniques car en plus d'une pulsation 3Hz j'ai ajouter une alarme audio double tonalité pour accompagner cette alarme visuelle. Voici le schéma de l'ampli.


L'entrée de l'ampli est branché sur la broche PORTB1. Même avec la résistance de 22K à l'entrée le son est suffisamment fort pour être entendu à la grandeur de la maison.

Interruption

L'un des concepts important de la programmation système et qu'il faut apprendre très tôt est celui d'interruption et c'est ce que nous allons étudier dans cette chronique.

Les micro-contrôleurs doivent répondrent à des événements extérieurs qui ne sont pas synchronisés avec leur horloge interne, donc qui peuvent survenir à n'importe quel moment dans l'exécution du programme. Il y a deux façon de répondre à ces événements, la première consiste à lire à intervalle régulier les entrées pour savoir quand l'une d'elle a changé d'état. C'est ce que fait la routine attend_pression_bouton présenté dans les chroniques précédentes. Mais cette façon de faire n'est pas très pratique pour plusieurs raisons. Premièrement elle demande beaucoup de temps CPU, temps pendant lequel les autres tâches sont misent en attendent. Si l'événement externe requiert une réponse rapide il faut vérifier l'entrée le plus fréquemment possible ce qui diminu d'autant le temps disponible pour les autres tâches. Cette technique dites de poling n'est vraiment pas efficace. Dans nos exemples précédent ça n'avait pas d'importance puisque le MCU n'avait aucune tâche à accomplir en attendant que le bouton soit enfoncé. Mais le plus souvent le MCU a plusieurs tâches concurrentes à accomplir et doit partager son temps le plus efficacement entre celles-ci.

C'est dans ces situations qu'on a besoin des interruptions. Une interruption comme son nom l'indique est un mécanisme par lequel le déroulement d'un programme est interrompu pour répondre à un événement qui s'est produit. Les sources d'interruptions peuvent-être interne ou externe. Par exemple une minuterie interne au MCU peut déclencher une interruption lorsqu'elle expire. Mais une interruption peut aussi être déclenchée lorsqu'une broche d'entrée a changée d'état, il s'agit alors d'un événement externe.

Dans un cas comme dans l'autre le MCU doit interrompre ce qu'il est en train de faire pour réponde à l'interruption. Les interruptions sont répondues par des routines spécifiques qui ne s'éxécutent pas dans le déroulement normal du programme mais seulement lorsqu'un événement qui les concernes c'est produit. On les appelle sans surprise les routines de service d'interruption, en anglais interrupt service routine, abrégé ISR.

Donc une interruption se produit et le MCU interrompt l'exécution normmale du programme. Mais ça ne peut se faire n'importe comment. D'abord il faut converser la valeur du pointeur d'instruction car lorsque l'ISR sera répondue il faudra revenir à l'endroit où le programme était rendu avant l'interruption. De plus tous les registres utilisés par la routine d'interruption doivent-être préservés. Toutes ces donnés sont conservées sur la pile. La même pile qui est utlisée pour conserver les adresses de retour lorqu'une qu'une sous-routine est appellée.

Ce n'est pas tout, le MCU doit savoir où se trouve la routine qui sert tel ou tel interruption. C'est le rôle de la table des vecteurs d'interruptions. Pour les MCU ATtiny cette table se trouve à l'adresse zéro de la mémoire programme. Chaque source d'interruption a un numéro qui correspond à un index dans cette table. Et le vecteur lui-même dans cette table est simplement une instruction de saut vers l'ISR requise. Pour le code d'aujourd'hui j'ai créé une ISR pour répondre au débordement du compteur TCNT0. Chaque fois que ce compteur retombe de 255 à 0 l'interruption no. 3 est déclenchée. voici de quoi à l'air la table des vecteurs et le code assembleur de la routine ISR.

Comme on peut le voir l'entrée 3 de la table contient une instruction rjmp .+86 qui conduit à l'adresse effective 0x5e qui est le début de l'ISR pour l'overflow du timer0, c'est à dire du compteur TCNT0. Si on examine cette routine on constate que les premières instructions push consiste en l'empilement des registres r0 et r1. L'ISR va se servir de ces 2 registres donc elle en préserve le contenu en l'envoyant sur la pile et va les restaurer avant de quitter avec les instructions pop. Cette routine est très longue et pourtant elle ne fait que basculer la valeur d'une variable (adresses 0x6c à 0x74). Il n'y a que 4 instructions pour le code effectif toutes les autres ne servent qu'à préserver et restaurer le contexte du progamme qui a été interrompu.

On va maintenant regarder comment on cré une interruption en 'C'.
J'ai modifié plusieurs choses par rapport au code d'hier mais on va se concentrer sur la création de la routine de service d'interruption. J'avais besoin de cette routine car lorsque la lumière est en clignotement urgence 3HZ je l'accompagne d'un signal audio double tonalité, 500hz et 750hz. La fréquence bascule en même temps que l'état de la LED. Je devais donc savoir quand la LED change d'état. En mode 3Hz j'ai configuré le PWM en mode bascule. C'est à dire que la sortie change d'état à chaque fois que le comparateur confirme que OCR0A==TCNT0. De plus J'ai configuré le mode 7, c'est à dire que le compteur TCNT0 retombe à zéro en même temps qu'il atteint la valeur de OCR0A. Cette configuration me permet d'obtenir une onde carré à la sortie de PORTB0. Donc lorsque TCNT0 retombe à zéro l'interruption est déclenchée. Cette interruption ne fait que basculer l'indicateur booléen bascule_tone qui est utilisé par la routine alarme() et lui permet de savoir qu'elle fréquence faire entendre.

Si vous regardez au début du code j'ai ajouté la ligne #include <avr/interrupt.h>
Ce fichier défini les macros nécessaires à la création des routines d'interruptions. Plus bas vous apercevez la déclaration de la routine elle-même.

La macro ISR sert justement à déclarer les fonctions qui servent de routine de service d'interruption. En argument de la macro on donne un identifiant indiquant de qu'elle interruption il s'agit. Ces identifiants sont définis dans le fichier avr/io.h. Pour voir le contenu d'un fichier inclus, cliquez avec le bouton droit de la souris sur le fichier et choisissez l'item goto implementation dans le menu surgissant.


Comme vous le voyez cette ISR ne fait vraiment pas grand chose. En passant notez que la variable bascule_tone doit-être une variable globale pour être accessible aussi bien par l'ISR que par la fonction alarme().

Autre point à notez dans ce programme, c'est le changement de la fréquence de l'oscillateur système. Pour obtenir une fréquence suffisamment basse pour générer un PWM de 3HZ j'ai du diviser la fréquence de l'oscillateur par 16, ce qui fait que le core du MCU durant le mode alarme fonctionne à 600Khz au lieu de 9,6Mhz. J'en ai tenu compte lorsque j'appelle les fonctions _delay_ms() et _delay_us() en divisant les valeurs de délais par 16. La fréquence de l'oscillateur principal est remise à 9,6Mhz avant de quitter le mode alarme.

Voilà ce sera tout pour cette chronique. Le code prend maintenant 41% de l'espace programme. Et si on ajoutait une nouvelle fonction! Par exemple la lumière pourrait signaler un appel de détresse en envoyant le code morse international S.O.S.: ...---...

Aucun commentaire:

Publier un commentaire