II2 : microcontrôleur 7) Appels de fonctions, pile Lors de l'écriture d'un programme informatique, même très simple, il est courant d'utiliser des fonctions. Cela a plusieurs intérêts, le principal étant de structurer l'application. main(){ .... .... foo(); a=2; ....; } void foo(void){ b=3; .... return; } Dans l'exemple, la fonction foo()est appelée depuis le programme main(). Le diagramme suivant indique, au cours du temps, quelle fonction (du main ou de foo) est en cours d'exécution par le microcontrôleur. foo() main() main() t Or, si il est clair dans le main() que le microcontrôleur doit aller exécuter la fonction foo(). Il n'est indiqué nulle part dans la fonction foo() où doit se poursuivre l'exécution une fois que la dernière instruction de cette fonction a été exécutée ! Qui plus est comme une fonction peut être appelée de nombreuses fois depuis différents endroits, il ne pourrait pas en être autrement. IL EST NECESSAIRE DE MEMORISER L'ADRESSE DE RETOUR DE FONCTION Lorsque le main appelle la fonction foo() , le microcontrôleur sauvegarde dans la PILE (Stack en anglais, voir chapitre « organisation mémoire ») l'adresse de la première instruction à exécuter APRES la fin d'exécution de la fonction appelée foo(). Ce mécanisme est gérée par le couple d'instruction CALL/RET (appel et retour de sous II2/Systèmes embarqués à microprocesseurs 1 programme et utilise la pile (registre poiteur de pile SP) pour sauvegarder l'adresse de retour. Sur les microcontroleurs AVR il y a 3 variantes de l'instruction CALL (RCALL, CALL, ICALL) mais cela ne change en rien ce mécanisme. L'exemple initial a été compilé, les lignes de ...... représentent des instructions inutiles pour la compréhension du mécanisme d'appel. La fonction foo débute à l'adresse 0x00000071 en mémoire programme (Flash) 10: void main (void){ ......... 12: foo(); +0069: 940E0071 CALL 0x0071 13: a=2; +006B: E082 LDI R24,0x02 +006C: 93800101 STS 0x0101,R24 .......................................... @0071: foo 16: void foo(){ ....................................... 18: b=3; +0075: E083 LDI R24,0x03 +0076: 93800100 STS 0x0100,R24 ......................... +007A: 9508 RET Call subroutine Load immediate Store direct to data space Load immediate Store direct to data space Principe d'un appel/retour de fonction (voir schéma) : mémorisation de l'adresse de retour dans la pile (STACK) – – – – PC = 0x00000069 : le microcontrôleur arrive a l'exécution de CALL 0x00000071 (appel de la fonction foo(). – Avant d'avoir entièrement décodé cette instruction, PC = 0x0000006B (en l'absence d'appel de fonction l'instruction suivante exécutée aurait du être LDI R24,0x02, située à l'adresse 0x00000071) Le microcontrôleur exécute l'instruction CALL: – Il mémorise dans la pile (le pointeur de pile est décrémenté ) l'adresse de LDI R24,0x02 (0x0000 006B) pour pouvoir exécuter cette instruction une fois foo() finie – Il charge PC avec l'adresse de début de foo 0x0000 0071 PC = 0x0000 0071, le microcontrôleur exécute les instructions de foo() jusqu'à RET (instruction assembleur de retour de fonction). – Le sommet de la pile contient l'adresse de retour (0x0000 006B, adresse de l'instruction qui doit être exécutée à la fin de l'appel de fonctionnement – Le microcontrôleur charge PC avec l'adresse contenue en haut de la pile (0x0000 006B) PC =0x0000 006B, l'exécution reprend normalement (LDI R24,0x03) Illustration 1: Instructions xCALL, RET II2/Systèmes embarqués à microprocesseurs 2 II2/Systèmes embarqués à microprocesseurs 3 8) Interruptions Une des intérêts des composants de type microcontrôleurs (en particulier la famille AVR) réside dans le nombre et la variété des périphériques intégrés. Ainsi un programme peut interagir avec des processus physiques en fonctions des événements qui surviennent : - attendre qu'un niveau logique passe à 1, passe à 0, change d'état - attendre qu'un caractère soit transmis - attendre qu'une tension dépasse un certain seuil - attendre qu'un timer déborde - etc..... par exemple la boucle suivante attend le passage à l'état haut de la pin PD2 de l'ATMéga16. do{ }while ((PIND&0x04) ==0x00); // attente que PIND2 soit au // // niveau logique 1 {Faire ce qu'il y a à faire maintenant que PD2 est passé à l'état haut} On appelle ce principe le « Polling » (ou scrutation) : le processeur scrute en permanence l'état de la pin PD2. Outre que le processeur consomme de l'énergie à scruter ce port (qcq mA ), il est difficile d'envisager la scrutation de 2, 3 ou 4 périphériques simultanément. Un mécanisme plus efficace est de permettre aux périphériques de signaler au microcontrôleur qu'un événement à eu lieu, charge a celui ci de traiter cet événement (ou de l'ignorer) . Ce mécanisme s'appelle le mode d'INTERRUPTIONS. Principe de fonctionnement : – – – – – Le microcontrôleur exécute son programme principal (éventuellement le microcontrôleur est en mode sommeil) Un événement (matériel) survient et provoque une demande d'interruption du programme principal (éventuellement réveille le uC). L'instruction du programme principal en cours d'exécution se termine Le programme d'interruption s'exécute. le programme principale reprend son exécution normale On reconnaîtra un fonctionnement similaire à un appel de fonction SAUF que c'est un événement matériel qui déclenche l'exécution du programme d'interruption. Pour fonctionner en mode d'interruptions plusieurs conditions doivent être réunies : – – – Le périphérique doit être configuré pour pouvoir générer une demande d'interruption La prise en compte par le microcontrôleur de cette demande à du être autorisée auparavant. Il doit y avoir une fonction d'interruption associée à cette interruption. II2/Systèmes embarqués à microprocesseurs 4 L'association interruption/fonction d'interruption se fait par l'intermédiaire de la Table de vecteurs d'interruptions qui est placée en tout début de mémoire programme (0x0000) Exemples: – si un événement sur la broche INT0 (par exemple un front montant) survient, l'interruption INT0 (préalablement autorisée) va interrompre le programme principal. Et l'instruction placée à l'adresse 0x0002 (vecteur de la table d'interruption) va s'exécuter. Cette instruction est général un SAUT vers le code de la fonction d'interruption. – Si le timer 0 déborde (TCNT0>255), c'est l'interruption TIMER0 OVF qui se déclenche avec II2/Systèmes embarqués à microprocesseurs 5 comme conséquence l'exécution de l'instruction placée à l'adresse 0x0012. A la fin de l'execution de la fonction d'interruption, le programme principal reprend son cours d'exécution (l'adresse avait été stockée dans la pile lors de l'interruption et restaurée lors du retour d'interruption par l'instruction assembleur RETI) 8.2) Interruptions externes INT0, INT1, INT2 3 entrées d'interruptions externes INT0, INT1, INT2 sont disponibles sur l'ATMega16. Elles partagent les broches avec PD2/3 et PB2. Pour les utiliser comme interruption externes, il est donc nécessaire de configurer ces bits en entrée..... Mode d'interruptions INT0/INT1 Ces 2 interruptions peuvent survenir, sur front montant ou descendant la broche d'entrée ou sur niveau Bas ou sur changement de niveau (front montant ou descendant donc). Cette configuration se fait par les 4 bits de poids faible du registre MCUCR (MCU Control Register) appelées ISC11 à ISC00 (Interrupt Sense Control) La table suivante présente la configuration de ces bits pour l'entrée INT0, le principe est identique pour INT1 et les bits ISC11/ISC10 L'interruption externe INT2 ne supporte que les interruptions sur front (montant ou descendant). Autorisation des Interruptions Externes INT2/INT1/INT0 II2/Systèmes embarqués à microprocesseurs 6 Pour qu'une demande d'interruption externe provenant de INT2/INT1/INT0 puisse être prise en compte par le microcontrôleur, il faut s'assurer que : – L'autorisation générale des interruptions soit active (Bit I du registre de statut SREG positionné – à 1) Les sources d'interruptions INT2/INT1/INT0 soiten individuellement autorisées à interrompre le microcontrôleur (bits INT1 INT0 ou INT2 respectivement positionné à 1). Vecteur d'interruption Enfin et en dernier lieu, il faut indiquer au compilateur quelle fonction d'interruption associer à l'interruption. L'exemple suivant permet de définir la routine d'interruption pour l'interruption externe INT0 : #include <avr/io.h> #include <avr/interrupt.h> unsigned char i=0; ISR (INT0_vect){ i++ ; } int main(void){ DDRD =0x00; // PORTD (PD2=INT0) en entrée MCUCR =0x02; // INT0 sur front descendant GICR =0x40 ; //INT0 autorisée sei(); // validation globale des IT do{ // attente de l'interruption }while(1); return 0; } II2/Systèmes embarqués à microprocesseurs 7 II2/Systèmes embarqués à microprocesseurs 8 Exemple Interruption INT0 sur front descendant de PD2 +0000: 940C002A +0002: 940C0049 +0004: 940C0047 ............ +0028: 940C0047 JMP JMP JMP 0x0000002A 0x00000049 0x00000047 Jump Jump INT0_VECT Jump JMP 0x00000047 Jump +0043: +0045: +0047: CALL JMP JMP 0x00000060 0x00000071 0x00000000 Call Main() Jump Jump Reset vector 940E0060 940C0071 940C0000 @00000049: __vector_1---- it0.c ------------------------6: ISR (INT0_vect){ +0049: 921F PUSH R1 Push register on stack +004A: 920F PUSH R0 Push register on stack +004B: B60F IN R0,0x3F In from I/O location +004C: 920F PUSH R0 Push register on stack +004D: 2411 CLR R1 Clear Register +004E: 938F PUSH R24 Push register on stack +004F: 93CF PUSH R28 Push register on stack +0050: 93DF PUSH R29 Push register on stack +0051: B7CD IN R28,0x3D In from I/O location +0052: B7DE IN R29,0x3E In from I/O location 7: i++ ; +0053: 91800060 LDS R24,0x0060 Load direct from data space +0055: 5F8F SUBI R24,0xFF Subtract immediate +0056: 93800060 STS 0x0060,R24 Store direct to data space +0058: 91DF POP R29 Pop register from stack +0059: 91CF POP R28 Pop register from stack +005A: 918F POP R24 Pop register from stack +005B: 900F POP R0 Pop register from stack +005C: BE0F OUT 0x3F,R0 Out to I/O location +005D: 900F POP R0 Pop register from stack +005E: 901F POP R1 Pop register from stack +005F: 9518 RETI Interrupt return @0060: main 10: int main(void){ +0060: 93CF PUSH R28 +0061: 93DF PUSH R29 +0062: B7CD IN R28,0x3D +0063: B7DE IN R29,0x3E 11: DDRD=0x00; // INT0 en entrée +0064: E3E1 LDI R30,0x31 +0065: E0F0 LDI R31,0x00 +0066: 8210 STD Z+0,R1 12: MCUCR =0x02; // INT0 sur front descendant +0067: E5E5 LDI R30,0x55 Load immediate +0068: E0F0 LDI R31,0x00 Load immediate +0069: E082 LDI R24,0x02 Load immediate +006A: 8380 STD Z+0,R24 Store indirect with displacement 13: GICR=0x40 ; //INT0 autorisée +006B: E5EB LDI R30,0x5B Load immediate +006C: E0F0 LDI R31,0x00 Load immediate +006D: E480 LDI R24,0x40 Load immediate +006E: 8380 STD Z+0,R24 Store indirect with displacement 14: sei(); // validation globale des IT +006F: 9478 SEI Global Interrupt Enable 18: }while(1); +0070: CFFF RJMP PC-0x0000 Relative jump II2/Systèmes embarqués à microprocesseurs 9 II2/Systèmes embarqués à microprocesseurs 10 II2/Systèmes embarqués à microprocesseurs 11 8.3) Interruptions TIMER Les timers permettent de générer des interruptions périodiques. Pour cela, et de manière similaire aux interruptions externes, il faut autoriser les interruptions TIMER (bit mis à '1' en provenance des timers à l'aide du registre TIMSK (TimerInterrupt MaSK register). Le Timer 0 peut générer 2 interruptions : – Timer0 Overflow (vecteur TIMER0 OVF à l'adresse programme 0x0012) : génère l'interruption TOV0 quand TCNT0 passe de 0xFF à 0x00 (bit OVF0 est positionné à '1' en même temps). – Timer0 Output Compare (Vecteur TIMER0 COMP à l'adresse programme 0x0026) : généere l'interruption correspondante quand le TNCT0==OCR0 (le bit OCR0 est positionné à '1' en meme temps) Le Timer 1 peut générer quant a lui 4 sources interruptions (overflow, compare *2, input) , ces 4 interruption sont à autoriser par les bits TICIE1, OCIE1A, OCIE1B, TOIE1 de TIMSK II2/Systèmes embarqués à microprocesseurs 12