[Resolu] Menu (Menu Backend fortement modifié) et PROGMEM (F)

Bonjour a tous
J'ai dans mon projet en 1.0.1, un Arduino UNO R2, un LCD 4x20, 4 boutons analogiques sur A0 et une interface de commande (deux leds pour les tests). Le tout fonctionne très bien.
le problème que je rencontre est que mon menu grossi et me prend toute la RAM de mon UNO. J'ai plusieurs modes d'utilisation qui ont chacun plusieurs paramètres. Je peux sauvegarder tout ça en EEPROM et grâce a une structure, à différents endroits pour avoir plusieurs config différentes. J'en suis a plus de 40 menu / sous menu / Modification de valeurs.
Je voudrais donc simplement utiliser le PROGMEM ou F() pour les textes de mon menu.
Je ne connais pas du tout comment ça marche. J'imagine qu'il faudrait que je mémorise le pointeur d'adresse du texte en Flash pour chaque menu mais c'est la que je ne sais pas comment faire. Non plus pour utiliser ce pointeur dans mon menu. Enfin si c'est la bonne solution.

Si quelqu'un a déjà fait un truc du genre, merci de m'aiguiller sur la façon de faire.

Je suis complètement bloqué, mon programme plante si je rajoute un menu et fonctionne très bien en l'enlevant. Je ne peux plus avancer par manque de RAM et j'en suis qu'au tiers des fonctions codées et 80% du menu.

Merci pour tout info, lien utile

Caape

PS:
Lib perso ou modifiée:

  • bouton analogique --> perso avec gestion dynamique du nombre de boutons (calcul des valeurs automatique) et appui court / long avec mode répétition
  • MenuBackend --> très fortement modifié pour faciliter la navigation et les possibilités (Encore en cour et c'est celui ci que je voudrais modifier)
  • LiquidCrystal --> Ajout d'une fonction Print2 qui prend 2 valeurs en plus pour la position ligne colonne du 1er caractère

Bonjour :slight_smile:

Regarde vers le milieu de cette page tu as tout ce qu'il faut :slight_smile:

C'est un peu bizarre comme méthode, mais ça fonctionne..

Edit: je viens d'écrire ces 2 macros, ça peut être utile pour simplifier un peu le code :slight_smile:

#define PGM_CreateString( name, str ) PROGMEM prog_char name[] = str; PROGMEM const char *p_##name = name
#define PGM_ReadString( name, buffer ) strcpy_P( buffer, (char*)pgm_read_word(&(p_##name) ) )

PGM_CreateString( string1, "blabla" );
PGM_CreateString( string2, "etc" );
PGM_CreateString( string3, "val = %d" );

void setup()			  
{
  Serial.begin( 9600 );
  delay( 500 );
  
  char buf[12];
  
  PGM_ReadString( string1, buf );
  Serial.println( buf );
  
  PGM_ReadString( string2, buf );
  Serial.println( buf );
  
  byte val = 14;
  PGM_ReadString( string3, buf );
  sprintf( buf, buf, val );
  Serial.println( buf );
}

void loop()			  
{
}

Bonjour,

Les macro c'est pratique mais ça cause plus de problèmes qu'autre chose au final.
Rien en vaut une bonne vieille déclaration à la main "propre" :wink:

Pour la macro de lecture une fonction "inline" serait plus adapté (vérif des arguments lors de la compilation).

Ps :

PROGMEM prog_char name[] = str;

Les types prog_... sont déprécié, comme le précise la référence de la avr-libc :
http://www.nongnu.org/avr-libc/user-manual/group__avr__pgmspace.html#gaa475b6b81fd8b34de45695da1da523b6

Il est désormais conseillé d'utiliser les types classiques (char, int, ...) avec le mot clef PROGMEM et de rendre la variable const !
(les nouvelles versions du compilateurs GCC mettent en flash toute variables de type const, le mot clef PROGMEM est tout simplement ignoré)

De plus il n'est pas forcément nécessaire de relire la chaine en RAM pour l'utiliser.
Toutes les classes dérivées de Stream/Print, ce qui inclus Serial, LiquidCrystal, LiquidCrystal_I2C, EthernetClient, Wire, et bien d'autres hérite des fonctions print() et println() dont une version sait lire directement en Flash.

Par exemple :

Serial.println( (__FlashStringHelper*)string_en_flash );

marche directement.

Le cast vers le type "bidon" __FlashStringHelper* sert à utiliser une surcharge (version) particulière de print() qui sait qu'elle doit aller chercher les caractères en Flash plutot qu'en RAM.

Bonsoir
Merci pour vos réponses.
Je suis pas trop fan des macros non plus. J'ai lu beaucoup de critiques négatives.
Je suis reparti de zéro pour faire un test de la fonction F() (Dur de trouver des infos avec les moteurs de recherche sur ce nom)
J'ai d'abord fais

Serial.println(F("Test"));

J'ai donc rechercher la définition de Println dans print.h (j'ai bien appris sur l’héritage avec le WRITE) et j'ai récupéré la définition et le code pour utiliser F() en argument de fonction.
le truc c'est que ça utilise une classe "__FlashStringHelper" comme type de pointeur. C'est la que je suis pommé pour l'instant. Le code pour mon menu fonctionne a peu prés mais j'ai pas tout compris et j'aime pas ça. Par contre, j'ai gagné plus de 1000 octets de RAM libre a l’exécution et pour ça, je suis bien contant.
Je vais m’atteler à faire ma lib de mon menu en plus clair, je comprendrais mieux. Je me mélange les pinceaux avec tous mes testes.

Si quelqu'un a des liens vers des explications plus poussées sur le fonction F() et son utilisation, je suis preneur.
Merci
A+
Caape

EDIT:
Je viens de tester dans mon vrai programme et je bloque sur la déclaration de mon menu

J'ai juste ajouter le F() par rapport à avant (Je suis hors procédure, déclaration global)

MenuItem m1 = MenuItem(F("Demarrer"));

Ca me fais une erreur de compilation que je ne comprends pas trop :

b_50_menu_declaration:9: error: statement-expressions are not allowed outside functions nor in template-argument lists

d'ou le code suivant

MenuItem m1;

Ca fonctionne mais je dois faire l’affectation dans une procédure donc double de ligne mais s'il le faut, je le ferais.

comment faire plus simple?
merci

Caape

skywodd:
Les macro c'est pratique mais ça cause plus de problèmes qu'autre chose au final.

Pour la macro de lecture une fonction "inline" serait plus adapté (vérif des arguments lors de la compilation).

Les types prog_... sont déprécié, comme le précise la référence de la avr-libc :

Quel genre de problème? Ca remplace juste du texte par un autre lors de la compilation :slight_smile: A part le manque de vérification du type d'arguments, il n'y a pas grand chose à reprocher aux macros, c'est utilisé partout :slight_smile:

Comment faire une fonction qui prend le nom d'un paramètre et y ajoute "p_" devant?

Je ne crois pas que la avr-lib qui est livrée avec l'IDE Arduino soit à jour...

caape:
Je suis pas trop fan des macros non plus. J'ai lu beaucoup de critiques négatives.
Je suis reparti de zéro pour faire un test de la fonction F()

Et pourtant.. F() ce n'est pas une fonction mais une macro :slight_smile: Tu peux la trouver dans WString.h.

Bonsoir
Désolé, je ne suis pas souvent connecté.

Quote from: caape on 25 October 2012, 20:19:06
Je suis pas trop fan des macros non plus. J'ai lu beaucoup de critiques négatives.
Je suis reparti de zéro pour faire un test de la fonction F()

Et pourtant.. F() ce n'est pas une fonction mais une macro Tu peux la trouver dans WString.h.

Quand je dis que je ne suis pas fan, c'est qu'une fois, j'ai fais une macro pour me simplifier le développement jusqu'au jour ou j'ai eu un bug ou j'ai mi plusieurs jours pour l'identifier. C'était une histoire de ++i dans l'argument d'appel qui ce faisait plusieurs fois dans le code de la macro. Après, c'est vrai que c'est bien pratique si on connait les pièges mais je préfère éviter car je ne connais pas suffisamment le C / C++ et les macros. Et en ce moment, c'est surtout sur les pointeurs où j'ai bien du mal entre les & et *.

Sinon , je n'ai pas trop avancé sur mon problème. et d'autres sont arrivés. ils sont peut-être lié.
je n'arrive pas a afficher le texte de mon menu en utilisant un pointeur de string (macro F() pour la déclaration dans un "__FlashStringHelper *name"). Ca m'affiche plein de caractères au hasard comme si ça me faisant toutes la flash jusqu’à un marqueur de fin de string. Je cherche encore

a+

Si tu montrais un exemple de ce que tu veux vraiment faire, on pourrais t'aider un peu plus :slight_smile: Un exemple qui fonctionne, avec des strings en RAM.. et à partir de là ca sera plus simple de résoudre le problème :slight_smile:

En attendant... essaie peut-être:

MenuItem m1(F("Demarrer"));

Enfin j'en sais rien je n'ai jamais utilisé F()...

Le problème est que le constructeur de MenuItem ne sait pas distinguer un pointeur en Flash d'un pointeur en RAM.

MenuItem m1(F("Demarrer"))

Va bien utiliser une chaîne de caractère stockée en Flash, mais la classe MenuItem ne le sait pas.
Elle reçoit un pointeur qu'elle croit être en RAM parce qu'elle n'a pas le code pour gérer un pointeur en Flash.

Pour utiliser directement, il faut modifier la lib MenuItem en profondeur.

Bonjour

guix:
En attendant... essaie peut-être:

MenuItem m1(F("Demarrer"));

Merci, je vais essayer ceci ce soir. Mais cela ne devrais juste m'aider sur la déclaration pour n'avoir qu'une ligne au lieu de deux.

barbudor:
Pour utiliser directement, il faut modifier la lib MenuItem en profondeur.

J'ai justement modifié cette lib pour quelle accepte ce type de pointeur (__FlashStringHelper* comme type de paramètre).
Mon problème actuel est que je dois avoir un bug au niveau de la détection de fin de chaîne de caractères, quoique je n'est pas non plus le début de mon texte, mais je n'ai pas encore regardé en profondeur.

pour info:
dans la lib menuItem, je reçois en paramètre un pointeur en flash avec la macro F() que je mémorise dans

__FlashStringHelper *name

et pour afficher le texte du menu je fais un simple

lcd.print(name)

Si je mets le tout dans le setup(), ça fonctionne. Je n'ai pas le code avec moi pour l'instant mais je retenterai des que possible.

Pour donner le code, ça va être dur car il est divisé sur pleins de fichiers avec du code désactivé pour les tests et qui provient de mon ancien programme sans la gestion de la Flash où je n'ai pas encore tout mis à jour. mais je vais essayer de faire quelque chose pour simplifier.

Je croix que je vais repartir de zéro plutôt que de tout modifier. je trouverai peut-être l'erreur plus facilement.

Merci
a+

Bonsoir
J'ai fais quelque tests supplémentaires.

MenuItem m1(F("Demarrer"));

ne fonctionne pas en dehors d'une procédure. Il FAUT utiliser F() dans une procédure . Il faut donc deux lignes, une pour la déclaration en global et une pour l'initialisation avec F().

Ensuite, j'ai mis pleins de Serial.print() un peu partout qui affichent l'adresse pointeur de la chaîne de caractères. Déjà la BUG. les valeurs ne sont pas bonne. donc recherche d'où vient l'erreur.
en cherchant, je me suis rendu compte que ça venait de mon constructeur du menu

class MenuItem {
public:

  MenuItem( void)
  {
    MenuItem(NULL, NULL);
  }

  MenuItem( __FlashStringHelper *itemName)
  {
    MenuItem(itemName, NULL);
  }



  MenuItem( __FlashStringHelper* itemName, void (*pt2Func) )
  {
    name=itemName;
    if (itemName!=NULL ) Serial.print((unsigned int)name);
    if (itemName!=NULL ) Serial.println(name);

    pt2Fct=pt2Func;
    before = right = after = left = 0;
  }

protected:
  __FlashStringHelper *name;
  MenuItem *before;
  MenuItem *right;
  MenuItem *after;
  MenuItem *left;

  void (*pt2Fct);    //pour la fonction quand USE
};

(Attention , extrait de code!)
Je faisais mon init avec la deuxième façon

 m1 = MenuItem(F("Retardateur"));

j'ai fais un test pour tester directement la 3eme en faisant m2 = MenuItem(F("Intervallometre"),NULL);
Et la, ça fonctionne
mais je ne comprends pas pourquoi ?
Quelqu'un peut-il m'expliquer où est mon erreur?

Je reprends mon code principal pour voir si ça fonctionne mieux comme ça.

A+

Ton problème vient que tu ne peut pas appeller un constructeur comme une simple fonction membre.
Un constructeur va forcément créé un nouvel objet.
Quand dans le 2eme constructeur tu appelles le 3eme, tu crée donc un nouvel objet qui n'est pas mémorisé au lieu d'appliquer les instructions a l'objet courant.
Le bon code serait plutot :

 MenuItem( void)
  {
    _init(NULL, NULL);
  }

  MenuItem( __FlashStringHelper *itemName)
  {
    _init(itemName, NULL);
  }

  MenuItem( __FlashStringHelper* itemName, void (*pt2Func) )
  {
    _init(itemName, pt2func);
  }

protected:
  _init( __FlashStringHelper* itemName, void (*pt2Func) )
  {
    ..... code d'init
  }

Mais le plus simple dans ce cas précis est d'utiliser les paramètres par défaut avec un seul constructeur ;

 MenuItem( __FlashStringHelper* itemName = NULL, void (*pt2Func) = NULL )
{
   ... code d'init
}

Cela permet de prendre par defaut la valeur NULL pour les paramètres non spécifiés.

Maintenant je ne vois pas pourquoi ca ne marche que dans une fonction. Mais je ne connais pas la macro F(). J'utilise plutot PSTR().
Où est déclarée F() que je regarde comment elle est codée ?

Merci Barbudor
Pour les constructeurs, Je ne voyais pas le fonctionnement de cette manière, d'où le fait que je ne trouvais pas mon erreur. Je pensais qu'on pouvait faire de cette manière car l'objet était déjà instancié.

Maintenant je ne vois pas pourquoi ca ne marche que dans une fonction. Mais je ne connais pas la macro F(). J'utilise plutot PSTR().
Où est déclarée F() que je regarde comment elle est codée ?

Elle est déclaré dans le fichier "WString.h" dans le core arduino.
je devrais peut-être utiliser PSTR() à la place, c'est dure de trouver une info sur F(), les moteurs de recherche n'aiment pas du tout faire des recherche sur un seul caractère.

Je vais maintenant pouvoir continuer de refaire le menu maintenant que j'ai suffisamment de RAM et plus de bug. Je ferais un post si ça intéresse quelqu'un sur mon menu.
Encore merci

a+