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

Arduino Discussion :

Programmation de fonctions


Sujet :

Arduino

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre éprouvé
    Inscrit en
    Juillet 2004
    Messages
    1 070
    Détails du profil
    Informations forums :
    Inscription : Juillet 2004
    Messages : 1 070
    Par défaut Programmation de fonctions
    Bonjour à tous,

    Désireux, de rendre plus pratique et présentable mon programme en cours , j'essaie de créer des onglets particuliers qui contiennent mes fonctions

    par exemple : un onglet "affichage.h" regroupant toutes mes fonctions utilisées dans mon programme etc ...
    j'ajoute que mes fonctions que je place dans le corps de mon programme ( après le LOOP ) se compilent sans erreur

    mais par contre plus rien ne fonctionne et je me heurte à de nombreuses erreurs de type "...was not declared in this scope"

    qu'elle en est la raison principale svp ?
    Images attachées Images attachées    

  2. #2
    Membre Expert
    Profil pro
    Inscrit en
    Septembre 2010
    Messages
    1 595
    Détails du profil
    Informations personnelles :
    Âge : 46
    Localisation : France

    Informations forums :
    Inscription : Septembre 2010
    Messages : 1 595
    Par défaut
    tes fichiers sont bien dans le même répertoire?
    ton fichier .h est bien enregistré? (bon ici, on oublie, ça s'enregistre automatiquement normalement)

  3. #3
    Membre éprouvé
    Inscrit en
    Juillet 2004
    Messages
    1 070
    Détails du profil
    Informations forums :
    Inscription : Juillet 2004
    Messages : 1 070
    Par défaut
    Salut umfred

    Oui, les fichiers sont bien dans le même répertoire

    mais au-délà de çà, c'est surtout le principe que je souhaiterai comprendre
    la fonction lorsqu'elle est sous "LOOP" fonctionne normalement
    mais lorsque je la déplace avec "#include affiche.h" là çà ne marche plus (?)

  4. #4
    Membre Expert
    Profil pro
    Inscrit en
    Septembre 2010
    Messages
    1 595
    Détails du profil
    Informations personnelles :
    Âge : 46
    Localisation : France

    Informations forums :
    Inscription : Septembre 2010
    Messages : 1 595
    Par défaut
    Je ne saurais dire, je ne rencontre pas le soucis en copiant ton code.
    Quelle version d'arduino IDE ? (j'ai testé sous la v2.3.6)

  5. #5
    Membre éprouvé
    Inscrit en
    Juillet 2004
    Messages
    1 070
    Détails du profil
    Informations forums :
    Inscription : Juillet 2004
    Messages : 1 070
    Par défaut
    même version Arduino


    En fait il ne s'agit que d'un exemple d'illustration , mon code est un peu plus volumineux mais je ne savais pas comment le mettre à disposition
    autrement que sous la forme d'un fichier zip
    puisqu'il comporte des sous-programmes .h

    En fait ma question serait plutôt orientée pour savoir comment utiliser efficacement les fichiers .h et .cpp

  6. #6
    Responsable Arduino et Systèmes Embarqués


    Avatar de f-leb
    Homme Profil pro
    Enseignant
    Inscrit en
    Janvier 2009
    Messages
    13 282
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : France, Sarthe (Pays de la Loire)

    Informations professionnelles :
    Activité : Enseignant

    Informations forums :
    Inscription : Janvier 2009
    Messages : 13 282
    Billets dans le blog
    48
    Par défaut
    Bonjour,

    Ce serait plutôt comme ci-dessous.

    sketch_oct15a.ino
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #include "affichage.h"
     
    void setup() {
      // put your setup code here, to run once:
      ma_fonction(10, 10);
    }
     
    void loop() {
      // put your main code here, to run repeatedly:
    }

    affichage.h
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    #ifndef AFFICHAGE_H
    #define AFFICHAGE_H
     
    void ma_fonction(int x, int y); // prototype de la fonction
     
    #endif
    affichage.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    void ma_fonction(int x, int y) {
      // code de la fonction ici
    }

  7. #7
    Expert confirmé

    Homme Profil pro
    mad scientist :)
    Inscrit en
    Septembre 2019
    Messages
    2 935
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Etats-Unis

    Informations professionnelles :
    Activité : mad scientist :)

    Informations forums :
    Inscription : Septembre 2019
    Messages : 2 935
    Par défaut
    mais sommes-nous obligés de répéter 2 fois la même variable
    une fois en déclaration puis en définition
    En C++, il est essentiel de bien distinguer la déclaration, qui informe le compilateur de l’existence d’une variable (en précisant son nom et son type) ou d’une fonction (en indiquant son nom, le type de ses paramètres et le type de retour), de la définition, qui correspond à l’allocation effective de la mémoire pour cette variable ou à la fourniture du corps de la fonction.

    Dans l’IDE Arduino, lorsqu’on crée plusieurs onglets avec l'extension .ino, l’IDE les fusionne automatiquement en un seul gros fichier .ino avant la compilation puis il parcourt ce fichier et génère automatiquement en début de code toutes les déclarations de fonctions nécessaires, en indiquant leur nom, leurs paramètres et leur type de retour.

    => Cela permet au compilateur de connaître toutes les fonctions, même si elles sont définies plus bas dans le code, et explique pourquoi on peut appeler une fonction avant sa définition sans problème. Grâce à ce mécanisme, il n’est souvent pas nécessaire d’utiliser des fichiers `.h`, `extern` ou d’autres séparations classiques.

    En revanche, dans le cadre d'une compilation séparée standard avec des fichiers `.h` et `.cpp, chaque fichier est compilé indépendamment.

    Il faut se souvenir que le préprocesseur, lorsqu’il rencontre un `#include`, ne fait rien d’autre que copier textuellement le contenu du fichier `.h` à l’endroit où le `#include` apparaît. Cela signifie que tout ce qui se trouve dans le `.h` — qu’il s’agisse de déclarations ou de définitions — est littéralement inséré dans chaque fichier qui inclut ce `.h`.

    Si ce fichier `.h` contient des définitions, c’est-à-dire des allocations mémoire pour des variables ou le corps complet de fonctions, et qu’il est inclus plusieurs fois dans un même projet ou dans plusieurs fichiers `.cpp`, le compilateur va générer plusieurs définitions identiques pour chaque fichier compilé.

    Puis, lors de l’édition de lien (qui consiste à assembler tous les fichiers compilés en un exécutable unique), le linker va se retrouver avec ces multiples définitions et produira une erreur, car il ne peut pas gérer plusieurs allocations pour la même variable ou plusieurs corps identiques pour la même fonction.

    Pour éviter ce problème, la règle d'or consiste à mettre uniquement les déclarations dans le fichier `.h`. Ces déclarations indiquent simplement au compilateur que telle fonction ou telle variable existe, avec son type et son nom, mais sans réserver de mémoire ni fournir le corps de la fonction et que le linker les retrouvera plus tard.

    Les définitions, qui correspondent à l’allocation réelle de la mémoire ou à la mise en place du code de la fonction, sont placées dans un fichier `.cpp` qui sera compilé une seule fois. De cette façon, on peut inclure le même fichier `.h` dans tous les fichiers nécessaires — qu’il s’agisse d’autres `.cpp` ou même d’un `.ino` dans l’IDE Arduino — et le compilateur saura de quoi il s’agit grâce aux déclarations, tout en évitant de créer plusieurs allocations mémoire pour la même entité.

    Donc pour votre question, OUI, on réptète deux fois, mais une fois c'est la DECLARATION et l'autre fois c'est la DEFINITION.

    pour une variable, quand on ne veut pas allouer la mémoire (ie on fait la déclaration) on utilise extern
    pour une fonction, quand on ne veut pas allouer la mémoire (sans le code donc) pas besoin de extern, on met juste le prototype de la fonction suivi d'un ; au lieu du corps de la fonction.





    C'est plus clair?

  8. #8
    Membre éprouvé
    Inscrit en
    Juillet 2004
    Messages
    1 070
    Détails du profil
    Informations forums :
    Inscription : Juillet 2004
    Messages : 1 070
    Par défaut
    Merci encore à tous les deux pour votre patience....

    Pour savoir si c'est clair je propose un exemple concret

    Dans mon fichier .ino , j'ai ceci :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    GxEPD2_BW<GxEPD2_213_BN, GxEPD2_213_BN::HEIGHT> display(GxEPD2_213_BN(/*CS=5*/ 5, /*DC=*/ 17, /*RST=*/ 16, /*BUSY=*/ 4)); 
    U8G2_FOR_ADAFRUIT_GFX u8g2Fonts; 
    --/--
    #include "affichage.h"
    dans mon fichier affichage.h , j'ai ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    #ifndef AFFICHAGE_H
    #define AFFICHAGE_H
     
    void DisplayAstronomySection(int x, int y);
     
    #endif // AFFICHAGE_H
    et enfin dans mon fichier affichage.cpp j'ai la fonction suivante :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    #include "affichage.h"
    #include <Arduino.h>
     
    //#########################################################################################
    void DisplayAstronomySection(int x, int y) {   <======
      display.drawRect(x, y+85 , 240, 25, GxEPD_BLACK);
      u8g2Fonts.setFont(u8g2_font_helvB08_tf);
      drawString(x+5,y+95, affichalarme, LEFT);
      display.display(false); // Full screen update mode
    }
    si je veux compiler par ex
    display.drawRect(x, y+85 , 240, 25, GxEPD_BLACK);


    je dois donc "découper" la dite fonction dans affichage.h en :

    void display(bool partial_update_mode = false) ;
    void drawRect(int16_t x, int16_t y, int16_t w, int16_t h,
    uint16_t color);
    etc ...

    c'est çà ?

  9. #9
    Expert confirmé

    Homme Profil pro
    mad scientist :)
    Inscrit en
    Septembre 2019
    Messages
    2 935
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Etats-Unis

    Informations professionnelles :
    Activité : mad scientist :)

    Informations forums :
    Inscription : Septembre 2019
    Messages : 2 935
    Par défaut
    Non ce serait dommage d'avoir à reprendre chaque fonction de la bibliothèque d'affichage comme drawRect() et d'en faire une fonction dans votre .cpp




    la question à vous poser est plutôt de savoir quelle est la logique des différents fichiers pour savoir quoi déclarer et où

    Dans votre exemple, vous voulez avoir des onglets qui gèrent les affichages (affichage.h et affichage.cpp) donc il me semblerait logique de mettre la définition de display et de u8g2Fonts dans affichage.cpp et vous conservez toutes le fonctions liées à l'affichage et qui utilisent display dans affichage.cpp. Les autres modules (si la logique est respectée) n'utilisant que les fonctions n'ont pas à connaître l'existence de ces variables. il faudra sans doute avoir dans affichage.cpp une fonction begin() qui se chargera de l'initialisation de l'écran et que vous appelez depuis le setup().

    C'est pour moi ce qui serait cohérent.




    Sinon si vous voulez rester avec votre structure actuelle, il faut que affichage.cpp connaisse l'existence de display et de u8g2Fonts.
    Il faudra donc rajouter dans affichage.h leur déclaration pour que affichage.cpp puisse l'utiliser. On mettrait donc

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    #ifndef AFFICHAGE_H
    #define AFFICHAGE_H
     
    extern GxEPD2_BW<GxEPD2_213_BN, GxEPD2_213_BN::HEIGHT> display;
    extern U8G2_FOR_ADAFRUIT_GFX u8g2Fonts;
     
    void DisplayAstronomySection(int x, int y);
     
    #endif // AFFICHAGE_H
    et le linker retrouvera ces 2 variables qui sont définies dans le .ino au moment de l'édition de lien.



    Sinon il est d'usage de mettre tous les include au début du fichier, donc pas après la déclaration de la variable
    ==>

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    // les includes
    #include "affichage.h"
     
    // les variables globales 
    GxEPD2_BW<GxEPD2_213_BN, GxEPD2_213_BN::HEIGHT> display(GxEPD2_213_BN(/*CS=5*/ 5, /*DC=*/ 17, /*RST=*/ 16, /*BUSY=*/ 4)); 
    U8G2_FOR_ADAFRUIT_GFX u8g2Fonts; 
     
    ...

  10. #10
    Membre éprouvé
    Inscrit en
    Juillet 2004
    Messages
    1 070
    Détails du profil
    Informations forums :
    Inscription : Juillet 2004
    Messages : 1 070
    Par défaut
    Salut Jay M

    la question à vous poser est plutôt de savoir quelle est la logique des différents fichiers pour savoir quoi déclarer et où

    Dans votre exemple, vous voulez avoir des onglets qui gèrent les affichages (affichage.h et affichage.cpp) donc il me semblerait logique de mettre la définition de display et de u8g2Fonts dans affichage.cpp et vous conservez toutes le fonctions liées à l'affichage et qui utilisent display dans affichage.cpp. Les autres modules (si la logique est respectée) n'utilisant que les fonctions n'ont pas à connaître l'existence de ces variables. il faudra sans doute avoir dans affichage.cpp une fonction begin() qui se chargera de l'initialisation de l'écran et que vous appelez depuis le setup().

    C'est pour moi ce qui serait cohérent.
    Oui, vous avez bien saisi ma demande, en fait je cherche effectivement à faire des onglets regroupant les entités comme ici l'affichage

    ce que je ne comprends pas , c'est l'appel de la fonction begin()
    dans non cas : display.init(115200, true, 2, false);
    je pensais qu'elle ne pouvait se faire que dans le setup()

    j'ai donc repris le contenu de affichage.h comme suit :
    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
     
    #ifndef AFFICHAGE_H
    #define AFFICHAGE_H
     
    enum alignmentType {LEFT, RIGHT, CENTER};
     
    extern bool P1;
    extern bool P2;
    extern const float voltage; 
    extern float AHT_Temp ;
    extern float AHT_Humidite;
    extern uint8_t percentage;
    extern String(affichalarme);
     
    void InitialiseDisplay() ;
    void drawString(int x, int y, String text, alignmentType alignment);
    void DisplayAstronomySection(int x, int y);
    void Draw_Heading_Section();
    void DrawBattery(int x, int y) ;
    void DrawPanneaux(int x, int y);
    void Draw_Temp_Hum_Section(int x, int y);
    void full_sreeen();
    void powerOff();
     
    #endif // AFFICHAGE_H
    puis affichage.cpp

    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
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
     
    #include <Arduino.h>
    #include <GxEPD2_BW.h>
    #include <U8g2_for_Adafruit_GFX.h>
     
    #include "affichage.h"
     
    #define ENABLE_GxEPD2_display 0
    #define SCREEN_WIDTH   212
    #define SCREEN_HEIGHT  104
    #define Large 7    // For best results use odd numbers
    #define Small 3    // For best results use odd numbers
    GxEPD2_BW<GxEPD2_213_BN, GxEPD2_213_BN::HEIGHT> display(GxEPD2_213_BN(/*CS=5*/ 5, /*DC=*/ 17, /*RST=*/ 16, /*BUSY=*/ 4)); 
    U8G2_FOR_ADAFRUIT_GFX u8g2Fonts; 
     
    #define pinVb                35                  //* mesure BATT  
     
     
    String(affichdate);
    String(affichHeure);
     
     
    //#########################################################################################
    void InitialiseDisplay() {
      display.init(115200, true, 2, false);
      display.setRotation(1);                    // Use 1 or 3 for landscape modes
      u8g2Fonts.begin(display);                  // connect u8g2 procedures to Adafruit GFX
      u8g2Fonts.setFontMode(1);                  // use u8g2 transparent mode (this is default)
      u8g2Fonts.setFontDirection(0);             // left to right (this is default)
      u8g2Fonts.setForegroundColor(GxEPD_BLACK); // apply Adafruit GFX color
      u8g2Fonts.setBackgroundColor(GxEPD_WHITE); // apply Adafruit GFX color
      u8g2Fonts.setFont(u8g2_font_helvB10_tf);   // Explore u8g2 fonts from here: https://github.com/olikraus/u8g2/wiki/fntlistall
      display.fillScreen(GxEPD_WHITE);
      display.setFullWindow();
    }
     
    //#########################################################################################
    void drawString(int x, int y, String text, alignmentType alignment) {
      int16_t  x1, y1; //the bounds of x,y and w and h of the variable 'text' in pixels.
      uint16_t w, h;
      display.setTextWrap(false);
      display.getTextBounds(text, x, y, &x1, &y1, &w, &h);
      if (alignment == RIGHT)  x = x - w;
      if (alignment == CENTER) x = x - w / 2;
      //if (alignment == LEFT) x = x + w ;
      u8g2Fonts.setCursor(x, y + h);
      u8g2Fonts.print(text);
    }
     
    //#########################################################################################
    void DisplayAstronomySection(int x, int y) {
      //display.drawRect(x+5, y+85 , 220, 25, GxEPD_BLACK);
      display.drawRect(x, y+85 , 240, 25, GxEPD_BLACK);
      u8g2Fonts.setFont(u8g2_font_helvB08_tf);
      drawString(x+5,y+95, affichalarme, LEFT);
      display.display(false); // Full screen update mode
    }
     
    //#########################################################################################
    void Draw_Heading_Section() {
      u8g2Fonts.setFont(u8g2_font_helvB08_tf);
      drawString(2, 1, affichdate+" "+affichHeure, LEFT);
      //display.drawLine(0, 11, 200, 11, GxEPD_BLACK);
    }
     
    //#########################################################################################
    void DrawBattery(int x, int y) {
     
      if (voltage > 1 ) { // Only display if there is a valid reading
        display.drawRect(x + 15, y - 12, 19, 10, GxEPD_BLACK);
        display.fillRect(x + 34, y - 10, 2, 5, GxEPD_BLACK);
        display.fillRect(x + 17, y - 10, 15 * percentage / 100.0, 6, GxEPD_BLACK);
        drawString(x + 60, y - 11, String(percentage) + "%", RIGHT);
      }
    }
     
    //#########################################################################################
    void DrawPanneaux(int x, int y){
      display.drawRect(x, y, x+184,y+60, GxEPD_BLACK);
      //Panneau droit
      display.drawRect(x+2, y+2, x+40,y+56, GxEPD_BLACK);  // P11
      display.drawRect(x+47, y+2, x+40,y+56, GxEPD_BLACK); // P12
      drawString(x + 23, y+27, "P1", CENTER);
      display.fillRect(x + 93, y, 3, 75, GxEPD_BLACK);
      //Panneau gauche
      display.drawRect(x+98, y+2, x+40,y+56, GxEPD_BLACK);  // P22
      display.drawRect(x+142, y+2, x+40,y+56, GxEPD_BLACK); // P21
      drawString(x + 164, y+27, "P2", CENTER);
      if (!P1) {
          display.fillRoundRect(x+49, y+4, x+36, y+52 ,4, GxEPD_BLACK);
      }else{
          display.fillRoundRect(x+4, y+4, x+36, y+52 ,4, GxEPD_BLACK);
      }
      if (!P2) {
          display.fillRoundRect(x+100, y+4, x+36, y+52,4, GxEPD_BLACK);   
      }else{
          display.fillRoundRect(x+144, y+4, x+36, y+52,4, GxEPD_BLACK);
      }
     
    }
     
    //#########################################################################################
    void Draw_Temp_Hum_Section(int x, int y){
      //sensors_event_t humidity, temp;
      //aht.getEvent(&humidity, &temp);// populate temp and humidity objects with fresh data
      //Serial.print("Temperature: "); Serial.print(temp.temperature,1); Serial.println(" degrees C");
      //Serial.print("Humidity: "); Serial.print(humidity.relative_humidity,0); Serial.println("% rH");
      display.drawRect(x, y,x+10,y+60, GxEPD_BLACK); 
      display.drawLine(x, y+36, x+60, y+36, GxEPD_BLACK);
      u8g2Fonts.setFont(u8g2_font_helvB18_tf);
      //drawString(210,35, String(temp.temperature,0)+"c", CENTER);
      drawString(210,35, String(AHT_Temp,0)+"c", CENTER);
      //drawString(210,70, String(humidity.relative_humidity,0)+"%", CENTER);
      drawString(210,70, String(AHT_Humidite,0)+"%", CENTER);
      delay(500);
    }
     
    void full_sreeen(){
      display.display(false); // Full screen update mode 
    }
     
    void powerOff(){
     display.powerOff();  
    }
    j'ai du créer 2 fonctions supplémentaires car appelées par mon .ino
    - void full_sreeen()
    - void powerOff()

    après de nombreuse galères , le programme se compile
    après la partie affichage , je continue avec la partie RTC ......

  11. #11
    Expert confirmé

    Homme Profil pro
    mad scientist :)
    Inscrit en
    Septembre 2019
    Messages
    2 935
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Etats-Unis

    Informations professionnelles :
    Activité : mad scientist :)

    Informations forums :
    Inscription : Septembre 2019
    Messages : 2 935
    Par défaut
    L’appel a begin se fait dans le set up mais rien ne vous empêche de mettre cela au fin fond d’un appel de fonctions imbriquées. L’important c’est que ce soit fait avant que vous ne commenciez à utiliser votre écran….

    setup() est juste une fonction comme une autre, appelée une seule fois par la fonction main() qui est générée automatiquement par l’IDE. Pas de magie.

    Vous avez main() qui appelle setup() qui peut appeler preparerEcran() qui appelle begin() par exemple

    ___

    Sinon, oui toutes les fonctions qui doivent être connues en dehors de votre .cpp doivent être déclarées dans le .h et ce .h inclus dans le fichier où vous voulez utiliser la fonction.

  12. #12
    Membre éprouvé
    Inscrit en
    Juillet 2004
    Messages
    1 070
    Détails du profil
    Informations forums :
    Inscription : Juillet 2004
    Messages : 1 070
    Par défaut
    Salut Jay M

    Je commence à comprendre ( du moins j'espère) le principe de base
    à savoir dans un fichier .h et .cpp

    mettre "extern" dans le fichier .h lorsqu'une variable est créée ailleurs dans un autre sous-programme
    mettre aussi la fonction "void trucmuche (..) ;"

    mais comment fait-on lorsque dans un fichier .cpp , j'utilise une fonction d'autre .cpp ?

  13. #13
    Expert confirmé

    Homme Profil pro
    mad scientist :)
    Inscrit en
    Septembre 2019
    Messages
    2 935
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Etats-Unis

    Informations professionnelles :
    Activité : mad scientist :)

    Informations forums :
    Inscription : Septembre 2019
    Messages : 2 935
    Par défaut
    Salut,

    Imaginez que vous ayez a.h, a.cpp et b.h et b.cpp

    Si dans b.cpp vous voulez utiliser une fonction fonctionA() définie dans a.cpp, l’approche traditionnelle est de déclarer le prototype de fonctionA() dans a.h et dans b.cpp vous faites un include de a.h

    De même, si dans a.cpp vous voulez utiliser une fonction fonctionB() définie dans b.cpp, vous déclarez le prototype de fonctionB() dans b.h et dans a.cpp vous faites un include de b.h

    En resumé le fichier header .h permet de déclarer tout ce qu’on veut faire connaître à l’extérieur de son .cpp. Tout autre fichier .cpp voulant utiliser un de ces éléments n’aura qu’à inclure ce .h

  14. #14
    Membre éprouvé
    Inscrit en
    Juillet 2004
    Messages
    1 070
    Détails du profil
    Informations forums :
    Inscription : Juillet 2004
    Messages : 1 070
    Par défaut
    ok merci bien Jay M
    je vais..tenter

    a-t-on le droit de décliner les mêmes << #includes >> de façon infinie
    dans chaque sous-programmes comme par ex
    #include <SPI.h>
    #include <Wire.h>

  15. #15
    Expert confirmé

    Homme Profil pro
    mad scientist :)
    Inscrit en
    Septembre 2019
    Messages
    2 935
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Etats-Unis

    Informations professionnelles :
    Activité : mad scientist :)

    Informations forums :
    Inscription : Septembre 2019
    Messages : 2 935
    Par défaut
    Vous pouvez avoir autant d’include que vous voulez, ça ne fait que insérer le texte du .h à cet endroit du fichier avant de compiler.

    L’usage de « gardes » en début de fichier .h qui disent « si ce fichier a déjà été importé pas la peine de le rajouter » permettra d’éviter des boucles dans les include (si a.h inclut b.h et inversement et que vous mettez les deux dans le même fichier tiers les inclusions de texte seraient infinie on rajoute a.h et b.h puis le compilateur relit et voit que a.h a aussi des include dont b.h donc injecte à nouveau b.h et qui contient l’inclusion de a.h etc et donc ça planterait.

    Donc c’est pour ça que Toto.h par exemple est toujours encadré par un #ifndef
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    #ifndef TOTO_H
    #define TOTO_H
     
    …
     
    #endif
    Comme ça si le define a déjà eu lieu lors de la prochaine tentative d’inclusion le code ne sera pas injecté et donc la boucle sans fin sera évitée.



    Sinon pour votre question, oui il faut inclure les DÉCLARATIONS de tout ce dont vous avez besoin. Comme expliqué une déclaration n’alloue pas de mémoire c’est juste une information que vous donnez au compilateur sur le nom et type d’une variable ou prototype de fonction. C’est OK de lui expliquer 10 fois la même chose - du moment que c’est bien la même chose. (Vous ne pouvez pas lui dire que vous avez une variable externe qui s’appelle toto et une fois c’est un entier et une autre fois c’est un float - il faut rester cohérent). C’est aussi pour cela que l’on ne met pas de DEFINITION dans un .h sinon on se retrouve avec la variable allouée plusieurs fois et ça n’est pas possible bien sûr .

    (Il y a quelques exceptions à tout cela mais c’est pour des concepts et besoins plus avancé donc appliquez déjà ces règles et vous verrez ça va tout seul).

    -> un module (.h et .cpp) doit avoir un sens et être cohérent (tout ce qui concerne l’affichage par exemple)
    -> le .h ne contient aucune définition, que des déclarations et a son #ifndef
    -> le .cpp reprend bien exactement le meme type où signature pour la définition des variables et fonctions.
    -> Chaque fichier .cpp doit inclure son propre .h en premier pour vérifier la cohérence des déclarations et éviter les dépendances cachées.
    -> si vous voulez utiliser un module ailleurs vous incluez son .h dans votre .cpp ou .ino (éventuellement on peut l’inclure dans le .h si des fonctions de ce nouveau module utilisent un type par exemple décrit dans l’autre .h)

  16. #16
    Membre éprouvé
    Inscrit en
    Juillet 2004
    Messages
    1 070
    Détails du profil
    Informations forums :
    Inscription : Juillet 2004
    Messages : 1 070
    Par défaut
    merci Jay M

    je souhaiterai vous soumettre mon croquis si vous êtes d'accord car je galère sur une partie
    à savoir l'affichage de la date du jour et de l'heure ( pourtant affichées sur la console mais que je n'arrive maintenant plus à afficher du le E-paper )
    mais comment faire s'il comporte plusieurs fichier.h et il me semble que pour la compréhension , il faut l'ensemble de tous les fichiers
    un zip peut-être ?

  17. #17
    Expert confirmé

    Homme Profil pro
    mad scientist :)
    Inscrit en
    Septembre 2019
    Messages
    2 935
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Etats-Unis

    Informations professionnelles :
    Activité : mad scientist :)

    Informations forums :
    Inscription : Septembre 2019
    Messages : 2 935
    Par défaut
    Citation Envoyé par cobra38 Voir le message
    il faut l'ensemble de tous les fichiers - un zip peut-être ?
    oui un zip. j'essayerais de jeter un oeil demain mais j'ai de la famille qui vient déjeuner.

  18. #18
    Membre éprouvé
    Inscrit en
    Juillet 2004
    Messages
    1 070
    Détails du profil
    Informations forums :
    Inscription : Juillet 2004
    Messages : 1 070
    Par défaut
    pour vous situer le problème il s'agit de la conversion de 3 variables ( je vous avez déjà solliciter sur ce problème dans un post précédent en Juillet)
    - affichdate
    - affichHeure
    - affichalarme

    pour mémoire ces 3 variables sont créés avec désormais dans 3 fonctions distincts
    par ex pour affichdate sous la forme de variable "static" dans le sous-programme "ephemeride.cpp"

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    //#########################################################################################
    void afficherDate(int jour, int mois, int annee)
    {
      char texte[20];
      static String(affichdate);  // <======================
      sprintf(texte, "%02d/%02d/%04d", jour, mois, annee);
      afficherTexte(texte);
      affichdate = " Date : ";
      affichdate += texte;
    }
    ou

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    //#########################################################################################
    void afficherHeure(int heure, int minute, int seconde)
    {
      char texte[22];
      static String(affichHeure); // <=========================
      sprintf(texte, "%02d:%02d:%02d", heure, minute, seconde);
      afficherTexte(texte);
      affichHeure = " Heure UTC: ";
      affichHeure += texte;
    }

    ensuite ces variables sont utilisées pour l'affichage dans une autre fonction mais dans un autre sous-programme : "affichage.cpp" par ex :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    //#########################################################################################
    void Draw_Heading_Section() {
      u8g2Fonts.setFont(u8g2_font_helvB08_tf);
      drawString(2, 1, affichdate+affichHeure, LEFT);
    }
    je précise que le programme .ino mais sans ces sous-programme se compile normalement sans erreur mais dès que je crée les sous-programme , j'obtiens ceci :

    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
     
    n file included from C:\Users\Utilisateur\Documents\Arduino\test_char\ephemeride.cpp:3:
    C:\Users\Utilisateur\Documents\Arduino\test_char\affichage.h:16:31: error: 'String' has not been declared
       16 | void drawString(int x, int y, String text, alignmentType alignment);
          |                               ^~~~~~
    In file included from C:\Users\Utilisateur\Documents\Arduino\test_char\DS3231.cpp:4:
    C:\Users\Utilisateur\Documents\Arduino\test_char\affichage.h:16:31: error: 'String' has not been declared
       16 | void drawString(int x, int y, String text, alignmentType alignment);
          |                               ^~~~~~
    C:\Users\Utilisateur\Documents\Arduino\test_char\test_char.ino: In function 'void setup()':
    C:\Users\Utilisateur\Documents\Arduino\test_char\test_char.ino:27:4: error: 'StartTime' was not declared in this scope; did you mean 'strptime'?
       27 |    StartTime = millis();
          |    ^~~~~~~~~
          |    strptime
    In file included from C:\Users\Utilisateur\Documents\Arduino\test_char\affichage.cpp:3:
    C:\Users\Utilisateur\Documents\Arduino\test_char\affichage.h:16:31: error: 'String' has not been declared
       16 | void drawString(int x, int y, String text, alignmentType alignment);
          |                               ^~~~~~
    C:\Users\Utilisateur\Documents\Arduino\test_char\affichage.cpp: In function 'void DisplayAstronomySection(int, int)':
    C:\Users\Utilisateur\Documents\Arduino\test_char\affichage.cpp:51:24: error: 'affichalarme' was not declared in this scope
       51 |   display.display(false); // Full screen update mode
          |                        ^~~~~~~~~~~~
    C:\Users\Utilisateur\Documents\Arduino\test_char\affichage.cpp: In function 'void Draw_Heading_Section()':
    C:\Users\Utilisateur\Documents\Arduino\test_char\affichage.cpp:58:20: error: 'affichdate' was not declared in this scope; did you mean 'afficherDate'?
       58 | }
          |                    ^         
          |                    afficherDate
    C:\Users\Utilisateur\Documents\Arduino\test_char\affichage.cpp:58:31: error: 'affichHeure' was not declared in this scope; did you mean 'afficherHeure'?
       58 | }
          |                               ^          
          |                               afficherHeure
    exit status 1
     
    Compilation error: 'String' has not been declared
    Vous l'aurez compris que mon problème se situe sur la "portance" des variables , je n'arrive à en comprendre le principe , tout en ayant pourtant pris soin
    de faire apparaitre dans chaque sous-programme les "include" concernés

    merci par avance
    Fichiers attachés Fichiers attachés

  19. #19
    Expert confirmé

    Homme Profil pro
    mad scientist :)
    Inscrit en
    Septembre 2019
    Messages
    2 935
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Etats-Unis

    Informations professionnelles :
    Activité : mad scientist :)

    Informations forums :
    Inscription : Septembre 2019
    Messages : 2 935
    Par défaut
    pour mémoire ces 3 variables sont créés avec désormais dans 3 fonctions distincts
    si je prends ce code par exemple

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    void afficherDate(int jour, int mois, int annee)
    {
      char texte[20];
      static String(affichdate);  // <======================
      sprintf(texte, "%02d/%02d/%04d", jour, mois, annee);
      afficherTexte(texte);
      affichdate = " Date : ";
      affichdate += texte;
    }
    La syntaxe correcte est

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    static String affichdate;
    Les parenthèses dans String(affichdate) sont interprétées comme une tentative d’appel de constructeur, ce qui n’a pas de sens ici pour une déclaration de variable

    donc il faut mettre (idem pour l'heure)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    void afficherDate(int jour, int mois, int annee)
    {
      char texte[20];
      static String affichdate;
      sprintf(texte, "%02d/%02d/%04d", jour, mois, annee);
     
      afficherTexte(texte);
     
      affichdate = " Date : ";
      affichdate += texte;
    }
    ensuite vient le souci de rendre visible la variable affichdate à l'extérieur de la fonction et du module, et c'est là que ça se gâte : Une variable déclarée avec static à l’intérieur d’une fonction n’est visible que dans cette fonction. Elle conserve sa valeur entre les appels, mais elle reste locale.

    Si vous souhaitez qu’une autre fonction y accède, il faut la définir en dehors de toute fonction, c’est-à-dire au niveau global de ce fichier, sans le mot-clé static (En C++, le mot clé `static` réduit la portée de la variable à l’unité de compilation (en gros le fichier) dans le cas d’une variable globale, ou à la fonction dans laquelle elle est déclarée pour une variable locale.), puis la déclarer dans le .h (ou ailleurs) avec extern.

    Par exemple dans ephemeride.cpp, au début vous définissez
    ;

    Et dans ephemeride.h :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    extern String affichdate;
    Tout fichier qui fera include de ephemeride.h connaîtra alors la variable affichdate. Il en va de même pour affichHeure. et autres variables que vous voulez exporter.






    Cela dit, ce n'est pas beau !

    par exemple dans
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    void Draw_Heading_Section() {
      u8g2Fonts.setFont(u8g2_font_helvB08_tf);
      drawString(2, 1, affichdate+affichHeure, LEFT);
    }
    Vous utilisez ces deux variables dans cette fonction mais cette fonction en elle même ne garantit pas que vous ayez mis à jour affichdate et affichHeure. ça va afficher ce qu'il y a dedans qui date potentiellement d'un vieil appel aux fonctions. C'est source d'erreurs.

    il faudrait restructurer un peu tout cela pour obtenir de la cohérence. J'ai déjà dû vous le dire par le passé, ce n'est pas une bonne pratique que de dépendre d'une variable stockée quelque par ailleurs et on ne sait pas trop comment elle évolue. On utilise généralement des paramètres pour passer du contenu et on laisse à l'appelant la décision du stockage de cette variable et combien de temps elle va être conservée.

    Vous pourriez par exemple avoir la fonction obetnirDate qui retourne la Date bien formatée (faire idem pour obtenirHeure)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    String obetnirDate(int jour, int mois, int annee)
    {
      char texte[20];
      snprintf(texte, sizeof texte, "%02d/%02d/%04d", jour, mois, annee);
      String affichdate = " Date : ";
      affichdate += texte;
      afficherTexte(texte);
      return affichdate;
    }
    La fonction vous retourne une String (même si je n'aime pas trop les Strings).

    De même, plutôt que de cacher dans la fonction Draw_Heading_Section() l'accès aux variables, la fonction pourrait prendre en paramètre le texte à afficher

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    void Draw_Heading_Section(String & heading) {
      u8g2Fonts.setFont(u8g2_font_helvB08_tf);
      drawString(2, 1, heading, LEFT);
    }
    et dans votre setup() vous feriez
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    void setup() 
    {
       Serial.begin(115200); 
      InitialiseDisplay();
      Init_RTC();
      String date = obetnirDate(jour,mois,annee);
      Serial.println("\n-----------");
      String heure  obtenirHeure(heure,minute,seconde);
      Serial.println("\n-----------"); 
      Infos_RTC();
      String enTete = date + heure;
      Draw_Heading_Section(enTete); 
      ...
    ce serait plus propre.

  20. #20
    Membre éprouvé
    Inscrit en
    Juillet 2004
    Messages
    1 070
    Détails du profil
    Informations forums :
    Inscription : Juillet 2004
    Messages : 1 070
    Par défaut
    Merci Jay M
    Mille pardons pour le "dérangement dominical"

    Je digère tout çà

    pascal

Discussions similaires

  1. Mettre a jour un programme en fonction de l'utilisateur
    Par morgan47 dans le forum VB 6 et antérieur
    Réponses: 6
    Dernier message: 02/05/2007, 11h33
  2. Réponses: 1
    Dernier message: 28/03/2007, 16h33
  3. Réponses: 10
    Dernier message: 11/01/2007, 22h45
  4. Programmer une fonction joindre_fichier
    Par leCcsympas dans le forum Réseau
    Réponses: 5
    Dernier message: 03/12/2006, 20h51
  5. Réponses: 2
    Dernier message: 31/05/2005, 10h50

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