[Lib] SoftTimer - Une lib pour faciliter les taches périodique ou liées au temps

Bonjour

Je me suis aperçu que beaucoup de débutants ne savait pas bien comment découper leur code proprement pour gérer plusieurs tâches sur un système qui n'a pas de noyau multi-tâches.
D'habitude, je pousse beaucoup sur l'utilisation de machines d'états qui sont de mon point de vue une manière propre, claire, maintenable de subdiviser une tâche principale en sous-tâches élémentaires et à gérer à la fois leur séquencement propre et "l'entremelage" manuel qu'il faut effectuer pour obtenir ce que j'appelle du multi-tâche coopératif.

Avant d'élaborer sur ce sujet (peut être dans un tuto), un problème récurrent est la gestion des opérations répétitives ou liées à l'expiration d'un délai. On voit trop souvent dans les codes des appels à la fonction delay() ou des attentes bloquantes. Or ces fonctions vont monopoliser le coeur de l'Arduino à ne rien faire qu'attendre alors qu'on a peut être d'autres choses à faire qui vont alors prendre du retard. Adieu le temps réel.

C'est pourquoi je vous propose une lib qui implémente une classe appellée SoftTimer. Ayant l'habitude d'autres environnements tels que .Net (C#) ou une telle classe existe de manière native je me suis empressée de la recréer.
Le principe est de gérer pour vous des timers purement logiciels, soit à usage unique (gestion de délai, timeout, ...) soit répétitifs. Ces timers doivent être basés sur une classe dérivée de SoftTimer. Une fois définie la période du timer, son mode (SINGLE ou REPEAT) et qu'on l'a démarré par start(), il vit sa vie. A condition bien sur d’appeler la fonction manage() depuis la loop() principale.

Cette lib n'utilise aucun timer hard ni interruption, donc elle a besoin que vous fassiez encore des efforts pour que loop() tourne bien et qu'il ne traine aucun code bloquant à l'intérieur.
Je comptais faire aussi une lib avancée basée timer hard et interruption mais je viens de m'appercevoir qu'elle existait déjà (TimerOne).
L'avantage de cette version purement soft est que n'utilisant aucun timer hard, elle ne rentre pas en conflit avec les libs PWM ou Servo.

Vous pouvez télécharger le ZIP de la lib incluant un soft d'exemple en bas de ce post, sur l'historique (On ne peut pas attacher de fichier à un post sur ce forum ?)

Les commentaires du code étant en anglais, je vous fais ici un résumé de l'utilisation en français.

But Permettre de gérer facilement des évènements répétitifs ou retardés.
La bibliothèque fournit une classe de base qu'il faut dériver afin de surcharger la méthode handler() qui fait le boulot pour laquelle le timer est créé.

Méthode de la classe

SoftTimer( period = -1, mode = REPEAT)
Constructeur.
Permet de définir une période initiale (par défaut égale au max possible) ainsi que le mode de fonctionnement par défaut.
Les 2 paramètres peuvent être changés à tout moment durant la vie du timer par les membres setPeriod() et start().

void begin( void )
Méthode statique la classe de base. Il faut impérativement appeller SoftTimer::begin() une fois dans setup() avant d'utiliser toute fonction de la bibliothèque.

void manage( void )
Méthode statique de la classe de base. Il faut impérativement appeller SoftTimer::manage() une fois dans loop().

void setPeriod( period )
Permet de changer a tout moment la période d'un timer, même quand il est actif.
Par défaut, la bibliothèque marche en microsecondes (changeable par un #define dans le code source)

void start()
Rend le timer actif avec le mode définit lors de la construction, ou le dernier mode utilisé par start( mode ).

void start( mode )
Rend le timer actif avec le mode passé en paramètre (SINGLE ou REPEAT). Ce mode devient le mode par défaut pour les prochains appels à start() sans paramètres.

void stop()
Rend le timer inactif.

bool isActive()
Renvoie true si le timer est actif. Permet par exemple de tester si un timer en mode SINGLE est arrivé à expiration (gestion de timeout par exemple).

void reset()
Remet le compteur à zéro et repart pour une période pleine.

void handler()
Fonction de traitement qu'il faut surcharger dans la classe dérivée. Le handler() sera appellé chaque fois que le compteur atteint la période programmée.

#define TIMER_DEBUG 1
Dans SoftTimer.h, cette macro (définie à 1 par défaut) active la définition de 2 méthodes supplémentaires pour le debug (voir ci-dessous).
En mettant cette ligne en commentaire, les 2 fonctions sont désactivées ce qui peut économiser quelques octets supplémentaires en flash.

void SoftTimer::printActive()
Méthode statique qui liste sur Serial tous les timers actifs avec leur paramètres.

void print()
Méthode qui affiche sur Serial les paramètres courant du timer (actif ou non).

Je vous invite à regarder le code exemple qui de mon point de vue montre bien combien il est facile d'utiliser cette bibliothèque de classe.
J'ai choisit volontairement le concept où il faut dériver sa propre classe de SoftTimer car je trouve que cela permet un code plus clair.

Commentaire ? Questions ?


Historique:
1.0 17 avril 2012 - Version Initiale
1.0a 18 avril 2012 - Support automatique IDE pre-1.0 et 1.0

SoftTimer-1_0a.zip (6.36 KB)

Merci très pratique. Cette librairie va plaire si on en croit le nombre de questions que l'on trouve sur le forum concernant le sujet.

Pour ceux qui sont encore sous des anciennes versions de l'IDE, il faut modifier le début de SoftTimer.cpp

#include "Arduino.h"
#include "SoftTimer.h"

par

#include "wiring.h"
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <HardwareSerial.h>
#include "SoftTimer.h"

Y a t'il un moyen par #if d'identifier l'IDE utilisé ?
Si on pouvait faire çà de manière automatique ?
Je m’aperçois aussi que j'ai laissé par défaut le #define TIMER_DEBUG

Dans SoftTimer.h

#define TIMER_DEBUG 1

Ce define permet d'activer 2 méthodes utiles pour le debug :
void SoftTimer::printActive()
void print()
En le mettant en commentaire, on gagne le code en question ce qui peut être utile quand la flash commence à être bien remplie.
Description rajoutée dans le début du post.

Y a t'il un moyen par #if d'identifier l'IDE utilisé ?

Il semblerait qu'il existe une solution utilisant #if defined(ARDUINO) && ARDUINO >= 100
http://arduino.cc/forum/index.php/topic,92018.0.html

Ce qui donne quelque chose comme ça:

#if defined(ARDUINO) && ARDUINO >= 100
  #include <arduino.h>
#else
  #include "wiring.h"
  #include <avr/pgmspace.h>
  #include <avr/interrupt.h>
  #include <HardwareSerial.h>
#endif
#include "SoftTimer.h"

Et ça compile correctement chez moi sur un IDE 022

Fait. Posté en 1.0a
Ajouté une section historique en fin de post avec les liens de téléchargements.
Bon, comme je découvre le Playground, je m'apperçois que cela a déjà été réalisé 25 fois minimum XD

Bonjour

Merci à toi pour tes petits programmes sympas

Néanmoins, étant relativement novice, j'ai quelques difficultés avec le softTimer

Quelle ordre faut-il activer pour déclencher une tempo single-shot ou /et un blinker dans le loop?

D'autre part, l'affichage des variables sur le sérial ne fonctionne pas, alors que dans le SoftTimer.h, il n'est pas en commentaire .

Merci d'avance

Bonjour
Tu parles de l'exemple fournis ou d'un programme a toi ?
Le sketch d'exemple montre exactement comment faire une fonction périodique pour faire clignoter une led et un one-shot pour une action retardée.
Et sur ma UNO, le fonction de debug print() et printActive() marchent.

Qu'as tu essayé exactement ?

Bonsoir

Ton programme fonctionne bien sûr sauf l'affichage qui refuse obstinément de fonctionner chez moi

Ma question portait simplement sur comment intégrer ton programme dans un programme utilitaire afin d'enclencher le clignotement ou le single-shot sur une condition

ex: if (digitalRead(A0) == HIGH) : devrait enclencher le clignotement, mais je ne vois pas quelle commande appeler dans le loop

Bien à toi

Bonjour,

C'est exactement le genre de librairie que je cherchait pour mon projet ! Merci :wink:

J'ai fait quelques essais en me basant sur l'exemple et cela fonctionne bien. Par contre je me demandais si il était possible, a l'instar de la méthode setpreriod, de modifier un paramètre de la classe dérivée, cela reviendrai par rapport à la démo à changer de "ledPin" alors que le timer est en cours ?

Peut être en ajoutant une méthode à la classe dérivée ?

Désolé je ne possède que des connaissances de bases en programmation.

D'avance merci.

casper22:
Ma question portait simplement sur comment intégrer ton programme dans un programme utilitaire afin d'enclencher le clignotement ou le single-shot sur une condition
ex: if (digitalRead(A0) == HIGH) : devrait enclencher le clignotement, mais je ne vois pas quelle commande appeler dans le loop

Pour un single-shot tu as dans mon exemple le déclenchement d'un single-shot sur appui sur un bouton. Je le fait sur interruption mais tu peut le faire aussi dans loop de la façon suivante :

void loop()
{
    if ( (digitalRead(A0) == HIGH) && (!monTimer.isActive()) )
        monTimer.start();

    SoftTimer::manage();
}

Comme tu peut le constater, je ne lance le timer que s'il est arrêté. Car si le signal est toujours HIGH quand on repasse une 2nde fois, il ne faut pas relancer le timer qui coure déjà.
Et bien sur il faut TOUJOURS continuer a appeller SoftTimer::manage() dans la boucle puisque c'est là que ce fait le vrai boulot.

darkio03:
Peut être en ajoutant une méthode à la classe dérivée ?

Exactement, tu peux ajouter à la classe dérivée ce qui est nécessaire pour changer la pin de la led dynamiquement si tu en as besoin.

Merci beaucoup pour cette librairie très intéressante.

Je vais regarder de plus près, pour m'en souvenir lorsque j'en aurai besoin, ce qui ne devrait pas tarder.

Salut

D'aaacord, c'est plus clair pour moi maintenant

Un grand merci

Salut Barbudor,

Je cherche un moyen de déclencher un code d'interruption 5ms précisément après la surevenue d'une PINinterruption, tout ceci hors du main loop, lancé directement pas ma PINinterruption.

Voici le résultat souhaité en image, la précision devra être de 4µs :

Penses-tu que je puisse utiliser ta librairie ? as-tu une autre idée plus "légère" ?

Sev

EDIT : Plus de précision sur mon projet ici, mais en anglais :

Bonjour

Ma lib est entièrement soft et repose sur la boucle loop() sans laquelle il faut appeller la fonction SoftTimer.manage();
Donc elle ne peut pas garantir une précision que si ton code lui garantie de s'exécuter suffisamment souvent.

La solution la plus fiable c'est d'utiliser un timer que tu armes depuis la routine d'interruption de l'entrée et qui déclenche un nouvelle interruption 5ms plus tard pour positionner ta sortie.
Si les 6 signaux d'entrée sont asynchrones, alors il te faut 6 entrées interruptions (ça c'est faisable) mais tu n'aura pas 6 timers.
Tu doit pouvoir t'en sortir avec un en faisant des calculs de dates pour régler le registre OC1

bonjour barbudor,
très bien la lib,
je suis débutant dans la prog de l'arduino.
et j'ai du mal à comprendre la mise en action d'un timer.
d’après ce que j'ai compris il faut faire une class de ce qu'on veut retarder ou faire clignoter ?
mais pour remplacer la fonction delay() on doit faire tout ce travail ou y a t il d'autre approche?
j'ai deux routines qui doivent arriver l'une après l'autre ( Routine 1 , Routine 2)seulement elles tournent en parallèle et bien sur ( Routine 2 va plus vite que Routine 1, j'ai bien tenté avec delay mais rien de bon.
Avec ta lib est ce possible de simuler une Fc° delay et que celle-ci ne paralyse pas le système?
merci de ta lecture.

Regarde les exemples et reviens poser des questions si tu ne comprend pas

barbudor:
...La solution la plus fiable c'est d'utiliser un timer que tu armes depuis la routine d'interruption de l'entrée et qui déclenche un nouvelle interruption 5ms plus tard pour positionner ta sortie.
Si les 6 signaux d'entrée sont asynchrones, alors il te faut 6 entrées interruptions (ça c'est faisable) mais tu n'aura pas 6 timers.
Tu doit pouvoir t'en sortir avec un en faisant des calculs de dates pour régler le registre OC1...

Merci Barbudor, je compte réutiliser le même timer puisque je n'ai jamais 2 canaux de sortie montés en simultanés.

Où puis-je trouver l'utilisation qui est faite des timer/counter par l'environnement Arduino ?
Je ne trouve pas cette info sur le site arduino, pourtant, les commandes micros() & millis() doivent bien utiliser un compteur avec le timer0...

Sev

Gardons ce topic pour parler de la lib SoftTimer.
Crée un nouveau topic dans le forum général - si tu ne l'as pas déjà fait - sur l'utilisation des timers hard.

Bonjour,

J'utilise softtimer avec une carte leonardo r3.
Mes tâches "maisons" s’exécutent normalement, mais je n'arrive pas à utiliser le debouncer.

Même l'exemple SoftTimer6Debouncer1 ne marche pas. Est-ce un pb lié aux interruption de la leonardo ?

Cordialement,
Edouard

Il n'y a pas de debounce ou d'exemple SoftTimer6Debouncer1 dans ma lib SoftTimer.
Vous devez parlez d'autre chose qui porte le même nom.