Est-il possible que plusieurs parties du programme sexécutent ensemble sans faire plusieurs choses dans le même bloc de code?
Une thread en attente dun périphérique externe tout en faisant clignoter une LED dans un autre thread.
Commentaires
- Vous devriez probablement dabord vous demander si vous avez vraiment besoin de threads. Les minuteries peuvent déjà convenir à vos besoins et elles sont prises en charge nativement sur Arduino.
- Vous pouvez également consulter Uzebox. Cest ‘ une console de jeux vidéo homebrew à deux puces. Ainsi, bien quil ne soit ‘ pas exactement un Arduino, tout le système est construit sur des interruptions. Ainsi, laudio, la vidéo, les commandes, etc. sont tous alimentés par des interruptions alors que le programme principal na ‘ à sinquiéter de rien. Peut être une bonne référence.
Réponse
Il ny a pas de support multi-processus, ni multi-threading sur lArduino. Vous pouvez faire quelque chose de proche de plusieurs threads avec certains logiciels.
Vous voulez regarder les Protothreads :
Les protothreads sont des threads sans pile extrêmement légers conçus pour les systèmes à forte contrainte de mémoire, tels que les petits systèmes embarqués ou les nœuds de réseau de capteurs sans fil. Les protothreads fournissent une exécution de code linéaire pour les systèmes événementiels implémentés dans C. Les protothreads peuvent être utilisées avec ou sans système dexploitation sous-jacent pour fournir des gestionnaires dévénements bloquants. Les protothreads fournissent un flux de contrôle séquentiel sans machines à états complexes ni multi-threading complet.
Bien sûr, il existe un exemple Arduino ici avec exemple de code . Cette question SO pourrait également être utile.
ArduinoThread est un bon aussi.
Commentaires
- Notez que lArduino DUE a une exception à cela, avec plusieurs boucles de contrôle: arduino.cc/en/Tutorial/MultipleBlinks
Réponse
Les Arduino basés sur AVR ne prennent pas en charge le filetage (matériel), je ne suis pas familier avec les Arduino basés sur ARM. Une façon de contourner cette limitation est lutilisation dinterruptions, en particulier les interruptions chronométrées. Vous pouvez programmer une minuterie pour interrompre la routine principale toutes les microsecondes, pour exécuter une autre routine spécifique.
Réponse
Il est possible de faire du multi-thread côté logiciel sur lUno. Le threading au niveau matériel nest pas pris en charge.
Pour réaliser le multithreading, il faudra limplémentation dun planificateur de base et le maintien dun processus ou dune liste de tâches pour suivre les différentes tâches à exécuter.
La structure dun ordonnanceur non préemptif très simple serait comme:
//Pseudocode void loop() { for(i=o; i<n; i++) run(tasklist[i] for timelimit): }
Ici, tasklist
peut être un tableau de pointeurs de fonction.
tasklist [] = {function1, function2, function3, ...}
Avec chaque fonction de la forme:
int function1(long time_available) { top: //Do short task if (run_time<time_available) goto top; }
Chaque fonction peut effectuer une tâche distincte telle que function1
effectuer des manipulations de LED et function2
faire des calculs de flottants. Il sera de la responsabilité de chaque tâche (fonction) de respecter le temps qui lui est alloué.
Jespère que cela devrait suffire à vous aider à démarrer.
Commentaires
- Je ne suis pas sûr de parler de » threads » lors de lutilisation dun planificateur non préemptif. À propos, un tel planificateur existe déjà en tant que bibliothèque arduino: arduino.cc/en/Reference/Scheduler
- @jfpoilpret – Coopérative le multithreading est une chose réelle.
- Oui, vous ‘ avez raison! Mon erreur; cela faisait si longtemps que je navais pas été confronté au multithreading coopératif que dans mon esprit, le multithreading devait être préventif.
Réponse
Selon la description de vos besoins:
- un thread en attente dun périphérique externe
- un thread clignotant une LED
Il semble que vous pourriez utiliser une interruption Arduino pour le premier « thread » (je préfère lappeler « tâche » en fait).
Les interruptions Arduino peuvent appeler une fonction (votre code) basée sur un événement (niveau de tension ou changement de niveau sur une broche dentrée numérique), qui déclenchera immédiatement votre fonction.
Cependant, un point important à garder à lesprit avec les interruptions est que la fonction appelée doit être aussi rapide que possible (en général, il ne doit y avoir aucun appel delay()
ou toute autre API qui dépendrait de delay()
).
Si vous avez une longue tâche à activer sur un déclencheur dévénement externe, vous pouvez potentiellement utiliser un planificateur coopératif et y ajouter une nouvelle tâche à partir de votre fonction dinterruption.
Une deuxième importante Le point concernant les interruptions est que leur nombre est limité (par exemple seulement 2 sur UNO). Donc, si vous commencez à avoir plus dévénements externes, vous devrez implémenter une sorte de multiplexage de toutes les entrées en une seule, et faire en sorte que votre fonction dinterruption détermine quel multiplexé inutile était le déclencheur réel.
Réponse
Une solution simple consiste à utiliser un Scheduler . Il existe plusieurs implémentations. Ceci décrit brièvement celui qui est disponible pour les cartes AVR et SAM. Fondamentalement, un seul appel démarrera une tâche; « sketch within a sketch ».
#include <Scheduler.h> .... void setup() { ... Scheduler.start(taskSetup, taskLoop); }
Scheduler.start () ajoutera une nouvelle tâche qui exécutera le taskSetup une fois, puis appellera à plusieurs reprises taskLoop comme le Le croquis Arduino fonctionne. La tâche a sa propre pile. La taille de la pile est un paramètre facultatif. La taille de pile par défaut est de 128 octets.
Pour permettre le changement de contexte, les tâches doivent appeler yield () ou delay ( ) . Il existe également une macro de support pour lattente dune condition.
await(Serial.available());
La macro est un sucre syntaxique pour ce qui suit:
while (!(Serial.available())) yield();
Attendre peut aussi être utilisé pour synchroniser les tâches. Voici un exemple dextrait de code:
volatile int taskEvent = 0; #define signal(evt) do { await(taskEvent == 0); taskEvent = evt; } while (0) ... void taskLoop() { await(taskEvent); switch (taskEvent) { case 1: ... } taskEvent = 0; } ... void loop() { ... signal(1); }
Pour plus de détails, consultez les exemples . Il existe des exemples de clignotement de plusieurs LED au bouton anti-rebond et dun simple shell avec une lecture de ligne de commande non bloquante. Les modèles et les espaces de noms peuvent être utilisés pour aider à structurer et réduire le code source. Ci-dessous, le sketch montre comment utiliser les fonctions de modèle pour le multi-clignotement. Il suffit de 64 octets pour la pile.
#include <Scheduler.h> template<int pin> void setupBlink() { pinMode(pin, OUTPUT); } template<int pin, unsigned int ms> void loopBlink() { digitalWrite(pin, HIGH); delay(ms); digitalWrite(pin, LOW); delay(ms); } void setup() { Scheduler.start(setupBlink<11>, loopBlink<11,500>, 64); Scheduler.start(setupBlink<12>, loopBlink<12,250>, 64); Scheduler.start(setupBlink<13>, loopBlink<13,1000>, 64); } void loop() { yield(); }
Il existe également un benchmark pour donner une idée de la performance, cest-à-dire du temps pour démarrer la tâche, changer de contexte, etc.
Enfin, il existe quelques classes de support pour la synchronisation et la communication au niveau des tâches; File dattente et Sémaphore .
Réponse
Je suis également venu à ce sujet lors de limplémentation dun écran LED matriciel.
En un mot , vous pouvez créer un planificateur dinterrogation en utilisant la fonction millis () et linterruption du minuteur dans Arduino.
Je suggère les articles suivants de Bill Earl:
https://learn.adafruit.com/multi-tasking-the-arduino-part-1/overview
https://learn.adafruit.com/multi-tasking-the-arduino-part-2/overview
https://learn.adafruit.com/multi-tasking-the-arduino-part-3/overview
Réponse
À partir dune incantation précédente de ce forum, la question / réponse suivante a été déplacée vers Génie électrique. Il a un exemple de code Arduino pour faire clignoter une LED en utilisant une interruption de minuterie tout en utilisant la boucle principale pour faire des E / S série.
Republier:
Les interruptions sont un moyen courant de faire avancer les choses pendant quil se passe autre chose. Dans lexemple ci-dessous, le voyant clignote sans utiliser delay()
. Chaque fois que Timer1
se déclenche, la routine de service dinterruption (ISR) isrBlinker()
est appelée. Il allume / éteint la LED.
Pour montrer que dautres choses peuvent se produire simultanément, loop()
écrit à plusieurs reprises foo / bar sur le port série indépendamment du clignotement de la LED .
#include "TimerOne.h" int led = 13; void isrBlinker() { static bool on = false; digitalWrite( led, on ? HIGH : LOW ); on = !on; } void setup() { Serial.begin(9600); Serial.flush(); Serial.println("Serial initialized"); pinMode(led, OUTPUT); // initialize the ISR blinker Timer1.initialize(1000000); Timer1.attachInterrupt( isrBlinker ); } void loop() { Serial.println("foo"); delay(1000); Serial.println("bar"); delay(1000); }
Ceci est une démo très simple. Les ISR peuvent être beaucoup plus complexes et peuvent être déclenchés par des minuteries et des événements externes (broches). La plupart des bibliothèques courantes sont implémentées en utilisant des ISR.
Réponse
Vous pouvez également essayer ma bibliothèque ThreadHandler
https://bitbucket.org/adamb3_14/threadhandler/src/master/
Il utilise un planificateur dinterruption pour permettre le changement de contexte sans relais yield () ou delay ().
Jai créé la bibliothèque parce que javais besoin de trois threads et que javais besoin de deux dentre eux pour sexécuter à un moment précis, peu importe ce que les autres faisaient. Le premier thread gère la communication série. Le second exécutait un filtre de Kalman en utilisant la multiplication de matrice flottante avec la bibliothèque Eigen. Et le troisième était un thread de boucle de contrôle de courant rapide qui devait pouvoir interrompre les calculs de la matrice.
Comment ça marche
Chaque thread cyclique a une priorité et un point. Si un thread, avec une priorité plus élevée que le thread en cours dexécution, atteint sa prochaine heure dexécution, le planificateur mettra en pause le thread en cours et basculera vers le thread de priorité plus élevé. Une fois que le thread de haute priorité a terminé son exécution, le planificateur revient au thread précédent.
Règles de planification
Le schéma de planification de la bibliothèque ThreadHandler est le suivant:
- Priorité la plus élevée en premier.
- Si la priorité est la même, alors le thread avec la première échéance est exécuté en premier.
- Si deux threads ont le même délai, le premier thread créé sexécutera en premier.
- Un thread ne peut être interrompu que par des threads avec une priorité plus élevée.
- Une fois quun thread sexécute, il bloquera lexécution pour tous les threads avec une priorité inférieure jusquà ce que la fonction dexécution revienne.
- La fonction de boucle a la priorité -128 par rapport aux threads ThreadHandler.
Comment utiliser
Les threads peuvent être créés via lhéritage C ++
class MyThread : public Thread { public: MyThread() : Thread(priority, period, offset){} virtual ~MyThread(){} virtual void run() { //code to run } }; MyThread* threadObj = new MyThread();
Ou via createThread et une fonction lambda
Thread* myThread = createThread(priority, period, offset, []() { //code to run });
Les objets Thread se connectent automatiquement au ThreadHandler lorsquils sont créés.
Pour démarrer lexécution des objets thread créés, appelez:
ThreadHandler::getInstance()->enableThreadExecution();
Un swer
Et voici encore une autre bibliothèque multitâche coopérative à microprocesseur – PQRST: une file dattente prioritaire pour lexécution de tâches simples.
- Page daccueil
- Documentation au niveau de la classe
- Référentiel , avec téléchargements et liste des problèmes
Dans ce modèle, un thread est implémenté en tant que sous-classe dun Task
, qui est planifié pour une date ultérieure (et éventuellement reprogrammé à intervalles réguliers, si, comme cest courant, il sous-classe LoopTask
à la place). La méthode run()
de lobjet est appelée lorsque la tâche arrive à échéance. La méthode run()
fait un peu de travail, puis retourne (cest le bit coopératif); il « maintiendra généralement une sorte de machine à états pour gérer ses actions lors dappels successifs (un exemple trivial est la variable light_on_p_
dans lexemple ci-dessous). Il faut repenser légèrement la façon dont vous organiser votre code, mais sest avéré très flexible et robuste dans un usage assez intensif.
Il est indépendant des unités de temps, donc il est aussi heureux de fonctionner en unités de millis()
as micros()
, ou tout autre tick qui vous convient.
Voici le programme blink implémenté en utilisant cette bibliothèque. Cela ne montre quune seule tâche en cours dexécution: dautres tâches sont généralement créées et démarrées dans setup()
.
#include "pqrst.h" class BlinkTask : public LoopTask { private: int my_pin_; bool light_on_p_; public: BlinkTask(int pin, ms_t cadence); void run(ms_t) override; }; BlinkTask::BlinkTask(int pin, ms_t cadence) : LoopTask(cadence), my_pin_(pin), light_on_p_(false) { // empty } void BlinkTask::run(ms_t t) { // toggle the LED state every time we are called light_on_p_ = !light_on_p_; digitalWrite(my_pin_, light_on_p_); } // flash the built-in LED at a 500ms cadence BlinkTask flasher(LED_BUILTIN, 500); void setup() { pinMode(LED_BUILTIN, OUTPUT); flasher.start(2000); // start after 2000ms (=2s) } void loop() { Queue.run_ready(millis()); }
Commentaires
- Il s’agit de tâches d’exécution à l’achèvement, non?
- @EdgarBonet I ‘ Je ne sais pas trop ce que vous voulez dire. Une fois que la méthode
run()
est appelée, elle nest pas interrompue, il a donc la responsabilité de terminer raisonnablement rapidement. En règle générale, cependant, il ‘ fera son travail puis se replanifiera (éventuellement automatiquement, dans le cas d’une sous-classe deLoopTask
) pour certains temps futur. Un modèle courant est pour la tâche de maintenir une machine à états interne (un exemple trivial est létatlight_on_p_
ci-dessus) afin quil se comporte correctement à la prochaine échéance. - Alors oui, ce sont des tâches RtC (run-to-completion): aucune tâche ne peut sexécuter avant que la tâche actuelle nait terminé son exécution en retournant de
run()
. Ceci est en contraste avec les threads coopératifs, qui peuvent générer le processeur, par exemple en appelantyield()
oudelay()
. Ou des threads préemptifs, qui peuvent être programmés à tout moment. Je pense que la distinction est importante, car jai vu que beaucoup de gens qui viennent ici à la recherche de threads le font parce quils préfèrent écrire du code de blocage plutôt que des machines à états. Bloquer les vrais threads qui donnent le CPU est très bien. Le blocage des tâches RtC nest pas. - @EdgarBonet Cest ‘ une distinction utile, oui. Je considérerais à la fois ce style et les threads de style yield comme de simples styles différents de thread coopératif, par opposition aux threads préemptifs, mais il ‘ est vrai qu’ils nécessitent une approche différente pour les coder. Il serait intéressant de voir une comparaison réfléchie et approfondie des différentes approches mentionnées ici; une belle bibliothèque non mentionnée ci-dessus est protothreads . Je trouve des choses à critiquer dans les deux, mais aussi à louer. Je préfère (bien sûr) mon approche, car elle semble la plus explicite et ne nécessite aucune pile supplémentaire.
- (correction: protothreads était mentionné, dans @sachleen ‘ )