Erreur de compilation de ma lib

Bonjour, voici l’erreur que j’obtien :

Et voici mon H :

/*
 PPMread.h - Library for reading PPM 6ch (negative modulated).
 V004
 You must use PIN 8 for PPM reading
 */

#ifndef PPMread_h
#define PPMread_h

#define LEDPPM 1   // Uncomment to flash Arduino LED 
// Quickly when Ok, slowly on failsafe


class PPMread
{
public:
  PPMread(int* rxPoint,unsigned char nbOfCh=6);
  void begin(unsigned char ecart=3);
  unsigned char read();
private:
  int* _rxPoint;
  unsigned char _nbOfCh;
};

#endif

Avez-vous besoin de plus de choses pour voir où ça coince ?

oula non, il n'y a pas besoin de plus de chose pour voir où ça coince :

Dans ta définition de classe PPMread, tu as comme propriété privé :

unsigned char _nbOfCh;

Qui est donc seulement accessible par les méthodes de la classe PPMread. Hors, tu tentes d'y accéder directement lors de la création de la variable globale vchUP. D'autre part le compilateur ne t'autorisera pas à créer un tableau avec une taille variable de cette façon.

Tu créés une librairie via une classe, il n y a donc aucune raison d'y trouver des création de variables globales. Tu passe ces variables dans la définition de ta classe en publique, si tu veux y accéder directement.

Pour ce qui est du tableau à taille dynamique, créé un pointeur en propriété de classe :

unsigned int* vchUP;

et dans le constructeur de ta classe, tu utilises malloc pour allouer de l'espace à ce tableau.

Merci Tonyryu,

Ce que je ne comprends pas, c’est pourquoi ça passe avec rxPoint & _rxPoint ?

Sev

PS : J’ai repris cette façon de faire de l’exemple de la librairie Morse :
http://arduino.cc/en/Hacking/LibraryTutorial

C'est tout a fait normal que ce la fonctionne avec _rxPoint, car comme dit précédemment, les propriétés privées ne sont accessibles que dans les méthodes de classe. Le constructeur PPMread(int* rxPoint,unsigned char nbOfCh=6); est une méthode un peu particulière de la classe PPMread, tu as donc accès à la propriété _rxPoint, dans son code.

Si tu regardes bien l'exemple Morse, tu constateras qu'il n'y a pas de code entre les différentes implémentations des méthode de classe.

C'est bien mon problème, l'exemple Morse est un peu aride, je souhaite en faire un peu plus mais je ne sais jamais ou déclarer mes variables, je ne sais même pas si je peux faire une librairie sans classe... et si oui comment... bref, je joue avec des choses que je ne maîtrise pas. Par magie, ça fonctionnait jusqu’à ce que je rajoute le paramètre nbOfCh.

J'ai du mal à trouver des exemples bien commentés de librairies, un peu plus évolués que Morse.

La seule chose que je connais bien, c'est mon besoin utilisateur, l'utilisateur de ma lib, doit pouvoir faire 2 choses et de la façon la plus simple possible :

  • Appeler/lancer la lecture PPM en arrière plan (aujourd'hui PPMread & PPMread.begin())
  • Demander la mise à jour des donnée (aujourd'hui la fonction PPMread.read())

Pour la première fonction,il n'y a pas de sortie mais il y a 3 paramètres d'entrée dont 2 sont optionnels : Le pointeur du tableau de variables, le nombre de canaux et la valeur de l'hystérésis)

Pour la seconde fonction, il n'y a pas de paramètre d'entrée. Cette fonction met à jour les valeurs directement en mémoire grâce au pointeur fournir dans la fonction précédente, et renvoi un code retour (0,1 ,2)

Tu peux bien évidemment créer des librairies sans utiliser les classes, mais le gros avantages du C++, est justement de pouvoir gérer des classes. Et crois moi, une classe bien faite, c’est que du bonheur à utiliser.

Dans ce que tu veux faire, tu souhaites gérer ton tableau de valeur depuis l’extérieur de ta classe, pourquoi ne pas justement le gérer en interne à ta classe, et de prévoir une fonction qui te retourne le pointeur sur ton tableau? Du coup pour ton constructeur, tu n’as plus que 2 paramètres, nombre de canaux et hystérisis, ton tableau était une propriété de ta classe, tu peux donc le créer avec un malloc de la taille passé en paramètre. la fonction PPMread.read() y aura accés également et pourra le modifier avec les mesures, et tu ajoutes une méthode PPMread.getTable() qui retourne ton pointeur.

Dans le sketch, en variable globale, tu te créé un pointeur de même type que le tableau que tu gères dans la classe, et également une variable de type de ta classe PPMread
Tu créés un nouvelle objet de classe PPMread sur ta variable de même type précédemment créé
Tu appels dans le setup la fonction PPM.getTable() pour récupérer l’adresse du tableau géré dans la classe et la mettre dans le pointeur global que tu auras créé.

Dans le loop, tu appels PPMread.read() qui mettra les valeurs du tableau à jour, et comme ton pointeur globale contient la bonne adresse, tu y auras accés directement dans le loop, sans autre intervention.

Exemple rapide :

PPMread.h

#ifndef PPMREAD_H
#define PPMREAD_H

#include "Arduino.h"

class PPMread
{
  public:
    PPMread(unsigned int pNbrCanaux = 0, unsigned int pHysteresis = 3);
    void read();
    unsigned int * getTable();
    
  private:
    unsigned int cHysteresis;
    unsigned int cNbrCanaux;
    unsigned int * cTabValeur;
};
#endif

PPMread.cpp

#include <PPMread.h>

PPMread::PPMread(unsigned int pNbrCanaux, unsigned int pHysteresis)
{
    cHysteresis = pHysteresis;
    cNbrCanaux = pNbrCanaux;
    cTabValeur = (unsigned int*)malloc(sizeof(unsigned int) * pNbrCanaux);
}

void PPMread::read()
{
  for(unsigned int idxCanal = 0; idxCanal <= cNbrCanaux; idxCanal ++)
  {
    cTabValeur[idxCanal] = 0;  // A remplacer par le code de lecture
  }
}

unsigned int * PPMread::getTable()
{
  return cTabValeur;
}

sketch :

#include <PPMread.h>

unsigned int * tabValeurPPMread;
PPMread objPPMread(3,4);


void setup()
{
  tabValeurPPMread = objPPMread.getTable();
}

void loop()
{
  objPPMread.read();
  // tu peux ajouter du code pour lire les données du tableau
}

Si j'ai bien compris ce que tu proposes, ça risque de compliquer la vie de l'utilisateur lambda, non ? :cold_sweat: J'essaye de rendre cette librairie très facilement utilisable, y compris par des modélistes qui ne sont pas forcément développeurs.

Et je trouve que la solution de leur laisser créer les variables et qu'il n'ayent plus qu'à passer le pointeur à la librairie est une solution simple, voir magique, puisqu'on modifie leurs variables directement et qu'ils n'ont pas à gérer de pointeurs (notion qu'ils peuvent ignorer)

Je ne comprends pas bien l’intérêt de faire l'inverse... Surtout que la librairie à ces propres tableaux "internes"

En revanche, si le problème vient du nombre de paramètres passés directement dans la classe, on peut tout à fait les déplacer dans une fonction de la classe, par exemple "begin", ça serait cohérent ?

Pas d'accord ?

[u]EDIT :[/u] Merci pour ton exemple de code, ça m'aide bien à mettre une réalité syntaxique sur notre conversation, je vais l'étudier !

Le fait de gérer le tableau de données dans la classe, permet justement à l'utilisateur de la librairie de ne pas s'en soucier. Certes la déclaration du type à récupérer est un pointeur, mais la lecture des données se fait directement en utilisant un offset : tabValeurPPMread[0] après l'appel à PPMread.read permet d'accéder au premier élément du tableau mis à jour par l'objet de classe.

L'ajout d'une méthode permettant d'initialiser les propriétés de classes sans passer par le constructeur, à l'intérêt de pouvoir être ré-appeler autant de fois qu'on le souhaite.

Bon, après avoir étudié ta proposition, il me semble que le problème va se situer dans le sketch, à ce niveau :

void loop()
{
  objPPMread.read();
  // tu peux ajouter du code pour lire les données du tableau
}

Cette partie va obliger l'utilisateur à lire le tableau à l'aide d'un pointeur ?

De plus, ma librairie alimente en arrière-plan ses tableaux "internes" à l'aide d'interruptions, il faut donc mettre en place une mécanique qui empêche la lecture/écriture simultanée d'une même donnée, sans arrêter pour autant les interruptions.

Je préfère une solution de ce type pour le sketch :

#include "PPMread.h"      // On inclue la librairie
#define NBOFCH 6          // On définit le nombre de canaux

unsigned int cha[NBOFCH]; // On crée le tableau de valeurs
PPMread ppm(&cha[0],NBOFCH);     // On lance l'instance, en fournissant le pointeur du tableau et le nombre de canaux

void setup()
{
  ppm.begin();      // On démarre la lecture PPM en arrière-plan avec un lissage de 3 par défaut
}

void loop()
{
  switch (ppm.read()) {   // read() retourne 0 si rien n'a changé depuis son dernier appel
  case 1:                 // read() retourne 1 si les valeurs ont été mises à jour
                     // On utilise ici DIRECTEMENT les nouvelles valeurs de cha[x]
   break;
  case 2:                 // read() retourne 2, si aucun signal valide n'est détecté
                    // Ici on a un code de "secour" au cas ou on a pas ou plus de signal radio.
  }
}

L'utilisation du code retour (0, 1 , ou 2) de la fonction read() sécurise l'accès en lecture/écriture aux donnés du tableau de l'utilisateur, car elles ne sont écrites QUE par la fonction read, et elle ne sont LUES que APRES avoir reçu un code retour 1 de la fonction read.

EDIT : Oups on a encore posté simultanément... je modifierai mon post après avoir compris le tien

Et non justement, c’est toute la magie d’un pointeur :

PPMread.h

#ifndef PPMREAD_H
#define PPMREAD_H

#include "Arduino.h"

class PPMread
{
  public:
    PPMread(unsigned int pNbrCanaux = 0, unsigned int pHysteresis = 3);
    unsigned char read();
    unsigned int * getTable();
    
  private:
    unsigned int cHysteresis;
    unsigned int cNbrCanaux;
    unsigned int * cTabValeur;
};

#endif

PPMread.cpp

#include <PPMread.h>

PPMread::PPMread(unsigned int pNbrCanaux, unsigned int pHysteresis)
{
    cHysteresis = pHysteresis;
    cNbrCanaux = pNbrCanaux;
    cTabValeur = (unsigned int*)malloc(sizeof(unsigned int) * pNbrCanaux);
}

unsigned char PPMread::read()
{
  unsigned char codeRetour = 0;

  for(unsigned int idxCanal = 0; idxCanal <= cNbrCanaux; idxCanal ++)
  {
    cTabValeur[idxCanal] = 0;  // A remplacer par le code de lecture et de vérification pour changer le code de retour
  }
  
  return codeRetour;
}

unsigned int * PPMread::getTable()
{
  return cTabValeur;
}

sketch :

#include <PPMread.h>

#define NBOFCH 6

unsigned int * cha;
PPMread objPPMread(NBOFCH, 4);


void setup()
{
  cha = objPPMread.getTable();
}

void loop()
{
  unsigned int idxTab;
  
  switch(objPPMread.read())
  {
    case 1: // On utilise ici DIRECTEMENT les nouvelles valeurs de cha[x] 
      for(idxTab = 0 ; idxTab <= NBOFCH; idxTab ++)
      {
        Serial.println(cha[idxTab]);
      }
      break;
      
    case 2: // Ici on a un code de "secour" au cas ou on a pas ou plus de signal radio.
    
      break;
      
  }
}

La surcharge d'opérateur (ici crochets) serait plus pratique à utiliser je pense ;) Aussi en C++ les pointeurs c'est du passé, les références c'est bien plus puissant (même si en interne c'est des pointeurs modifié).

Les "getter" de pointeur en lecture seul (const) ça ce fait, mais là tu peut carrément taper dans le tableau. Ça fou en l'air le principe d'encapsulation des membres de l'objet et ça c'est pô bien. (aprés c'est à la charge du dév de choisir si il veut faire un code propre ou non :grin:)

Aussi dans les constructeurs les variables peuvent être initialisé à l'instanciation :

PPMread::PPMread(unsigned int pNbrCanaux, unsigned int pHysteresis) :
cHysteresis(pHysteresis), cNbrCanaux(pNbrCanaux)
{
    cTabValeur = (unsigned int*)malloc(sizeof(unsigned int) * pNbrCanaux);
}

Ya plein de truc sympa dans ce genre qu'on peut faire en C++ :grin:

Moi qui voulait pas trop le brusquer et pas le faire fuir, vu qu'il commencer tout juste à apprendre la construction d'une classe. Là skywodd, c'est des coups à ce qu'Unisev se tape une crampe neuronale ^_^.

C'est vrai qu'il existe de nombreuses syntaxes permettant d'automatiser des actions en c++, mais vaut mieux y aller par étape.

Bon,

J’essaye de vous suivre les gars, même si je ne comprends pas tout alors surtout ne me lâchez pas ! :grin:

Voici mon sketch :

#include "PPMread.h"      // On inclue la librairie

#define NBOFCH 6          // On définit le nombre de canaux

int * cha;                // On crée le pointeur qui permettra de lire les valeurs

PPMread ppm(NBOFCH);      // On lance l'instance avec le nombre de canaux et l'hystérésis voulu (3 si non spécifié)

void setup()
{
  Serial.begin(115200);   // On prepare la sortie série
  Serial.println("");
  Serial.println("PPM read, librarie test");
  cha=ppm.getTable();      // On récupère le pointeur du tableau de données
}

void loop()
{
  switch (ppm.read()) {   // read() retourne 0 si rien n'a changé depuis son dernier appel
  case 1:                 // read() retourne 1 si les valeurs ont été mises à jour
    for (unsigned char i=0; i < NBOFCH; i++){ 
      Serial.print(cha[i]);  // On imprime donc les nouvelles valeurs sur le port série
      Serial.print(";");
    }
    Serial.println("");    
    break;
  case 2:                 // read() retourne 2, si aucun signal valide n'est détecté
    Serial.println("FAILSAFE");
  }
}

Le H :

/*
 PPMread.h - Library for reading PPM 6ch (negative modulated).
 V004
 You must use PIN 8 for PPM reading
 */

#ifndef PPMread_h
#define PPMread_h

class PPMread
{
public:
  PPMread(unsigned char nbOfCh,unsigned char hyst=3);
  int * getTable();
  unsigned char read();
private:
  unsigned char _nbOfCh;
  unsigned char _hyst;  
  int * _ptrTabVal;
};

#endif

Pour le CPP ça se corse :

/*
 PPMread.cpp - Library for reading PPM 6ch (negative modulated).
 V004
 Uniformiser les datatypes & rendre le nombre de canaux paramétrable
 V003
 Le code retour 2 (failsafe) n'est envoyé qu'une seule fois par trame (32ms)
 Chaque appel de la fonction read dans cet interval donnera un code retour 0
 V002
 Intégration d'un paramètre d'hystérésis
 */

#include "PPMread.h"
#include <Arduino.h>

static unsigned char c_hyst;
static unsigned char c_nbOfCh;


PPMread::PPMread(unsigned char nbOfCh,unsigned char hyst):
_nbOfCh(nbOfCh), _hyst(hyst)
{
  _ptrTabVal= (int*)malloc(sizeof(int) * _nbOfCh);
  // Timer1 setup
  TCCR1A=B00000000; // OCR2A/OCR2B : disconnected, Timer: normal mode
  TCCR1B=B00000010; // Falling edge CAPTUREPIN detection, Timer:normal mode, Prescaler=clk/8 
  TIMSK1=B00100001; // Activate CAPTUREPIN interrupt & OVF interrupt
  c_nbOfCh=_nbOfCh;
  c_hyst=hyst;
}

int * PPMread::getTable()
{
  return _ptrTabVal;
}

// Données de capture du Rx
static unsigned int vchPos[2][c_nbOfCh];
static unsigned char endTrame=0,rW=0,NrW=1;

ISR(TIMER1_CAPT_vect)
{
  static unsigned char curEdge=1;
  static unsigned int vchUp[c_nbOfCh];

  vchUp[curEdge]=ICR1; // Capture des timestamp 1 à 6
  curEdge++;
  if (curEdge>c_nbOfCh+1) {     // A partie du 7ème...   
    TCNT1=0;           // RESET du counter pour éviter le FailSafe
    if ((vchUp[c_nbOfCh+1]-vchUp[1]) > 30000) {  //Si la trame totale dépasse 15ms - Trame KO ou mauvaise synchro
      curEdge=0;            //Remise à 0 du compteur de fronts                      
    }
    else {                  //Sinon, une bonne trame est capturée, on calcule donc la valeur des canaux
      curEdge=1;
      unsigned int vchPosTMP;
      for (int i=0;i<c_nbOfCh;i++) {
        vchPosTMP=(vchUp[i+2]-vchUp[i+1]); //Mesure des canaux (diviser par 2 pour obtenir des µs)
        if (vchPosTMP > (vchPos[NrW][i]+c_hyst) || vchPosTMP<(vchPos[NrW][i]-c_hyst)) {
          vchPos[rW][i]=vchPosTMP; 
        }
        else vchPos[rW][i]=vchPos[NrW][i];
      }
      if (rW) {
        rW=0;
        NrW=1;
      }
      else{
        rW=1;
        NrW=0;
      }
      endTrame=1;           // Pour dire à la fonction read que de nouvelles valeurs sont disponibles
    }
  }
}

ISR(TIMER1_OVF_vect)
{
  endTrame=2;  // Le compteur a attends sa limite (32ms), on déclenche donc le FailSafe
}

unsigned char PPMread::read()
{
  switch (endTrame) {
  case 2:
    endTrame=0;
    return 2;
    break;
  case 1:
    endTrame=0;
    {
      int* movingPoint=_rxPoint;

      for (int i=0;i<c_nbOfCh;i++) { 
        *movingPoint=vchPos[NrW][i];
        movingPoint++;
      }
    }
    return 1;
    break;
  default:
    return 0;
  }
}

C’est dans ce dernier que les ennuis s’amoncellent :

  • Certaines données sont utilisées par les fonctions & les interruptions, je les déclare donc dans le CPP (voir c_nbOfCh & c_hyst), mais est-ce la bonne méthode ?
  • Pire, je dois crée 2 tableaux identiques à celui qui sert à transférer les données (voir vchPos) qui seront utilisés principalement dans l’interruption (TIMER1_CAPT_vect) mais aussi par la fonction read(), dois-je encore utiliser malloc mais dans le CPP ce coup-ci ?

Alors il y a moyen de gérer les interruptions comme 'friend' a une classe, et via la gestion de classe par singleton, mais ton code est très peu lisible pour moi, il y'a beaucoup trop de variable avec des noms très proches, et le domaine métier qu'il couvre m'est inconnu. j'ai passé une heure a tenter de réécrire la librairie en vain.

Un exemple de ce qui pourrait se faire : http://waterproofman.wordpress.com/2007/03/02/avr-uart-c-example/ Regarde les fichier uart.h et uart.cpp

Moi qui croyais avoir si bien commenté =(

Ma librairie ne fait que lire ce signal : A l'aide d'une fonction harware de l'ATmega328P, qui s'appelle l'INPUT CAPTURE UNIT.

Merci pour ton lien, et j'y ai vu des "friends" et aussi des variable "public static" utilisées par des interruptions dans une librairie...

[u]EDIT:[/u] Pour mes noms de variables, j'essaye de les faire courts mais c'est vrai que du coup ils se ressemblent inutilement... je vais aussi voir ce point.

tonyryu:
Moi qui voulait pas trop le brusquer et pas le faire fuir, vu qu’il commencer tout juste à apprendre la construction d’une classe. Là skywodd, c’est des coups à ce qu’Unisev se tape une crampe neuronale ^_^.

Je lui est pas donné la liste complète de tout les trucs qu’il pourrait amélioré, c’est pas mon but de faire fuir les membres du forum :grin:
Je donne juste par dose homéopathique quelques astuces de C++ :grin:

Bha moi je suis pour la réécriture complète du truc en C++ dans les meilleures pratiques... mais bon, il va falloir m'expliquer longtemps parce que je comprends pas vite ;) Ton constructeur par exemple il me botte bien parce qu'il simplifie... moins ya de code, mieux j'me porte !

Avez-vous vu mon message en fin de page précédente ? il devrait vous aider concernant l'utilité de la lib non ?

[u]EDIT :[/u] Un petit schéma en plus qui explique un peu le fonctionnement souhaité : Flêches BLEUES: passage de "flag", ROUGES: Ecriture de données, VERTES: Lecture de données

skywodd:

tonyryu: Moi qui voulait pas trop le brusquer et pas le faire fuir, vu qu'il commencer tout juste à apprendre la construction d'une classe. Là skywodd, c'est des coups à ce qu'Unisev se tape une crampe neuronale ^_^.

Je lui est pas donné la liste complète de tout les trucs qu'il pourrait amélioré, c'est pas mon but de faire fuir les membres du forum :grin: Je donne juste par dose homéopathique quelques astuces de C++ :grin:

Des paroles, des paroles... :grin:

Bon si je refais l'inventaire des problèmes sous un nouveau jour, voici ce qui continue de m’embêter :

  • Je ne sais pas comment déclarer proprement une ISR dans ma librairie
  • Je ne maîtrise pas assez bien malloc pour pouvoir créer dans ma lib un tableau de taille "paramétrable"

UniseV:
Je ne sais pas comment déclarer proprement une ISR dans ma librairie

Tu n’as pas trop de choix il n’existe qu’une syntaxe pour les ISR :

#include <avr/interrupt.h>

ISR(NOMDUVECTEUR_vect) {
  // Code ...
}

UniseV:
Je ne maîtrise pas assez bien malloc pour pouvoir créer dans ma lib un tableau de taille “paramétrable”

Dans l’idéal il faudrait éviter les allocations dynamiques, sur AVR ça peut facilement causer des fragmentations de mémoire et donc des bugs aléatoires.
En plus dans une branche de avr-gcc (je me rappelle plus laquelle) les allocations dynamiques sont complétement buggé.
cf: http://code.google.com/p/arduino/issues/detail?id=857

Le problème c'est que sous forme d'ISR, je n'arrive pas à déclarer mes interruptions en tant que "friends" de ma classe. :(

Pour l'allocation dynamique, ok on oublie malloc(), ça m'arrange.

Une solution proposée sur le forum international est de créer un tableau avec une valeur MAX (par exemple 12 canaux) et puis de n'utiliser que ce dont l'utilisateur a besoin... Sinon pourquoi ne pas passer par un #define dans le sketch de l'utilisateur ?

#define NBOFCH 12

J'ai vu ça dans d'autre librairies... mais je n'arrive pas à le mettre en pratique sur la mienne : http://arduino.cc/forum/index.php/topic,151314.msg1137306.html#msg1137306