Zum Inhalt springen

Rotary Encoder – Aufbau Abfragemenü

    In diesem DIYTechAdventure geht es um die Vorbereitung zu meinem Projekt Reifeschrank für Salami und Schinken.

    Ziel soll es sein, dem Nutzer eine Möglichkeit zur Einstellungen von Soll- und Offsetwerten per Rotary Encoder Abfragemenü mit einem einzigen Drehpotentiometer zur Verfügung zu stellen. Mit ein wenig Anpassung ist das Menü auch für andere
    Einsätze verwendbar. Hier geht es lediglich um die Darstellung, wie ich das umsetzen will.

    Folgendes Menü habe ich in einer ersten Version bereitgestellt:

    • Solltemperatur
    • Soll Luftfeuchtigkeit
    • Abfrage ob Offsets eingestellt werden sollen – ja/nein
    • Offset Heizer an
    • Offset Heizer aus
    • Offset Kühler an
    • Offset Kühler aus
    • Offset Befeuchter an
    • Offset Befeuchter aus
    • Offset Frischluft an
    • Offset Frischluft aus

    Was ist mit den Offsets an/aus gemeint?
    Mit Offset Heizer an kann eingestellt werden, wann in Bezug zur Solltemperatur die Heizung eingeschaltet werden soll. Und Heizer aus markiert den Offset, ob und wieviel vor Erreichen der Solltemperatur die Heizung abgeschaltet wird. Einfach um den Nachlauf besser zu kompensieren.

    Fritzing Aufbau Rotary Encoder Abfragemenü
    Logo Fitzing.org

    Und hier der Code:

    #include <Arduino.h>
    #include <LiquidCrystal_I2C.h>
    #include <EEPROM.h>
    #include <Wire.h>
    
    // Pins für Rotary Encoder und Taster
    #define encoderPinA 3
    #define encoderPinB 2
    #define sw 7
    #define offset
    
    // LCD-Display initialisieren
    LiquidCrystal_I2C lcd(0x27, 16, 2); // I2C-Adresse 0x27, Display mit 16 Zeichen und 2 Zeilen
    
    // Globale Variablen
    volatile float encoderPos = 20; // Startposition des Encoders
    float old_encoderPos;
    byte taster_down = 0;
    byte wahl = 0;
    boolean A_set = false;
    boolean B_set = false;
    static boolean rotating = false; // Flag für Entprellung (Debounce)
    
    const int MIN_TEMP = 0;
    const int MAX_TEMP = 40;
    const int MIN_HUM = 20;
    const int MAX_HUM = 99;
    const int MIN_OFFSET = 0;
    const int MAX_OFFSET = 100;
    
    float soll_temp = 20;
    float soll_hum = 90;
    float offset_heizer_an = -1.0;
    float offset_heizer_aus = 0;
    float offset_kuehler_an = 1.0;
    float offset_kuehler_aus = 0.5;
    float offset_feuchte_an = -1.0;
    float offset_feuchte_aus = 0;
    float offset_frische_an = 1.0;
    float offset_frische_aus = 0;
    
    const unsigned int nk = 0; // Anzahl der Nachkommastellen für Temperatur- und Feuchtigkeitsanzeigen
    const unsigned int nk1 = 1; // Anzahl der Nachkommastellen für Temperatur- und Feuchtigkeitsanzeigen
    bool handleTemperatureSetting();
    bool handleHumiditySetting();
    bool handleOffsetSetting(const String &label, float &value, unsigned int nk, int min, int max);
    void handleError(const String &message, byte step);
    bool handleOffsetPrompt();
    
    // Interrupt Service Routine für Encoder-Pin A
    void doEncoderA() {
      if (rotating) delay(1); // Entprellung
      if (digitalRead(encoderPinA) != A_set) {
        A_set = !A_set;
        if (A_set && !B_set) {
          encoderPos += (taster_down >= 0 && taster_down <= 2) ? 1 : 0.1;
          rotating = true;
        }
      }
    }
    
    // Interrupt Service Routine für Encoder-Pin B
    void doEncoderB() {
      if (rotating) delay(1); // Entprellung
      if (digitalRead(encoderPinB) != B_set) {
        B_set = !B_set;
        if (B_set && !A_set) {
          encoderPos -= (taster_down >= 0 && taster_down <= 2) ? 1 : 0.1;
          rotating = true;
        }
      }
    }
    
    // Funktion zur Ausgabe von Text auf dem LCD-Display
    void printlcd(byte posx, byte posy, String text) {
      lcd.setCursor(posx, posy);
      lcd.print(text);
    }
    
    // Funktion zum Abfragen des Offsets (Ja/Nein)
    void checkoff(bool &check) {
      delay(300);
      while (!check) {
        encoderPos = constrain(encoderPos, 0, 1);
        wahl = static_cast<byte>(encoderPos);
        printlcd(0, 0, "Offset? N=0;J=1");
        printlcd(0, 1, "Einstellen: " + String(wahl) + "  ");
        if (digitalRead(sw) == LOW) {
    delay(50); // Entprellungszeit für den Taster
          delay(300);
          while (digitalRead(sw) == LOW); // Warte, bis der Taster losgelassen wird
          check = true;
        }
      }
    }
    
    // Hauptmenü zur Einstellung der Parameter
    void einstellen() {
      lcd.clear();
      delay(500);
      encoderPos = soll_temp;
      while (taster_down != 0) {
        switch (taster_down) {
          case 1:
            if (handleTemperatureSetting()) taster_down = 2;
            break;
          case 2:
            if (handleHumiditySetting()) taster_down = handleOffsetPrompt() ? 3 : 0;
            break;
          #ifdef offset
          case 3:
            if (handleOffsetSetting("Heizer an ", offset_heizer_an, nk, MIN_OFFSET, MAX_OFFSET)) taster_down = 4;
            break;
          case 4:
            if (handleOffsetSetting("Heizer aus ", offset_heizer_aus, nk, MIN_OFFSET, MAX_OFFSET)) {
              if (offset_heizer_an < offset_heizer_aus) taster_down = 5;
              else handleError("an muss < aus!", 3);
            }
            break;
          case 5:
            if (handleOffsetSetting("Kuehler an ", offset_kuehler_an, nk, MIN_OFFSET, MAX_OFFSET)) taster_down = 6;
            break;
          case 6:
            if (handleOffsetSetting("Kuehler aus ", offset_kuehler_aus, nk, MIN_OFFSET, MAX_OFFSET)) {
              if (offset_kuehler_an > offset_kuehler_aus) taster_down = 7;
              else handleError("an muss > aus!", 5);
            }
            break;
          case 7:
            if (handleOffsetSetting("Feuchte an ", offset_feuchte_an, nk, MIN_OFFSET, MAX_OFFSET)) taster_down = 8;
            break;
          case 8:
            if (handleOffsetSetting("Feuchte aus ", offset_feuchte_aus, nk, MIN_OFFSET, MAX_OFFSET)) {
              if (offset_feuchte_an < offset_feuchte_aus) taster_down = 9;
              else handleError("an muss < aus!", 7);
            }
            break;
          case 9:
            if (handleOffsetSetting("Frische an ", offset_frische_an, nk, MIN_OFFSET, MAX_OFFSET)) taster_down = 10;
            break;
          case 10:
            if (handleOffsetSetting("Frische aus ", offset_frische_aus, nk, MIN_OFFSET, MAX_OFFSET)) {
              if (offset_frische_an > offset_frische_aus) {
                taster_down = 0;
                lcd.clear();
                encoderPos = soll_temp;
              } else handleError("an muss > aus!", 9);
            }
            break;
          #endif
        }
        delay(200);
      }
    }
    
    void setup() {
      Wire.begin();
      Serial.begin(9600);
      lcd.init();
      lcd.backlight();
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Menu Test");
      lcd.setCursor(0, 1);
      lcd.print("mit Rotary Enc.");
      delay(2000);
      lcd.clear();
    
      pinMode(encoderPinA, INPUT_PULLUP);
      pinMode(encoderPinB, INPUT_PULLUP);
      pinMode(sw, INPUT_PULLUP);
      attachInterrupt(digitalPinToInterrupt(encoderPinA), doEncoderA, CHANGE);
      attachInterrupt(digitalPinToInterrupt(encoderPinB), doEncoderB, CHANGE);
    
      byte Temp = EEPROM.read(0);
      byte Hum = EEPROM.read(1);
      soll_temp = (Temp >= MIN_TEMP && Temp <= MAX_TEMP) ? Temp : 20;
      soll_hum = (Hum >= MIN_HUM && Hum <= MAX_HUM) ? Hum : 90;
    }
    
    void loop() {
      rotating = false;
      if (digitalRead(sw) == LOW) {
        taster_down = 1;
        einstellen();
      }
    }
    
    bool handleTemperatureSetting() {
      printlcd(0, 0, "Soll_Temp: " + String(soll_temp, nk) + String((char)223));
      if (encoderPos != old_encoderPos) {
        soll_temp = constrain(encoderPos, MIN_TEMP, MAX_TEMP);
        old_encoderPos = encoderPos;
        printlcd(0, 0, "Soll_Temp: " + String(soll_temp, nk) + String((char)223));
      }
      if (digitalRead(sw) == LOW) {
        EEPROM.update(0, soll_temp);
        encoderPos = soll_hum;
        return true;
      }
      return false;
    }
    
    bool handleHumiditySetting() {
      printlcd(0, 1, "Soll_Hum:  " + String(soll_hum, nk) + String("%"));
      if (encoderPos != old_encoderPos) {
        soll_hum = constrain(encoderPos, MIN_HUM, MAX_HUM);
        old_encoderPos = encoderPos;
      }
      if (digitalRead(sw) == LOW) {
        EEPROM.update(1, soll_hum);
        return true;
      }
      return false;
    }
    
    bool handleOffsetSetting(const String &label, float &value, unsigned int nk, int min, int max) {
      printlcd(0, 0, label + String(value, nk) + "   ");
      if (encoderPos != old_encoderPos) {
        value = constrain(encoderPos, min, max);
        old_encoderPos = encoderPos;
      }
      return digitalRead(sw) == LOW;
    }
    
    void handleError(const String &message, byte step) {
      lcd.clear();
      printlcd(0, 0, "Fehler !");
      printlcd(0, 1, message);
      delay(2000);
      lcd.clear();
      taster_down = step;
    }
    
    bool handleOffsetPrompt() {
      bool check = false;
      checkoff(check);
      return wahl != 0;
    }

    Das Ziel dieses Programms ist es, mit einem Rotary Encoder verschiedene Parameter einzustellen, darunter die Soll-Temperatur, die Soll-Feuchtigkeit und verschiedene Offset-Werte. Das Programm verwendet ein LCD-Display, um die aktuellen Einstellungen anzuzeigen.

    1. Initialisierung (Setup)
    Im setup()-Teil wird das LCD-Display initialisiert und eine Willkommensnachricht ausgegeben. Danach werden die Pins für den Rotary Encoder und den Taster gesetzt und Interrupts konfiguriert, um auf Änderungen des Encoders zu reagieren. Schließlich werden gespeicherte Werte aus dem EEPROM gelesen.

    2. Menüsteuerung (Loop und einstellen())
    Wenn der Taster gedrückt wird, startet das Menü durch Setzen von taster_down auf 1. Die Funktion einstellen() führt durch das Menü. Hier wird die Soll-Temperatur, die Soll-Feuchtigkeit und je nach Bedarf die Offset-Werte eingestellt.

    3. Rotary Encoder (doEncoderA() und doEncoderB())
    Die Funktionen doEncoderA() und doEncoderB() dienen dazu, die Rotation des Encoders zu erfassen und entsprechend den Wert von encoderPos zu ändern. Diese Änderungen werden genutzt, um im Menü die gewünschten Werte zu erhöhen oder zu verringern.

    4. Einstellungen speichern
    Nachdem der Benutzer einen Wert mit dem Taster bestätigt hat, wird dieser in den EEPROM geschrieben, sodass er beim Neustart des Geräts wieder verfügbar ist. Die Funktionen handleTemperatureSetting() und handleHumiditySetting() sind für das Einstellen und Speichern der Soll-Temperatur bzw. Soll-Feuchtigkeit verantwortlich.

    5. Offset-Abfrage
    Wenn Offsets notwendig sind, fragt das Programm mit der Funktion handleOffsetPrompt() den Benutzer, ob er Offset-Einstellungen vornehmen möchte. Bei Bedarf werden in den nächsten Menüschritten die entsprechenden Offset-Werte abgefragt und gespeichert.

    Dieses Programm ermöglicht die einfache Navigation und Anpassung der Einstellungen über einen Rotary Encoder und ein Taster. Es ist ideal für eine Arduino Nano-basierte Steuerung, bei der Benutzerparameter wie Temperatur und Feuchtigkeit einfach angepasst werden können.

    OK, schreibe mir bitte in den Kommentaren, was du von diesem Vorschlag zur Menüführung mit dem Rotary Encoder hältst.


    0 Kommentare
    Inline Feedbacks
    View all comments