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.
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)
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
/*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.
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 !
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
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 ...