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++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2009
    Messages
    4 493
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Loire Atlantique (Pays de la Loire)

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

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 493
    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 282
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 42
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ingénieur électricien

    Informations forums :
    Inscription : Septembre 2008
    Messages : 1 282
    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
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    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 147
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 147
    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

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    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 202
    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);.

  6. #6
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2009
    Messages
    4 493
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Loire Atlantique (Pays de la Loire)

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

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 493
    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 ?

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, 14h32
  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, 11h34
  3. Retrouver qui a implémenté une class de TObject.
    Par billbocquet dans le forum Langage
    Réponses: 2
    Dernier message: 05/05/2006, 20h33
  4. destruction d'une classe qui herite de CDialog
    Par philippe V dans le forum MFC
    Réponses: 2
    Dernier message: 03/02/2004, 17h39

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