Go Down

Topic: error : was not declared in this scope (Read 10062 times) previous topic - next topic

eeaeea

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 :

Code: [Select]
#include "test.h"
a=0;


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



Voici le .cpp

Code: [Select]
#include "test.h"

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


et le .h

Code: [Select]

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 !

bricoleau

bêêêêrk  :smiley-yell:

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.
Code: [Select]
void write(int quoi)
{
  digitalWrite(22, bitread(quoi, 0));
}


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




Tutoriels arduino : http://forum.arduino.cc/index.php?topic=398112.0

kamill

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;

bricoleau

#3
Feb 03, 2016, 05:50 pm Last Edit: Feb 03, 2016, 05:50 pm by bricoleau
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.

Code: [Select]
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 :
Code: [Select]
void write(byte pin, byte octet, byte bit)
{
  digitalWrite(pin, bitread(octet, bit));
}


Tutoriels arduino : http://forum.arduino.cc/index.php?topic=398112.0

eeaeea

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

Code: [Select]
"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 ?

kamill

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.

eeaeea

test.h

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


testt.cpp

Code: [Select]
#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

kamill

#7
Feb 04, 2016, 01:39 pm Last Edit: Feb 04, 2016, 01:40 pm by kamill
Il faut déclarer lcd dans test.h
Code: [Select]
#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)
Code: [Select]
LiquidCristal lcd(...); // arguments suivant mode et bibliotheque

bricoleau

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 globale
Code: [Select]
LiquidCrystal 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
Tutoriels arduino : http://forum.arduino.cc/index.php?topic=398112.0

kamill

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).

bricoleau

Pas de souci, je comprends bien ;)
Mais je pense que les causes du mal sont plus profondes et du coup c'est là que je propose d'intervenir.
Tutoriels arduino : http://forum.arduino.cc/index.php?topic=398112.0

eeaeea

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

Code: [Select]
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...

bricoleau

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
Code: [Select]
#ifndef toto_h
#define toto_h
...
... le contenu du .h
...
#endif
Tutoriels arduino : http://forum.arduino.cc/index.php?topic=398112.0

eeaeea

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

.h

Code: [Select]
#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
Code: [Select]

#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]"

kamill

#14
Feb 05, 2016, 05:31 pm Last Edit: Feb 05, 2016, 05:32 pm by kamill
Bonjour,

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


Code: [Select]
    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

Go Up