Projet menu arborescent universel Arduino + Encodeur optique Page très ancienne n'ayant pas été remise à jour |
Maj : 18/04/12
Abstract :
Résumé : |
Pour diverses applications d'automates, j'ai recherché d'autres solutions que le classique clavier pour explorer des menus complexes. Lors de mise sous tension le menu de base d'indice 1000 est exécuté. En continuant vers la droite après 9000, blocage avec une séquence sonore signalant la butée de droite. |
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.
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 ;
////////////////////////
/* 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) ;
}
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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.
Depuis 2022 je n’utilise plus qu’exclusivement |