Problème création librarie

bonsoir, je suis dans le même cas ! Je veux créer ma première librairie. Je crée un répertoire dans libraries j'y met un .cpp et son .h. Je relance l'IDE. La librairie apparait bien. Quand je l'installe, l'IDE ajoute un #include gvc_communications.h" au somme du .ino. Et quand je compile, toutes les variables déclarées dans le .h sont affichées comme étant déclarées 2 fois : une fois dans le répertoire libraries et un fois dans le répertoire sketch du répertoire temporaire où l'Ide range les fichiers crées.
Je pense avoir tout essayé depuis 3 jours : supprimer les répertoires, ajouter un fichier library.properties, ... rien n'y fait.
C'est effectivement à en devenir dingue ! Ce soir j'en ai ras le bol, surtout que je bataille depuis plus d'une heure pour me connecter sur ce forum. Quand je serai calmé, je pourrai vous joindre le .cpp, le .h et la liste des erreurs.

Bonne nuit à tous
Jean à Grenoble

Bonjour,

J'ai déplacé ton post vers un nouveau topic car ton problème n'a pas grand chose à voir avec le sujet sur lequel tu t'es greffé.

J'ai l'impression que tu confonds déclaré et défini.
Quand tu dis que tu as déclaré tes variables dans le .h, je pense que en fait tu les as défini dans le .h.
Les variables doivent être définies dans un .cpp (ou dans un .ino) et si nécessaire déclarées dans le .h, sinon comme le .h sera en général appelé plusieurs fois durant les compilations, il y aura multi définitions à l'édition de lien (et non multi déclarations)

définition:
int toto;

déclaration:
extern int toto;

Bonjour,

j'ai fait un sketch minimum, j'ai 'importé' ma librairie et j'ai les mêmes erreurs ... Je vous joins les fichiers
gvc_communications.cpp (6,6 Ko)
gvc_communications.h (5,4 Ko)
test_librairie.ino (380 Octets)
erreurs.txt (25,1 Ko)

Bonne journée
Jean

Le problème est exactement celui que je t'ai dit
Les variables globales ne doivent pas être définies dans le .h, mais dans le .cpp et seulement déclarées dans le .h

Ca donne ça:
gvc_communications.h

/*JME 23/02/2025
  D:\developpements\arduino_2024\carnet_croquis\libraries\GVC_communications\gvc_communications.h
  déclarations des variables, des fonctions et des classes pour la communication entre 
  les arduino UNO R4 du 
  Grésivaudan Vapeur Club 
  593 rue Jean PELLERIN
  F38530 PONTCHARRA
  Les communications peuvent se faire soit en filaire soit en LORA
*/
/*
UNO R4 Minima, UNO R4 WiFi	serial1 pins	0(RX0), 1(TX0)
A VERIFIER PARCE QU'IL Y A DES INFORMATIONS CONTRADICTOIRES :
  la communication série est gérée par la classe serial1
  la broche D0 réception, D1 émission
  on peut gérer 2 LEDs : LED_RX et LED_TX. Il faut pinmode et
    digitalWrite. Attention, il semble que LOW allume la LED
*/

/* la plus grande trame utilisée pour la signalisation fait 24 octets
   il faut lui ajouter 2 octets d'en-tête et 2 octets de fin
   plus un dernier octet pour l'éventuel \0
*/
/* Normalement (et c'est à vérifier), les variables et pointeurs
   déclarés ici sont global et donc connus dans toute l'application
   gvc_communications.h est en effet inclus dans le .h de l'application.
*/
#ifndef _LIB_GVC_COMMUNICATION_H
#define _LIB GVC_COMMUNICATION_H
#include "arduino.h"

  constexpr uint8_t  scu8_nb_max_car_buffer = 30;
  extern char gch_caractere_recu;
  extern int gi_nb_octet_arecevoir;

  union gu_decodage_commande // permet de transformer 2 char de la trame en un entier
  {
    char trame_char[2];
    uint8_t trame_int;
  };

  struct struct_comunications  // 05/02/25 contient toutes les données concernant les transmissions
  {
    char buffer_reception[scu8_nb_max_car_buffer];
    char buffer_emission[scu8_nb_max_car_buffer];
    int  indice_caractere_recu = 0;    // indice dans le buffer reception
    int  indice_caractere_emis = 0;    // indice dans les buffer émission
    int  nb_octet_atransmettre = 0;
    int  etat_automate_reception = 0;  // état de l'automate d'états fini gérant la 
                                      // réception des trames
    int  etat_automate_emission = 0;  // état de l'automate d'états fini gérant 
                                      // l'émission des des trames
    int  taille_buffer_emission = 0;  // nbre d'octet dans le buffer émission (Serial1.availableForWrite())
    char caractere_recu = 0;
    byte etat_liaison_central = 0;    // 0 inactive; 1 réception; -1 émission Cette variable
                                      // est liée à la broche scu8_optocoupleur_transmission
    
  };
  extern struct_comunications gs_comunications;

/* toutes les structures ci-dessous vont être plaquées (avoir la même adresse) 
   que gs_communications.buffer_reception ou gs_communications.buffer_emission.
   C'est pour cela qu'on y accède par l'intermédiaire d'un pointeur.
   Elles servent à faciliter le décodage ou le codage des trames.
   Comme ce pointeur va pointer sur des structures très différentes, il faut
   utiliser reinterpret_cast pour éviter que C++ ne donne une erreur
*/
  struct st_details_trame_commande_recue
  {
    uint8_t code_message  : 6;          // 6 bits  reception commande code 25 = 3*8+1
    uint8_t categorie_emetteur : 5;     // 5 bits
    uint8_t categorie_destinataire: 5;  // 5 bits  pour un signal : 5 (mécanique) ou 6 (lumineux)
    uint8_t adresse_emetteur : 8;       // 8 bits
    uint8_t adresse_destinataire : 8;   // 8 bits
    uint16_t numero_message;            // 16 bits
    uint16_t dixieme_minuit;            // 16 bits
    uint16_t valeur_numerique;          // 16 bits
    uint16_t valeurs_binaires;          // 16 bits
  };  // longueur : 12 octets  (c'est une définition qui ne prend pas de place en mémoire)

  extern st_details_trame_commande_recue * ptr_trame_cmde_recus;
  extern st_details_trame_commande_recue * ptr_details_cmde_confirmation;

  struct st_details_trame_envoi_heure
  {
    uint8_t code_message  : 6;          // 6 bits  reception commande code 25 = 3*8+1
    uint8_t filler1       : 2;          // 2 bits
    uint16_t dixieme_minuit;            // 16 bits
    uint8_t  heure        :5;
    uint8_t  minute       :6;
    uint8_t  seconde      :6;
    uint8_t  dixieme      :4;
    uint8_t  centieme     :4;
    uint8_t  filler2      :7;
  };  // longueur : 7 octets  (c'est une définition qui ne prend pas de place en mémoire)
  // tout comme le pointeur ci-dessous ne prend presque pas de place
  extern st_details_trame_envoi_heure * ptr_details_envoi_heure;

  struct st_details_trame_initialisation
  {
    uint8_t code_message  : 6;          // 6 bits  reception commande code 33 = 4*8+1
    uint8_t categorie_emetteur : 5;     // 5 bits
    uint8_t categorie_destinataire: 5;  // 5 bits  pour un signal : 5 (mécanique) ou 6 (lumineux)
    uint8_t adresse_emetteur : 8;       // 8 bits
    uint8_t adresse_destinataire : 8;   // 8 bits
    uint16_t numero_message;            // 16 bits
    uint16_t dixieme_minuit;            // 16 bits
    uint8_t  heure        :5;
    uint8_t  minute       :6;
    uint8_t  seconde      :6;
    uint8_t  dixieme      :4;
    uint8_t  centieme     :4;
    uint8_t  filler2      :7;
  };  // longueur : 12 octets  (c'est une définition qui ne prend pas de place en mémoire)
  extern st_details_trame_initialisation * ptr_details_initialisation;

// déclarations des fonctions
  void fonction_vide();
  void raz_buffer_reception();
  void raz_buffer_emission();
  void parametrage_liaison_central(uint8_t, unsigned long);


#endif


gvc_communications.cpp

/*JME 23/02/2025
  D:\developpements\arduino_2024\carnet_croquis\libraries\GVC_communications\gvc_communications.ino
  développement (définitions) des fonctions et des classes pour la communication entre 
  les arduino UNO R4 du 
  Grésivaudan Vapeur Club 
  593 rue Jean PELLERIN
  F38530 PONTCHARRA
  Les communications peuvent se faire soit en filaire soit en LORA

  ATTENTION : IL N'EST PAS SUR QUE CETTE LIBRAIRIE SOIT UTILISABLE POUR UN AUTRE
              APPAREIL QU'UN SIGNAL !
  NOTE après réflexion : dans cette librairie, ce qui nous interesse surtout c'est
       le .h qui définit toutes les trames possibles. Le nombre de méthode est
       réduit et se limite à quelques utilitaires
  PB : le .h est en lecture seule !!  
*/
/*
UNO R4 Minima, UNO R4 WiFi	serial1 pins	0(RX0), 1(TX0)
A VERIFIER PARCE QU'IL YA DES INFORMATIONS CONTRADICTOIRES :
  la communication série est gérée par la classe serial1
  la broche D0 réception, D1 émission
  on peut gérer 2 LEDs : LED_RX et LED_TX. Il faut pinmode et
    digitalWrite. Attention, il semble que LOW allume la LED
*/

#include "gvc_communications.h"

// variables globales
  char gch_caractere_recu = 0;
  int gi_nb_octet_arecevoir = 0;

    struct_comunications gs_comunications;
  st_details_trame_commande_recue * ptr_trame_cmde_recus;
  st_details_trame_commande_recue * ptr_details_cmde_confirmation;
st_details_trame_envoi_heure * ptr_details_envoi_heure;
st_details_trame_initialisation * ptr_details_initialisation;


/* cette librairie utilise des variables globales (dont le nom
   commence normalement par g) :
   char gch_caractere_recu (char)
   int gi_nb_octet_arecevoir

   NOTE : suite à la création de cette librairie à partir de l'application
   commande_gare, il n'est pas sûr que gs_comunications soit une variable globale !
*/
// scu8_optocoupleur_transmission, 
//   gcl_appareil_courant.r4_gvc_communications.vitesse
void parametrage_liaison_central(uint8_t pi_pin_optocoupleur=-1, 
   unsigned long pi_vitesse = 9600uL)
{
  /* si pi_pin_optocoupleur >= 0, on gère un optocoupleur qui permet de
     paramétrer la ligne soit en réception soit en émission.
     Pour autoriser la réception (cas normal), il faut que la broche n°
		pi_pin_optocoupleur soit à 1
     Pour autoriser l'émission, il faut que cette broche soit à 0
  */
  raz_buffer_reception();
  raz_buffer_emission();
//  digitalWrite(scu8_optocoupleur_transmission, HIGH); // réception
  if (pi_pin_optocoupleur>=0)
    digitalWrite(pi_pin_optocoupleur, HIGH); // réception
  gs_comunications.etat_liaison_central = 1;  // réception
//  Serial1.begin(gcl_appareil_courant.r4_gvc_communications.vitesse);
  Serial1.begin(pi_vitesse);
  gs_comunications.taille_buffer_emission  = Serial1.availableForWrite();
#if entest
    Serial.print("librairie gvc_communications : paramétrage serial1");
#endif
} //parametrage_liaison_central()

void raz_buffer_reception()
{ int li_indice;
  for(li_indice = 0; li_indice < scu8_nb_max_car_buffer; li_indice++)
  {
    gs_comunications.buffer_reception[li_indice] = 0;
  }
  gch_caractere_recu = 0;
  gs_comunications.indice_caractere_recu = 0;  // a priori inutilisé !
  gi_nb_octet_arecevoir = 0;       // nombre de caractère à recevoir (3éme octet de la trame)
}

void raz_buffer_emission()
{ int li_indice;
  for(li_indice = 0; li_indice < scu8_nb_max_car_buffer; li_indice++)
  {
    gs_comunications.buffer_emission[li_indice] = 0;
  }
}


// scu8_optocoupleur_transmission
void traitement_emission_confirmation(uint8_t pi_pin_optocoupleur=-1)
{ /* on utilise un petit automate pour pouvoir insérer des petites 
     temporisations entre les différentes étapes. Pour l'instant on ne 
	 fait que passer d'une boucle loop à l'autre  */
  /* si pi_pin_optocoupleur >= 0, on gère un optocoupleur qui permet de
     paramétrer la ligne soit en réception soit en émission.
     Pour autoriser la réception (cas normal), il faut que la broche n°
		pi_pin_optocoupleur soit à 1
     Pour autoriser l'émission, il faut que cette broche soit à 0
  
  */

  switch(gs_comunications.etat_automate_emission)
  {
    case 0:
    {
      if (pi_pin_optocoupleur>=0)
	    digitalWrite(pi_pin_optocoupleur, LOW); // on passe la ligne en émission
      gs_comunications.etat_automate_emission = 1;
      break;
    }
    case 1:
    {
      if(Serial1.availableForWrite()> gs_comunications.nb_octet_atransmettre+5)
      { // On peut émettre la trame
        // la trame à émettre à été créée, il faut lui mettre les 3 caractères de
        // début et les deux de fin
        Serial1.write(1) ;    // SOH Start of Heading
        Serial1.write(14) ;   // SO Shift Out
        Serial1.write(gs_comunications.nb_octet_atransmettre);
        Serial1.write(gs_comunications.buffer_emission, gs_comunications.nb_octet_atransmettre);
        Serial1.write(15) ;   // SI Shift In
        Serial1.write(23) ;   // ETB End of Transmission Block
        // il faut prévoir une petite attente ici (une ou deux boucles) avant de 
        // repasser la ligne en réception
        gs_comunications.etat_automate_emission = 2;
      }
      else
      {
        // il y a une erreur à l'émission
        gs_comunications.etat_automate_emission = -1;

      }
      break;
    }
    case 2:
    { // a priori, l'envoi de la trame est asynchrone (le programme continue de se dérouler
      // pendant l'envoi). Il faut donc attendre la fin de cet envoi. Je n'ai pas trouvé
      // d'autre moyen que de vérifier que le buffer émission est vide, c'est à dire que
      // le nombre d'octets possibles est égal au nombre d'octets disponibles initial
      // on reste donc dans cet état tant que l'émission n'est pas terminée

      if(gs_comunications.taille_buffer_emission  >= Serial1.availableForWrite())
        gs_comunications.etat_automate_emission = 3;
      break;
    }
    case 3:
    { // l'émission est terminée, mais on attend un peu avant de repasser la ligne en réception
      gs_comunications.etat_automate_emission = 4;
      break;
    }
    case 4:
    { // l'émission est terminée, on peut repasser la ligne en réception
      if (pi_pin_optocoupleur>=0)
		digitalWrite(pi_pin_optocoupleur, HIGH); // on passe la ligne en réception
      gs_comunications.etat_automate_emission = 0;
      gs_comunications.etat_automate_reception = 0;
      gs_comunications.etat_liaison_central = 1;    // 0 inactive; 
	    // 1 réception; -1 émission Cette variable
        // est liée à la broche pi_pin_optocoupleur
      break;
    }
  } // switch(gs_comunications.etat_automate_emission)

  
  // voir s'il ne faudrait pas une petite attente ici (par exemple boucle suivante)

} // traitement_emission_confirmation()

void fonction_vide()
{
  // cette fonction sert juste à avoir la possibilité d'avoir un pointeur sans
  // conséquence
  int li_sertarien;
  li_sertarien = 0;
}

Les déclarations des variables dans le .h ne sont nécessaire que pour les variables qui seront utilisées ailleur que dans le .cpp.

C'est bien ce que je veux !

J'ai rectifié ma librairie selon vos exemples : j'ai d'autres erreurs ...

Jean

Quelles erreurs?

Bonjour Kamill,

j'ai réglé le problème en partant de vos remarques. En fait j'étai persuadé que le fait d'avoir mis

#ifndef _LIB_GVC_COMMUNICATION_H
#define _LIB GVC_COMMUNICATION_H

cela allait empêcher les doubles déclarations (une fois dans gvc_communications.cpp et une fois dans test_librairie.cpp) et en fait non. Donc déclarer les types dans le .h ok mais pour déclarer les variables, c'est bien dans le .cpp

pour info, j'ai beaucoup travaillé avec le C et le C++ dans ma vie professionnelle mais jamais je n'ai eu les problèmes que je rencontre avec mon application de gestion de signaux pour un train miniature !!! De même ici il a fallu que j'utilise pour la première fois de ma carriere : reinterpret_cast !

merci et bonne fin de journée
Jean

Content que ça fonctionne.

Moi aussi dans ma vie professionnelle j'ai beaucoup travaillé en C et C++ et je ne me souvient pas d'avoir utilisé reinterpret_cast. Pour le casting j'utilisais l'ancienne notation comme (long) var.

Pour ne pas inclure plusieurs fois le .h dans une compilation, à la place de #ifndef /#define tu peux utiliser la directive #pragma once

Bonjour,

Pourtant, c'est un classique du genre en C++ :wink:
Cf. par exemple: A Simple Approach to C++ Callbacks

Bonsoir,

j'ai l'impression que j'ai raté beaucoup d'évolutions depuis une dizaine d'année. Déjà que dans l'automatisme industriel on n'utilisait que des outils 'fiables et connus' donc ... anciens !

Demain c'est ma journée 'ferroviaire', je regarderai #pargma once jeudi ...

Bonne soirée à tous
Jean