TP interruptions sur microcontrôleur 8 bits

É. Carry, J.-M Friedt

Questions sur ce TP :

  1. Quel est le rôle d’une interruption matérielle ? Donner un exemple d’utilisation d’une interruption timer.

  2. Donner 3 rôles d’un compteur dans un microcontrôleur. On pourra penser à un mode de commande de vitesse de rotation de moteurs.

  3. Pourquoi utiliser une interruption pour recevoir les données sur un port de communication ?

  4. À quoi sert un chien de garde ?

  5. Quelle est la conséquence d’une erreur de 20% sur la vitesse d’horloge dans une communication asynchrone ?

  6. Combien de signaux sont nécessaires à une liaison compatible RS232 ?

Généralités sur les interruptions : cas du GPIO

Au lieu de continuellement sonder l’état des broches du microcontrôleur pour détecter un changement d’état (au risque d’induire une latence excessive si le programme est chargé d’autres opérations de calcul entre deux lectures du port), il est souhaitable d’utiliser les interruptions. Dans ce cas, le programme principal est configuré pour exécuter séquentiellement ses opérations de calcul, et un changement d’état sur une broche de GPIO induit une interruption de l’éxécution séquentielle du programme pour sauter dans le gestionnaire d’interruption (Interrupt Service Routine – ISR). Le traitement du changement d’état du GPIO n’est donc pas explicitement traité dans le programme principal.

Extrait du schéma de l’Olimexino-32U4

Identifier sur le schéma 1 la broche associée à INT2, l’interruption matérielle externe 2.

Afin de ne pas empiler les interruptions, il est cependant judicieux d’y confiner un minimum de code – généralement définir un drapeau (flag) et retourner au plus vite dans le programme principal qui traitera le problème en temps voulu. Seul le code qui doit être impérativement exécuté immédiatement à la détection de l’interruption (e.g. “couper les moteurs” lorsque l’évènement “risque de collision” est détecté) se trouve dans l’ISR, et les conséquences plus longues du traitement (e.g. “trouver une trajectoire de sortie”) sont prises en compte dans le programme principal.

Un exemple de gestion d’interruption sur GPIO est proposé dans le code [irq0].

// avr-gcc -mmcu=atmega32u4 -Os  -Wall  -o gpio_int.out gpio_int.c 
// TODO : relier un fil entre GND et D0 (PD2) pour allumer/eteindre la diode orange

#define F_CPU 16000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>  // _delay_ms
 
ISR(INT2_vect)
{PORTB^=1<<PORTB5;
 _delay_ms(100);         // software debounce
}	
 
int main(void)
{ MCUCR &=~(1<<PUD);
  DDRD  &= ~(1<<PORTD2); //int2 sur PD2 qui est note' D0
  PORTD |= (1<<PORTD2);  //pullup on, cf doc p62 

  DDRB |=(1<<PORTB5);
  DDRE |=(1<<PORTE6);
  PORTB &= ~(1<<PORTB5);
  PORTE &= ~(1<<PORTE6);

// vv NECESSAIRE SI ON VEUT UTILISER PD0 (sinon le FET d'uext est desactif et GPIO est en pull down)
// DDRB|=1<<PB4;                   // UEXT powered (FET)
// PORTB&=~1<<PB4;
 
  EIMSK = 1<<INT2;	//enable int2
  USBCON=0;	        // desactive l'interruption sur USB (activ'ee par bootloader)
  sei();		//enable interrupts
  while(1) {PORTE^=1<<PORTE6;_delay_ms(200);} 
}

Pour ce faire, on se rappellera que l’interruption est appelée continuellement tant que la broche liée au signal est maintenue au niveau bas. Afin de cesser l’appel intempestif au gestionnaire d’interruption (ISR), on pourra y placer l’ordre d’ignorer les interruption (cli(); pour clear interrupt) et réactiver les interruptions (sei();) une fois le drapeau traité dans le programme principal.

L’attribution des vecteurs d’interruption, qui définissent l’emplacement mémoire à laquelle le pointeur de programme saute pour trouver l’instruction à exécuter lors de l’interruption (saut à l’ISR), se retrouve en désassemblant le binaire compilé et lié aux fonctions d’initialisation fournies par gcc (Fig. 2).

Gauche : extrait de la datasheet explicitant les vecteurs d’interruption. Droite : code déassemblé par avr-objdump.
Gauche : extrait de la datasheet explicitant les vecteurs d’interruption. Droite : code déassemblé par avr-objdump.
  1. Valider que l’interruption INT2 a été assignée à la bonne fonction en désassemblant l’exécutable par avr-objdump -dSt executable. Que se passe-t-il si une interruption non-initialisée est sollicitée ?

  2. Pourquoi ne trouve-t-on pas ce résultat en observant le code assembleur généré lors de avr-gcc -S du code source ?

  3. Observer le code désassemblé si le lien avec le fichier de démarrage est désactivé par -nostartfiles.

  4. Pour le moment le déclenchement de l’interruption se fait sur un niveau. Modifier le code pour déclencher l’interruption sur un front – transition d’état de bas vers haut par exemple.

  5. La solution de sonder périodiquement un périphérique, que ce soit le bouton poussoir pour changer la direction de défilement ou pour définir la transition d’un état à l’autre par timer tel que nous l’avons vu dans le TP précédent (chenillard), est peu élégante. Ces périphériques sont tous associés à des évènements déclenchés de façon asynchrone lorsqu’un évènement se déclenche en vue d’interrompre l’exécution séquentielle du programme principal, agir en conséquence de l’évènement, et reprendre l’exécution du programme principal où il avait été interrompu.

  6. En reprenant le montage du chenillard, identifier l’interruption associée à la broche à laquelle est connectée le bouton poussoir. Remplacer le test périodique de l’état du bouton poussoir par le gestionnaire d’interrpution associée.

  7. Remplacer le test sur l’état du timer vu dans le TP précédent (chenillard) par le déclenchement périodique d’une interruption induisant la transition d’état.

À l’issue de ces optimisations du programme, la boucle principale doit être vide. Elle est donc disponible pour des opérations utiles telles que calcul d’une boucle d’asservissement, loi de commande ou interaction avec l’utilisateur.

Interruptions timer

Overflow

Dans le mode overflow, le compteur 0 codé sur 8 bits va de 0 à 255, le compteur 1 codé sur 16 bits de 0 à 65535 (Fig. 5).

Configuration de l’horloge cadençant le timer1p.133
Configuration de l’horloge cadençant le timer1p.133

Configuration de l’horloge cadençant le timer1p.133
Configuration de l’horloge cadençant le timer1

Sachant que la fréquence du quartz qui cadence le microcontrôleur est 16 MHz, quelle est la fréquence de clignotement de la LED ?

Même question avec le timer0 : pourquoi avons nous ajouté la condition sur c pour commuter l’état de la diode ? Quelle condition sur c faudrait-il ajouter pour que la diode commute à 0,5 Hz ?

#define F_CPU 16000000UL

#include <avr/io.h>
#include <stdbool.h>
#include <avr/interrupt.h>

volatile int c=0;

ISR(TIMER1_OVF_vect) {PORTE^=1<<PORTE6;}
ISR(TIMER0_OVF_vect) {c++; if (c==5) {PORTB^=1<<PORTB5;c=0;}}

void timer_init()
{TCCR1B = (1 <<CS11)|(1<<CS10); 
 TIMSK1=(1<<TOIE1);               // diode verte
 
 TCCR0B = (1 <<CS02)|(1<<CS00);
 TIMSK0=(1<<TOIE0) ;
 sei();
}

int main( void ) {
  DDRB |=1<<PORTB5;
  DDRE |=1<<PORTE6;
  PORTB |= 1<<PORTB5;
  PORTE &=~1<<PORTE6;

  USBCON=0; // desactivation interrupt sur USB
 
  timer_init();
  while(1) {}
}

Output Compare

Nous avions vu que le timer peut se configurer pour ne pas atteindre sa limite de stockage mais pour s’arrêter en cours de décompte (Output Compare). Ce mode de fonctionnement est compatible avec les interruptions tel que illustré dans le code [toc]

#include <avr/io.h>
#include <avr/power.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>

#define F_CPU 16000000UL

#define delai_leds (16000*2)

#define avec_interruption

ISR (TIMER1_COMPA_vect) {PORTB^=1<<PORTB5;PORTE^=1<<PORTE6;}

void timer1_init()  // Timer1 avec prescaler=64 et CTC
{
 TCCR1A=0;
 TCCR1B= (1 << WGM12)|(1 << CS11)|(1 << CS10);
 TCNT1=0;
 OCR1A = delai_leds;      // valeur seuil <- delai
#ifdef avec_interruption
 TIMSK1= (1 << OCIE1A);   // active interruption
 sei();
#endif
}

int main(void)
{
#ifdef avec_interruption
  USBCON=0;   // https://github.com/pololu/a-star/issues/3
#endif

  DDRB |=1<<PORTB5;
  DDRE |=1<<PORTE6;
  PORTB |= 1<<PORTB5;
  PORTE &= ~(1<<PORTE6);

  timer1_init();
  while(1) {
#ifndef avec_interruption
       if (TIFR1 & (1 << OCF1A)) 
          {PORTB^=1<<PORTB5;PORTE^=1<<PORTE6;}
       TIFR1 |= (1 << OCF1A);
#endif
  }
}

Reprendre le programme du chenillard du premier TP et le modifier pour que la boucle infinie de la fonction principale soit vide, avec l’évolution du motif des LEDs calculé exclusivement dans le gestionnaire d’interruption.

Input Capture

Les timers ont jusqu’ici été exploités en sortie, i.e. pour générer un signal. Ils peuvent aussi être utilisés pour enregistrer l’état du compteur lors d’une transition d’état, et ainsi mesurer finement un intervalle de temps ou une fréquence. Il s’agit du mode input capture.

Nous profitons de l’opportunité de devoir générer un signal périodique pour tester le mode PWM (Pulse Width Modulation). La PWM est un mode classique de commande dans lequel la période des impulsions est constante mais la largeur des impulsions varie et encode l’information transmise. Il s’agit d’un mode couramment supporté par les timers puisqu’utilisés par exemple en commande de servo moteur (de modélisme notamment) ou pour générer une tension (en l’absence de DAC, un filtre passe-bas derrière une PWM fait office de sortie de tension continue commandée numériquement).

Observer à l’oscilloscope la sortie D9. Justifier la période observée par rapport à la valeur programmée dans ICR1.

Connecter D13 (Input Capture du timer 3) à D9 (PWM liée au timer 1). Observer le décompte.

#define F_CPU 16000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>  // _delay_ms

#undef rs  // affichage sur RS232 (#define) ou sur USB (#undef)

volatile unsigned short res;

#ifdef rs
#define USART_BAUDRATE 9600

void transmit_data(uint8_t data)
{while ( !( UCSR1A & (1<<UDRE1)) );
 UDR1 = data;
}

void usart_setup()              // initialisation UART
{unsigned short baud;
 baud  = (((( F_CPU / ( USART_BAUDRATE * 16UL))) - 1));
 DDRD  |= 0x18;
 UBRR1H = (unsigned char)(baud>>8);
 UBRR1L = (unsigned char)baud;
		
 UCSR1C = (1<<UCSZ11) | (1<<UCSZ10); //Set data frame format: asynchronous mode,no parity, 1 stop bit, 8 bit size
 UCSR1B = (1<<RXEN1 ) | (1<<TXEN1 ); //Enable Transmitter and Receiver
}
#else

#include "VirtualSerial.h"

extern USB_ClassInfo_CDC_Device_t VirtualSerial_CDC_Interface;
extern FILE USBSerialStream;

void transmit_data(uint8_t data)
{char buffer[2];
 buffer[0]=data;buffer[1]=0;
 fputs(buffer, &USBSerialStream);

}
#endif

void write_char(unsigned char c)
{if ((c>>4)>9) transmit_data((c>>4)-10+'A'); else transmit_data((c>>4)+'0');
 if ((c&0x0f)>9) transmit_data((c&0x0f)-10+'A'); else transmit_data((c&0x0f)+'0');
}

void write_short(unsigned short s)
{write_char(s>>8);
 write_char(s&0xff);
}

void myicp_setup() // initialisation timer3
{TCCR3A = 1<<WGM31 | 1<<WGM30;
 TCCR3B = 1<<ICES3 | 1<<WGM33 | 1<<CS31;
 OCR3A  = 32000;
 TIMSK3 = 1<<ICIE3;
 TIFR3  = 1<<ICF3;
}

ISR(TIMER3_CAPT_vect)
{res  = ICR3;
 TCNT3 = 0;
}

// http://blog.saikoled.com/post/43165849837/secret-konami-cheat-code-to-high-resolution-pwm-on-your
void mypwm_setup()  {
  DDRB |= (1<<7)|(1<<6)|(1<<5); // B5/OC1A, B6/OC1B, B7/OC1C 

  // TCCR1A is the Timer/Counter 1 Control Register A.
  // TCCR1A[7:6] = COM1A[1:0] (Mode for Channel A)
  // TCCR1A[5:4] = COM1B[1:0] (Mode for Channel B)
  // TCCR1A[3:2] = COM1C[1:0] (Mode for Channel C)
  // COM1x[1:0] = 0b10 (Clear on match. Set at TOP)

  // Clock Prescaler Stuff
  // TCCR1B[2:0] = CS1[2:0] (Clock Select for Timer 1)
  // CS1[2:0] = 0b001 (No Prescaler)

  // Waveform Generation Mode Setup Stuff

  // TCCR1A[1:0] = WGM1[1:0] (WGM for Timer 1 LSB)
  // TCCR1B[4:3] = WGM1[3:2] (WGM for Timer 1 MSB)
  // WGM[3:0] = 0b1110 (i.e. Mode 14)
  // Mode 14 - Fast PWM, TOP from ICR1, Set OC1x at
  // TOP, clear at OCR1x.

  // ICR1 is a 16-bit register that should be written
  // with the desired value for TOP. This is the value
  // at which the PWM cycle restarts and defines the
  // resolution.

  // For 16-bit resolution, we need to program ICR1 with
  // 0xFFFF.
  ICR1 = 0x3e80;  // intervalle de repetition du crenau = 1 ms
  TCCR1A = 0xAA;
  TCCR1B = 0x19;

  OCR1A=0x3e80/2; // largeur du crenau = 1/2 ms
}

int main(void)
{
  DDRB |=1<<PORTB5;
  DDRE |=1<<PORTE6;
  PORTB |= 1<<PORTB5;
  PORTE &= ~1<<PORTE6;
  mypwm_setup();
  myicp_setup();
//  sei();
#ifdef rs
  usart_setup();
#else
  USBCON=0;
  SetupHardware();
  CDC_Device_CreateStream(&VirtualSerial_CDC_Interface, &USBSerialStream);
  GlobalInterruptEnable();
#endif
	
 while(1)
  {if (res!=0) {write_short(res); transmit_data(0x0A); transmit_data(0x0D); res=0;
  }
#ifdef rs
#else
     CDC_Device_ReceiveByte(&VirtualSerial_CDC_Interface);

     CDC_Device_USBTask(&VirtualSerial_CDC_Interface);
     USB_USBTask();
#endif
   //_delay_ms(500);
  }
}

Faire varier la période du signal de PWM (registre ICR1) : qu’observe-t-on ?

Faire varier la largeur du signal de PWM (registre OCR1A) : qu’observe-t-on ?

Le chien de garde

Un timer bien particulier garantit qu’un programme sur système embarqué ne doit pas pouvoir bloquer le système qu’il contrôle. Il s’agit d’un compteur qui ne peut être manipulé par logiciel pour d’autres fonctions qu’initialiser sa période et le réinitialiser. Si ce compteur atteint sa borne de répétition, il induit une réinitialisation du microcontrôleur (reset). Le programmeur doit prendre soin, dans sa boucle infinie, de réinitialiser le compteur pour s’assurer que cette condition n’arrive jamais (fonction wdt_reset()). Il s’agit donc d’une fonctionnalité très utile qui garantit le redémarrage du microcontrôleur qui se trouve dans une situation de bloquage (lecture d’un capteur défectueux par exemple). La solution de réinitialiser le microcontrôleur n’est certes pas toujours satisfaisante, mais toujours meilleure qu’attendre indéfiniment un évènement qui n’arrive pas et bloque le système que le microcontrôleur commande . Le nom de ce timer est le chien de garde, ou watchdog dans la terminologie anglo-saxone.

#define F_CPU 16000000UL
#include <avr/io.h>
#include <avr/wdt.h>     // voir /usr/lib/avr/include//avr/wdt.h pour les cst
#include <util/delay.h>  // _delay_ms
#include <avr/power.h>
#include <avr/interrupt.h>

#define USART_BAUDRATE 9600

void transmit_data(uint8_t data)
{while ( !( UCSR1A & (1<<UDRE1)) );
 UDR1 = data;
}

void usart_setup()              // initialisation UART
{unsigned short baud;
 baud  = (((( F_CPU / ( USART_BAUDRATE * 16UL))) - 1));
 DDRD  |= 0x18;
 UBRR1H = (unsigned char)(baud>>8);
 UBRR1L = (unsigned char)baud;
		
 UCSR1C = (1<<UCSZ11) | (1<<UCSZ10); //Set data frame format: asynchronous mode,no parity, 1 stop bit, 8 bit size
 UCSR1B = (1<<RXEN1 ) | (1<<TXEN1 ); //Enable Transmitter and Receiver
}

int main (void)
{DDRB |=1<<PORTB5;
 PORTB |= 1<<PORTB5;

 _delay_ms(1000); 

 wdt_enable(WDTO_120MS);
 usart_setup();
 USBCON=0;
 while (1)
   {wdt_reset(); // pour reinitialiser le WDT
    PORTB&=~1<<PORTB5;
    _delay_ms(20); // 20 ms ici + USB ~ 120 ms
    transmit_data('.');
   }
}

Compiler le programme ci-dessus, le flasher et sonder le port de communication par cat << /dev/ttyACM0. Que se passe-t-il ?

Modifier la valeur de seuil du watchdog (fonction wdt_enable() avec un des intervalles de temps visible dans
/usr/lib/avr/include//avr/wdt.h – par exemple WDTO_60MS. Commenter.

Communication – UART

La communication asynchrone UART (sur périphérique USART capable de supporter les transmissions synchrones ou asynchrones) est relativement lente car limitée car l’écart potentiel de vitesse entre les horloges des deux interlocuteurs qui supposent être cadencées au même rythme entre émission et réception. En pratique, selon la fréquence du quartz et sa capacité à générer le baudrate, un écart jusqu’à 10% de la vitesse nominale peut être observée (Fig. 6).

Débits de transferts accessibles en fonction des fréquences du quartz cadençant le microcontroleur, sachant que le facteur de division permettant de s’approcher au mieux du baudrate recherché est nécessairement un entier.
Débits de transferts accessibles en fonction des fréquences du quartz cadençant le microcontroleur, sachant que le facteur de division permettant de s’approcher au mieux du baudrate recherché est nécessairement un entier.

Mode polling

uextrs,Branchement de la liaison RS232 sur connecteur UEXT (en tenant le circuit vers soi).] Le port de communication asynchrone est probablement le périphérique le plus utile sur un microcontrôleur. Après avoir initialisé l’horloge qui définit le débit de données (baudrate) ainsi que la nature des transactions (nombre de bits par mot transmis, encapsulation), les caractères sont transmis dès que le registre d’émission est libre. L’universalité de ce mode de communication tient en sa simplicité : tout système numérique programmable propose ce port de communication, même sur des plateformes aussi complexes qu’un routeur wifi (http://www.rwhitby.net/projects/wrt54gs) ou une console de jeu (http://nil.rpc1.org/psp/remote.html).

Le périphérique du PC de transactions sur le port série asynchrone virtuel sur USB se nomme /dev/ttyUSB0. Afin d’afficher les messages transmis sur un port de communication, un logiciel dédié sonde l’état des ports de communication et affiche les messages qui y sont transmis : un terminal. Sous GNU/Linux, minicom est un terminal qui se lance en précisant le périphérique par minicom -D /dev/ttyUSB0 et qui se configure par CTRL-a puis o (Options) pour définir le débit de communication (baudrate). Sous MS-Windows, putty propose des fonctionnalités identiques.

Qu’affiche le programme ci-dessous ? Quelle est la durée de transmission de chaque caractère ?

 

#include <avr/io.h>      // voir /usr/lib/avr/include/avr/iom32u4.h
#define F_CPU 16000000UL
#include <util/delay.h>  // _delay_ms

#define USART_BAUDRATE (57600)

// https://github.com/tomvdb/avr_arduino_leonardo/blob/master/examples/uart/main.c
void uart_transmit( unsigned char data )
{while (!(UCSR1A&(1<<UDRE1))) ;
 UDR1 = data;
}

unsigned char uart_receive(void)
{while (!(UCSR1A&(1<<RXC1))) ;
 return UDR1;
}

int main(void){
unsigned short baud;
char c=42;  // '*'
  DDRB |=1<<PORTB5;
  DDRE |=1<<PORTE6;
  PORTB |= 1<<PORTB5;
  PORTE &= ~1<<PORTE6;

  UCSR1A = 0;                         // importantly U2X1 = 0
  UCSR1B = 0;                         
  UCSR1B = (1 << RXEN1)|(1 << TXEN1); // enable receiver and transmitter
  UCSR1C = _BV(UCSZ11) | _BV(UCSZ10); // 8N1
  // UCSR1D = 0;                         // no rtc/cts (probleme de version de libavr)
  baud  = (((( F_CPU / ( USART_BAUDRATE * 16UL))) - 1));
  UBRR1H = (unsigned char)(baud>>8);
  UBRR1L = (unsigned char)baud;

  while (1){
    PORTB^=1<<PORTB5;PORTE^=1<<PORTE6; // LED
    _delay_ms(10);  

    uart_transmit(c);                  // affiche le message
    c++; if (c==127) c=32;
  }
  return 0;
}

Proposer une fonction chargée d’afficher une chaı̂ne de caractères. Afficher la phrase “Hello World”.

Proposer un programme qui reçoit des caractères sur le port série, et renvoie sur le port série le caractère suivant de l’alphabet de celui qui a été reçu.

Passage de paramètres – par valeur et par référence

Une fonction en C reçoit son argument par valeur si nous la déclarons de la forme void fonction(int argument) . Dans ce cas, une copie de la valeur de l’argument passé à l’appel de la fonction est placée sur la pile, la fonction traite l’argument, mais la valeur initiale de l’argument n’est jamais modifiée. Si la valeur de l’argument doit être modifiée par la fonction, il faut passer l’adresse mémoire à laquelle la valeur est stockée : il s’agit d’un passage par référence.

La déclaration d’une variable short i; se traduit par l’affectation de 2 octets contigüs en mémoire pour stocker la valeur de i. L’emplacement mémoire alloué pour stocker i est indiquée par le pointeur vers i. Le pointeur vers une variable s’obtient en préfixant le nom de la variable par &. Ainsi, l’adresse à laquelle est stockée i s’obtient par &i. Le contenu de cet emplacement mémoire est indiqué en préfixant le pointeur du symbole . Nous avons donc l’équivalence entre i et (&i).

Le passage de paramètre par référence se fait donc en déclarant une fonction void fonction(int* argument) qui indique que la fonction attend un pointeur, et le passage de paramètre se fait en fournissant le pointeur vers l’argument. De cette façon, la pile qui s’occupe du passage de l’argument contient cette fois l’adresse de l’argument lors de l’appel à la fonction, et toute modification au contenu de l’adresse sera reproduit dans la fonction appelante.

Proposer une fonction qui reçoit en argument un caractère obtenu sur le port série, et le transforme en la majuscule si le caractère reçu est une lettre minuscule, et réciproquement. On notera que dans la table ASCII, les majuscules et minuscules sont séparées de 32.

Interruption USART

Afin de ne pas handicaper l’exécution séquentielle d’un programme par la lecture périodique du port de communication asynchrone, il est souhaitable d’interrompre l’exécution séquentielle lorsque l’évènement occasionnel de réception d’un caractère se produit. Cependant, nous ne voulons pas passer de temps inutilement dans l’interruption qui doit se réduire uniquement au code critique (dans ce cas recevoir le caractère). À ces fins, nous définissons un drapeau (flag) qui indique au programme principal la réception d’un caractère et qu’une action doit être prise en conséquent.

#include <avr/io.h>      // voir /usr/lib/avr/include/avr/iom32u4.h
#include <avr/interrupt.h>
#define F_CPU 16000000UL
#include <util/delay.h>  // _delay_ms

# define USART_BAUDRATE 9600

#ifdef atmega32u2
// pour simavr
#include "avr_mcu_section.h"
AVR_MCU(F_CPU, "atmega32");

const struct avr_mmcu_vcd_trace_t _mytrace[]  _MMCU_ = {
                { AVR_MCU_VCD_SYMBOL("PORTB"), .what = (void*)&PORTB, },
        };
// fin simavr

#include <stdio.h>
static int uart_putchar(char c, FILE *stream) {
  if (c == '\n')
    uart_putchar('\r', stream);
  //loop_until_bit_is_set(UCSR0A, UDRE0);
  //UDR0 = c;
  return 0;
}

static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL,
                                         _FDEV_SETUP_WRITE);
#endif

volatile char received,flag=0;

ISR(USART1_RX_vect) {received=UDR1;flag=1;}

void uart_transmit( unsigned char data )
{while (!(UCSR1A&(1<<UDRE1))) ;
 UDR1 = data;
}

int main(void){
unsigned short baud;
  DDRB |=1<<PORTB5;    // LEDs
  PORTB |= 1<<PORTB5;
#ifndef atmega32u2
  DDRE |=1<<PORTE6;
  PORTE &= ~1<<PORTE6;
#endif

  UCSR1A = 0;                           // importantly U2X1 = 0
  UCSR1B = (1 << RXEN1) | (1 << TXEN1); // enable receiver and transmitter
  UCSR1B|= (1 << RXCIE1);               // ACTIVATION INTERRUPTION RX Completed
  UCSR1C = _BV(UCSZ11) | _BV(UCSZ10);   // no parity, 8 data bits, 1 stop bit
// UCSR1D = 0;                           // no cts, no rts
  baud  = (((( F_CPU / ( USART_BAUDRATE * 16UL))) - 1));
  UBRR1H = (unsigned char)(baud>>8);
  UBRR1L = (unsigned char)baud;

  USBCON=0; // desactive l'interruption USB
  sei();

#ifndef atmega32u2
  stdout=&mystdout;
#endif 

  while (1){
    PORTB^=1<<PORTB5; 
#ifndef atmega32u2
    PORTE^=1<<PORTE6;
#endif
    _delay_ms(100);  //Attente de 500ms

    if (flag!=0) {flag=0;uart_transmit(received+1);}
  }
  return 0;
}

Justifier de la définition de variables globales pour échanger des informations entre programme principal et gestionnaire d’interruption.

Proposer une solution pour recevoir plusieurs caractères entre deux traitements du flag par le programme principal.

Conclusion

Proposer un programme qui fait clignoter une diode périodiquement, détecte une transition d’état sur un GPIO, et communique par USART, en limitant la boucle infinie dans la fonction principale main() à while (1) {};

1 Atmel, 8-bit Microcontroller with 16/32K Bytes of ISP Flash and USB Controller – ATmega16U4 & ATmega32U4, disponible à https://ww1.microchip.com/downloads/en/DeviceDoc/7799S.pdf (version 7799ES–AVR–09/2012) J. Matijevic & E. Dewell, Anomaly Recovery and the Mars Exploration Rovers, disponible à https://trs.jpl.nasa.gov/handle/2014/39897