Anthias
mail

arduino

Projet menu arborescent universel

Arduino + Encodeur optique

arduino


Introduction
Le programme complet
Conclusion

Maj : 18/04/12

Abstract :
This project is part of Arduino's applications (see my Arduino main page). It describes a universal tree menu working with an alone button, an optical encoder.
It can replace and emulate keyboard, potentiometer or any other device of command.
Actions are only : Turn left, Turn right, Push.

Résumé :
Ce projet s'inscrit dans le cadre des applications Arduino (voir ma page principale Arduino). Il décrit un menu arborescent universel commandé par un bouton unique, un encodeur optique,
Il peut remplacer et émuler un clavier, potentiomètre ou tout autre périphérique d'entrée.
Les actions sont seulement : Tourne à gauche, Tourne à droite, Appui.

arduino  Introduction

Pour diverses applications d'automates, j'ai recherché d'autres solutions que le classique clavier pour explorer des menus complexes.
J'ai utilisé un seul encodeur pour tout faire et gérer des menus arborescents en définissant une logique simple.

-Menus de niveau 0

Lors de mise sous tension le menu de base d'indice 1000 est exécuté.
En tournant le bouton d'un cran vers la droite, le menu suivant s'exécute :
1000 > 2000 > 3000 >…> 9000
Soit dans la version la plus simple, neuf menus au même niveau

En continuant vers la droite après 9000, blocage avec une séquence sonore signalant la butée de droite.
En tournant vers la gauche, évidemment parcours décroissant de 9000 à 1000 puis autre son indiquant la butée de gauche.

ky040

-Menus de niveau supérieurs

Dans un menu quelconque, disons 3000, la pression sur le bouton de l'encodeur monte au niveau supérieur:
Exemple menu 3000 + pression > 3100  puis en tournant vers la droite 3200…> 3900.
En enchainant les pressions : 3000 -> 3100 ->3110 -> 3111
Avec les menus de niveaux supérieurs, il faut aussi pouvoir redescendre !
Donc en tournant à gauche toute, séquence sonore de butée gauche et en continuant à tourner, redescente d'un niveau.
On peut parcourir ainsi les 8999 menus de 1000 à 9999, sur 4 niveaux

Une astuce permet aussi d'utiliser le même bouton pour des réglages à l'intérieur d'un menu en déclenchant une temporisation qui change la fonction du bouton pour quelques secondes, et sans action, redescente au menu inférieur.

Cette version ne semble pas présenter de bug connu et devrait s'adapter à toutes les situations.

-Logiciel final

Voici le logiciel définitif implanté sur une carte avec quelques périphériques basiques :
Afficheur LCD 4 lignes 20 caractères
Led trichrome
Petit haut parleur

Les menus qui s'exécutent ne font qu'afficher leur numéro, vous les remplacez par les votres en ne modifiant qu'une seule ligne !
Exemple :

  case 3000:
       menu_3000();   <<<< Remplacer par :  monMenuAMoi () ;
  break ;

Haut de page

arduino  Le programme complet

////////////////////////

/*  Christian Couderc 2015-07-15

Tree exemple for this demo of menus: add a new menu by adding in menuSwitch only -> case wxyz / menu name / break

Level 0 . . . . . . . . . . . . . . Level 1 . . . . . . . . . . . . . . Level 2 . . . . . . . . . . . . . . Level 3

 

1000 ______________________________ 1100 ______________________________ 1110 ______________________________ 1111
  !           !
  !           !____________________ 1200 ______________________________ 1210
  !           !                                    !
  !           !                                    !___________________ 1220 ______________________________ 1221
  !           !                                                                        !
  !           !                                                                        !___________________ 1222
  !           !                                                                        ! 
  !           !                                                                        !___________________ 1223
  !           !____________________ 1300
  !
  !
2000
  !
  !
3000
  !
  !
4000 ______________________________ 4100 ______________________________ 4110 ______________________________ 4111
                                                                                      !
                                                                                      !____________________ 4112
                                                                                        
                                                                                         
(9000)                                                                  With 4 levels 10 branches, maximum: 9999
                                                                                            (Easy to extend....)
*/

 

// http://www.baldengineer.com/arduino-f-macro.html

/*
Option : RollingH :
No : 1 > 2 > 3=last > (Beep + return 3)
Yes : 1 > 2 > 3 > 1 > 2 > 3 ...
*/

//////////////////////////////////////// Bibliotheques

#include <OneWire.h>      // Dallas
//#include <DS3232RTC.h>    //http://github.com/JChristensen/DS3232RTC   RTC + AT24C32
//#include <Time.h>         //http://www.arduino.cc/playground/Code/Time
#include <Wire.h>         //http://arduino.cc/en/Reference/Wire (included with Arduino IDE)void setup()
///*#include <Adafruit_BMP085.h>  // Pressure
#include <Encoder.h>
#include <LiquidCrystal_I2C.h>

char buffer [50] ;
char buffer2 [50] ;

///////////////////////////// LCD

byte PCF8574_8IO = 0x20 ; // I2C basic adress of expander port
byte I2C_LCD  = 7 + PCF8574_8IO  ; // (configuration of the 3 adresse lines)  + I2C basic adress =Final address of display port
byte nbcharparligne = 20   ;    // LCD display size
byte nblignes       =  4   ;    // LCD display size

/////////////////////////////////////// Led Colors
//                       1  2  4
#define Black   0    //  .  .  .
#define Red     1    //  R  .  .
#define Green   2    //  0  G  .
#define Yellow  3    //  R  G  .
#define Blue    4    //  .  .  B
#define Magenta 5    //  R  .  B 
#define Cyan    8    //  .  G  B
#define White   7    //  R  G  B

char partition [] = {Red , Green , Blue , Yellow , Cyan , Magenta, Black , White , Black} ; // Tri color led animation 

boolean anode ; // Common anode = TRUE or cathode = FALSE

///////////////////////////////////// General purpose /////////////////////////////////

long Chrono ;
byte Cmpt ;
///////////////////////////////////// Hardware Wiring for Uno /////////////////////////////////

const int DS3232Alarm = 0 ; // Pin 19  Mega2560
// Encoder knobLeft(3, 4) ; //   Good Performance: only the first pin has interrupt capability
// Pins 0,1 not avalaible (Rx Tx  reserved for serial USB)
// Pins 2 (int  0 UNO) Alarm fronm clock) // Pin 3 (int 1 UNO) + 4 used by optical encoser
const int PushButton = 5 ; // Pushbutton encodeur optique Fil vert eau
// 6 ; Free
const int Buzzer = 7 ;       // Small loudspeaker
const int LedBurn = 8 ;          // 0 : Active chaudière (led R sur platine éteinte)
// 9 : Free pin
//const int SS   = 10 ;  // SD card    https://tutoarduino.com/les-cartes-sd/
//const int MISO = 12 ;  // SD card    http://www.geeetech.com/wiki/index.php/Arduino_SD_card_Module
//const int MOSI = 11 ;  // SD card
//const int SCLK = 13 ;  // SD card

const int PushS = 9 ;
const int LedTriRed = 10 ;    // Commun cathode Fils bonne couleur
const int LedTriGreen = 11 ;
const int LedTriBlue = 12 ;
const int LedCard = 13 ;

const int Pot10t = A0 ;      // Potentiomètre 10p Analog A1 10 bits : Fil violet
const int Pot270 = A1 ;       // Potentiomètre 270 Analog A0 10 bits : Fil blanc
const int VbatCel = A2 ;     //
const int VbatMot = A3 ;     //
// A4, A5 : Free pins

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++

long NewLeft , PositionLeft  = -999;
byte Order[4] = {0,0,0,0} ; // For hierarchical menu 1,2,3,..9 ,11, 12,.. 99, 111,112,...999, 1111, 112, ..9999 : weight 10 ^ i Fix level allowed (unliited)
byte BkLevel, BkOrd , ActualLevel = 0 ; //indice of 4 levels array 0 to 3
byte OrderSize = sizeof (Order) ;

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++

// Initialisations, must to be placed here before setup for global visibility
//time_t myTime; // Clocm DS3232
//tmElements_t tm;
Encoder knobLeft(3, 4) ; // Opto  constructor
OneWire ds(6); // Dallas DS18s20 constructor
//Adafruit_BMP085 bmp; // Initialise pressure sensor
LiquidCrystal_I2C lcd(I2C_LCD , nbcharparligne , nblignes);  // LCD constructor

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++

void setup()
{
Serial.begin(115200); // Start the serial interface
Wire.begin(); // Start the I2C interface
initPorts () ; // For leds and pushbutton
initLcd20x4() ; // initialize the lcd 
chronoStart () ;

//knobLeft.write(0);
menuSelect (1) ;
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++

 

void loop()
{
menuSelect (ckeckEncoderOrder ()) ;
delay (100) ;
Cmpt ++  ; 
flashLeds (Yellow, Cmpt/16)  ;
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++

void menuSelect (byte posEnc)        //////////////////////  Menu Niveau + Branche
{
//  byte Order[4] = [0,0,0,0] ; // For hierarchical menu 1000..9999 : weight 10 ^ i
//  byte Level = 0 ; //indice of 4 levels array 0 to 3
if (posEnc != 128) // New event
     {
     if (posEnc == 129)   
        soundLeft () ;
     else
          {
          BkLevel = ActualLevel ;  // actual valid menu, Save Vertical
          BkOrd = Order[ActualLevel] ; // Horizontal           
      
         if (posEnc == 255)   
                 encreaseLevel () ; // Push on button : Level Encrease
         else
             {
             posEnc = 1 + ((posEnc/4) %10) ; // Rotate : 0 .. 9, 10 branches max by level
             Order[ActualLevel] = posEnc ;
             }
          }     
     }
menuSwitch (posEnc) ; // For dynamics menus only
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++

byte ckeckEncoderOrder ()
{
if (digitalRead (PushButton) == 0 )  // Action level up down
     {
     debounce ();
     return 255 ;
     }   
else
          {
          NewLeft = knobLeft.read();  // Optical encoder
          if (NewLeft != PositionLeft)
              {
              PositionLeft = NewLeft ; 
              if (PositionLeft >=0)
                   return ((PositionLeft)%127) ;
              else
                  {
                  if (PositionLeft > -5)
                     return 129;
                  else 
                     return (decreaseLevel () ) ;
                  }
             }
          else // Knob don't move         
                return 128 ;
          }
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++

long ckeckEncoderValue () // For menus adjusting parameter only !
{
if (digitalRead (PushButton) == 0 )  //  Validation of value > encrease
     {
     encreaseLevel(); 
     menuSelect (0) ; // 5400 -> 5410 = Write position
     }
else
     return ( knobLeft.read());  // Optical encoder
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++

int menuCalc ()
{
//  byte Order[4] = [0,0,0,0] ; // For hierarchical menu 100 to 9999 , without 0 inside : weight 10 ^ (3-i)
 if (ActualLevel <3) // Clean by security
           Order[3] = 0 ;
if (ActualLevel <2)
           Order[2] = 0 ;         
if (ActualLevel <1)
           Order[1] = 0 ;                   
   
int calc =  1000 * Order[0]  +  100 * Order[1] +  10 * Order[2] + Order[3] ; //indice of 4 levels array 0 to 3
return calc ;
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++

void menuDebug ()     // Shows all menu parameters, to be replaced by true menu
{
lcd.setCursor (0,0) ;
lcd.print(buffer);
lcd.setCursor (0,1) ;
sprintf (buffer,"Order %d %d %d %d  ", Order[0], Order[1], Order[2], Order[3] );
lcd.print(buffer);
lcd.setCursor (0,2) ;
sprintf (buffer,"Menu %d  Level %d  ", menuCalc () , ActualLevel);
lcd.print(buffer);
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++

void menuNotExist ()     // Restaute aat ald good value
{     
/*sprintf (buffer, "!!! Menu  absent!!!") ;
menuDebug (); 
delay (3000) ;*/

ActualLevel = BkLevel;  // Vertical
Order[ActualLevel] = BkOrd ; // Horizontal  

NewLeft = 987 ; // To re read encoder if push knob
knobLeft.write(4*(BkOrd-1)) ;
soundMenuNotExist ();
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++

void encreaseLevel () // Enter by knob push only from <menuselect>
{
if (ActualLevel < OrderSize-1)
        {
        ActualLevel++ ;
        Order [ActualLevel] = 1 ;
        knobLeft.write(0) ;
        PositionLeft =  543 ; // To avtivate menuswitch
        }   
// else  no action

soundEncreaseLevel ();
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++

byte decreaseLevel ()  // Rotate negative
{          
byte knobVal ;
if (ActualLevel > 0)
        ActualLevel = ActualLevel-1 ;  // Restore before increment
       
knobLeft.write( 4 * (Order[ActualLevel]) - 1 ) ;
PositionLeft = 765 ; // To avtivate menuswitch
soundDecreaseLevel ();
return 0 ;   
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++  

void restoreValidState ()
{
ActualLevel = BkLevel;  // Vertical
Order[ActualLevel] = BkOrd ; // Horizontal  

NewLeft = 987 ; // To re read encoder if push knob
knobLeft.write(4*(BkOrd-1)) ;
soundRestoreValidState ();
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++  

void menuSwitch (byte oldOrdLev1)
{
int menuName = menuCalc()  ; //  clean bad values , return 1000...9999 

switch (menuName)
  {
case 1000:
      menu_1000(); 
break ;

case 1100:
      menu_1100(); 
break ;

case 1110:
      menu_1110(); 
  break ;
 
  case 1111:
      menu_1111();  
  break ;
     
  case 1200:
      menu_1200();  
  break ;
 
  case 1210:
      menu_1210();  
  break ;
 
  case 1220:
      menu_1220();  
  break ;
 
  case 1221:
      menu_1221();  
  break ;
 
 case 1222:
      menu_1222();  
  break ;
 
  case 1223:
      menu_1223();  
  break ;
    
  case 1300:
  menu_1300();  
  break ;
 
  case 2000:
       menu_2000();  
  break ;
 
  case 3000:
       menu_3000();  
  break ;
 
  case 4000:
       menu_4000();  
  break ;

  case 4100:
       menu_4100();  
  break ;
 
   case 4110:
       menu_4110();  
  break ;
 
    case 4111:
       menu_4111();  
  break ;
 
     case 4112:
       menu_4112();  
  break ;
 
  default :      
        menuNotExist () ;
  break ;
  }
// else no move, nothing to do !

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++

void menu_1000 ()
{
sprintf (buffer,"****  Menu 1000 ****") ;
menuDebug () ;  // Good menu, no error in increasing level
}

void menu_1100 ()
{
sprintf (buffer,"****  Menu 1100 ****") ;
menuDebug () ;
}

void menu_1110 ()
{
sprintf (buffer,"*Dernier menu 1110**") ;
menuDebug () ;
}

void menu_1111 ()
{
sprintf (buffer,"****  Menu 1111 ****") ;
menuDebug () ;;
}

void menu_1200 ()
{
sprintf (buffer,"****  Menu 1200  ****") ;
menuDebug () ;
}

void menu_1210 ()
{
sprintf (buffer,"****  Menu 1210 ****") ;
menuDebug () ;
}

void menu_1220 ()
{
sprintf (buffer,"****  Menu 1220 ****") ;
menuDebug () ;
}

void menu_1221 ()
{
sprintf (buffer,"****  Menu 1221 ****") ;
menuDebug () ;
}

void menu_1222 ()
{
sprintf (buffer,"****  Menu 1222 ****") ;
menuDebug () ;
}

void menu_1223 ()
{
sprintf (buffer,"****  Menu 1223 ****") ;
menuDebug () ;
}

void menu_1300 ()
{
sprintf (buffer,"*Dernier menu 1300**") ;
menuDebug () ;
}

void menu_2000 ()
{
sprintf (buffer,"****  Menu 2000 ****") ;
menuDebug () ;
}

void menu_3000 ()
{
sprintf (buffer,"****  Menu 3000 ****") ;
menuDebug () ;
}

void menu_4000 ()
{
sprintf (buffer,"*Dernier Menu 4000 *") ;
menuDebug () ;
}

void menu_4100 ()
{
sprintf (buffer,"*Dernier Menu 4100 *") ;
menuDebug () ;
}

void menu_4110 ()
{
sprintf (buffer,"*Dernier Menu 4110 *") ;
menuDebug () ;
}

void menu_4111 ()
{
sprintf (buffer,"*Dernier Menu 4111 *") ;
menuDebug () ;
}

void menu_4112 ()
{
sprintf (buffer,"*Dernier Menu 4112 *") ;
menuDebug () ;
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// +++++++++++++++++++++  Not specific for menus        +++++++++++++++++++++++++++++++++++
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 

 

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ LEDS

void triColorLeds (int color)  
{
if (anode)
    color = 7 - color  ;
digitalWrite(LedTriRed, ((color&1)==1)); // 0 allume la led
digitalWrite(LedTriGreen, ((color&2)==2));
digitalWrite(LedTriBlue, ((color&4)==4));
}   

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++

void flashLeds (int color, int indx)   // Visual animation
{
if (indx%2) 
    triColorLeds (color);
else
    triColorLeds (Black); // éteint
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++

void flashLedLoop (int color, byte nbLoops)
{
for (int x=0 ; x<nbLoops ; x++)
  {
  flashLeds (color, x) ;
  delay (50) ;
  }
triColorLeds (Black);
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ I/O ports

void initPorts (void)  // For leds and pushbutton
{
pinMode(LedTriRed, OUTPUT)  ; 
digitalWrite(LedTriRed, 0);
pinMode(LedTriGreen, OUTPUT)  ; 
digitalWrite(LedTriGreen, 0);
pinMode(LedTriBlue, OUTPUT)  ; 
digitalWrite(LedTriBlue, 0);
pinMode(LedCard, OUTPUT)  ; 
digitalWrite(LedCard, 0);
pinMode(LedBurn, OUTPUT)  ;
digitalWrite(LedBurn, 0);
pinMode (PushButton, INPUT_PULLUP) ;
pinMode (PushS, INPUT_PULLUP) ;
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ LCD

void initLcd20x4()
{
lcd.init();       // initialize the lcd
lcd.backlight();
lcd.setCursor(0, 0); 

lcd.clear (); 
}

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++

void ClearLineLCD (byte line)

{
lcd.setCursor(0, line);
for (int x =0 ; x < nbcharparligne ; x++)
  lcd.print (" ");
lcd.setCursor(0, line); // Cursor start of line 
}
// cursor // noCursor // blink // noBlink

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Buzzer

void beepDurFreq (int duration, int Frequency) 
{
//digitalWrite(ledPin, LOW); 
tone (Buzzer,  Frequency ); // Example : 440 = La 3
delay(duration);
//digitalWrite(ledPin, HIGH); 
noTone(Buzzer);
}
// ++++++++++++++++++++ Sounds utilities
void soundRecord ()
{
beepDurFreq (25,  1000 ) ; // Duration  Frequency
}
// ++++++++++++++++++++
void soundLeft ()
{
for (int i=20 ; i >0 ; i--) 
    beepDurFreq (25, i*5) ; // Alarm left
//delay (1000) ;
}
// ++++++++++++++++++++
void soundMenuNotExist ()
{
for (int i=0 ; i<6 ; i++)
    beepDurFreq (25, 220 * (1+4*(i%2))) ;  // 220 La 3 // 880 La 5
}
// ++++++++++++++++++++
void soundEncreaseLevel ()
{
for (int i=0 ; i<6 ; i++)
    beepDurFreq (25, 1046 * (1+4*(i%2))) ;  // 1046 Do 5 // 4186 Do 7
}   
// ++++++++++++++++++++
void soundDecreaseLevel ()
{
for (int i=0 ; i<12 ; i++)
    beepDurFreq (25, 41 * (1+4*(i%2))) ;  // 41 Mi 0 // 165 Mi 3
}
// ++++++++++++++++++++
void soundRestoreValidState ()
{
for (int i=0 ; i<6 ; i++)
    beepDurFreq (25, 220 * (1+4*(i%2))) ;  // 220 La 3 // 880 La 5
}

// ++++++++++++++++++++

void debounce ()
{
triColorLeds (Cyan) ;
while (digitalRead (PushButton) == 0 )
    delay (100);
 triColorLeds (Black) ;
 }

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++  Debug tools

void chronoStart (void)
{
Chrono = millis (); 
}
 
  // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++  

long chronoStop (void)
{
long bkCh = Chrono ; 
return ((millis () - bkCh)) ;   // Dont modify starting value
}
 
  // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++  
 
long chronoReset (void) // Reset count value
{
long bkCh = Chrono ; 
Chrono = millis (); 
return (Chrono  - bkCh) ;
}
 
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++  

          void showTree ()
          {
          char buffer1 [60] ;
          Serial.print (chronoReset ()) ;
          sprintf (buffer1, "  ... Level: [%d] . <%d> ..  Position: %u     New: %u  ..." , ActualLevel, menuCalc(), (int)PositionLeft, (int)NewLeft) ;
          Serial.println (buffer1) ;
          }

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++  

arduino

 Haut de page

 

arduino  Conclusion

Ce menu a été appliqué à divers automatismes et ne semble pas présenter de bugs connus. Il est très rapide et gaspille peu de ressources.

Retour vers ma page principale Arduino arduino

© Christian Couderc 1999-2019     Toute reproduction interdite sans mon autorisation


* Page vue   65   fois       IP : 100.24.122.228

 Haut de page         Dernière retouche le 27 Juin 2019 à 15 h         Retour page précédente

   Collector