Table of Contents

Le composant logiciel LED

Retour à la séance 5

Thinking Component

L'objectif du C++ n'est pas de faire le code le plus court, C++ a pour objectif de simplifier la lisibilité, la maintenance et la réutilisation du code.

Sur le montage que vous avez, nous avons des composants physiques. Pour l'instant, nous n'avons que deux familles de composants :

L'idée est d'abstraire les composants par des classes qui offrent les fonctionalités d'une diode et d'un bouton poussoir en cachant l'ensemble des opérations de bas-niveaux que sont digitalRead(), digitalWrite(), pinMode() etc.

Etape 1 : Classe Led

En vous inspirant de ce qui a été fait sous Tinkercad, proposer une classe Led en définissant :

et pourquoi pas proposer aussi des opérations :

operator != ()

Tout ceci vous permettra de réaliser les opérations classiques sur la Led.

Correction

Correction

L'état de la diode est caractérisé par deux variables :

  • le port sur lequel la diode est connectée (c'est un numéro de port, soit un nombre entier),
  • l'état de la diode, soit allumée, soit éteinte (c'est une valeur booléen, true est allumée, false est éteinte).

Ceci donne deux champs internes de la classe Led, soit :

class Led
{
  private:
    bool m_isSwitchedOn;
    uint32_t m_portNumber;
};

Pour les constructeurs, il faut initialiser les variables d'états :

  • le port (essentiel)
  • l'état de la diode (soit on, soit off, par défaut, nous pouvons dire off).

Ceci nous donne deux constructeurs :

class Led
{
  private:
    bool m_isSwitchedOn;
    uint32_t m_portNumber;
  public:
    Led(uint32_t thePort): Led(thePort, false) {}
    Led(uint32_t thePort, bool switchOnWhenStarted): m_portNumber(thePort), m_isSwitchedOn(switchOnWhenStarted)
    {}
};

Il faut ensuite ajouter les fonctions qui permette de questionner l'état de la diode. Il y en a deux, l'une qui demande l'état de la diode isSwitchOn() const et une autre qui récupère le port getPort() const. Ces deux fonctions ne modifient pas l'objet Led et elle sont donc marquées comme constante.

class Led
{
  private:
    bool m_isSwitchedOn;
    uint32_t m_portNumber;
  public:
    Led(uint32_t thePort): Led(thePort, false) {}
    Led(uint32_t thePort, bool switchOnWhenStarted): m_portNumber(thePort), m_isSwitchedOn(switchOnWhenStarted)
    {}
 
    bool isSwitchOn() const { return m_isSwitchedOn; }
    bool getPort() const { return m_portNumber; }
};

Ensuite, nous avons les fonctions correspondant à des actions que supporte le composant diode. Nous vons deux actions, l'une correspondant à allumer la diode et l'autre à éteindre la diode.

class Led
{
  private:
    bool m_isSwitchedOn;
    uint32_t m_portNumber;
  public:
    Led(uint32_t thePort): Led(thePort, false) {}
    Led(uint32_t thePort, bool switchOnWhenStarted): m_portNumber(thePort), m_isSwitchedOn(switchOnWhenStarted)
    {}
 
    bool isSwitchOn() const { return m_isSwitchedOn; }
    bool getPort() const { return m_portNumber; }
 
    void switchOn() 
    { 
      m_isSwitchedOn = true;
    }
    void switchOff() 
    { 
      m_isSwitchedOn = false;
    }
};

Nous avons pour l'instant créer un composant virtuel, il correspond à une diode mais ne commande pas la diode physique. Pour commander la diode physique, il reste deux étapes à faire :

  • Les constructeurs doivent initaliser la diode physique.
  • Les fonctions correspondant à des actions doivent exécuter l'action sur la diode physique.

Pour ce faire, il faut modifier le constructeur principal en ajoutant la configuration du port :

    Led(uint32_t thePort, bool switchOn): m_portNumber(thePort), m_isSwitchedOn(switchOn)
    {
        pinMode(m_portNumber, OUTPUT);
        if(switchOnWhenStarted)
          switchOn();
    }
<code>
 
ainsi que les deux fonctions ''switchOn()'' et ''switchoff()'' :
 
<code cpp>
    void switchOn() 
    { 
      if(!m_isSwitchedOn)
      {
        m_isSwitchedOn = true;
        digitalWrite(m_portNumber, HIGH);
      }
    }
    void switchOff() 
    { 
      if(m_isSwitchedOn)
      {
        m_isSwitchedOn = false;
        digitalWrite(m_portNumber, LOW);
      }
    }

Et voilà, la classe est terminée.

class Led
{
  private:
    bool m_isSwitchedOn;
    uint32_t m_portNumber;
  public:
    Led(uint32_t thePort): Led(thePort, false) {}
    Led(uint32_t thePort, bool switchOnWhenStarted): m_portNumber(thePort), m_isSwitchedOn(switchOnWhenStarted)
    {
        pinMode(m_portNumber, OUTPUT);
        if(switchOnWhenStarted)
          switchOn();
    }
 
    bool isSwitchOn() const { return m_isSwitchedOn; }
    bool getPort() const { return m_portNumber; }
 
    void switchOn() 
    { 
      if(!m_isSwitchedOn)
      {
        m_isSwitchedOn = true;
        digitalWrite(m_portNumber, HIGH);
      }
    }
    void switchOff() 
    { 
      if(m_isSwitchedOn)
      {
        m_isSwitchedOn = false;
        digitalWrite(m_portNumber, LOW);
      }
    }
};

Etape 2 : Implanter le code de la classe Led

  1. Implanter le code dans le programme blink que vous allez sauvegarder dans un répertoire.
  2. Effectuer la modification du code dans les fonctions setup() et loop(). Normalement, il ne devrait y avoir aucune information dans le code setup().
  3. Compiler & télécharger le code sur la carte.

Correction

Correction

En fait, nous allons désormais déclarer la diode builtinLed comme une variable globale. En effet, c'est un composant physique, ce n'est pas quelque chose que l'on va ajouter ou enlever. On peut donc le créer au moment du chargement du programme, puisque qu'il serait toujours accessible.

class Led
{
  private:
    bool m_isSwitchedOn;
    uint32_t m_portNumber;
  public:
    Led(uint32_t thePort): Led(thePort, false) {}
    Led(uint32_t thePort, bool switchOnWhenStarted): m_portNumber(thePort), m_isSwitchedOn(switchOnWhenStarted)
    {
        pinMode(m_portNumber, OUTPUT);
        if(switchOnWhenStarted)
          switchOn();
    }
 
    bool isSwitchOn() const { return m_isSwitchedOn; }
    bool getPort() const { return m_portNumber; }
 
    void switchOn() 
    { 
      if(!m_isSwitchedOn)
      {
        m_isSwitchedOn = true;
        digitalWrite(m_portNumber, HIGH);
      }
    }
    void switchOff() 
    { 
      if(m_isSwitchedOn)
      {
        m_isSwitchedOn = false;
        digitalWrite(m_portNumber, LOW);
      }
    }
};
 
Led builtinLed(LED_BUILTIN);

Ensuite, la fonction setup() va être simplifié, il n'y a plus besoin d'initialiser le port, c'est le constructeur de la classe Led qui le fait. Donc le contenu de setup() devient trivial.

void setup()
{}

Reste ensuite le code de la boucle où l'on appelle désormais les méthodes switchOn() et switchOff() de la classe Led.

void loop()
{
    builtinLed.switchOn();
    delay(1000);
    builtinLed.switchOff();
    delay(1000);
}

C'est vrai que la lecture du code en devient évidente. Plus besoin même de commentaire.

Etape 3 : Ajouter une option ''debug'' à la classe ''Led''

L'idée est que vous ajouter une option debug qui par défaut est à faux. De plus, il serait bien dans ce cas d'avoir aussi le nom de la “diode” pour pouvoir identifier le composant qui envoie des messages.

Correction

Correction

Pour ce faire, nous devons ajoutons des informations complémentaires notre classe Led :

  • le nom de la diode (une chaîne de caractères),
  • une variable booléenne qui indique si le mode debug est actif ou non.

Donc nous ajoutons deux champs privés à la classe :

  const char* m_name;
  bool m_debug;

Nous devons modifier les constructeurs pour initialiser ces champs supplémentaires.

  Led(uint32_t thePort, const char* theName, bool debugIsActive = false): 
    Led(thePort, false, theName, debugIsActive)
  {}
  Led(uint32_t thePort, bool switchOnWhenStarted, const char* theName, bool debugIsActive = false): 
    m_portNumber(thePort), m_isSwitchedOn(switchOnWhenStarted),
    m_name(theName), m_debug(debugIsActive)
  {
    pinMode(m_portNumber, OUTPUT);
  }

Nous modifions aussi les actions puisque nous souhaitons envoyer un message si le mode debug est actif :

    void switchOn() 
    { 
      if(!m_isSwitchedOn)
      {
        m_isSwitchedOn = true;
        digitalWrite(m_portNumber, HIGH);
        if(m_debug)
            Serial << "Led: " << m_name << ": switch on";
      }
    }
    void switchOff() 
    { 
      if(m_isSwitchedOn)
      {
        m_isSwitchedOn = false;
        digitalWrite(m_portNumber, LOW);
        if(m_debug)
            Serial << "Led: " << m_name << ": switch off";
      }
    }  

Voilà, il ne reste plus qu'à penser à initialiser Serial dans la fonction setup(), ce qui donne le code complet suivant :

template<class T>
UARTClass& operator << (UARTClass& theSerial, const T& theValue)
{
    theSerial.print(theValue);
    return theSerial;
}
 
class Led
{
private:
    bool m_isSwitchedOn;
    uint32_t m_portNumber;
    const char* m_name;
    bool m_debug;
public:
    Led(uint32_t thePort, const char* theName, bool debugIsActive = false): 
      Led(thePort, false, theName, debugIsActive)
    {}
    Led(uint32_t thePort, bool switchOnWhenStarted, const char* theName, bool debugIsActive = false): 
        m_portNumber(thePort), m_isSwitchedOn(switchOnWhenStarted),
        m_name(theName), m_debug(debugIsActive)
    {
        pinMode(m_portNumber, OUTPUT);
    }
    bool isSwitchOn() const { return m_isSwitchedOn; }
    bool getPort() const { return m_portNumber; }
 
    void switchOn() 
    { 
        if(!m_isSwitchedOn)
        {
            m_isSwitchedOn = true;
            digitalWrite(m_portNumber, HIGH);
            if(m_debug)
                Serial << "Led: " << m_name << ": switch on\n";
        }
    }
    void switchOff() 
    { 
        if(m_isSwitchedOn)
        {
            m_isSwitchedOn = false;
            digitalWrite(m_portNumber, LOW);
            if(m_debug)
                Serial << "Led: " << m_name << ": switch off\n";
        }
    }  
};
 
Led builtinLed(LED_BUILTIN, "Builtin", true);
 
// the setup function runs once when you press reset or power the board
void setup() 
{
  Serial.begin(9600);
}
 
// the loop function runs over and over again forever
void loop()
{
    builtinLed.switchOn();
    delay(1000);
    builtinLed.switchOff();
    delay(1000);
}

Etape 4 : Profiter de l'héritage

En fait le mode débogage est un mode qui est intéressant pour tous les composants. On peut donc mutualiser partie du code pour tous les composants.

L'idée est de proposer une classe de base que l'on appellera CustomComponent dont héritera l'ensemble des autres classes composants comme par exemple Led.

    bool isSwitchOn() const { return m_isSwitchedOn; }
    bool getPort() const { return m_portNumber; }
 
    void switchOn() 
    { 
      if(!m_isSwitchedOn)
      {
        m_isSwitchedOn = true;
        digitalWrite(m_portNumber, HIGH);
      }
    }
    void switchOff() 
    { 
      if(m_isSwitchedOn)
      {
        m_isSwitchedOn = false;
        digitalWrite(m_portNumber, LOW);
      }
    }
};

</hidden>

  class CustomComponent
  {
  protected;
    CustomComponent(const char* name, bool debug)
    {}
  public:
    bool debugIsActive() const;
 
    const char* getName() const;    
    const char* header() const; // Génère l'entête de message avec le nom du composant.    
  };
 
class CustomComponent
{
  private: 
    bool m_Debug;
    const char* m_Name;
  protected;
    CustomComponent(const char* theName, bool debug = false): 
        Name(theName), m_Debug(debug)
    {}
  public:
    bool debugIsActive() const { return m_Debug; }
 
    bool EnableDebug() { m_Debug = true; }
    bool DisableDebug() { m_Debug = false; }
 
    const char* getName() const { return m_Name; }
    String header() const  { return (String)m_Name + ": "; }
 
};
 
class Led: public CustomComponent
{
private:
    bool m_isSwitchedOn;
    uint32_t m_portNumber;
  public:
    Led(uint32_t thePort, const char* theName): Led(thePort, theName, false) {}
    Led(uint32_t thePort, const char* theName, bool switchOnWhenStarted): CustomComponent(theName, false),
        m_portNumber(thePort), m_isSwitchedOn(switchOnWhenStarted)
    {
        pinMode(m_portNumber, OUTPUT);
        if(switchOnWhenStarted)
          switchOn();
    }
 
 
 
};

Navigation

Précédent: Obtenir des informations

Suivant: Gérer deux LEDs