Display Menü mit Bedienung durch Encoder (Drehgeber)

Hallo zusammen,
für mein Projekt möchte ich ein Menü erstellen, sodass verschiedene Szenarien Auswählbar sind. Dieses Menü möchte ich implementieren und auf meinem 16x2 LCD-Display anzeigen lassen.

Meine Idee: Ich möchte mit einem Encoder zwischen verschiedenen Menüpunkten auswählen. Das soll in etwa wie folgt aussehen:
| Größe wählen: |
| klein | an dieser Stelle soll zwischen "klein", "mittel", "groß" und "sehr klein" gewählt werden

Umgesetzt habe ich es bisher wie folgt: Ich habe den Wertebereich des Encoders von 1 bis 80 beschränkt. Nun soll bei den Werten 1-20 "sehr klein" erscheinen, bei 21-40 "klein" usw. und anschließend mit dem Button des Encoders der jeweilige Parameter wählbar sein.
Für jeden Parameter (klein, mittel, groß, sehr klein" soll dann eine bestimmt Menge an Wasser durch eine Pumpe in ein Gefäß gefüllt werden. (Noch nicht implementiert)

Mein Code sieht wie folgt aus:

#include <Encoder.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C meinLCD(0x27,16,2);

int newPosition = 3;  // Definieren der Variable newPosition
int oldPosition  = -999; // Definieren der Variable oldPosition mit fiktiver Startposition ungleich der neuen Position


Encoder meinEncoder(5, 6); //Rotary Encoder definieren


void setup() {
  Serial.begin(9600);  //starten des seriellen Monitors
  Serial.println("Basic Encoder Test:");
  meinLCD.init();  
  meinLCD.backlight();
 
}


void loop() {
  newPosition = meinEncoder.read();  //Abfrage der Encoderposition
  newPosition = constrain(newPosition,0,80);  //Begrenzung des Wertebereichs von 0-80
  if (newPosition != oldPosition) {
    oldPosition = newPosition;
    Serial.println(newPosition);
    textausgabe();  //Aufruf der Funktion "textausgabe"
  }
}

void textausgabe() {
  //Je nach Wert des Encoders, sollen verschiedene Infos auf dem LCD Display zu sehen sein:
  if (newPosition < 21) 
  { meinLCD.clear();
    meinLCD.print("Groesse waehlen:");
    meinLCD.setCursor(0,1);
    meinLCD.print("sehr klein");}
  if (newPosition < 41 && newPosition >20)
  { meinLCD.clear();
    meinLCD.print("Groesse waehlen:");
    meinLCD.setCursor(0,1);
    meinLCD.print("klein");}
  if (newPosition < 61 && newPosition >40)
  { meinLCD.clear();
    meinLCD.print("Groesse waehlen:");
    meinLCD.setCursor(0,1);
    meinLCD.print("mittel");}
  if (newPosition < 81 && newPosition >60)
  { meinLCD.clear();
    meinLCD.print("Groesse waehlen:");
    meinLCD.setCursor(0,1);
    meinLCD.print("gross");} 
  
}

Problem: Führe ich den Code aus, ohne die Funktion "textausgabe" zu nutzen (deaktivieren der Funktion durch "//"), so ändern sich die Werte des Drehgebers im seriellen Monitor sehr konstant und flüssig.
Sobald ich die Funktion "textausgabe" einbeziehe (so wie im geposteten Code), ändern sich die Werte sehr schleppend. Außerdem kommt es vor, dass ich nach rechts drehe und sich die Werte (wie gewollt) erhöhen. Drehe ich dann anschließend nach links, bleibt der Wert stehen oder erhöht sich ebenfalls (ungewollt! er soll sinken!).

Habt ihr Ideen, woran das liegen kann? Außerdem bin ich auch über Alternativvorschläge zur Verwirklichung meines kleinen Projektes dankbar (andere Libs, andere Ansätze etc.)

Vielen Dank schonmal! :slight_smile:

Mal als Grundüberlegung: Schaue Dir doch mal das Menü von Jomelo an (Hier im Forum suchen).

Gruß Tommy

Was Tommy meint.

Danke, ich war zu faul den Link zu suchen.

Gruß Tommy

Tommy56:
Danke, ich war zu faul den Link zu suchen.

Gruß Tommy

Kein Problem.
Da ich da öfter rein schau, liegt er bei mir auf dem Desktop.

Habt ihr das Forum von Jomelo schon mal "durchgearbeitet"? Könnt ihr mir bitte folgendes beantworten:

**1. Die Projektvorstellung von Jomelo umfasst wohl mehrere Kapitel. Leider endet Kapitel 2.3 (Inbetriebnahme) bereits auf Seite 1 und wurde bis Seite 5 nicht fortgesetzt (weiter habe ich nicht gesucht..). Wo ist der Rest zu finden? **

**2. Die Beiträge auf den ersten Seiten sind veraltet und beziehen sich auf die Versionen (z.B. der Lib), die ganz zu Anfang aktuell war. Nun gibt es einige Aktualisierungen, die nicht mehr mit den Erklärungen übereinstimmen. Es ist also ziemlich schwer den Erklärungen mit der aktuellsten Version zu folgen und generell reinzufinden in die Thematik. Gibt es da vielleicht ein Vorgehen oder eine Seite, die ihr empfehlen könnt? **

Releases · Jomelo/LCDMenuLib · GitHub
Hier sind die ganzen Versionen die ich ansprach

Du findest eigentlich alles wichtige auf Github. Einfach mal durchklicken.

Z.B.hier.

mdziad:
Ich habe den Wertebereich des Encoders von 1 bis 80 beschränkt.

Wieso denn das?!

Das Geniale an einem Rotationsencoder ist doch gerade, dass man quasi endlos drehen kann, oder nicht?

Gruß

Gregor

Moin,

ich lese hier ja immer etwas mit.. Am besten kannst du die LCDMenuLib2 verwenden. Die von dir gewünschte Funktion ist dort bereits implementiert und vieles mehr. Wenn du fragen hast brauchst du nicht die 80 Seiten durchzulesen sondern kannst diese direkt stellen. Schau dir am besten zu erst die Beispiele an, dort ist schon vieles erklärt.

Jomelo:
Moin,

ich lese hier ja immer etwas mit.. Am besten kannst du die LCDMenuLib2 verwenden. Die von dir gewünschte Funktion ist dort bereits implementiert und vieles mehr. Wenn du fragen hast brauchst du nicht die 80 Seiten durchzulesen sondern kannst diese direkt stellen. Schau dir am besten zu erst die Beispiele an, dort ist schon vieles erklärt.

Hallo Jomelo, vielen Dank für das Angebot mir zu helfen!
Also ich bin dein Kapitel 2.1 mal durchgegangen. Aktuell versuche ich den Code der Datei "LCDML_000_serialMonitor.ino" der Bibliothek LCDMenuLib2 zu verstehen. Im seriellen Monitor klappt auch alles soweit, also kann zumindest die Menüeinträge ändern und zwischen den verschiedenen Ebenen switchen. Grob gesagt verstehe ich folgenden Part:

  // For beginners
  // LCDML_add(id, prev_layer, new_num, lang_char_array, callback_function)
  LCDML_add         (0  , LCDML_0         , 1  , "Information"      , mFunc_information);       // this menu function can be found on "LCDML_display_menuFunction" tab
  LCDML_add         (1  , LCDML_0         , 2  , "Time info"        , mFunc_timer_info);        // this menu function can be found on "LCDML_display_menuFunction" tab
  LCDML_add         (2  , LCDML_0         , 3  , "Program"          , NULL);                    // NULL = no menu function
  LCDML_add         (3  , LCDML_0_3       , 1  , "Program 1"        , NULL);                    // NULL = no menu function
  LCDML_add         (4  , LCDML_0_3_1     , 1  , "P1 dummy"         , NULL);                    // NULL = no menu function
  LCDML_add         (5  , LCDML_0_3_1     , 2  , "P1 Settings"      , NULL);                    // NULL = no menu function
  LCDML_add         (6  , LCDML_0_3_1_2   , 1  , "Warm"             , NULL);                    // NULL = no menu function
  LCDML_add         (7  , LCDML_0_3_1_2   , 2  , "Cold"             , NULL);                    // NULL = no menu function
  LCDML_add         (8  , LCDML_0_3_1_2   , 3  , "Back"             , mFunc_back);              // this menu function can be found on "LCDML_display_menuFunction" tab
  LCDML_add         (9  , LCDML_0_3_1     , 3  , "Back"             , mFunc_back);              // this menu function can be found on "LCDML_display_menuFunction" tab
  LCDML_add         (10 , LCDML_0_3       , 2  , "Program 2"        , mFunc_p2);                // this menu function can be found on "LCDML_display_menuFunction" tab
  LCDML_add         (11 , LCDML_0_3       , 3  , "Back"             , mFunc_back);              // this menu function can be found on "LCDML_display_menuFunction" tab
  LCDML_add         (12 , LCDML_0         , 4  , "Special"          , NULL);                    // NULL = no menu function
  LCDML_add         (13 , LCDML_0_4       , 1  , "Go to Root"       , mFunc_goToRootMenu);      // this menu function can be found on "LCDML_display_menuFunction" tab
  LCDML_add         (14 , LCDML_0_4       , 2  , "Jump to Time info", mFunc_jumpTo_timer_info); // this menu function can be found on "LCDML_display_menuFunction" tab
  LCDML_add         (15 , LCDML_0_4       , 3  , "Back"             , mFunc_back);              // this menu function can be found on "LCDML_display_menuFunction" tab

Nun möchte ich das von mir eingangs genannte Problem implementieren. Zunächst Frage 1: **Wie beziehe ich das Display ein ? Soll das Display an vorgeschriebene Pins verkabelt werden? **

Außerdem habe ich eine kommentierte Stelle gefunden, die ziemlich genau das beschreibt, was ich haben möchte:

  // Example for one function and different parameters
  // It is recommend to use parameters for switching settings like, (small drink, medium drink, big drink) or (200ml, 400ml, 600ml, 800ml) ...
  // the parameter change can also be released with dynParams on the next example
  // LCDMenuLib_add(id, prev_layer,     new_num, condition,   lang_char_array, callback_function, parameter (0-255), menu function type  )
  LCDML_addAdvanced (16 , LCDML_0         , 5  , NULL,          "Parameter"      , NULL,                0,            _LCDML_TYPE_default);                    // NULL = no menu function
  LCDML_addAdvanced (17 , LCDML_0_5       , 1  , NULL,          "Parameter 1"      , mFunc_para,       10,            _LCDML_TYPE_default);                    // NULL = no menu function
  LCDML_addAdvanced (18 , LCDML_0_5       , 2  , NULL,          "Parameter 2"      , mFunc_para,       20,            _LCDML_TYPE_default);                    // NULL = no menu function
  LCDML_addAdvanced (19 , LCDML_0_5       , 3  , NULL,          "Parameter 3"      , mFunc_para,       30,            _LCDML_TYPE_default);                    // NULL = no menu function
  LCDML_add         (20 , LCDML_0_5       , 4  , "Back"             , mFunc_back);              // this menu function can be found on "LCDML_display_menuFunction" tab

Hier wäre meine 2. Frage: **Wie bekomme ich es hin, dass auf dem Display zunächst 4 verschiedene Menüpunkte zu erkennen sind? (sehr klein, klein,mittel, groß) Hinter jedem Menüpunkt soll dann eine Pumpe angesteuert werden (also ein Stromkreis geschlossen werden) und das ganze für z.B. 10 Sekunden falls klein gewählt wurde oder für 30 Sekunden wenn groß gewählt wurde. Achja, ich verwende ein 16x2 LCD Display (mit i2c), falls dies auch berücksichtigt werden muss. **

Ich wäre dir super dankbar wenn du mir hier Tipps zur Umsetzung geben kannst :slight_smile:

Moin,
gesagt getan :wink:

Nimm das Beispiel "LCDML_i2c_display_20x4" als Basis.
Finden kannst du dies unter Examples LCDMenuLib2 -> 03_diplay_types -> lcd -> LCDML_i2c_display_20x4

  // settings for LCD
  #define _LCDML_DISP_cols  16 // <- diese Zeile von 20 auf 16 anpassen
  #define _LCDML_DISP_rows  2 // <- diese Zeile von 4 auf 2 anpassen

  #define _LCDML_DISP_cfg_cursor                     0x7E   // cursor Symbol
  #define _LCDML_DISP_cfg_scrollbar                  1      // enable a scrollbar   

  // LCD object
  // for i2c there are many different steps for initialization, some are listed here
  // when the rows and cols are not set here, they have to be set in the setup
  //LiquidCrystal_I2C lcd(0x27);  // Set the LCD I2C address
  //LiquidCrystal_I2C lcd(0x27, BACKLIGHT_PIN, POSITIVE);  // Set the LCD I2C address
  LiquidCrystal_I2C lcd(0x27,_LCDML_DISP_cols,_LCDML_DISP_rows);
  //LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  // Set the LCD I2C address

Nun müsstest du für dein Display das passende Headerfile und die I2C Adresse anpassen. Im Beispiel ist mal die 0x27 gesetzt. Für dein Display kennst nur du die werten.

Den oberen Punkt solltest du erstmal zum Laufen bekommen ohne das Fehlermeldungen auftauchen.

Frage 2:
Das Menü unterstützt pro Zeile nur einen Eintrag. Sprich für 4 Menu Elemente muss immer gescrollt werden.

Die Logik, die die Pumpe ansteuert liegt weiterhin klassisch in der Loop Funktion und muss von dir geschrieben werden. Beispiel.

Anlegen von globalen Variablen (Das sind die Variablen die Funktionsübergreifend ihren Wert behalten) außerhalb der Setup und Loop Funktion. Diese Variablen können in Menüfunktionen vom Wert her verändert werden. Sprich im Menü werden nur die Variablen verändern, das direkte Ansteuern und zeitliche Stoppen geschieht in der Loop Funktion.
Man kann diese Funktionen auch fest im Menü hinterlegen, aber sobald ein Menüfunktion verlassen wird wird, müsstest du dich damit beschäftigen z.B. die Pumpe sicher zu stoppen. Daher ist der erste Ansatz der bessere.

Zu der Dynamics Funktion schau dir erstmal die Beispiele an. Arbeite dich Schritt für Schritt dadurch. Als Einsteiger ist das nicht einfach, aber fragen kannst du immer.

Für deine Wartezeiten kannst du die Timer Funktion aus der Lib verwenden, dann ist der zu schreibende Code recht gering.

Beispiel:

Globale Variable anlegen:
(Ich bennene globale Variablen immer mit einem "g_" am Anfang, so dass ich diese bei größeren Programmen schneller von lokalen Variablen unterscheiden kann)

// Pseudocode für dein Ansatz einer Statemashine um die Pumpe zu steuern
unsigned long g_timer_pumpe_an = 0;

enum g_zustand_pumpe { PUMPE_AUS, PUMPE_EIN_10s, PUMPE_EIN_30s};
bool g_zustand_switched = false;

angepasste Loopfunktion

void loop()
  {    
    // split loop() into loop_control() and loop_menu()
    LCDML.loop_control();    


     // Statemashine
     switch(g_zustand_pumpe)
     {
          case     PUMPE_EIN_10s :
           if(LCDML.TIMER_ms(g_timer_pumpe_an , 10000))
           {
               if(g_zustand_switched == false) 
               {
                   g_zustand_switched = true;
                     // pumpe einschalten


              } 
              else
             {
                   g_zustand_pumpe = PUMPE_AUS;
              }
           }
          break;


         case     PUMPE_EIN_30s :
          if(LCDML.TIMER_ms(g_timer_pumpe_an , 30000))
           {
               if(g_zustand_switched == false) 
               {
                   g_zustand_switched = true;
                     // pumpe einschalten


              } 
              else
             {
                   g_zustand_pumpe = PUMPE_AUS;
              }
           }
         break;




        case     PUMPE_AUS :
           // pumpe ausschalten




          g_zustand_switched = false;
 
          break;


        default: // default sollte immer vorhanden sein um fehler zu finden
         Serial.println("sollte nicht passieren");
        break;
     };



  

    // call the "normaly" menu content
    LCDML.loop_menu();
  }

Bei dem Code handelt es sich nur um ein Beispiel. Fang erstmal klein an und probiere Stück für Stück was geht,

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.