[LCD] optimisation de la lib liquidCrystal

Salut à tous.

Sur un petit projet qui fait chauffer mon Léonardo, je me suis rendu compte que la lib liquidCrystal me pourri bien la vie. J’ai donc décidé de m’y mettre, à grands coups de #define pour virer tous ces méchants digitalWrite() qui me donnent des boutons! Dans l’idée, il suffit de bien câbler son nono, puis de faire quelques #define AVANT le #include de la lib (les #define seront pris en compte avant la compilation de la lib) :

Dans le sketch :

#define LCD_USE_DATA PORTF  // data LCD sur port F
#define LCD_DATA_NUM MSB      // On utilise les bits 4 à 7, LSB pour les bits 0 à 3 et 8BITS pour un fonctionnement en 8 bits
#define LCD_USE_RS PORTF    // RS LCD sur port F
#define LCD_RS_NUM 1          // bit 1
#define LCD_USE_EN PORTD    // EN LCD sur port D
#define LCD_EN_NUM 1          // bit 1
/* R/W non connecté, donc pas besoin de déclarer
#define LCD_USE_RW PORTF   // R/W LCD sur port F
#define LCD_RW_NUM 0          // bit 0  */
#include <LiquidCrystal.h>

Le reste de l’utilisation de la lib reste inchangé, les #define permettent juste d’optimiser la compilation de la lib.

Cette lib, justement, voici un apperçu de mes modifs :

#ifdef LCD_USE_EN                      // Shorting time for pulse using direct port writing
#define PULSE \
  LCD_USE_EN &= ~(1 << LCD_EN_NUM); \
  delayMicroseconds(1);             \
  LCD_USE_EN |= (1 << LCD_EN_NUM);  \
  delayMicroseconds(1);             \
  LCD_USE_EN &= ~(1 << LCD_EN_NUM); \
  delayMicroseconds(50)   

#else
#define PULSE pulseEnable()
#endif
// --------------- (...) ----------------------


inline void LiquidCrystal::command(uint8_t value) {
#ifdef LCD_USE_RS
  LCD_USE_RS &= ~(1 << LCD_RS_NUM);
  send(value);
#else
  send(value, LOW);
#endif  
}

inline size_t LiquidCrystal::write(uint8_t value) {
#ifdef LCD_USE_RS
  LCD_USE_RS |= (1 << LCD_RS_NUM);
  send(value);
#else
  send(value, HIGH);
#endif  
  return 1; // assume sucess
}

/************ low level data pushing commands **********/

// write either command or data, with automatic 4/8-bit selection
#ifdef LCD_USE_RS
void LiquidCrystal::send(uint8_t value) {
#else
void LiquidCrystal::send(uint8_t value, uint8_t mode) {
  digitalWrite(_rs_pin, mode);
#endif

#ifdef LCD_USE_DATA                   // Using direct port writing
#ifdef LCD_USE_RW                     // Using pin R/W
  LCD_USE_RW &= ~(1 << LCD_RW_NUM);   // R/W = 0 
#endif
#ifdef LCD_DATA_NUM
#if LCD_DATA_NUM == "MSB"           // Using MSB 4 bits of the port
  LCD_USE_DATA &= 0x0F;
  LCD_USE_DATA |= (value & 0xF0);  // MSB
  PULSE;
  LCD_USE_DATA &= 0x0F;
  LCD_USE_DATA |= (value << 4);    // LSB
  PULSE; 
#elif LCD_DATA_NUM == "LSB"         // Using LSB 4 bits of the port
  LCD_USE_DATA &= 0xF0;
  LCD_USE_DATA |= (value >> 4);    // MSB
  PULSE;
  LCD_USE_DATA &= 0xF0;
  LCD_USE_DATA |= (value & 0xF0);  // LSB
  PULSE; 
#elif LCD_DATA_NUM == "8BITS"       // Using 8 bits of the port
  LCD_USE_DATA = value;
  PULSE;
#else
#error Bad LCD_DATA_NUM define.  
#endif  
#else
#error Missing define LCD_DATA_NUM setting.
#endif

#else                                // old lib code so... slowly...

  // if there is a RW pin indicated, set it low to Write
  if (_rw_pin != 255) { 
    digitalWrite(_rw_pin, LOW);
  }
  if (_displayfunction & LCD_8BITMODE) {
    write8bits(value); 
  } else {
    write4bits(value>>4);
    write4bits(value);
  }
#endif
}

en gros, avec mes #define, le code de la lib devient :

inline void LiquidCrystal::command(uint8_t value) {
  PORTF &= 0xFD;
  send(value);
}

inline size_t LiquidCrystal::write(uint8_t value) {
  PORTF |= 0x02;
  send(value);
  return 1; // assume sucess
}

/************ low level data pushing commands **********/

// write either command or data, with automatic 4/8-bit selection
void LiquidCrystal::send(uint8_t value) {
  PORTF &= 0x0F;
  PORTF |= (value & 0xF0);  // MSB
  PORTD &= 0xFD; 
  delayMicroseconds(1);             
  PORTD |= 0x02;  
  delayMicroseconds(1);             
  PORTD &= 0xFD; 
  delayMicroseconds(50)   
  PORTF &= 0x0F;
  PORTF |= (value << 4);    // LSB
  PORTD &= 0xFD; 
  delayMicroseconds(1);             
  PORTD |= 0x02;  
  delayMicroseconds(1);             
  PORTD &= 0xFD; 
  delayMicroseconds(50)   
}

Pour utiliser cette optimisation, il faut savoir sur quels ports on connecte son LCD, et ça n’a souvent rien à voir avec le numéro des pins de la carte, la léonardo en est un exemple terrible… Ca oblige aussi à utiliser 4 bits consécutifs d’un même port pour les data du LCD, mais je pense que mon code tournera pas loin de 20 fois plus vite que celui d’origine. Ca limite aussi l’utilisation d’un seul LCD, mais qui utilise deux LCD sur un petit nono?

Je n’ai pas modifié l’initialisation qui dure une petite centaine de ms, ce qui me gênait plus, c’est dans le prog lui-même…

Je n’ai pas encore testé à l’upload, mais à la compilation, ça passe. Je vous ai joint le fichier cpp modifié de la lib.

Ce n’est qu’un début, mais je compte bien m’attaquer aux autres libs!

Je prends toutes vos suggestions!

LiquidCrystal.cpp (9.88 KB)

Mouais, bon... je viens de m'apercevoir que mes #define dans le sketch ne sont pas pris en compte dans la lib. une idée sur ces préprocessions? ça avait l'air tellement beau... snif!

Salut,

Pas d'idée concernant ton problème, mais en ce qui concerne l'idée de base, pourquoi ne pas faire deux prototypes pour l'instanciation : - un avec les numéro de pin => affection à 1 d'un boolean pour signaler le fonctionnement en mode "Arduino pur et dur" - un avec le port + pin de départ => affection à 0 du boolean Ainsi la lib reste transparente pour l'utilisateur, il n'a pas à trafiquer la lib, et tu peux ainsi créer gérer plusieurs LCD

Hello,

Je pense que tu devras inclure tes defines dans le .h, car le .cpp de la lib ne va pas lire ce qu'il y a dans ton sketch, donc seules les définitions seront changées, mais pas l'implémentation.

Donc crée un fichier "liquidCrystal_hacks_config.h", dans lequel tu affines tes choix.

Ou alors, tu les définis plus haut, dans un Makefile (ou une option pour ajouter -D MON_DEFINE à gcc)

Dans l'idée, c'était de créer / modifier des libs pour utiliser la compilation conditionnelle. En gros, on déclare un certain nombres de paramètres, puis on inclus la lib. sur le papier, c'est très joli, et ça permet de réaliser de grandes choses! Mais ça ne marche pas (j'ai collé des #warning pour voir ce qu'il se passe à la compilation, mais rien). Dommage, j'avais un projet de refaire tout le core arduino sur ce prindipe (en incluant par exemple la déclaration d'utilisation de ressources matérielles et gestion de conflits)...

j'ai googeulé pas mal de trucs, mais le net ne propose que des defs standard du préprocesseur, sans trop s'étendre. Pourtant, sur des trucs comme les µC, ça serait grave utile.

Les libs sont pourtant capables de se passer des variables de préprocesseur, mais dans mon cas, ça passe pas... j'ai essayé d'inverser des trucs, mais toujours rien, pourtant, j'ai bien modifié la bonne lib, celle que j'appelle...

Certes, mais est-ce que tes #define sont bien lus lors de la compilation de la lib ? S'ils sont dans le sketch, ce ne sera pas le cas.

Je pense comme Xavier que les #define ne sont pas connus quand LiquidCrystal.cpp est compilé.

Ah bin j’avais pas vu que les defines était dans le sketch, donc oui je confirme les #define "ne passent pas " à la lib dans ce cas, d’où ma suggestion sur la création d’un autre prototype d’instanciation.

XavierMiller:
Certes, mais est-ce que tes #define sont bien lus lors de la compilation de la lib ? S’ils sont dans le sketch, ce ne sera pas le cas.

alors là, tu répondrais à une de mes questions!

J’ai fait un test :

mon sketch :

#define SUPER_CINCI
#ifdef SUPER_CINCI
#warning ok pour le sketch
#endif
#include <liquidCrystal.h>

et dans la lib :

#warning Ouverture de la lib
#ifdef SUPER_CINCI
#warning super_cinci est dans la lib!
#endif

Dans la console, je retrouve dans l’ordre :

sketch.ino : warning : #warning ok pour le sketch
(...)
liquidCrystal.cpp : #warning Ouverture de la lib

donc le #define est bien pris en compte dans le sketch, mais n’est pas accessible à la lib. Pourtant, le header du core arduino.h fait un tas de #define qui se retrouvent ensuite dans les libs!!! j’ai commencé à lire la doc du gcc, mais c’est long!!!

Pourquoi mes #define ne sortent pas du sketch??? y a-t-il une directive particulière à appliquer pour que ça passe? J’ai essayer de passer par un "#include “config.h” (config.h se trouve dans le même répertoire que le sketch, et il n’est pas question de le mettre dans les libs…) qui contient mes #define. Je retrouve bien mes define dans le sketch, mais pas plus loin. et pas moyen de mettre le #include <LiquidCrystal.h> dans le config.h

C'est du standard C. Ne lis pas la doc de GCC, mais celle du C/C++ ;) un #define n'est valable que dans l'unité de compilation où il est défini et n'en sort pas. Donc visible depuis ton sketch et les .h qu'il charge, mais surtout pas dans le .cpp de la lib.

Si tu veux que ce soit accessible partout, ajoute un #include "mes_settings.h" partout, ou modifie le .h original

Pas con l'idée! mais voilà, il faut que le compilateur vienne chercher le config.h du sketch actuel, car chaque sketch aura alors le sien! Ce qui veut donc dire rajouter l'argument -I à l'appel de avr-g++.exe, via l'IDE... et là, c'est le drame!

Pas de miracle, : tu devras configurer ce fichier à chaque fois... Vu le côté "compilation dynamique", il faudrait déplacer ce fichier de lib dans tes sketches.

Une idée en passant: vu que c'est du C++, rien ne t'empêcherait de réécrire la classe pour qu'elle soit en fait un template, ou une série de classes qui prédéfinissent des comportements en assemblant des bouts communs. Le compilateur fera le nettoyage nécessaire pour virer les modules non utilisés.

XavierMiller:
Une idée en passant: vu que c’est du C++, rien ne t’empêcherait de réécrire la classe pour qu’elle soit en fait un template, ou une série de classes qui prédéfinissent des comportements en assemblant des bouts communs. Le compilateur fera le nettoyage nécessaire pour virer les modules non utilisés.

Je croyais que les gros-mots étaient proscrits sur le forum!

blague à part… en 1997, en BTS, j’étais un champion de l’assembleur (68HC11), mais je faisais l’impasse sur le C, trop “abstrait” pour moi. j’ai décidé de franchir le pas il y a 3 ans, et maintenant, je pense avoir de bonnes bases, mais la notion classes/objets, si je vois bien le truc sur un PC, sur un petit nono, j’en vois pas trop l’utilité (quoique la classe “print” semble permettre le LCD.print…)

Bref…

Pas de miracle

Mince!

J’y ai pensé, à copier non pas le fichier, mais l’ensemble de la lib (.cpp et .h et tout ce qui va avec) dans le répertoire de mon sketch, mais on perd totalement le bénéfice du #include… et le jour où on apporte une modif primordiale, bah personne n’en profite.(1)

je pensais qu’il y avait moyen de faire de mes “macros” #define des “macros” globales visibles de toutes les libs en include…

(1) : mais ça marche!!!
sketch (test_leonardo.ino) :

#define LCD_EN_NUM 1          // bit 1
#include "config.h"

config.h :

#ifdef LCD_EN_NUM
#warning il est là!!!
#endif

console de compilation :

In file included from test_leonardo.ino:21:
/config.h:4:2: warning: #warning il est là !!!

alors pourquoi j’y arriverais pas avec une lib normale???

EDIT : bah, est-ce qu’on pourrait faire un #include de la lib avec son chemin réel (C:\programm files\Arduino…) ?

Je code en C++ pur sur mon arduino, et tant que ça rentre dans la mémoire, c'est que c'est bon. De toutes façons, LiquidCrystal est une classe. Et un template n'est qu'un générateur de classe, donc je ne vois pas le souci ;-)

XavierMiller: donc je ne vois pas le souci ;-)

bah je veux bien comprendre, mais moi, j'ai pas été élevé à ça. chaque ligne de code que je pond, j'imagine toujours l'équivalent en assembleur, car le µC, lui, il ne travaille qu'en assembleur. et les histoires de classres, ben va traduire ça en assembleur...

ceci dit, tu m'as apporté une grande aide à mon souci et je t'en remercie. Je ne désespère pas cependant de trouver LA solution : la "compilation conditionnelle".

L'ide arduino tranforme le sketch en pseudo librairie pour un programme "MAIN.C" qui est le même pour tout le monde. donc c'est dans le main.c qu'il peut être possible de faire quelque chose genre #dire que tous les define sont globaux

Si tu crées des classes dérivées de LiquidCrystal, qui reprennent chacune une configuration que tu avais #définie, tu vas ainsi proposer plusieurs classes pré-câblées différentes, et le compilateur embarquera uniquement celle dont il a besoin.

Ainsi, tu crées - CinciLCD1, qui correspond à #define 1 et #define 2 - CinciLCD2 ... - ...

Tu vois ce que je veux dire ?

oui oui! mais à raison de (pour la méga2560 par exemple) 11 ports possibles, 3 configs possible de branchement des datas sur un port, et 8 possibilités par port et par pins EN, RS, R/W... on compte? dans les 7500 possibilités de câblage, donc instancier 7500 classes?

Je pensais faire plus simple... XD

Pas moyen de paramétrer certains "blocs" ?