Contacts
Enseignant: Jérôme Collin, responsable (local M-4013, poste 5060)
Support technique supplémentaire: Laurent Tremblay (local M-4011, poste 7181)
Chargés de laboratoire: Section 1: Kais Fallouh (Lundi AM)
Romain Lebbadi-Breteau (Mercredi PM)
Section 2: Tristan Rioux (Mardi PM)
Gaëtan Florio (Jeudi AM)
Section 3: Luciano Garin-Iriarte (Lundi PM)
Ely-Cheikh Abass (Jeudi PM)
Section 4: Dorine Dantrun (Mardi AM)
Meriam Ben Rabia (Vendredi AM)
Section 5: Amélie Simard (Mercredi AM)
Abdul-Wahab Chaarani (Vendredi PM)
Section 6: Marc-Antoine Manningham (Mardi soir)
Laurent Bourgon (Jeudi soir)

INF1900

Projet initial de système embarqué

Travail pratique No. 4

Les registres spéciaux et les périphériques internes



Objectif: Explorer les ressources internes de l'AVR

Durée: Une semaine

Travail préparatoire: Aucun

Documents à remettre: Aucun fichier à remettre. Par contre, il s'agit d'un laboratoire sujet à un quiz prochainement et le code développé pour ce laboratoire pourra être réutilisé assez directement pour le fonctionnement du système finale.

Présentation en classe: fichier PowerPoint


«L'ordinateur obéit à vos ordres, pas à vos intentions»

-

Anomyme

Introduction


Jusqu'ici, le projet a permis d'explorer le fonctionnement des ports du microcontrôleur et de la carte mère en général. Il est maintenant temps de regarder d'un peu plus près les ressources internes de l'AVR, spécialement celles qui nous seront utiles pour contrôler le robot.


Les microcontrôleurs AVR disposent de plusieurs éléments de contrôle à l'intérieur même de la puce. La plupart de ces sous-systèmes peuvent être configurés et utilisés par des registres spéciaux placés à des adresses précises en mémoire. L'ajout de tels dispositifs matériels permet de leur déléguer certaines responsabilités et de libérer l'unité centrale pour lui permettre de faire d'autres traitements. Par contre, ces nouveaux périphériques internes doivent être gérés correctement et l'on doit toujours garder à l'esprit qu'ils travaillent en parallèle avec le reste du système. La synchronisation des activités devient donc importante.


Pour entreprendre cette étape, nous ferons un survol des mémoires et des périphériques internes de l'AVR. Pour les détails, il vous faudra lire la documentation du ATmega324PA de toute façon. Par la suite, vous referez certains exercices effectués durant les semaines précédentes, mais en réutilisant cette fois les ressources internes dédiées pour arriver aux mêmes résultats. Il y aura aussi des exercices qui introduiront de nouveaux concepts. Cependant, il faudra commencer par une présentation des mémoires de l'AVR puisqu'il s'agit probablement du meilleur point de départ pour arriver à saisir l'interaction avec les périphériques internes.


Mémoires de l'AVR ATmega324PA


Il y a 3 mémoires principales dans le microcontrôleur utilisé, soit la mémoire de programmation de type flash, la mémoire vive (RAM) et la mémoire morte (ROM). Chaque mémoire est indépendante des autres et est adressable séparément. La première est celle où le programme réside. Depuis le début du projet, c'est elle qui héberge vos programmes. Comme vous l'avez déjà utilisée depuis quelques semaines, il ne reste pas énormément de détails à ajouter sur cette mémoire de programmation. Le fait d'avoir une zone séparée pour loger le programme à exécuter permet l'utilisation d'une technologie spécifique de mémoire qui ne s'efface pas facilement. Ainsi, le programme peut résider longtemps à l'intérieur du circuit, et ce, même si l'alimentation est coupée, sans devoir être rechargé à chaque démarrage du microcontrôleur. Les ordinateurs PC procèdent de façon totalement différente et chargent les programmes constamment en mémoire vive. Mais les PC viennent avec un disque dur interne ce qui n'est pas le cas de notre carte mère...


Le second type de mémoire de l'AVR est une mémoire morte à laquelle on peut accéder par des instructions spéciales pour contenir des données pour une longue période de temps. Elle ne sera pas utilisée durant le cadre du projet puisqu'on lui préférera la mémoire externe disponible sur la carte mère et qui sera introduite la semaine prochaine. Il ne reste donc qu'à parler de la mémoire vive. On dit mémoire vive, mais en fait, il y a trois sections distinctes dans cet espace adressable comme le montre la figure ci-dessous tirée du livre de Christian Tavernier «Microcontrôleur AVR - Description et mise en oeuvre» (voir la section références).




Section de la mémoire vive du ATmega324PA


Comme on peut le voir, les registres de travail du processeur, au nombre de 32 (soit de 0 à 31 - 0x1F en hexadécimal), font partie de la mémoire vive! C'est une caractéristique assez particulière de la série AVR. La section suivante est réservée pour les registres spéciaux. Enfin, la dernière section est la «vraie» mémoire vive à usage général au sens où on l'entend le plus couramment. En réalité, du point de vue de la programmation, des mnémoniques sont associées aux adresses mémoires des deux premières sections. Ce que cette organisation signifie est que vous n'avez pas vraiment à savoir les détails précis des adresses des deux premières sections puisque vous y accéderez plutôt avec des noms de registres. Vous direz, par exemple, pour accéder au registre d'état (Status REGister):


uint8_t gMaChereVariable = SREG ;


En réalité, ce registre se situe dans la mémoire vive à l'adresse 0x3F, mais vous n'avez pas besoin d'en tenir compte. Si cette façon de voir vous déplaît, vous pouvez aussi considérer que la mémoire vive n'est constituée pratiquement que de la dernière section et que celle-ci commence à l'adresse 0x100. Une façon encore plus simple de voir la chose est de se dire que le compilateur verra lui-même à gérer l'utilisation des registres de base de même que de la dernière section de la mémoire vive et que nous accéderons aux registres spéciaux de la deuxième section par leur nom dans nos programmes tout simplement! Ces précisions devraient vous permettre de lire la documentation plus facilement sans trop vous perdre dans des détails utiles uniquement pour une utilisation spécialisée du microcontrôleur ou lorsque la programmation est en langage d'assemblage.


Les ressources internes, les interruptions et la scrutation


La figure suivante montre l'architecture interne de l'AVR (également tirée du livre de Christian Tavernier). Comme on peut le voir, la partie de gauche est semblable à ce qu'on peut trouver pour n'importe quel processeur avec un fichier de registres et une unité arithmétique et logique (UAL, ALU en anglais). Par contre, dans la section de droite, on peut voir certains blocs de périphériques. Dans le cadre du projet, nous aurons surtout besoin des minuteries (timers) et du UART. Plus tard, nous aurons aussi besoin du convertisseur analogique/numérique. Nous décrirons ici en quelques lignes les ressources utiles, mais il convient d'abord de parler des interruptions.





Architecture générale d'un microcontrôleur AVR


Mais en premier lieu, qu'est-ce qu'une interruption? L'interruption est l'une des deux méthodes pour savoir où en est un sous-système, l'autre étant la scrutation (polling en anglais). Ces concepts deviennent un peu plus clairs à partir d'exemples. Vous avez une montre à votre poignet et vous ne devez absolument pas manquer un rendez-vous important dans 2 heures. Il y a deux façons de vous y prendre pour gérer votre temps d'ici là tout en n'oubliant pas votre rendez-vous. La première consiste à regarder votre montre à intervalles plus ou moins réguliers pour savoir le temps qu'il reste avant votre rendez-vous. L'autre méthode est d'ajuster l'alarme sur votre montre pour qu'elle se mette à sonner juste avant votre rendez-vous pour vous avertir qu'il est temps de vous y rendre. La scrutation caractérise la première méthode alors que la seconde peut assez bien représenter ce qu'est une interruption. Avec la scrutation, le système principal demande à un sous-système de lui dire où il en est rendu dans sa tâche alors que par interruption, le sous-système avertit lui-même la partie principale de la fin d'une étape importante (et peut-être finale) de sa tâche. Dans la programmation des microcontrôleurs, les 2 méthodes sont employées pour synchroniser des opérations, entre elles ou par rapport à un déroulement principal.


Depuis le début du projet, vous avez employé la scrutation pour arriver à savoir si le bouton-poussoir sur la carte avait été enfoncé par l'utilisateur pour changer la couleur de la DEL. Par contre, vous auriez pu avoir recours à la programmation par interruption pour arriver aux mêmes fins. C'est d'ailleurs ce que vous devrez faire plus loin comme exercice. La facilité de la programmation par scrutation vient du fait que le code suit une évolution prévisible puisque la succession des opérations est dictée ligne par ligne dans un déroulement linéaire. Avec les interruptions, ce cheminement est moins évident puisque le cours de l'évolution du programme peut être changé à tout moment pour répondre aux actions prévues par une interruption. Ce n'est pas tout. Une fois que les instructions d'une interruption ont été accomplies, il faut revenir au déroulement de ce qui était en cours juste avant l'interruption. Vous savez tous comment il peut être difficile de se replonger dans la lecture d'un document après avoir été interrompu par un coup de téléphone inattendu. Le principe est le même, il faut prendre le temps de noter où on en était avant l'interruption pour éventuellement pouvoir retourner au déroulement de l'action principale au point où on l'avait laissée. Dans le cas de la lecture d'un livre, on laissera un signet au bon endroit dans le livre. Dans le cas des processeurs, on sauvegarde le contenu de certains registres pour les recharger avec les bonnes valeurs après l'interruption. Heureusement, le compilateur que nous utiliserons ici se chargera pour nous de bien gérer lui-même les registres lors d'interruptions ce qui nous facilitera la tâche.


Pour en revenir à notre figure montrant l'architecture de l'AVR, on peut donc dire de façon générale que les périphériques de la partie droite et l'unité centrale sur la gauche peuvent se synchroniser en utilisant soit une approche par scrutation ou soit par interruption. Généralement, des registres spéciaux sont ajustés par programmation pour permettre ou non à certaines interruptions d'agir sur le déroulement du programme si on le souhaite. Si on préfère la scrutation, le programme ira voir lui-même l'état de certains autres registres pour voir ce qui se passe avec les périphériques internes.


Remarque très importante


Atmel est une compagnie qui a été achetée par la rivale Microchip. En octobre de la même année, la documentation du microcontrôleur a subi une révision. Malheureusement, certaines modifications ont été introduites qui font que la version 2.00 de la librairie AVRLibC ne fonctionne par avec cette révision. Nous vous recommandons donc vivement de prendre la version 2015 du même document. Le bas de page de la documentation, vers la droite, vous indique le mois et l’année de la date de publication.


Il vous faudra consulter régulièrement ce fichier PDF au cours de la session. Il est recommandé de la placer dans votre compte pour éviter de le télécharger inutilement à répétition. Éviter également de le placer sous Git où il prendra trop d’espace inutilement. Si vous voulez vraiment aller plus loin avec Linux, vous pouvez par contre créer un alias qui permet de démarrer un visualisateur de fichier PDF (comme evince, par exemple) pour accéder à la documentation plus rapidement. Vous pouvez par la suite placer cet alias dans votre .bashrc, qui, lui, devra être dans la racine de votre compte Linux. Ceci est un sujet plus avancé qui ne fait pas directement partie du cours, mais qui permet d'approfondir votre connaissance de Linux tranquillement. Rien d’obligatoire sur ce point.


Problèmes


Problème 1: Contrôle d'une del par interruption


Il vous faudra reprendre le code écrit pour le problème 2 de la semaine 2. Il vous faudra effectuer des modifications pour en arriver à un contrôle par interruption plutôt que par scrutation comme vous l'avez fait la première fois, sans trop le savoir:


<avr/interrupt.h>: Interrupts


volatile uint8_t gEtat = 0; // selon le nom de votre variable



// placer le bon type de signal d'interruption

// à prendre en charge en argument

ISR ( 'modifier ici' ) {

// laisser un délai avant de confirmer la réponse du

// bouton-poussoir: environ 30 ms (anti-rebond)

_delay_ms ( 30 );

// se souvenir ici si le bouton est pressé ou relâché

'modifier ici'

// changements d'état tels que ceux de la

// semaine précédente

'modifier ici'

// Voir la note plus bas pour comprendre cette instruction et son rôle

EIFR |= (1 << INTF0) ;

}


void initialisation ( void ) {

// cli est une routine qui bloque toutes les interruptions.

// Il serait bien mauvais d'être interrompu alors que

// le microcontrôleur n'est pas prêt...

cli ();


// configurer et choisir les ports pour les entrées

// et les sorties. DDRx... Initialisez bien vos variables

'modifier ici'


// cette procédure ajuste le registre EIMSK

// de l’ATmega324PA pour permettre les interruptions externes

EIMSK |= (1 << INT0) ;


// il faut sensibiliser les interruptions externes aux

// changements de niveau du bouton-poussoir

// en ajustant le registre EICRA

EICRA |= 'modifier ici' ;


// sei permet de recevoir à nouveau des interruptions.

sei ();

}





«When an interrupt occurs, the Global Interrupt Enable I-bit is cleared and all interrupts are disabled. The user software can write logic one to the I-bit to enable nested interrupts. All enabled interrupts can then interrupt the current interrupt routine. The I-bit is automatically set when a Return from Interrupt instruction – RETI – is executed.»



«... Interrupt Flags can also be cleared by writing a logic one to the flag bit position(s) to be cleared. If an interrupt condition occurs while the corresponding interrupt enable bit is cleared, the Interrupt Flag will be set and remembered until the interrupt is enable, or the flag is cleared by software. Similarly, if one or more interrupt conditions occur while the Global Interrupt Enable bit is cleared, the corresponding Interrupt Flags(s) will be set and remembered until the global interrupt enable bit is set, and will then be executed by order of priority.»


«When the AVR exits from an interrupt, it will always return to the main program and execute one more instruction before any pending interrupt is served.»



Problème 2: Jeu de réflexe avec l'utilisation d'une minuterie


Le défi est de concevoir un jeu de réflexe. Quand le microcontrôleur démarre, il attend 10 secondes et fait clignoter la lumière rouge pendant 1/10 de seconde. Quand la lumière est éteinte, le joueur doit peser sur le bouton aussitôt que possible. Si le joueur pèse sur le bouton à l'intérieur d'une seconde, la DEL devient verte. Si le joueur pèse sur le bouton passé une seconde ou ne pèse pas du tout, la lumière prend la couleur rouge. Dans un cas comme dans l'autre, la DEL conserve sa couleur indéfiniment. Il faut peser sur reset pour recommencer.


Le délai maximum d'une seconde pour permettre à l'utilisateur de peser sur le bouton sera calculé par le compteur de la minuterie qui générera une interruption après la période de temps allouée. Les autres délais (celui de 1/10 de seconde et de 10 secondes) peuvent être faits avec des fonctions de délais ou par la minuterie, comme bon vous semble. La détection du bouton-poussoir se fera également en détectant une interruption externe, tout comme pour l'exercice précédent. Voici une procédure:



volatile uint8_t gMinuterieExpiree;

volatile uint8_t gBoutonPoussoir;



ISR ( 'modifier ici' ) {

gMinuterieExpiree = 1;

}


ISR ( 'modifier ici' ) {

gBoutonPoussoir = 1;

// anti-rebond

'modifier ici'

}


Pour arriver à avoir une interruption de la minuterie («timer»), on doit ajuster des registres. On placera tout ce qu'il faut dans une routine qu'on appellera au moment opportun. Il faudra fouiller dans la documentation d’Atmel pour savoir comment le tout peut être ajusté:


void partirMinuterie ( uint16_t duree ) {

gMinuterieExpiree = 0;

// mode CTC du timer 1 avec horloge divisée par 1024

// interruption après la durée spécifiée

TCNT1 = 'modifier ici' ;

OCR1A = duree;

TCCR1A = 'modifier ici' ;

TCCR1B = 'modifier ici' ;

TCCR1C = 0;

TIMSK1 = 'modifier ici' ;

}



do {

// attendre qu'une des deux variables soit modifiée

// par une ou l'autre des interruptions.

} while ( gMinuterieExpiree == 0 && gBoutonPoussoir == 0 );


// Une interruption s'est produite. Arrêter toute

// forme d'interruption. Une seule réponse suffit.

cli ();

// Verifier la réponse

'modifier ici'


Problème 3: Le PWM de façon matérielle


Lorsque l’on voudra que les roues d’un robot avancent de façon autonome, il sera fastidieux de générer une onde PWM de la façon dont vous l'avez fait la semaine dernière (semaine 3) puisque le microcontrôleur sera occupé à faire bien d'autres choses. Il vaudra mieux utiliser les ressources internes pour arriver au même but.


Le timer1 du ATmega324PA possède un mode de fonctionnement qui permet de générer 2 signaux et de les faire sortir directement sur des broches du port D. Il vous faudra peut-être ajuster votre matériel dans SimulIDE pour utiliser les bons ports dans les branchements vers le pont-en-H. On utilisera donc cette minuterie, mais cette fois, il n'y a pas lieu de générer d'interruption. Les moteurs peuvent fonctionner tout seuls sans autre assistance. Plus tard dans le projet, il faudra ajuster la vitesse des roues pour arriver à tourner, mais ce détail a peu d'intérêt pour l'instant. Par contre, tout comme la semaine passée, il vous faudra donner un signal de direction au circuit du pont en H. Le seul détail qui change par rapport à la semaine précédente est la fréquence du signal PWM. On se contentera d'une seule fréquence (donnée plus bas), mais on continuera de générer des signaux PWM de 0%, 25%, 50% et 75% et 100% pour des durées de 2 secondes chacun. Le rapport a/b du PWM sera réglé en passant correctement un ou des arguments de la fonction. Ces arguments permettront d'ajuster certains registres spéciaux.



void ajustementPwm ( 'modifier ici' ) {

// mise à un des sorties OC1A et OC1B sur comparaison

// réussie en mode PWM 8 bits, phase correcte

// et valeur de TOP fixe à 0xFF (mode #1 de la table 16-5

// page 130 de la description technique du ATmega324PA)

OCR1A = 'modifier ici' ;

OCR1B = 'modifier ici' ;


// division d'horloge par 8 - implique une fréquence de PWM fixe

TCCR1A = 'modifier ici' ;

TCCR1B = 'modifier ici' ;

TCCR1C = 0;

}



Ressources supplémentaires


Le document de Philippe Proulx devrait déjà vous être familier à ce point-ci, mais il n’est jamais trop tard pour commencer à le consulter, surtout pour ce travail pratique. Le site MaxEmbbeded est une ressource de premier choix également, en particulier sa section sur les minuteries!


Le saviez-vous (rappel!) ?...


L’éditeur VS Code peut être contrôlé très rapidement à l’aide de combinaisons de touches au clavier sans avoir à utiliser la souri et les menus. Certains programmeurs aiment en avoir une liste sous forme de fiche pour une consultation rapide.


La qualité du code


Opérer si près des bits et des registres renforce l’importance de règles de la qualité du code… tout en plaçant des bémols sur certaines! La règle 10 prévoit d’utiliser l’opérateur :: pour référer aux variables globales. Une plus vieille est d’utiliser un g en préfix du nom de la variable (pour globale) pour bien marquer cette particularité. Cette plus vieille façon de faire est plus proche d’un style langage C pur alors que celle de la règle 10 est beaucoup plus dans le style C++. Souvent, en embarqué, on risque de travailler avec de plus vieilles librairies qui sont plus proches du langage C. Certaines respecteront très peu les règles de qualité du code, purement et simplement...


Même sans l’utilisation de l’orienté objet dans ce travail pratique, on remarque tout de même l’importance de la règle 19 prend de l’importance avec la configuration du matériel.


Les règles 44 à 46 touchent vraiment les sujets discutés ici. Par contre, on se trouve exactement dans la situation où on n’a pas le choix d’utiliser les variables et fonctions globales. Par contre, dans plusieurs situations à venir dans le cours, on sera tenté d’utiliser les variables globales pour d’autres usages que de gérer une routine d’interruption. Il faudra éviter de succomber à cette tentation!


Les règles 61 et 62 sont intéressantes, mais aussi assez intuitives. La règle 60 est cependant capitale pour ce qu’on fait dans ce travail pratique. Il faut éviter les chiffres qui ne signifient rien à la lecture et qu’il faut définir nous-mêmes ou, encore mieux, utiliser les valeurs déjà définies pour nous dans AVRLibC. Un exemple expose mieux ce point:


// mauvais, difficile à relire et à «décortiquer» :

REGISTER = 0x1A;


// imparfait, mais déjà mieux…

REGISTER = 1 << 4 | 1 << 3 | 1 << 1;


// Le mieux qu’on puisse faire:

// périphérique ABC en mode DEF avec interruptions GHI, page JKL,

REGISTER = 1 << ONSET | 1 << THISON | 1 << ENABLE;


La librairie AVLibC définit souvent pour nous ces constantes et registres dans un fichier .h quelque part (souvent io.h). Il suffit de les utiliser! Aussi, remarquez la présence du commentaire où sa précision et son explication facilitent la lecture du code.


On veut minimiser l’utilisation de commentaires, surtout pour les choses banales qu’on comprend assez facilement en lisant le code. Par contre, avec l’assignation de registres où la situation devient rapidement obscure, de bons commentaires sont nécessaires. Les règles 85 et 86 sont à relire même si elles sont relativement faciles à suivre.


Suivi logiciel et Git


Le code développé pour résoudre ces trois problèmes devra être placé dans votre entrepôt Git. Avec 3 problèmes et les possibilités plus grandes d’explorer aussi par vous-mêmes beaucoup de variantes possibles dans les modes de fonctionnement, soumettre une révision (commit) dès qu’un bout de code fonctionne prend de l’importance. On peut ainsi beaucoup plus facilement partager le code dans l’équipe de deux et aussi partager la compréhension associée des concepts impliqués. Les noms des fichiers et les répertoires où ils doivent être placés sont laissés à votre discrétion. Vous aurez absolument besoin de ce code plus tard dans la session. De plus, la matière étudiée ici est sujette à une évaluation lors du quiz qui arrivera prochainement. Elle en constituera même la matière principale.