IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C++ Discussion :

Extraire ce qui varie d'une classe LCD


Sujet :

C++

  1. #1
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 481
    Points : 13 678
    Points
    13 678
    Billets dans le blog
    1
    Par défaut Extraire ce qui varie d'une classe LCD
    Hello,

    Pour m'amuser, je fais joujou avec du C++ sur un MCU 16 bits avec 16k bytes de ROM et 500 bytes de RAM. En ce moment, je gère un LCD avec deux lignes de 16 caractères, comme ceux gérer par CrystalLiquid d'Arduino. Ces écrans ont deux modes, un avec une interface 8 bits, l'autre avec une interface 4 bits. J'ai commencé par la version 8 bits et je suis arrivé à une classe LCD donc le listing des fonctions peut se résumer à ça :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
        void init();
     
        void writeCommand(uint8_t command);
        void writeData(uint8_t data);
     
        void setRegisterSelect(RegisterSelect selection);
        void pulseEnable();
        void write(uint8_t value);
    init() est une succession d'appels à writeCommand(), c'est une fonction d'assez haut niveau. writeCommand() et writeData() sont très semblables, sont des fonctions de niveau intermédiaire, et elles font toutes les deux appel à setRegisterSelect(), pulseEnable() et write(). Ces 3 dernières fonctions sont des fonctions de bas niveau, en faisant bouger des GPIO sur MCU.

    J'aimerais maintenant passer en mode 4 bits, ce qui me permet d'économiser 4 GPIOs et sur un MCU avec 20 pins, c'est énorme.

    Mon idée était de passer init() en paramètre du constructeur du LCD, mais il fait appel à des méthodes de la classe, ce n'est pas pratique. Comme il n'existe que 2 initialisations possibles, je me suis dit que je pouvais gérer ça facilement avec un constructeur prenant en paramètre le mode (4 ou 8 bits) et fait un simple if / else dans init().

    write() écrit 8 bits (sur 8 GPIOs d'un coup) et pour gérer le mode 4 bits, il me faut aussi une fonction write4bits(), mais ça ne change pas fondamentalement mon problème que je vais enfin vous exposer

    Mon problème est que j'aimerai ben extraire ces 3 (ou 4) fonctions de bas niveau de ma classe LCD puisque leur implémentation varie considérablement en fonction de la manière donc l'écran est raccordé au MCU. J'ai un temps pensé à extraire ça dans une classe LCDLowLevel et donner à manger à LCD une référence vers une instance de LCDLowLevel. Sauf que dés que j'utilise de la virtualité, le linker me dit que je n'ai pas assez de place en ROM. Visiblement, la virtualité semble coûter 15k...

    Une classe LCD template parait tentant, mais comme tout template, je ne peux plus passer de LCD en paramètre d'une fonction, puisque ce sera LCD<truc>...

    Ma dernière solution est de ne pas implémenter ces 3 (ou 4) fonctions mais de laisser leurs déclarations dans ma classe et dans chaque projet où j'utiliserai cette classe / bibliothèque LCD, j'implémenterai différemment ces fonctions.Ce n'est pas très C++esque, mais ça marche bien ^^

    Au final, je me demandais si vous aviez d'autres idées sur la questions

    Mârchi !
    Bktero

  2. #2
    Modérateur

    Homme Profil pro
    Ingénieur électricien
    Inscrit en
    Septembre 2008
    Messages
    1 266
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ingénieur électricien

    Informations forums :
    Inscription : Septembre 2008
    Messages : 1 266
    Points : 4 810
    Points
    4 810
    Par défaut
    Bonsoir

    Je vais suivre avec intérêt cette discussion
    Comme cela à froid, je dirais utiliser le pré-compilateur avec le besoin de définir dans le programme principale avant l'#include un #define LCD4BITS ou un #define LCD8BITS puis l'utilisation des #ifdef pour les parties de code divergentes. Avec soit une erreur de compilation, ou l'un par défaut si le #define n'est pas fait. C'est ce qui est fait pour les temporisations.

    Dans l'aide d'Atmel pour la programmation des micro AVR, c'est clairement dit que le C++, c'est possible mais non recommandé justement car cela bouffe les capacités des micro.

    Delias

  3. #3
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 611
    Points
    30 611
    Par défaut
    Salut,

    Déjà, je voudrais savoir : à quel moment veux tu pouvoir décider du mode de fonctionnement au plus tard à la compilation ou à l'exécution étant donné l'objectif (économiser 4 GPIO), je présumes que tu voudrais le faire à la compilation, mais, comme je peux me tromper...

    Ceci étant dit, je crois que tu devrais aller un peu plus loin dans le raisonnement associé aux template, que tu me sembles avoir écarté un peu trop vite

    Car, de ce que j'ai compris (et pour autant que tu veuilles effectivement choisir au plus tard à la compilation), ce qui t'ennuie le plus, c'est de devoir passer un LED<quelque chose>, mais il n'y a rien qui t'empêche de définir un alias de type pour faire sauter la partie template.

    Plusieurs exemples peuvent être tirés de la STL pour comprendre le principes :

    std::string et std::wstring sont des alias de type de std::basic_string, pour lesquels le paramètre template CHAR_T est défini soit à un char soit à un wchar. Il en va de même pour... en gros, tous les flux que nous pouvons manipuler

    En créant une classe template BasicLed<flag> les alias de types Led4 et/ ou Led8 qui correspondent respectivement à BasicLed<flag4> et BasicLed<Flag8>, tu résoudras ton problème sans même avoir besoin de virtualité et de gestion dynamique de la mémoire (qui, il est vrai, est assez limitée avec 500 bytes de RAM )
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  4. #4
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 113
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 113
    Points : 32 958
    Points
    32 958
    Billets dans le blog
    4
    Par défaut
    Salut,

    héritage sans virtualité : CRTP
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  5. #5
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 186
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 186
    Points : 17 126
    Points
    17 126
    Par défaut
    A tout hasard, fais voir ton code avec les virtuals, notamment les trois classes (base et deux héritières), et quelques fonctions appelantes.

    Qu'est-ce qui t'empêche d'avoir des fonctions templates dans ta bibliothèque? La déduction automatique des paramètres templates est là pour ça.

    par exemple, tu pourrais avoir
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    template <bool is8bits>
    class Led {}; //volontairement vide (SFINAE friendly)
     
    template <>
    class Led<true> {
    public:
        using data_type = uint8_t;
        void writeData(data_type data);//définie dans le .cpp
    };
     
    template <>
    class Led<false> {
    public:
        using data_type = uint8_t;
        void writeData(data_type data);//définie dans le .cpp
    };
     
    using Led4 = Led<false>;
    using Led8 = Led<true>;
     
    template<bool led8bits>
    void writeStream(std::vector<uint8_t> data, Led<led8bits> & led) {
        for ( auto const& d : data) led.writeData(d);
    }
    Ce qui te permettrai d'appeler directement Led8 led; writeData({8,12,32}, led);.
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

  6. #6
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 481
    Points : 13 678
    Points
    13 678
    Billets dans le blog
    1
    Par défaut
    Merci les amis pour vos réponses !

    J'ai rapidement éliminé les templates car dans une discussion précédente, j'avais dit que j'essayais de placer des templates un peu partout et on m'avait dit qu'il ne fallait pas le faire. Maintenant on dit qu'il faut quand même en mettre... Je crois que j'ai encore du boulot avec eux !

    @Delias : c'est une approche C de la chose. Et mon objectif ici est justement d'essayer de faire du C++, dans un but purement d'apprentissage et d'expérimentation. Cette remarque d'Atmel est très tranchée. J'ai fait des choses simples et pratiques en C++ pour le moment, sans que ça ne consomme plus que du C. En revanche, je fais attention et j'ai déjà trouvé quelques chemins où ne pas aller. Pour toi et les autres, j'ai mis le code sur GitHub : https://github.com/Bktero/Planteur/tree/lcd/launchpad

    @koala01: c'est bien sur sélection à la compilation. La solution d'Arduino est ultra simple et flexible et permet de choisir à l'exécution. 2 contreparties : de la RAM pour stocker les GPIO et pas mal de code pour gérer les GPIO. Je n'ai envie ni de l'un ni de l'autre.

    Ce qui m'embête dans les templates, c'est que LCD<truc> peut être caché derrière un typedef, mais je ne vois pas comment faire autre qu'une fonction qui utilise un LCD<nimporte lequel> ne soit pas elle aussi template. Voir mon code plus bas

    Il s'agit d'un LCD et non de LED (même si la réflexion reste la même)

    @Bousk : oula ! Il va falloir que je potasse ça !

    @ternel : oula ! Il falloir que je potasse ça aussi ! Pourrais-tu m'expliquer pourquoi créer LCD en 3 parties et pourquoi faire des using plutôt que des typedef (de ce que j'ai lu, c'est équivalent et using est un plus seulement dans les templates). J'ai lu l'article Wiki sur SFINAE mais je ne vois pas trop le rapport là.... Ca peut failer ? Autre chose qu'un booléen ?

    Je n'ai plus le code avec virtual, mais c'était vraiment le basique du basique, avec des virtuels purs par contre.






    Du coup, j'ai tenté le template et c'est pas mal :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    #include <iostream>
    #include <vector>
    using namespace std;
     
    class LCDLowLevelImpl
    {
    public:
        static void write(uint8_t byte)
        {
            cout << "LCDLowLevelImpl::write(" << (unsigned int) byte << ")" << endl;
        }
    };
     
    class BadImpl
    {
    };
     
    template<class LCDLowLevel, bool use4bits>
    class LCD
    {
    public:
        LCD()
        {
            if(use4bits)
                cout << "LCD::LCD() 4 bits" << endl;
            else
                cout << "LCD::LCD() 8 bits" << endl;
        }
     
        void writeCommand(uint8_t command)
        {
            cout << "LCD::writeCommand(" << (unsigned int) command << ")" << endl;
            LCDLowLevel::write(command);
        }
    };
     
    template<class Impl, bool use4bits>
    void write_commands(LCD<Impl, use4bits> & lcd, std::vector<uint8_t> commands)
    {
        for (auto const& d : commands)
            lcd.writeCommand(d);
    }
     
    int main()
    {
        LCD<LCDLowLevelImpl, true> lcd8;
        lcd8.writeCommand(42);
     
        LCD<LCDLowLevelImpl, false> lcd4;
        lcd4.writeCommand(99);
     
        LCD<BadImpl, false> bad_lcd;
        //bad_lcd.writeCommand(66); // generate clear compiler errors, OK :)
     
        vector<uint8_t> my_commands= {1, 2, 3};
        write_commands(lcd8, my_commands);
     
        return 0;
    }
    Dans mon main(), j'arrive bien à créer plusieurs sortes de LCD et à m'en servir. Je ne sais pas comment m'est venue l'idée d'utiliser des fonctions statiques mais c'est très bien ! Ca ne consomme pas de RAM et évite les indirections pour appeler les fonctions qu'il y a dans la solution où LCD encapsule un objet héritant d'une interface LCDLowLevel (solution avec de la virtualité pure).

    Ce qui me dérange, c'est la signature de la fonction write_commands(), ou encore de writeStream() proposée par ternel. Je veux bien que la flexibilité des templates ait un côut, mais cela alourdi énormément toutes les fonctions (ou classes, qui devront elles aussi être templates) qui voudront utiliser un LCD quelconque. En fait, c'est mon éternel blocage avec les templates...

    Que pensez-vous de tout ça ?

  7. #7
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 611
    Points
    30 611
    Par défaut
    Citation Envoyé par Bktero Voir le message
    @koala01: c'est bien sur sélection à la compilation. La solution d'Arduino est ultra simple et flexible et permet de choisir à l'exécution. 2 contreparties : de la RAM pour stocker les GPIO et pas mal de code pour gérer les GPIO. Je n'ai envie ni de l'un ni de l'autre.

    Ce qui m'embête dans les templates, c'est que LCD<truc> peut être caché derrière un typedef, mais je ne vois pas comment faire autre qu'une fonction qui utilise un LCD<nimporte lequel> ne soit pas elle aussi template. Voir mon code plus bas

    Il s'agit d'un LCD et non de LED (même si la réflexion reste la même)
    Ben, le plus simplement du monde : une fonction
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    temlate <typename Flag>
    void foo(BasicLed<Flag> /* const */ & f){
        /* ... */
    }
    si la fonction se fout pas mal de savoir si tu utilises le mode 4bits ou le mode 8 bits; une fonction spécifique (éventuellement surchargée) proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /* soient Led4 alias de BasicLed<flag4> et
     * Led8 alias de BasicLed<flag8> 
     * (pour reprendre les termes de mon exemple précédant)
     */
    void fonctionSpecifiqueAuMode4Bits(Led4 /* const*/ &){
        /* ... */
    }
    void autreFonctionSpecifiqueAuMode8Bits(Led8 /* const*/ &){
        /* ... */
    }
    Car je présumes que, même si tu venais à brancher deux LCD (si tant est que ce soit possible, mais considérons que ce le soit, pour allimenter la discussion ), tu les brancherais, de toutes façons, tous les deux sur le même mode, si bien que tu n'aurais -- a priori -- jamais besoin des deux spécialisations en même temps.

    Et, quand bien même tu prendrait la décision de brancher un LCD en mode 4 bits et un autre en mode 8 bits (encore moins probable, mais bon, sait on jamais ), il serait sans doute particulièrement intéressant de faire la différence entre les deux au niveau du code, pour s'assurer que l'on n'essaye pas de filer celui qui est branché en mode 4 bits à une fonction qui est prévue pour être appelée avec... celui qui serait branché en mode 8 bits
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  8. #8
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 481
    Points : 13 678
    Points
    13 678
    Billets dans le blog
    1
    Par défaut
    Voici des LED sur 4 bits (ce que tu penses que j'utilise ) :
    Nom : LEDs-2-fiche.jpg
Affichages : 95
Taille : 44,3 Ko

    Voici un LCD avec un bus de données sur 4 ou 8 bits (ce que moi j'utilise ):
    Nom : lcd-e7d1a.jpg
Affichages : 94
Taille : 23,8 Ko

    C'était la blague du matin.

    Ben, le plus simplement du monde : une fonction
    Finalement, la même que ce que j'ai déjà ? Je présume que c'est la contre-partie d'avoir une classe LCD template (avec les avantages qui font avec).

    Car je présumes que, même si tu venais à brancher deux LCD (si tant est que ce soit possible, mais considérons que ce le soit, pour allimenter la discussion ), tu les brancherais, de toutes façons, tous les deux sur le même mode, si bien que tu n'aurais -- a priori -- jamais besoin des deux spécialisations en même temps.

    Et, quand bien même tu prendrait la décision de brancher un LCD en mode 4 bits et un autre en mode 8 bits (encore moins probable, mais bon, sait on jamais ), il serait sans doute particulièrement intéressant de faire la différence entre les deux au niveau du code, pour s'assurer que l'on n'essaye pas de filer celui qui est branché en mode 4 bits à une fonction qui est prévue pour être appelée avec... celui qui serait branché en mode 8 bits
    Je pense que dans 90 % des cas, il n'y aurait qu'un seul LCD. Une solution avec des #define ou alors avec des fonctions déclarées mais non définies (que l'utilisateur de la bibliothèque devra fournir) serait alors tout à fait OK. Mais ce n'est pas impossible d'avoir 2 LCD en même temps et alors il me faut une autre solution.

    Un LCD 4 bits et un LCD 8 bits fournissent exactement les mêmes fonctionnalités. Les LCD comme celui de la photo supportent physiquement les 2 modes. Tu choisis de câbler un mode ou l'autre et tu adaptes alors ton logiciel en conséquence. Les fonctions init(), writeCommand() et writeData() sont différentes puisque dans un cas on envoie un byte en une fois et que dans l'autre il faut l'envoyer en deux fois. Toutes les actions de plus haut niveau se font avec des 3 fonctions. Et ces 3 fonctions se reposent sur quelques fonctions de plus bas niveau, fournies par le paramètre template. C'est pour ça qu'au final, n'importe quelle variation de la classe LCD est équivalente au niveau applicative et que j'ai pas besoin de les distinguer. Il n'y a pas de risque de faire une action 8 bits sur un écran 4 bits (vue l'interface publique fournie par la classe LCD).



    Hier soir en me brossant les dents, j'ai eu une idée supplémentaire : faire en sorte que le paramètre template LCDLowLevel fournissent le mode, puisque après tout, c'est bien lui qui décide du mode. J'ai fait ça ce matin au saut du lit :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    template<class LCDLowLevel>
    class LCD
    {
    public:
        enum Mode {Mode_4bits, Mode_8bits};
     
        LCD()
        {
            if(LCDLowLevel::mode == Mode_4bits)
                cout << "LCD::LCD() 4 bits" << endl;
            else
                cout << "LCD::LCD() 8 bits" << endl;
        }
     
        void writeCommand(uint8_t command)
        {
            cout << "LCD::writeCommand(" << (unsigned int) command << ")" << endl;
            LCDLowLevel::write(command);
        }
    };
     
    class LCDLowLevelImpl
    {
    public:
        using LCDMode = LCD<LCDLowLevelImpl>::Mode;
        static const LCDMode mode = LCDMode::Mode_4bits;
     
        static void write(uint8_t byte)
        {
            cout << "LCDLowLevelImpl::write(" << (unsigned int) byte << ")" << endl;
        }
    };
    On simplifie la création d'un LCD et on allège aussi les prototypes des fonctions prenant un LCD en paramètre :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    template<class Impl>
    void write_commands(LCD<Impl> & lcd, std::vector<uint8_t> commands);
    Pas mal hein ?

Discussions similaires

  1. [ATL] Accéder à un attribut d'une classe qui hérite d'une classe abstraite
    Par chekaoui dans le forum Eclipse Modeling
    Réponses: 0
    Dernier message: 22/07/2014, 15h32
  2. Un attribut qui puisse être une classe A,B ou C ?
    Par visiwi dans le forum Langage
    Réponses: 6
    Dernier message: 29/08/2008, 12h34
  3. Retrouver qui a implémenté une class de TObject.
    Par billbocquet dans le forum Langage
    Réponses: 2
    Dernier message: 05/05/2006, 21h33
  4. destruction d'une classe qui herite de CDialog
    Par philippe V dans le forum MFC
    Réponses: 2
    Dernier message: 03/02/2004, 18h39

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo