II2 : microcontrôleur

publicité
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
Téléchargement