Arduino Forum

International => Français => Topic started by: zeric on Apr 09, 2012, 09:30 pm

Title: [Resolu] Programmation de base ...
Post by: zeric on Apr 09, 2012, 09:30 pm
Bonjour,
Bête question que voilà :
Pourquoi je n'affiche pas mon message (ni l'animation) de la boucle "for" du loop  sur le lcd,
alors que je reste appuyé sur le bouton "select" du keypad shield ?
Je vois bien sur le terminal qu'il est dans la boucle, vu le temps qu'il met à afficher le chiffre suivant,
mais rien sur le lcd !
Je pense que je suis fatigué, je n'y arrive plus ... merci d'avance pour votre aide!

Code: [Select]
#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
char S1[] = "Arduino Rocks ?";
byte bouton;
int vBouton; //LCD keypad shield value

void setup(){
Serial.begin(9600);
pinMode(A0, INPUT);
lcd.begin(16, 2);
titre();
}

void titre(){
lcd.setCursor(15,0);
lcd.print(S1);
 for (byte pos = 0; pos < 15; pos++){
lcd.scrollDisplayLeft();
delay(190);
}
}

void appui(){
vBouton =analogRead(0);
if (vBouton >1010) bouton = 0;
else if (vBouton >715 && vBouton <725) bouton = 1; //select
else if (vBouton >473 && vBouton <483) bouton = 2; //left
else if (vBouton >123 && vBouton <133) bouton = 3; //up
}

void loop(){
appui();
Serial.println(bouton,DEC);
delay(150);
if (bouton==1){
lcd.setCursor(1,1);
lcd.print("En cours");
for (byte i=10; i<15; i++){
lcd.setCursor(i,1);
lcd.print("*");
delay(200);
}
for (byte i=14; i>9; i--){
  lcd.setCursor(i,1);
lcd.print("-");
delay(200);
}
}
}
Title: Re: Programmation de base ...
Post by: barbudor on Apr 09, 2012, 09:39 pm
Comment est reliée la broche Vo (broche numéro 3) de ton LCD ?
Essaye en la reliant à GND.

(J'ai perdu presque 1 journée pour la même co....rie)
Title: Re: Programmation de base ...
Post by: zeric on Apr 09, 2012, 09:59 pm
c'est celui là :

http://docs.google.com/leaf?id=0B6hLuy3sB5ntYzNmNDQ0OTMtOWZmYi00NjI3LTllZGYtNWM2ZWQ0M2FmODUw&hl=en (http://docs.google.com/leaf?id=0B6hLuy3sB5ntYzNmNDQ0OTMtOWZmYi00NjI3LTllZGYtNWM2ZWQ0M2FmODUw&hl=en)

Edit: Regardez bien le cablage de ce shield, le transistor est un CMS caché sous le LCD et piloté par la borne D10, il sera donc bienvenu
de ne pas utiliser cette borne dans un sketch sous peine de :
- dans le meilleur des cas, le sketch ne fonctionne pas comme vous voulez.
- dans le pire des cas, D10 = game over !
(ce problème n'interfère pas dans mon code :()
Title: Re: Programmation de base ...
Post by: zeric on Apr 09, 2012, 10:02 pm
Et ce n'est pas une histoire de masse, le message lancé depuis le setup s'affiche très bien ...
Title: Re: Programmation de base ...
Post by: barbudor on Apr 09, 2012, 11:25 pm
Ok. Parce que avec le montage indiqué et le curseur du potar au milieu, sur mon montage rien ne s'affichait.
Il fallait mettre le curseur très près de la masse pour avoir un contraste qui permettait de voir quelque chose.

Donc le "titre()" s'affiche mais pas le reste.

Sinon, tu n'es pas clair : qu'est-ce qui ne marche pas exactement ?
- la détection du bouton
- l'affichage sur LCD dans la boucle ?

Cas 1 : détection du bouton

Qu'affiche le Serial.println(bouton,DEC); dans la boucle ?
Tu vois bien bouton passer à 1 ?

Les fenètres de comparaison me semblent bien serrées
J'aurais écrit quelque chose du genre :
Code: [Select]

if ( vBouton < 256 )
  bouton = 3;
else if ( vBouton < 512 )
  bouton = 2;
else if ( vBoutton < 768 )
  bouton = 1;
else
  bouton = 0;


Cas 2 : Affichage

Vire tout ce qui concerne le bouton dans la boucle et ne garde que le code d'affichage sur le LCD.
Ca marche ?
Title: Re: Programmation de base ...
Post by: zeric on Apr 10, 2012, 10:51 am
Bonjour,
Tout d'abord, le code que j'ai posté est directement chargeable dans un arduino, que l'on "coiffe" d'un LCD Keypad Shield standard.
Je vais faire plus clair, j'utilise une Duemilanove et l'IDE 0022.
La détection des 3 boutons fonctionne très bien, seul l'affichage de "en cours" et de la petite animation ne se fait pas sur le LCD.
Les fenêtres de comparaison sont très larges à mon goût (le delta ne dépasse jamais 1 lors de l'appui sur un bouton).
Je n'ai pas jugé utile de dire que l'affichage de la boucle fonctionnais sans le bouton, puisque le but est justement de comprendre
pourquoi cela ne fonctionne pas dans ce cas précis.
Merci d'avance.
Title: Re: Programmation de base ...
Post by: zeric on Apr 11, 2012, 12:14 am
Si personne ne trouve le pourquoi du comment, je suis dans la mouise, parce-que c'est un problème en 2 étapes !
Allez, ça aiguisera peut-être l'appétit des plus fort :smiley-sleep: en codage  :
Si j'utilise un variable bistable (à la place de mon test sur le bouton) pour rentrer dans les 2 boucles "for", je ne peux
pas sortir de ces mêmes boucles avec un break conditionné à l'intérieur, et je suis donc obligé d'attendre la fin des 2 boucles...

Code: [Select]
for (byte i=10; i<15; i++){
if (bouton == 2) break; //test du bouton, marche pas!
if (vBouton >473 && vBouton <483) break; //test de la valeur du bouton, marche pas!
if ( (bouton == 2) || (vBouton >473 && vBouton <483) break; //ben décidemment, marche pas!
lcd.setCursor(i,1);
lcd.print("*");
delay(200);
}


...je vais quand même pas faire comme la pub à la télé du gars qui explose ces écrans LCD quand ça le gonfle  ]:D

Title: Re: Programmation de base ...
Post by: barbudor on Apr 11, 2012, 12:31 am
Je suis partit en déplacement mais demain si je suis pas trop crevé quand je rentre je compte faire l'essai moi-même.
j'ai justement un écran LCD sur ma table ;)
A moins que quelqu'un d'autre le fasse avant...
Mais c'est vrai que comme cela, il n'y a rien qui me saute aux yeux.

Quoi que je n'ai pas regardé la specs de ton shield.
Il viens d'où ?
Lien ?
Schémas ?
Title: Re: Programmation de base ...
Post by: zeric on Apr 11, 2012, 12:45 am
Euh, comme quoi, je ne suis pas le seul à être fatigué  :P, le lien qui donne le schéma est un un peu plus haut ;)
Title: Re: Programmation de base ...
Post by: barbudor on Apr 11, 2012, 09:09 pm
:P toi-même 
:D

J'avais inversé D8 et D9
Je reteste
I'll be back
Title: Re: Programmation de base ...
Post by: barbudor on Apr 11, 2012, 10:04 pm
J'ai compris.
Ca a été long (et pas très bon  :smiley-sad-blue:) mais j'ai compris

Remplace ton code dans la boucle par :
Code: [Select]
if (bouton==1){
lcd.setCursor(15,1);
lcd.print("En cours");
for (byte i=10; i<15; i++){
lcd.setCursor(15+i,1);
lcd.print("*");
delay(200);
}
for (byte i=14; i>9; i--){
  lcd.setCursor(15+i,1);
lcd.print("-");
delay(200);
}
}


:D
Si ça marche aussi chez toi, réfléchit.
je t'accorde 24H.....

EDIT: Alors ? Tu as trouvé ? Le suspens est à son comble..... Plus que quelques heures !  :smiley-mr-green:
Title: Re: Programmation de base ...
Post by: zeric on Apr 13, 2012, 01:12 pm
Bonjour,
Désolé pour les 24h, je ne suis pas connecté depuis  :smiley-red:
Ça m'a permis de me reposer :smiley-sleep:
J'ai donc réfléchi, comme tu me le demandais  ]:D au manque de logique de ton décalage (i+15) !
Et après 2/3 essais  :* , je pense avoir pointé la cause ! (sans avoir à mettre ce décalage)
Ma routine titre() ne devrait normalement pas interférer avec le loop vu qu'elle est appelée une seule et unique fois (dans le setup)
L'enlever étant la solution de facilité, j'ai re-réfléchi  ;) :
C'est pas le positionnement du curseur à 15, c'est pas la boucle for, c'est le "lcd.scrollDisplayLeft();" :smiley-eek:
J'ai bien essayé de comprendre la librairie "LiquidCrystal.cpp", mais le newbie que je suis à besoin d'apprendre  :smiley-roll-sweat:
Donc c'est moi qui n'ai pas compris ou c'est un bug de la librairie ? (je penche pour la 1° solution)
Title: Re: Programmation de base ...
Post by: barbudor on Apr 13, 2012, 03:39 pm
Salut

Ce n'est pas un bug de la lib, ou alors un bug de nommage.
Tout le problème tient dans la définition de ce que fait la fonction "lcd.scrollDisplayLeft()", ou plutot la commande correspondante qui est envoyé au LCD.

En fait quand on lit le nom de cette fonction on s'attend à ce qu'elle prenne tous les caractères qui sont dans l'affichage et les décales vers la gauche. En fait ce n'est pas le cas.

Tous les écrans de ce genre sont basés sur le même composant de gestion HD44780 (http://www.sparkfun.com/datasheets/LCD/HD44780.pdf) qu'ils soient 2x16, 2x20, jusqu'à 2x40
A l'intérieur du composant il y a une mémoire organisé en 2x40 caractères
Quand l'écran est plus petit que 2x40, ce qui est affiché est une fenêtre sur cette mémoire.
Or cette fenêtre est glissante.

La commande "lcd.scrollDisplayLeft()" ne va pas déplacer les caractères dans la mémoire vers la gauche mais elle va déplacer la fenêtre d'affichage vers la droite.

A l'initialisation, la fenêtre affiche les colonnes 0 à 15.
Ensuite quand tu place ton curseur en 0,16 et que tu écris "Arduino Rocks ?", tu écris dans les colonnes 16 à 32 en mémoire.
La commande lcd.scrollDisplayLeft() va ensuite décaler la fenêtre de visu vers les colonnes 16 à 32 et non pas déplacer les caractères vers 0 à 15.

Donc pour que la phrase "En cours" soit visible en dessous de "Arduino Rocks ?", il faut l'écrire 16 caractères plus loin en (1,16)

Donc ton code marchait bien.
Individuellement les fonctions marchaient toutes seules mais dès qu'on les met ensemble elles donnaient l'impression de ne plus marcher car on ne voyait pas "En cours" s'afficher puisque en dehors de la fenêtre de visu courante.

A+
Title: Re: Programmation de base ...
Post by: zeric on Apr 13, 2012, 09:35 pm
Merci barbu !
Je l'aurais vu dès le départ si j'avais mis depuis le départ la dernière modif de mon programme !
(au début j'affichais mon texte au bon endroit, un scroll à droite hors de l'écran, et un retour à gauche vers l'origine)
La fonction ne me plaît pas, je vais faire une routine de scroll...
Bon, je vais retenter la 2°étape et je reviens  XD
Title: Re: Programmation de base ...
Post by: zeric on Apr 17, 2012, 08:10 pm
Me revoilà!
Code: [Select]
#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

boolean bp1;
byte bouton;
int vBouton; //LCD keypad shield value

void setup(){
Serial.begin(9600);
pinMode(A0, INPUT);
lcd.begin(16, 2);
}

void appui(){
vBouton =analogRead(0);
if (vBouton >1010) bouton = 0;
else if (vBouton >715 && vBouton <725) bouton = 1; //select
else if (vBouton >473 && vBouton <483) bouton = 2; //left
else if (vBouton >123 && vBouton <133) bouton = 3; //up
}

void loop(){
appui();
Serial.println(bouton,DEC);
delay(150);
if (bouton == 1) bp1 = true;

if (bp1 == true){
lcd.setCursor(1,1);
lcd.print("En cours");
for (byte i=10; i<15; i++){
if (bouton == 2) break;
lcd.setCursor(i,1);
lcd.print("*");
delay(200);
}
for (byte i=14; i>9; i--){
if (bouton == 2) break;
  lcd.setCursor(i,1);
lcd.print("-");
delay(200);
//bp1 = false;
}
}
}


Bon, pourquoi mes "break" conditionnés ne fonctionnent que lors de la sortie des 2 boucles ?
Une bonne âme ?
Title: Re: Programmation de base ...
Post by: barbudor on Apr 17, 2012, 09:48 pm
je ne comprend ni ta question, ni ton code (qui n'est pas très bien indenté d'ailleurs).

Tu rentre dans le if si bp1 est true, ce qui ne peut arriver que si bouton vaut 1 une fois.
Ensuite, puisque tu as commenté bp1=false, tu devrais rentrer dans le if pour toujours.
Sauf que si tu appuie sur le bouton 2, alors tu devrais rentrer dans le if mais jamais exécuter le contenu des 2 boucles, jusqu'à ce que tu relâche le bouton 2 (en fait tu rentre dans la boucle mais tu en sort tout de suite).

Si c'est çà que tu veux faire, c'est franchement moche comme codage. Peu clair et peu lisible.
Si tu ne veux pas rentrer dans la boucle, n'y rentre pas, test bouton==2 avant. De toute façon, bouton ne va pas changer de valeur tant que tu ne sort pas de là pour retourner dans appui().
Si ce n'est pas çà que tu veux faire, alors çà prouve que tu n'arrive même pas à relire ton propre code.
;)

Title: Re: Programmation de base ...
Post by: zeric on Apr 18, 2012, 11:05 pm
Merci barbu d'être le seul à bien vouloir m'aider.
Pour mon problème, ça prouve que je débute en codage et que j'ai d'énormes progrès à faire, merci de me le rappeler !

Quote
c'est franchement moche comme codage. Peu clair et peu lisible.

Relire ce que je viens juste d'écrire au-dessus ! Et si tu peux m'aider à faire mieux, montre moi, je suis preneur.

//bp1=false, mis en commentaire exprès pour passer dans la boucle constamment et bien voir le problème.

Quote
Sauf que si tu appuie sur le bouton 2, alors tu devrais rentrer dans le if mais jamais exécuter le contenu des 2 boucles, jusqu'à ce que tu relâche le bouton 2 (en fait tu rentre dans la boucle mais tu en sort tout de suite).

Ben mince alors, c'est justement ce que je veux..., et si tu l'essais, tu verras que ce n'est pas le cas, ou alors j'avais bu quelques whiskys avant de faire mes essais  :smiley-red:

Bon, pour une explication claire et concise, en littéral :
-J'appuie sur un bouton "n" du keypad, ça me déclenche une animation avec des "for" qui doit tourner constamment jusqu'à une autre intervention.
-Si j'appuie sur un autre bouton "p" du keypad, je dois arrêter l'animation de suite (et pas seulement à la fin des 2 boucles comme ça fonctionne actuellement, malgré les "break").
(je rajoute pour être bien compris que, bien sûr, je fais en sorte que bp1 ne reste pas actif tout le temps)

:~ :~
Title: Re: Programmation de base ...
Post by: barbudor on Apr 19, 2012, 11:03 am
Il y a plusieurs façon d'aborder le problème.

1ere approche : méthode débutant
Puisque le test du boutton se fait dans la fonction appui(), si tu souhaites qu'un appui sur le bouton sorte de la boucle, il faut donc appeller appui() dans la boucle afin de retester et mettre à jour la variable bouton
Exemple (simplifié à une seule boucle et avec une indentation correcte):
Code: [Select]
if (bp1 == true) {
    lcd.setCursor(1,1);
    lcd.print("En cours");
    for (byte i=10; i<15; i++) {
        appui();
if (bouton == 2) break;
lcd.setCursor(i,1);
lcd.print("*");
delay(200);
     }
}


L'inconvénient de cette approche c'est que si elle marche encore bien alors que ton code reste simple (tu ne gère que 2 choses en même temps : le bouton et le LCD), elle va devenir infernale dès que tu va vouloir ajouter plus de tâches.
Notamment, le fait d'utiliser une boucle avec un delay(200) monopolise le CPU dans cette boucle.
Imagine que maintenant il faille rajouter :
- gérer la liaison série,
- contrôler un moteur,
...

Il faut donc découper chaque tâche en modules élémentaires non bloquants qui vont pouvoir être appelés dans loop() sans empêcher loop() de tourner en continue pour passer la main successivement à chaque tache.

En gros, tu devrait pouvoir écrire :
Code: [Select]
loop()
{
  gere_bouton();
  gere_lcd();
}


Dans chaque tâche, tu ne dois pas rester bloqué. Tu rentre dans la fonction, tu regarde s'il y a quelque chose a faire. Si oui, tu le fais, si non tu ressort immédiatement. Jamais tu n'attends!
Il faut donc définir chaque tâche comme une machine d'états qui va passer d'un état à un autre en fonction d'évènements. Si l'évènement ne se produit pas, la fonction retourne pour passer la main à une autre tâche.

Par exemple, essayons de définir la tâche qui va gérer le bouton.
Je choisit le fonctionnement suivant :
La tache à 2 états :
1) Aucun bouton n'a été pressé
2) Un bouton à été pressé
Les évènements :
Ax) Un bouton x est pressé
B) Le bouton a été acquitté par une autre tache
Le déroulement:
- Si dans l'état 1 et évènement Ax alors je passe dans l'état 2, signalant à l'extérieur qu'un bouton est pressé
- Si dans l'état 2 et évènement Ax (même bouton), je ne change rien (le but est qu'un appui long ou plusieurs appuis sur le même bouton ne sont pas pris en compte plusieurs fois)
- Si dans l'état 2 et évènement Ay (autre bouton), je change la signalisation du bouton
Une tâche extérieure peut remettre l'état 1) pour confirmer qu'elle a pris en compte le bouton et qu'un nouvel appui sera un nouvel évènement.

On peut écrire comme cela :
Code: [Select]
enum { ATTENTE_BOUTON, BOUTON_APPUYE } etat_bouton = ATTENTE_BOUTON;
int bouton = 0;
void gere_bouton( void )
{
  switch( etat_bouton )
  {
  case ATTENTE_BOUTON:
    int b = appui(); // modier appui() pour retourner une valeur plutôt que modifier la var. globale
    if ( b > 0 )
    {
      bouton = b;
      etat_bouton = BOUTON_APPUYE;
    }
    break;
  case BOUTON_APPUYE:
    int b = appui(); // modier appui() pour retourner une valeur plutôt que modifier la var. globale
    if ( b > 0 )
    {
      bouton = b;
    }
    break;
  }
}

// fonction pour une autre tâche afin de savoir si un bouton a été pressé, si oui récupérer sa valeur et acquitter.
int lit_bouton( void )
{
  int b;
  switch( etat_bouton )
  {
  case ATTENTE_BOUTON:
    // pas de bouton pressé depuis la dernière fois, je retourne 0
    b = 0;
    break;
  case BOUTON_APPUYE:
    // je prend la valeur du bouton pour la renvoyer
    b = bouton;
    // et je remet l'automate en attente de bouton
    bouton = 0;
    etat_bouton = ATTENTE_BOUTON;
    break;
  }
  return b;
}


Dans la tache gere_xxxx() tu appelle lit_bouton() pour savoir si un bouton a été appellé depuis la dernière fois.

Ok, d'accord, c'est un marteau pilon pour écraser une mouche.
Mais c'est plus facile d'introduire le concept sur un exemple simple pour le comprendre.
En pratique même moi je n'écrirait pas quelque chose d'aussi compliqué pour cela.
Le code suivant est aussi efficace mais reste propre:

Code: [Select]
int bouton = 0;
void gere_bouton( void )
{
    int b = appui(); // modier appui() pour retourner une valeur plutôt que modifier la var. globale
    // Le test est important. Je ne remet pas a zéro bouton si un nouvel appui n'a pas eu lieu.
    if ( b > 0 )
      bouton = b;
}

// fonction pour une autre tâche afin de savoir si un bouton a été pressé, si oui récupérer sa valeur et acquitter.
int lit_bouton( void )
{
  int b = bouton;
  bouton = 0;
  return b;
}


Maintenant vient le plus gros morceau : l'automate de gestion du LCD.

Prend un papier et un crayon, et essaye de découper la tâche du LCD en un certains nombre d'états.
Quels sont les évènements à considérer :
- bouton appuyé
- temps écoulé

Savoir qu'un bouton a été pressé, c'est la fonction lit_bouton().
Savoir qu'un temps a été écoulé.
Pour le temps, il faut bannir la fonction delay() sauf quand tu as des temps très court et critiques à gérer de manière atomique (atomique signifie qu'on n'a pas le droit de découper, sans interruption).
Je te propose le code suivant :
Code: [Select]
unsigned long timer_debut;
void timer_start()
{
  timer_debut = millis();
}
bool timer_ecoule( unsigned long duree_ms )
{
  if ( (millis() - timer_debut) > duree_ms )
    return true;
  return false;
}


Conseil : tu n'est pas forcement obligé de considérer des états distincts pour les différentes étapes de la boucle d'animation. Tu peut avoir un état ANIMATION_1 et un compteur. Peut être te faudra t'il 2 états pa animation ...... ;)

A ton crayon, relevé des copies dans quelques heures :D
Title: Re: Programmation de base ...
Post by: zeric on Apr 19, 2012, 10:58 pm
Un grand merci  XD

Quote
De toute façon, bouton ne va pas changer de valeur tant que tu ne sort pas de là pour retourner dans appui().

Tu m'avais pourtant donné la solution juste avant :smiley-roll: , comme quoi le dire c'est bien, l'écrire c'est mieux (enfin pour moi!)
L'appel de "appui()" dans les boucles juste avant le test est la solution.

Pour les "delay" je le fait déjà, toute ma partie critique (loop) ne comporte aucun "delay" (la partie mise ici dans le loop l'est uniquement
pour le débogage et est en fait dans une fonction [sous-programme] qui ne tourne qu'au début de mon appli -un certain temps-, mais jamais pendant).
Mon loop est très très court et ne comporte quasiment que des appels à des fonctions (n'ayant aucun "delay", même de 1ms), je gère du calcul pour rafraîchissement
d'affichage au centième, il faut donc qu'il tourne en dessous de la milliseconde...
Sur ce coup, j'étais passé complètement à côté du rafraîchissement des variables dans une boucle, je le ferais plus, promis :smiley-mr-green:

Par contre, ton approche pour le retour de valeurs au lieu de jouer avec les var globales, je m'y mets tout juste, ça va m'aider, j'apprends, j'apprends...
Et aussi aider ceux qui le liront (cf. le titre ;)), surtout que c'est pour du matos standard, donc directement utilisable...