Clock()

Clock est une classe de métronomes. Ce sont des horloges qui vont délivrer un événement onTime régulièrement.

Toutes les classes horloges doivent dériver de cette classe.

 

Exemples d'utilisation

Clock metronome(); // Crée metronome (période de 1s par défaut)
Clock metronome(2000); // Crée metronome avec une période de 2s
Clock metronome = new Clock(); // Crée une instance dynamique metronome
Clock metronome = new Clock(500); // Pareil mais période de 0,5s
new Clock(); // Crée une instance dynamique sans pointeur dessus

metronome.actif=false; // Interdit, utiliser stop()
metronome.actif=true; // Interdit, utiliser start()
if (metronome.actif) ... // permet d'interroger son activité

metronome.duree=2000; // Change la période
metronome.duree = metronome.duree*2; // Double la période

Serial.println("On en est à "+String(millis()-metronome.depart)+"ms"); // "On en est à 100ms":

metronome.onTimeFunction = &action; // Appel la fonction void action(void) en fin de comptage

metronome.start(); // Redémarre le métronome si on l'avait arrêté

metronome.stop(); // Arrête le métronome, il repartira sur metronome.start();

metronome.onTime(); // Ne fait rien par défaut!

 

Référence

class Clock
 public:
  Clock(unsigned long duree_ms=1000);
  boolean actif;
  unsigned long duree_ms;
  unsigned long depart;
  void (*onTimeFunction)(void);
  Clock *horlogeSuivante;
  void start(void);
  void stop(void);
  virtual void onTime(void);

Attributs et méthodes

Clock(): Constructeur, s'insère dans la liste des horloges gérées. duree_ms: période en ms
actif: true par défaut . Si true, déclenche une action régulière, si false ne fait plus rien. Lecture seule
duree_ms: Période exprimée en millisecondes
depart: Temps en ms de la dernière fin de comptage
*onTimeFunction: Pointeur sur une fonction sans paramètre et ne retournant rien qui sera appelée à chaque fin de comptage
*horlogeSuivante: Pointeur sur l'horloge suivante dans la liste des horloges
start(): Permet de faire repartir le métronome si on l'a arrêté, réinitialise le comptage si il est actif
stop(): Suspend l'activité de ce métronome
onTime(): Appelée en fin de comptage, elle peut être surchargée pour définir des comportements supplémentaires. Par défaut elle ne fait rien.

 

Voir aussi

- scanEvent(); Moteur de la gestion des événements
- Timer; Temporisateur (une action une seule fois)

 

Notes

Quand un métronome est crée, il déclenche son action toutes les périodes déterminées par l'attribut "milliseconde". La première occurrence a lieu une période après sa création.

La plupart du temps, on ne fera que deux choses:
- déclarer l'instance, bien souvent par Clock variable;
- associer une fonction pour la fin du comptage genre variable.onTimeFunction = &action;
Le reste est en supplément, souvent parce que la gestion a besoin de ces méthodes

Si une fonction retarde la scrutation d'un métronome, celui-ci rattrapera son retard en raccourcissant le temps de comptage suivant. Ainsi sur un temps long le nombre d'appel de fin de comptage est correct. Les déclenchements moyens ne dépendent pas de la charge de travail des différentes fonctions.

C'est scanEvent() qui, en visitant une fois chaque horloge, va s'occuper de savoir si une horloge doit intervenir ou pas. C'est pour cela que scanEvent() est mis dans loop(). La résolution des horloges est égal à l'intervalle séparant deux appels à scanEvent(). Mais sur le long terme, la résolution est de 1ms.

Si la période du métronome est plus petite que l'intervalle d'appel de scanEvent(), le métronome déclenchera à chaque appel de scanEvent().

La période est donnée par un entier long non signé (duree). Naturellement la période maximale est d'environ 50 jours. Pour avoir des périodes plus longues, on peut ne déclencher l'action voulue que si le métronome a fini son comptage X fois.

La fonction onTime() n'est utile que pour la surcharger en créant une classe fille. Voir les exemples complets.

 

Exemples

Dans les exemples qui suivent un métronome change la couleur de l'écran toutes les deux secondes. Il y a plusieurs façons de le faire, on peut la plupart du temps n'utiliser que la première forme. D'autres sont intéressantes si on a plusieurs métronomes identiques, mais dans l'exemple simplifié, comme c'est la mise en place qui me préoccupe, les programmes sont réduits, et l'intérêt de telles déclarations n'apparaît plus.

Les 7 premiers exemples montrent 7 façons (parfois assez semblables) de déclarer un métronome. De la même façon on peut déclarer tous les objets (horloges, mais aussi boutons...). Ces 7 exemples ne seront pas repris systématiquement, sauf pour le premier bouton.

Le huitième exemple est une application sympathique.

 

Premier exemple: déclaration statique

C'est la façon la plus simple d'utiliser un métronome. L'instance est statique.

PecheuxGraph_ILI9341_8bits.zip\PecheuxGraph_ILI9341_8bits\examples\Documentation\Exemple-801-Clock-Statique-onTimeFunction\Exemple-801-Clock-Statique-onTimeFunction.ino (dans votre fichier téléchargé):

// Mise en place d'un métronome. Toutes les 2s, il change la couleur du fond
// de l'écran

// Version 1:
// - L'horloge a une définition statique
// - Utilise onTimeFunction pour exécuter l'action à faire 

#
  $menu = ":PG";
  include <PecheuxGraph_ILI9341_8bits.h> // Appel de la bibliothèque 

 
// Déclaration statique de l'instance, doit se faire en dehors du setup
// Si on la définissait dans le setup elle serait détruite au sortir du setup et
// en particulier lorsque l'on en a besoin (dans le loop).
Clock metronome(2000); // Déclaration  d'un métronome, action toutes les 2 secondes


// Déclaration d'une fonction qui fera ce qu'il faut faire toutes les fin de comptage
// Peut se mettre avant ou après la déclaration de la variable
// Cette fonction ne doit pas avoir de paramètres et ne doit rien retourner
// Le nom peut être quelconque
void onTimeAction(void)
{
  clrscr(RANDOM_COLOR); // Action à faire chaque 2 secondes
}


void setup()
{
  setGraphMode(PAYSAGE); // Pour pouvoir utiliser clrscr()

  // Association de la fonction qui va faire le travail avec le métronome
  // Attention, il y a un caractère "&" avant le nom de la fonction.
  // Cette ligne veut dire: metronome a une propriété onTimeFunction et on va
  // affecter cette propriété avec l'adresse (présence du "&") de onTimeAction
  metronome.onTimeFunction=&onTimeAction; // Fonction à appeler toutes les 2 secondes
}


void loop() 
{
  scanEvent(); // Gestion des boutons et des horloges, le plus souvent seul dans le loop
}

 

Deuxième exemple: déclaration dynamique avant le setup.

Quand on déclare l'instance, on obtient un pointeur sur metronome. les pointeurs sont utiles pour parcourir différents objets. En pricipe, cela a peu d'intérêt. Si on voulait accéder à une liste d'hologe, on peut passer par la liste du gestionnaire.

La déclaration est faite ici avant le setup

PecheuxGraph_ILI9341_8bits.zip\PecheuxGraph_ILI9341_8bits\examples\Documentation\Exemple-802-Clock-Dynamique-onTimeFunction\Exemple-802-Clock-Dynamique-onTimeFunction.ino (dans votre fichier téléchargé):

// Mise en place d'un métronome. Toutes les 2s, il change la couleur du fond
// de l'écran

// Version 2:
// - L'horloge a une définition dynamique avant le setup
// - Utilise onTimeFunction pour exécuter l'action à faire 

#
  $menu = ":PG";
  include <PecheuxGraph_ILI9341_8bits.h> // Appel de la bibliothèque 

 
// Déclaration dynamique de l'instance, peut se faire en dehors du setup
// Dans ce cas horloge est accessible partout
Clock *horloge = new Clock(2000); // Déclaration  d'un métronome, action toutes les 2 secondes


// Déclaration d'une fonction qui fera ce qu'il faut faire toutes les fin de comptage
// Peut se mettre avant ou après la déclaration de l'instance horloge
// Cette fonction ne doit pas avoir de paramètres et ne doit rien retourner
// Le nom peut être quelconque
void onTimeAction(void)
{
  clrscr(RANDOM_COLOR); // Action à faire chaque seconde
}


void setup()
{
  setGraphMode(PAYSAGE); // Pour pouvoir utiliser clrscr()

  // Association de la fonction qui va faire le travail avec le métronome
  // Attention, il y a un caractère "&" avant le nom de la fonction.
  // Cette ligne veut dire: metronome a une propriété onTimeFunction et on va
  // affecter cette propriété avec l'adresse (présence du "&") de onTimeAction
  horloge->onTimeFunction=&onTimeAction; // Fonction à appeler toutes les secondes
}


void loop() 
{
  scanEvent(); // Gestion des boutons et des horloges, le plus souvent seul dans le loop
}

 

Troisième exemple: déclaration dynamique dans le setup.

C'est pratiquement la même chose que dans l'exemple précédent, mais l'instance est déclarée dans le setup. Dans ce cas, l'instance est dans la pile et est accessible tout le temps, mais seul le pointeur est détruit au sortir du setup. Ou pourrait aussi sur le même principe déclarer l'instance dans une fonction.

Même si le pointeur est détruit au sortir de la fonction, pendant qu'il existe, on peut accéder à l'objet. Cela permet en particulier d'utiliser une boucle pour définir plusieurs objets semblables dans le style:

Clock *horloge;
for (byte numero=0; numero<10; numero++)
{
  horloge = new Clock(random(100)*100+1000*numero); // Déclaration  d'un métronome, action n'importe quand
  horloge->onTimeFunction=&onTimeAction; // Fonction à appeler
}

Dans cet exemple, il va y avoir 10 métronomes qui battent avec des périodes différentes. Au bout du compte, la fonction onTimeAction() va être appelée pseudo-aléatoirement.

PecheuxGraph_ILI9341_8bits.zip\PecheuxGraph_ILI9341_8bits\examples\Documentation\Exemple-803-Clock-Dynamique-setup-onTimeFunction\Exemple-803-Clock-Dynamique-setup-onTimeFunction.ino (dans votre fichier téléchargé):

// Version 3:
// - L'horloge a une définition dynamique dans le setup()
// - Utilise onTimeFunction pour exécuter l'action à faire

#
  $menu = ":PG";
  include <PecheuxGraph_ILI9341_8bits.h> // Appel de la bibliothèque 


// Déclaration d'une fonction qui fera ce qu'il faut faire toutes les fin de comptage
// Cette fonction ne doit pas avoir de paramètres et ne doit rien retourner
// Le nom peut être quelconque
void onTimeAction(void)
{
  clrscr(RANDOM_COLOR); // Action à faire chaque seconde
}


void setup()
{
  setGraphMode(PAYSAGE); // Pour pouvoir utiliser clrscr()

  // Déclaration dynamique de l'instance, faite dans une fonction
  // Dans ce cas l'instance est accessible partout, mais le pointeur
  // horloge sera détruit en sortant de la fonction (ici du setup())
  Clock *horloge = new Clock(2000); // Déclaration  d'un métronome, action toutes les 2 secondes
  // Association de la fonction qui va faire le travail avec le métronome
  // Attention, il y a un caractère "&" avant le nom de la fonction.
  // Cette ligne veut dire: metronome a une propriété onTimeFunction et on va
  // affecter cette propriété avec l'adresse (présence du "&") de onTimeAction
  horloge->onTimeFunction = &onTimeAction; // Fonction à appeler toutes les secondes
}


void loop()
{
  scanEvent(); // Gestion des boutons et des horloges, le plus souvent seul dans le loop
}

 

Quatrième exemple: Surcharge de onTime, déclaration statique.

Si on a besoin de plusieurs horloges ayant des comportements identiques, on a vu qu'on peut écrire une fonction externe pour gérer la fin du comptage. Pour chaque horloge, il faudra alors lui associer cette fonction externe. En définissant une classe fille de Clock et en surchargeant onTime(), on aura plus qu'a déclarer l'instance, la fonction étant dedans. C'est un peu plus lourd, mais le code peut être plus clair.

Notez que l'on n'a pas besoin d'accéder au métronome un fois défini, le nom de la variable ne nous sert à rien

PecheuxGraph_ILI9341_8bits.zip\PecheuxGraph_ILI9341_8bits\examples\Documentation\Exemple-804-Clock-Statique-Classe\Exemple-804-Clock-Statique-Classe.ino (dans votre fichier téléchargé):

// Mise en place d'un métronome. Toutes les 2s, il change la couleur du fond
// de l'écran

// Version 4:
// - On définir une classe Metronome, dérivée de Clock, avec le bon comportement
// - Metronome a une définition statique

#
  $menu = ":PG";
  include <PecheuxGraph_ILI9341_8bits.h> // Appel de la bibliothèque 


class Metronome: public Clock // Nouvelle classe pour redéfinir onTime 
{
 public: 
  Metronome(unsigned long duree):Clock(duree){} // Constructeur qui copie la classe mère
  virtual void onTime(void) // Nouveau comportement
  {  
    clrscr(RANDOM_COLOR); // Action à faire chaque seconde 
  }
}; // Ne pas oublier le ; à la fin de la déclaration d'une classe


// Déclaration statique de l'instance, doit se faire en dehors du setup
// Si on la définissait dans le setup elle serait détruite au sortir du setup et
// en particulier lorsque l'on en a besoin (dans le loop).
Metronome metronome(2000); // Déclaration  d'un métronome, action toutes les 2 secondes


void setup()
{
  setGraphMode(PAYSAGE); // Pour pouvoir utiliser clrscr()
}


void loop() 
{
  scanEvent(); // Gestion des boutons et des horloges, le plus souvent seul dans loop()
}

 

Cinquième exemple: Surcharge de onTime, déclaration dynamique.

Si l'on crée une nouvelle classe, il est aussi possible de définir des insances sur les pointeurs.

PecheuxGraph_ILI9341_8bits.zip\PecheuxGraph_ILI9341_8bits\examples\Documentation\Exemple-805-Clock-Dynamique-Classe\Exemple-805-Clock-Dynamique-Classe.ino (dans votre fichier téléchargé):

// Mise en place d'un métronome. Toutes les 2s, il change la couleur du fond
// de l'écran

// Version 5:
// - On définir une classe Metronome, dérivée de Clock, avec le bon comportement
// - Metronome a une définition dynamique

#
  $menu = ":PG";
  include <PecheuxGraph_ILI9341_8bits.h> // Appel de la bibliothèque 


class Metronome: public Clock // Nouvelle classe pour redéfinir onTime
{
  public:
    Metronome(unsigned long duree): Clock(duree) {} // Constructeur qui copie la classe mère
    virtual void onTime(void) // Nouveau comportement
    {
      clrscr(RANDOM_COLOR); // Action à faire chaque seconde
    }
}; // Ne pas oublier le ; à la fin de la déclaration d'une classe


// Déclaration dynamique de l'instance, peut se faire en dehors du setup
// Dans ce cas metronome est accessible partout
Metronome *metronome = new Metronome(2000); // Déclaration  d'un métronome, action toutes les 2 secondes


void setup()
{
  setGraphMode(PAYSAGE); // Pour pouvoir utiliser clrscr()
}


void loop()
{
  scanEvent(); // Gestion des boutons et des horloges, le plus souvent seul dans loop()
}

 

Sixième exemple: Surcharge de onTime, déclaration dynamique dans une fonction.

Comme cela a été fait plus haut, que l'on dérive une nouvelle classe ou pas, on peut créer l'instance metronome dans une fonction. Comme tout à l'heure, l'instance existe partout, mais le pointeur metronome n'est accessible que dans la fonction qui l'a définie.

PecheuxGraph_ILI9341_8bits.zip\PecheuxGraph_ILI9341_8bits\examples\Documentation\Exemple-806-Clock-Dynamique-setup-Classe\Exemple-806-Clock-Dynamique-setup-Classe.ino (dans votre fichier téléchargé):

// Mise en place d'un métronome. Toutes les 2s, il change la couleur du fond
// de l'écran

// Version 6:
// - On définir une classe Metronome, dérivée de Clock, avec le bon comportement
// - Metronome a une définition dynamique dans une fonction

#
  $menu = ":PG";
  include <PecheuxGraph_ILI9341_8bits.h> // Appel de la bibliothèque 


class Metronome: public Clock // Nouvelle classe pour redéfinir onTime
{
  public:
    Metronome(unsigned long duree): Clock(duree) {} // Constructeur qui copie la classe mère
    virtual void onTime(void) // Nouveau comportement
    {
      clrscr(RANDOM_COLOR); // Action à faire chaque seconde
    }
}; // Ne pas oublier le ; à la fin de la déclaration d'une classe


void setup()
{
  setGraphMode(PAYSAGE); // Pour pouvoir utiliser clrscr()
  
  // Déclaration dynamique de l'instance, peut se faire dans une fonction
  // Dans ce cas l'instance est accessible partout, mais le pointeur
  // horloge sera détruit en sortant de la fonction (ici du setup())
  Metronome *metronome = new Metronome(2000); // Déclaration  d'un métronome, action toutes les 2 secondes
}


void loop()
{
  scanEvent(); // Gestion des boutons et des horloges, le plus souvent seul dans loop()
}

 

Septième exemple: Surcharge de onTime, déclaration dynamique dans une fonction, sans pointeur.

Dans la dernière version, on voit que l'on n'a pas besoin du pointeur. Dans ce cas, ce n'est pas la peine d'en avoir un.

PecheuxGraph_ILI9341_8bits.zip\PecheuxGraph_ILI9341_8bits\examples\Documentation\Exemple-807-Clock-SansPointeur-Classe\Exemple-807-Clock-SansPointeur-Classe.ino (dans votre fichier téléchargé):

// Mise en place d'un métronome. Toutes les 2s, il change la couleur du fond
// de l'écran

// Version 7:
// - On définir une classe Metronome, dérivée de Clock, avec le bon comportement
// - Metronome a une définition dynamique sans pointeur

#
  $menu = ":PG";
  include ;lt&PecheuxGraph_ILI9341_8bits.h> // Appel de la bibliothèque 


class Metronome: public Clock // Nouvelle classe pour redéfinir onTime
{
  public:
    Metronome(unsigned long duree): Clock(duree) {} // Constructeur qui copie la classe mère
    virtual void onTime(void) // Nouveau comportement
    {
      clrscr(RANDOM_COLOR); // Action à faire chaque seconde
    }
}; // Ne pas oublier le ; à la fin de la déclaration d'une classe


void setup()
{
  setGraphMode(PAYSAGE); // Pour pouvoir utiliser clrscr()
  
  // Déclaration dynamique de l'instance, mais on n'a pas besoin du pointeur
  // On peut donc créer une instance sans avoir une référence. 
  new Metronome(2000); // Déclaration  d'un métronome, action toutes les 2 secondes
}


void loop()
{
  scanEvent(); // Gestion des boutons et des horloges, le plus souvent seul dans loop()
}

 

Huitième exemple: Le sapin de Noël.

35 métronomes avec une Uno, et une boucle quasi obligatoire pour tout initialiser, en dérivant une nouvelle classe. On peut mettre 250 métronomes avec une Mega

PecheuxGraph_ILI9341_8bits.zip\PecheuxGraph_ILI9341_8bits\examples\Documentation\Exemple-808-Clock-Sapin_de_Noel\Exemple-808-Clock-Sapin_de_Noel.ino (dans votre fichier téléchargé):

// Ce programme dessine un sapin de Noël puis 35 métronomes vont faire
// changer de couleur les 35 boules de Noël à des instants légèrement
// différents (entre 2 et 2,1 seconde). Au début, les boules changent de
// couleur presque en même temps, puis à la longue, elles vont devenir
// indépendantes
// Avec un Mega, on peut mettre 250 métronomes

#
  $menu = ":PG";
  include <PecheuxGraph_ILI9341_8bits.h>

// Comme le comportement de chaque métronome est légèrement différent, a
// savoir que la boule associée est positionnée différemment pour chacun
// d'eux, il faut que chaque métronome mémorise ces coordonnes. Dériver
// une nouvelle classe est une bonne solution
const word couleurs[]={RED, GREEN, BLUE, CYAN, MAGENTA, YELLOW};
class Boule: public Clock
{
  public:
    Boule(int x, int y, unsigned long duree): Clock(duree)
    {
      this->x = x; this->y = y; // Dans ce constructeur, on rajoute l'initialisation des coordonnées
    }
    int x; int y; // Coordonnée de la boule associée
    virtual void onTime(void) // Nouveau comportement
    {
      fillRect(x, y, x+2, y+2, couleurs[random(6)]); //On change de couleur la boule associée (5 chances sur 6!)
    }
};


void setup()
{
  long x, y; // Coordonnés d'un point
  setGraphMode(PORTRAIT);

  // Le sapin
  if (bpxDraw("TOOLS/NOEL.BPX", 0, 0)) // Photo d'un sapin
  { // Pas de carte ou pas de fichier, on va tout dessiner à la main
    line(0, 240, 134, 223, BISQUE); lineTo(239,258); fill(134,250, BISQUE); // Sol
    fill(0,0,LIGHT_YELLOW); // Mur
    line(121, 38, 50, 241, DARK_GREEN); lineTo(177, 241); lineTo(121, 38); // Contour du sapin
    fill(121, 50, DARK_GREEN); fill(121, 239, DARK_GREEN); //Vert du sapin
    fillRect(110,240,132,290,DARK_RED); // Tronc
  }

  // Déclaration des 40 métronomes. Comme il y en a beaucoup, une boucle
  // est bienvenue. On est donc obligé d'avoir une déclaration dynamique. On
  // n'a pas besoin du pointeur créé, on n'en utilise pas
  for (byte nombre = 0; nombre < 35; nombre++) // Au delà de 40 avec un Uno, la pile déborde et le programme plante
  {
    do
    {
      x = random(178); // Coordonnées au hasard, mais pour l'instant cela risque de dépasser du sapin
      y = random(242); 
    }
    // Pour la condition pas trop à gauche, le sapin étant vaguement
    // triangulaire, il ne faut pas que les points soient à gauche de la
    // droite passant par (121,38) et (50,241). Il faut calculer l'équation
    // de la droite du type ax+by+c; si c'est nul, on est sur la droite, si
    // c'est négatif, on est dans un des deux demi-plan, si c'est positif on
    // est dans l'autre demi-plan. Un essai nous dira si on a pris le bon.
    // le point (x,y) est sur la droite passant par (x1,y1) et (x2,y2) si
    //   x - x1      x1 - x2
    //   ------  =   -------
    //   y - y1      y1 - y2
    // L'équation de la droite recherchée est:
    // (x - x1)*(y1 - y2) - (y - y1)*(x1 - x2) = 0
    // on testera si le membre de droite est nul (on est sur la limite du sapin)
    //                                       positif (on est d'un côté)
    //                                       négatif (on est de l'autre côté)
    while ((203*x+71*y-27261<=0) // Pas trop à gauche
           || (203*x-56*y-22425>=0)); // Pas trop à droite
    new Boule(x-1, y-1, 500 + nombre); // (x,y) étant dans le sapin, on créé le métronome
  }
}

void loop()
{
  scanEvent(); // Gestion des boutons et des horloges, le plus souvent seul dans loop()
}

Résultat:

 

Côté technique

En fait Clock ne fait pas grand chose, car c'est le gestionnaire scanEvent() qui vient lire les propriétés de Clock, qui calcule si le comptage du temps est fini ou non, et si oui Clock alors réarme le métronome en mettant à jour la propriété depart. Puis scanEvent() appelle la méthode onTime(),appelle la fonction pointée par *onTimeFunction si elle existe.

La mesure des temps se fait avec la fonction millis().

Pour réarmer le métronome, on peut:
- ajouter la valeur duree_ms à depart. Ainsi quelle que soit l'instant effectif de prise en compte de l'événement, les suivants ne seront pas impactés. Mais par contre si on bloque le programme un certain temps avec un traitement trop long (par exemple un chargement d'image), tous les métronomes vont s'activer à chaque appel de scanEvent() pour rattraper le retard.
- prendre comme nouveau depart la valeur de millis(), il n'y aura pas de rattrapage, seulement une période plus longue.
C'est la première solution qui a été choisie, un métronome a ainsi une période moyenne correcte.