error : was not declared in this scope

Bonjour tout le monde ! J'ai un problème avec la gestion de ma bibliothèque... J'ai dans mon dossier : test.ino, test.h et testt.cpp

Dans le .ino, toutes les variables que j'ai déclaré sont en global, mais lorsque j'essaye d'affecter une de ces variables à une sorties numériques de mon arduino en passant par une fonction je recois l'erreur suivante qui est : 'a' was not declared in this scope. Voici l'exemple tout bete qui illustre mon probleme.

Voici le .ino :

#include "test.h"
a=0;


void setup()
{
 pinMode(22, OUTPUT);
 write();
}

Voici le .cpp

#include "test.h"

void write()
{
  digitalWrite(22,bitRead(a,0));
}

et le .h

void write();

Biensur ce n'est pas mon programme, mais cet exemple illustre bien mon problème. J'ai trouvé des tuto expliquant la gestion des bibliothèques, mais dans ces tuto aucune des fonctions du type " void nom_fonction() " n'ont à l'intérieur des variables utilisées dans le .ino.
En fin de compte j'aimerais juste faire cela pour rendre plus lisible mon programme, en nommant des morceaux par des fonctions avec un nom clair pour que le setup et loop soient lisible. Merci !

bêêêêrk :stuck_out_tongue_closed_eyes:

Y a pas de tuto là-dessus, et pour cause !
C'est une hérésie de vouloir référencer une variable globale du programme principal, depuis une bibliothèque.

Tu inverses la construction.
Le principe d'une bibliothèque, c'est d'être auto-suffisante (éventuellement en s'appuyant sur d'autres bibliothèques de niveau inférieur).

Elle doit pouvoir être appelée depuis plusieurs programmes différents, sinon elle perd son principal intérêt qui est de factoriser du coder.

Si ta fonction write() a besoin d'une variable externe, elle doit être passée en paramètre d'appel.

void write(int quoi)
{
  digitalWrite(22, bitread(quoi, 0));
}

Et tu l'appelles depuis ton programme principal par write(a);

Bonjour,

  1. A l'instanciation de la variable (dans le ino) le type doit être déclaré -> int a=0;
  2. La variable doit être connue quand on veut l'utiliser le moyen logique est de la déclarer dans test.h -> extern int a;

Mouais, techniquement y a des solutions.

Mais plus on met de variables globales partagées entre plusieurs parties du code, et plus on crée un plat de spaghettis difficile à mettre au point et faire évoluer.

Qu'elle se trouve dans une bibliothèque ou non, une fonction bien codée ne devrait jamais s'appuyer sur des variables globales.

void ma_fonction(int param1, long param2, etc.)
{
  ... ici du code qui n'utilise QUE les variables passées en paramètre
}

Ainsi, une fois mise au point et validée, cette fonction peut aisément être copiée d'un programme à l'autre sans mauvaise surprise, ou mieux : être mise en bibliothèque. C'est une manière de la mettre sur étagère, à disposition de tout programme qui en aurait besoin.

Exemple :

void write(byte pin, byte octet, byte bit)
{
  digitalWrite(pin, bitread(octet, bit));
}

D'accord.. désolé pour le blasphème alors...
J'ai donc adapté la majorité de mes fonctions avec des arguments mais j'ai un problème pour deux d'entres elles :

  • Le premier probleme est que la fonction que je voudrais créer utilise elle meme des fonctions de la bibliothèque LiquidCrystal.h, et meme en rajoutant le #include je recois comme message d'erreur
"exit status 1
'lcd' was not declared in this scope"
  • L'autre fonction que je souhaiterais inclure dans mon .h est une fonction de test, elle a besoin de 6 variables présentes dans mon loop et de 2 tableaux d'objets dont j'afficherais le contenu grace a une autre fonction. Le but etant d'ameliorer la lisibilité, il me parait un peu bizarre de l'adapter avec 8 arguments a renseigner, je ne pense pas que ca ameliorera la clarté du code. J'ai donc essayé de passer les variables extern, mais pour les objets cela ne semble pas fonctionner.. Auriez vous d'autres idées ou pensez vous que le mieux reste de faire des passages par arguments ?

Bonjour,

Sans le source c'est difficile de t'aider. Est ce que tu a bien déclarer lcd. Il ne suffit pas d'include le .h, encore faut il déclarer l'objet lcd.

Bien sur que extern fonctionne aussi pour les objets. La aussi sans le source difficile de t'aider.

test.h

void afficher(char chaine1[20], char chaine2[20],char chaine3[20], char chaine4[20]);

testt.cpp

#include "test.h"
#include "Arduino.h"
#include <LiquidCrystal.h>

void afficher(char chaine1[20], char chaine2[20],char chaine3[20], char chaine4[20])
{
 lcd.setCursor(0,0);
 lcd.print(chaine1[20]);
 lcd.setCursor(0,1);
 lcd.print(chaine2[20]);
 lcd.setCursor(0,2);
 lcd.print(chaine3[20]);
 lcd.setCursor(0,3);
 lcd.print(chaine4[20]);
}

message d'erreur :

exit status 1
'lcd' was not declared in this scope

Il faut déclarer lcd dans test.h

#include <LiquidCrystal.h>

extern LiquidCrystal  lcd;

void afficher(char chaine1[20], char chaine2[20],char chaine3[20], char chaine4[20]);

Et l'instancier dans le .ino (par exemple)

LiquidCristal lcd(...); // arguments suivant mode et bibliotheque

Hello

Techniquement, ça fonctionne mais sur le principe je ne partage pas trop l'avis de kamill.
Quand on crée une telle bibliothèque avec une fonction afficher(), l'objet LiquidCrystal devrait être déclaré uniquement à l'intérieur du .cpp, non accessible depuis le reste du programme.

eeaeea, je pense que tu as un problème d'approche de la conception de bibliothèques, et te conseille d'aborder tes questions sous cet angle.

De manière générale, une bibliothèque permet de regrouper des lignes de codes cohérentes entre elles, et de les encapsuler dans une boîte noire dont seules les primitives de manipulation de haut niveau sont accessibles via le .h
Les deux principaux objectifs visés sont :

  1. la réutilisabilité / factorisation du code : plusieurs programmes peuvent utiliser les mêmes composants logiciels
  2. maîtrise de la complexité globale d'un programme, en isolant / séparant ses différentes parties
    Cette maîtrise passe par la mise au point et validation de chaque bibliothèque unitairement. Pour chaque bibliothèque créée, il est indispensable de créer aussi un programme spécifique, dans le but de valider le bon fonctionnement de toutes les primitives exposées dans le .h
    Ainsi, un programme très complexe peut être décomposé en parties moins complexes, isolées dans des librairies et mises au point séparément avant d'être assemblées.
    Autre point très important dans la maîtrise de la complexité globale : cela oblige à se poser la question des liens entre chaque partie du programme. Au final : quelles sont les données / fonctions qui doivent rester internes à la bibliothèque, et quelles sont celles qui doivent être exposées dans le .h
    On appelle ça de l'urbanisation de code.
    Il n'y a rien de pire qu'un programme principal mono bloc, avec plein de fonctions non hiérarchisées qui s'appellent les unes les autres dans tous les sens, et un gros paquet de variables globales au milieu. Horreur de mise au point, bugs résiduels et effets de bord garantis à chaque fois qu'on veut le modifier.

Maintenant pour revenir dans le concret, prenons LiquidCrystal.h :

Celle-ci définit une classe, avec toutes ses méthodes, mais sans aucune instance.
C'est un peu différent de certaines bibliothèques qui définissent à la fois la classe et une instance de cette classe, car celle-ci a vocation à être unique dans le programme (exemples : Wire, Serial, ...).

Le code qui utilise LiquidCrystal.h (programme principal ou bibliothèque) doit créer lui-même une instance de la classe.

Usuellement LiquidCrystal.h est appelée par le programme principal, où on trouve un objet créé en variable globaleLiquidCrystal lcd(...);
Et cela convient car la classe LiquidCrystal apporte toutes les fonctionnalités de base permettant de manipuler un lcd.

Quelles pourraient être les raisons de vouloir utiliser LiquidCrystal depuis une bibliothèque ?

Si c'est juste pour urbaniser un programme complexe, le modèle serait d'avoir une bibliothèque gestionLCD.h.
Celle-ci pourrait par exemple contenir la déclaration de l'objet LiquidCristal dans son cpp, et exposer juste quelques méthodes dans le .h : allumer, éteindre, déplacerCurseur, afficher, etc.
Et aucune autre partie du code (programme principal ou autres bibliothèque) ne devrait avoir besoin d'accéder directement à l'objet LiquidCristal.
Tout doit passer via gestionLCD.h
Au passage, cette manière de procéder peut s'avérer fort judicieuse le jour où on veut changer le modèle d'écran LCD.

L'autre raison principale serait de vouloir ajouter des fonctionnalités à celles proposées par LiquidCrystal, et d'en faire profiter tous ses programmes.

Par exemple, on pourrait souhaiter ajouter une couche logicielle qui affiche proprement les caractères accentués et autres caractères spéciaux non gérés en natif par le controleur HD44780 (dont le caractère °, souvent nécessaire pour afficher une température), chose que je me suis déjà amusé à faire.
Cela permettrait de mettre à disposition une fonction println(), qui analyse le contenu de la chaîne à afficher, et utilise les 8 caractères personnalisables du lcd pour afficher les caractères spéciaux (mise à jour de la CGRAM à la volée, substitution de caractères entre ce qui est reçu et ce qui est transmis au lcd, etc.).
Dans mon cas, je me suis même amusé à appliquer cette mécanique pour le caractère g, dont l'affichage par défaut est vraiment trop moche.
On peut ainsi gérer des dizaines de caractères spéciaux, avec cependant la contrainte de ne pouvoir en afficher que 8 simultanément.

Pour ton second point, si une primitive d'une bibliothèque a besoin d'avoir beaucoup de données diverses en entrée, c'est souvent lié à une définition imprécise des objets manipulés.
Tu cites le cas de 6 variables + 2 tableaux à passer en paramètre d'une fonction.
Mais j'imagine que toutes ces données ont un rapport entre elles.
Si c'est bien le cas, la bonne approche est de définir la fonction et le modèle de données qu'elle manipule (une classe, un struct, ... ) dans la même bibliothèque.
Le programme principal instancie une occurrence de ce modèle, en tant que variable globale, et le passe en paramètre d'appel de la fonction.

Pour aller plus loin dans l'analyse de tes questions, il est vraiment nécessaire que tu détailles le pourquoi du comment de ce que tu souhaites faire, de manière très concrète. Je pourrai alors t'aider d'avantage

Je répondais simplement sur la déclaration des variables globales.
Je suis d'accord avec toi, s'il s'agit d'encapsuler la fonction d'affichage, lcd doit être instancié dans le fichier de cette fonction (ce serait encore mieux de créer une classe d'affichage) pour pouvoir afficher sur un autre périphérique sans toucher le programme principal (en théorie).

Pas de souci, je comprends bien :wink:
Mais je pense que les causes du mal sont plus profondes et du coup c'est là que je propose d'intervenir.

Merci beaucoup pour vos réponses je prend en compte tout vos conseils pour modifier mon code ! Mais je suis deja confronté a un probleme... Peut etre que je suis encore entrain de blasphemer le langage informatique sans le savoir mais :

.h

void fonctiontest(char a,String b);

message d'erreur :

"exit status 1
'String' has not been declared"

Est ce interdit d'avoir pour argument des objets ? Je ne pense pas que ce soit un probleme d'oubli d' #include car j'utilise des string dans mon .ino sans avoir d'erreur de ce genre et sans include...

La compilation du programme et des bibliothèques se fait de manière séparée.
Là, tu as une erreur de compil sur la bibliothèque, car l'objet string n'est pas défini.
Tu aurais la même erreur lors de la compil du programme si l'IDE Arduino n'ajoutait pas d'office ses bibliothèques.

=> ajouter #include <Arduino.h> au début du .h

De plus, la structure d'un toto.h devrait toujours être

#ifndef toto_h
#define toto_h
...
... le contenu du .h
...
#endif

D'accord merci beaucoup ! Dernier soucis sur la fonction afficher que je vous ai mise plus haut :

.h

#ifndef osf_h
#define osf_h
#include "Arduino.h"
void afficher(char chaine1[20], char chaine2[20], char chaine3[20], char chaine4[20]);
#endif

.cpp

#include "osf.h"
//#include "Arduino.h"
#include <LiquidCrystal.h>
LiquidCrystal lcd(39,38,41,40,43,42);
// Affichage ligne par ligne
void afficher(char chaine1[20], char chaine2[20],char chaine3[20], char chaine4[20])
{
lcd.setCursor(0,0);
lcd.print(chaine1[20]);
lcd.setCursor(0,1);
lcd.print(chaine2[20]);
lcd.setCursor(0,2);
lcd.print(chaine3[20]);
lcd.setCursor(0,3);
lcd.print(chaine4[20]);
}

.ino

#include "osf.h"
#include <LiquidCrystal.h>

// Initialisation LCD
LiquidCrystal lcd(39,38,41,40,43,42);  

 
void setup() 
{
char affichage1[20]=""; // chaine de caractere pour gestion de l'affichage
char affichage2[20]="";// chaine de caractere pour gestion de l'affichage
a=0;
c=0;
String objet[3]={"abcd","efgh","ijkl"};

lcd.begin(20,4);
    afficher(
    "test", 
    "affichage  ",
    "sur lcd",
    "20 * 4");

delay(5000);
lcd.clear();

    afficher(
    "deuxieme test",
    "avec sprintf    ", 
    (sprintf(affichage1,"variable c = %d  ", c)),
    (sprintf(affichage2,"variable a =%d ", a)));

delay(5000);
lcd.clear();

    afficher(     
      "test 3   ",
      objet[0], 
      "   ",
      "   ");

J'ai a chaque fois ce meme genre d'erreurs " exit status 1
invalid conversion from 'int' to 'char*' [-fpermissive]"

Bonjour,

sprintf retourne un entier qui est le nombre de caractères 'imprimés'
Il faut passer l'adresse du buffer.

    afficher(
    "deuxieme test",
    "avec sprintf    ", 
    (sprintf(affichage1,"variable c = %d  ", c),affichage1),
    (sprintf(affichage2,"variable a =%d ", a),affichage2));

Je déconseille néanmoins cette notation qui n'est pas très explicite. Il vaut mieux faire les sprintf avant l'appel à afficher puis passer affichage1 et affichage 2

kamill:
Il faut passer l'adresse du buffer.

Aurais tu un exemple s'il te plait ?
Et du coup mettre en argument des int, puis dans la fonction créer 4 chaines des de caractères, les passer dans sprintf puis afficher les "caractères imprimés" cela marcherait ?

Tu as l'exemple dans le code que je t'ai donné

Exact merci beaucoup ! Je n'arrive toujours pas a afficher le contenu d'un tableau d'objet ( le test 3), meme en utilisant ton exemple :confused: aurais tu un dernier tuyau ?

Est ce que tu arrives à compiler ton programme?

       lcd.clear();
       afficher(
       (sprintf(affichage1,"test n°%d",e),affichage1),
       obj[e-1][0],
       "et                  ",  
       (sprintf(affichage1,obj[0][1]),affichage1));

Oui, j'ai essayé les 2 manières, pour la ligne 4 ce n'est pas étonnant,
la ligne 3 fonctionne
la ligne 4 : error: cannot convert 'String' to 'char*' for argument '2' to 'void afficher(char*, char*, char*, char*)'

obj[0][1]);

mais ligne 6 : error: cannot convert 'String' to 'const char*' for argument '2' to 'int sprintf(char*, const char*, ...)'

(sprintf(affichage1,obj[0][1]),affichage1));