On se trouve rapidement limité avec les possibilités de base offertes par l’Arduino. En effet, avec une boucle principale et des fonctions de pause, cela devient vite difficile de faire de gérer plusieurs choses à la fois pour faire du pseudo mutli-tâche ou réagir rapidement à des évènements. Nous allons voir différentes manières pour exécuter des fonctions toutes les x millisecondes. Ces explications vous fourniront une bonne introduction à la notion d’interruption matérielle.
Exemples d’application
Il y a plusieurs applications qui nécessite la mise en place d’un tel modèle de développement :
- Lire ou émettre une information à intervalle régulier.
- Piloter un composant en envoyant un signal régulièrement.
- Générer un signal périodique.
- Jouer une bande son sur haut-parleur (musique polyphonique, ou voix humaine).
D’ailleurs l’Arduino s’appuie sur les techniques présentées dans cet article. Par exemple, pour les fonctions delay() ou micros(). Car elles ont besoin d’attendre ou de compter les impulsions émises par une horloge. De même pour la fonction tone() qui permet de générer un signal sonore périodique en respectant une fréquence.
Utiliser cette possibilité dans vos projets
Il existe plusieurs possiblités que l’on peut résumer ainsi
- Gérer la périodicité dans la boucle (loop) principale du programme Arduino. C’est de cette manière que sont écrites les différentes bibliothèques de type task scheduler. Ce n’est pas une méthode que je préconise.
- Utiliser les timers hardware internes à l’Arduino. C’est la méthode que je préconise.
- Remplacer le bootloader Arduino par un système temps réel.
Utiliser les timers hardware internes
Explications techniques
Les cartes Arduino contiennent plusieurs timers hardware. Il s’agit de composants internes au micro-contrôleur qui interrompent régulièrement le programme en cours d’exécution et — selon les paramètres en cours dans certains registres du CPU — passent la main à une fonction périodique. À la fin de l’exécution de cette fonction, le programme principal continue son exécution à partir du point où il avait été interrompu. De cette manière on peut obtenir un code qui sera appelé à intervalle régulier. Les ordonnanceurs de tâches de nos PC se basent d’ailleurs sur ce principe pour faire croire que l’ordinateur est multitâches. En fait, mais cela devient moins vrai avec les multiprocesseurs, il n’y a pas plusieurs tâches exécutées en même temps mais des tâches qui utilisent à tour de rôle le CPU. Comme tout cela ce passe en quelques millisecondes ou microsecondes, l’utilisateur est berné.
Voici un petit schéma concernant l’Arduino et qui illustre ces notions de tâches principales et régulières ainsi que la notion d’interruption.
En lisant ce schéma on comprend qu’il faut faire preuve de prudence dans le développement de la fonction périodique. Il faut un code qui s’exécute avant la fin de l’intervalle de temps. Par exemple, si la tâche est appelée une fois par seconde, elle doit s’exécuter en moins d’une seconde. Et durant ce laps de temps d’une seconde, il faut aussi laisser du temps à la tâche principale. Le temps d’exécution d’une fonction varie d’un modèle de carte Arduino à l’autre. Bien que tous les modèles Arduino soient cadencés à 16Mhz, les différentes cartes n’exécutent pas le même nombre d’instructions dans un laps de temps donné. Voici un tableau comparatif de quelques modèles.
Modèle de carte | µ Contrôleur | Nombre d’opération / s |
MEGA | ATmega2560 | 16 MIPS |
UNO | ATmega328 | 1 MIPS |
LEONARDO | ATmega32u4 | 16 MIPS |
PRO | ATmega168 | 1 MIPS |
Mesurer le temps d’exécution d’une fonction
Si vous avez un doute sur la durée d’exécution de votre fonction périodique vous pouvez la mesurer à l’aide de la fonction micros(), comme expliqué dans le code suivant :
duration = micros(); f(); duration = micros() - duration;
Débit de la liaison série
Si un transfert vers la liaison série est exécuté dans la fonction période et que vous constatez que le temps d’exécution de la fonction est trop long, vous pouvez augmenter le débit jusqu’à 115200 bauds au lieu des 9600 bauds — valeur utilisée dans la plupart des exemples de code. À condition, bien sûr, que l’équipement de réception le supporte.
Si cela n’est pas suffisant et que la fonction périodique doit à tout prix être exécutée le plus souvent possible, vous pourriez déléguer la tâche de transmission au programme principal auquel la fonction périodique communiquera les données à transmettre par l’intermédiaire d’un buffer.
Exemple de code
Voici un exemple de code qui manipule le timer n°1 (il existe plusieurs timers et le nombre total varie en fonction de votre modèle de carte Arduino. Sachez simplement que le timer n°0 est utilisé par les fonctions contenues dans le bootloader. Ce n’est donc pas une bonne idée de l’utiliser). Dans cet exemple, nous allons faire clignoter une LED branchée sur la broche digitale n°13 deux fois par seconde. La fréquence d’appel est donc de 2Hz.
#define ledPin 13 void setup() { pinMode(ledPin, OUTPUT); // initialiser le timer1 noInterrupts(); // désactiver toutes les interruptions TCCR1A = 0; TCCR1B = 0; TCNT1 = 0; OCR1A = 31250; // 16MHz/256/2Hz TCCR1B |= (1 << WGM12); // CTC mode TCCR1B |= (1 << CS12); // 256 prescaler TIMSK1 |= (1 << OCIE1A); // Activer le mode de comparaison interrupts(); // activer toutes les interruptions
}
ISR(TIMER1_COMPA_vect) // fonction périodique { digitalWrite(ledPin, digitalRead(ledPin) ^ 1); // Basculer la LED allumée/éteinte } void loop() { // Votre application ici... }
Je vous invite à consulter la documentation officielle si vous souhaitez une explication détaillée de toutes les lignes du code. Ne vous inquiétez pas, il existe des bibliothèques qui masque cette complexité aux développeurs. Je fournissais ce code à des fins d’explication.
Bibliothèque de gestion de la périodicité
Il en existe au moins deux ; à choisir en fonction de ce que vous voulez faire :
- Timer1 : cette bibliothèque permet de définir une fonction périodique. Le code est simple et documenté sur le site Arduino.
- timer-scheduler : cette bibliothèque permet de gérer jusqu’à 16 timers virtuels (autrement dit, vous pourrez définir 16 fonctions périodiques différentes).
Autres applications et documentation
Vous trouverez plus d’explication et un tutoriel complet sur ce site, dans le cas où vous souhaiteriez créer votre propre implémentation.
Vous trouverez sur le site Arduino un exemple d’utilisation d’un haut-parleur pour jouer autre chose que des notes à fréquences fixes (autre chose que des mélodies monophoniques).
Bibliothèques de type task scheduler
Il en existe des dizaines. Leur principe est, en général, de se baser sur la boucle principale du programme et de se mettre en pause jusqu’au prochain appel. Elles sont assez simples à mettre en oeuvre et peuvent correspondre à votre besoin. Voici quelques liens vers des biblitohèques tierces :
Utiliser un système temps réel
Dernière option : vous pouvez remplacer le bootloader d’Arduino par un système d’exploitation temps réel tel que DuinOS.
DuinOS est un système d’exploitation temps réel avec un système en multitache préemptif pour les cartes Arduino utilisant des microcontrôleurs Atmel AVR. Il est développé par RobotGroup. Il est basé sur un noyau solide qui est FreeRTOS et qui est complètement open source. Ce système s’intègre dans l’environnement de développement de l’Arduino.
3 réponses sur « Exécution de fonctions Arduino périodiques »
Salut,
merci beaucoup pour la clarté de cet article, j’aimerai savoir est ce qu’il est possible de réaliser une attente via la fonction delay() dans une fonction d’interruption
Bonjour, merci bcp pour cet article clair.
Je cherche actuellement à obtenir de l’information sur la programmation sous Duinos ou tout autre système multi-tâches en temps réel car le projet que nous avons actuellement nécessite le fonctionnement simultané de 3 programmes, où chaque micro-seconde a son importance. Auriez-vous svp des sites qui expliquent en détail la façon de programmer des systèmes multi-tâches ?
Merci d’avance pour votre aide précieuse.
le code proposé ne fonctionne pas si on est obligé d’arrêter l’interruption avec noInterrupt(); cette fonction n’arrête pas l’interruption du timer configuré comme présentée.
et il ne fonctionne pas dans mon cas si je ne reset pas le flag :
TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt
code avec arrêt pris dans TimerOne.cpp
ici, je ne désactive pas les interruptions mais stop seulement le timer pendant l'exécution d'une tache trop longue (envoi sur port série) sinon ça plante!
#define ledPin 13
void setup()
{
pinMode(ledPin, OUTPUT);
// initialize timer1
noInterrupts(); // disable all interrupts
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;
OCR1A = 31250; // compare match register 16MHz/256/2Hz
TCCR1B |= (1 << WGM12); // CTC mode
TCCR1B |= (1 << CS12);
TCCR1B |= (1 << CS10); // 1024 prescaler
TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt
interrupts(); // enable all interrupts
}
//!!!!!!! interrupt(); et noInterrupt ne fonctionne pas ds ce cas
void Timer1Stop()
{
TCCR1B &= ~(_BV(CS10) | _BV(CS11) | _BV(CS12)); // clears all clock selects bits
}
void Timer1Start()
{
TCCR1B |= (1 << CS12);
TCCR1B |= (1 << CS10); // 1024 prescaler // set all clock selects bits
}
ISR(TIMER1_COMPA_vect) // timer compare interrupt service routine
{
digitalWrite(ledPin, digitalRead(ledPin) ^ 1); // toggle LED pin
TIMSK1 = _BV(TOIE1); // sets the timer overflow interrupt enable bit
}
void loop()
{
// your program here…
}