Aktualisierung OLED durch Drehgeber

Hallo zusammen,

ich versuche gerade mein erstes Projekt mit dem Arduino umzusetzen. Habe bisher zwar Erfahrung mit verschiedenen Programmiersprachen (C,C++, Java...), aber bei dem Projekt scheitert es gerade an einer (wahrscheinlich) simplen Sache, die ich einfach nicht seh.

Der unten gezeigte Code funktioniert soweit und ändert auf Basis des Drehgeber-Inputs Menüeinträge, die auf einem OLED-Display angezeigt werden. Mein Problem derzeit ist, dass der Code nicht mehr funktioniert, sobald ich die renderDisplay()-Methode in der Loop-Funktion außerhalb des if-Kontexts aufrufe. Also die Loop-Funktion wie folgt schreibe:

void loop() 
{
  uint8_t x = R.read();
  if(x != 0){
   Serial.println(x);
   processEncoder(x);
  }

   renderDisplay(menuEntry);
}

In dem Beispiel auch nicht weiter schlimm. Das Projekt soll aber noch um eine RFID-Funktionalität ergänzt werden, sodass der aktive Menüeintrag aktiviert wird, sobald ein RFID-Tag erkannt wird. Das soll eigentlich auch funktionieren, ohne dass vorher der Drehgeber betätigt wird. Beim Debuggen konnte ich feststellen, das bei der oberen Loop-Funktion nur noch die zweite Funktion (renderDisplay()) ausgeführt wird, der Wert des Drehgebers aber immer 0 bleibt.

#include <MD_REncoder.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>


// set up encoder object
MD_REncoder R = MD_REncoder(3, 2);

#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

int menuEntry;

void setup() 
{
  Serial.begin(57600);
  
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  R.begin();
  menuEntry = 0;
  display.clearDisplay();
  renderDisplay(menuEntry);
}

void loop() 
{
  uint8_t x = R.read();
  if(x != 0){
   Serial.println(x);
   processEncoder(x);
   renderDisplay(menuEntry);
  }
}


void processEncoder(int encoderValue){
    if(encoderValue == DIR_CW){
      menuEntry + 1 <= 3 ? menuEntry++ : menuEntry = 0;
    }else{
      menuEntry - 1 >= 0 ? menuEntry-- : menuEntry = 3;
    }
}

void renderDisplay(int currentEntry){
    display.setTextSize(1);
    currentEntry == 0 ? display.setTextColor(BLACK, WHITE) : display.setTextColor(WHITE, BLACK);
    display.setCursor(5,0);
    display.println("Entry 1");
    currentEntry == 1 ? display.setTextColor(BLACK, WHITE) : display.setTextColor(WHITE, BLACK);
    display.setCursor(5,15);
     display.println("Entry 2");
    currentEntry == 2 ? display.setTextColor(BLACK, WHITE) : display.setTextColor(WHITE, BLACK);
    display.setCursor(5,30);
    display.println("Entry 3");
    currentEntry == 3 ? display.setTextColor(BLACK, WHITE) : display.setTextColor(WHITE, BLACK);
    display.setCursor(5,45);
    display.println("Entry 4");
    display.display();
}

Hallo,

du übergibts ihr den Wert menuEntry. Nur kommt der her? Wo wird dieser aktualisiert?

Doc_Arduino:
Hallo,

du übergibts ihr den Wert menuEntry. Nur kommt der her? Wo wird dieser aktualisiert?

menuEntry ist als Instanzvariable deklariert. Erstmalig initialisier ich die Variable in der Setup-Methode mit 0 und in der processEncoder()-Methode wird entsprechend des Encoder-Wertes hoch bzw. runtergezählt.

Das funktioniert ja auch soweit, solang ich die renderDisplay()-Methode nur innerhalb des ifs aufrufe.

Hallo,

wo kommt DIR_CW her?

Lasse dir mal die Werte ausgeben. Richtung, alten und neuer menuEntry. Vielleicht siehste dann schon wo es klemmt.

void processEncoder (int encoderValue)
{
    Serial.print(DIR_CW); Serial.print('\t');
    Serial.print(menuEntry); Serial.print('\t'); 
    if(encoderValue == DIR_CW) {
      menuEntry + 1 <= 3 ? menuEntry++ : menuEntry = 0;  
      Serial.print(menuEntry); Serial.print('\t');   
    }
    else {
      menuEntry - 1 >= 0 ? menuEntry-- : menuEntry = 3;   
      Serial.print(menuEntry); Serial.print('\t'); 
    }
    Serial.println(); 
}

Was ich noch nicht überblicke ist, was du mit menuEntry = encoderValue vorhast? Die sind gleich durch die Parameterübergabe.
Das Wirrwarr aufgelöst wäre wie folgt, jetzt siehst du schon wo es klemmt, hoffe ich.

void processEncoder ()
{
    if(menuEntry == DIR_CW) {
      menuEntry < 3 ? menuEntry++ : menuEntry = 0;  
    }
    else {
      menuEntry > 0 ? menuEntry-- : menuEntry = 3;  
    }
}

Doc_Arduino:
Hallo,

wo kommt DIR_CW her?

...

wo kommt DIR_CW her? --> Das ist eine Konstante der genutzten Bibliothek, die dem Drehgeberwert entspricht, wenn dieser im Uhrzeigersinn gedreht wird.

Was ich noch nicht überblicke ist, was du mit menuEntry = encoderValue vorhast? --> Das rufe ich doch eigentlich nirgends auf. EncoderValue ist der Drehgeberwert, der an die Methode übergeben und ausgewertet wird. Entsprechend wird der menuEntry um eins hoch bzw. runtergezählt. menuEntry ist nur ein Zähler im Bereich [0..3], der den selektieren Menüeintrag wieder gibt.

Die Auswertung des Drehgeberwertes funktioniert eigentlich auch. Wie gesagt das im ersten Post angegebene lange Beispiel ist fehlerfrei ausführbar. Ich habe nur das Problem, dass folgendes funktioniert:

void loop() 
{
  uint8_t x = R.read();
  if(x != 0){
   Serial.println(x);
   processEncoder(x);
   renderDisplay(menuEntry);
  }

}

Das hier aber nicht -> renderDisplay(menuEntry) wird ausgeführt, aber die ganze Drehgeberauswertung nicht mehr:

void loop() 
{
  uint8_t x = R.read();
  if(x != 0){
   Serial.println(x);
   processEncoder(x);
  }

   renderDisplay(menuEntry);
}

Wird bei der zweiten Variante renderDisplay(menuEntry) einfach zu oft aufgerufen, sodass der Drehgeberwert nicht mehr ordentlich ausgewertet werden kann?

beno1008:
Wird bei der zweiten Variante renderDisplay(menuEntry) einfach zu oft aufgerufen, sodass der Drehgeberwert nicht mehr ordentlich ausgewertet werden kann?

In diese Richtung würde meine Vermutung weisen. Der I2C-Bus hat eine endliche Geschwindigkeit, den in jedem Durchlauf von loop mit Daten zu füttern, könnte kritisch sein. Daher ist der Ansatz, nur zu schreiben, wenn sich was verändert hat, richtig.

Wenn Du mehrere Kriterien hast, nutzt Du einen Merker für die Aktualisierungsanforderung der Anzeige.

Drehgeberänderung -> Merker setzen
RFIDänderung -> Merker setzen
Wenn Merker gesetzt, dann Ausgabe aktualisieren.

Hallo,

okay, ich hatte die Funktionen verwechselt. Entschuldigung. Ich bin manchmal zu schnell für diese Welt. :wink:

Allerdings trifft das Gesagte dann fast 1:1 auf die renderDisplay Funktion zu. Nun gut, lassen wir das, ist jetzt nicht die Baustelle die man zuerst angehen sollte. Weil das ändert sich noch wenn du agmues Vorschlag umsetzt. Und den musst du umsetzen. Eine generelle Aktualisierung aller 0,5s oder 1s muss auch noch rein.

Wobei deine renderDisplay Funktion an sich schon etwas seltsam ist. Jedesmal legst du die Schriftgröße fest. Ändert sich aber nie. Jedesmal wird 4x der Cursor gesetzt und die gleichen Zeichen geschrieben. Es ändert sich nichts. Was scheinbar komplett übersehen wird ist, du änderst mit jedem Aufruf der Funktion 2x bis 3x mal die Farbe. Gehe mal die Abfragen durch was alles wie passiert. Ich wette du wolltest du hier switch case verwenden.

Ich könnte vermuten das Farben setzen kosten die meiste Zeit.

Mich würde mal interessieren wieviel Zeit das kostet.

void loop()
{
  uint8_t x = R.read();
  if(x != 0){
   Serial.println(x);
   processEncoder(x);
   unsigned long start = micros();
   renderDisplay(menuEntry);
   Serial.println(micros()-start);
  }
}

@agmue Jep genau daran lags. Wenn ich den vorherigen Menüeintrag in eine Variable schreibe und nur aktualisiere, wenn der neue Eintrag != dem alten Eintrag ist, dann geht's. Hätte nicht gedacht, dass das Aktualisieren des Displays so teuer ist. Selbiges gilt dann auch für den RFID-Lesevorgang. Führe den jetzt nur alle 0,5s aus, dann funktioniert soweit alles. Danke für den Tipp, an die eingeschränkten Ressourcen muss ich mich noch gewöhnen. ;D

@Doc_Arduino Auch dir vielen Dank, du hast mir sehr geholfen. Ja die renderDisplay() ist noch nicht optimal. Ist auch noch viel von den Tests der Einzelmodule rüber kopiert, da immer mal hier und da eine halbe Stunde gebastelt wird :wink: Will das aber definitiv noch verbessern.

Zu deiner Frage: Die Ausführung der Methode dauert ca. 0,34s... hätte nicht gedacht, dass es so teuer ist. Das Setzen der ganzen Cursor, Farben, Texte etc. scheint aber so gut wie nichts zu kosten, denn das reine Rendern des (leeren) Displays über display.display() kostet schon 0,33s. Da muss ich mal in der Bibliothek stöbern, ob man da nicht optimieren kann (nur teilweise aktualisieren o.ä.)

beno1008:
Da muss ich mal in der Bibliothek stöbern, ob man da nicht optimieren kann (nur teilweise aktualisieren o.ä.)

Oder Du schaust nach einer anderen Bibliothek, beispielsweise der von Olli Kraus. Ob die schneller ist, kann ich nicht sagen, aber sie hat schöne Fonts und geht mit dem Speicher sparsamer um. Es gibt auch die reine Textversion.

Ein Versuch dürfte sich lohnen :slight_smile:

Hallo,

das sind ja satte 330ms, ja das ist zu viel. Ich habe mit meinem Handdrehencoder schon leichte Aussetzer wenn ich ihn nur aller 2ms statt 1ms Abfrage und ihn dabei sehr sehr schnell drehe. Okay - extremer Extremfall.

Ja, da kann man schon mal hinter die Kulissen schauen ob da in der Lib etwas bremst und ob das wirklich so sein muss.
Was ist das für ein OLED Display überhaupt?

Ansonsten könnte ich mir sowas als Grundgerüst durchaus vorstellen. Wobei man wie schon erwähnt immer gleiche Dinge rausnehmen sollte. Das weißt du ja schon. :wink:

void update_Display (const unsigned int interval, const int entry)                        
{
  static unsigned long last_ms = 0;  
  unsigned long ms = millis();
  
  if (ms - last_ms >= interval) {
    last_ms = ms;
    renderDisplay(entry);
  }
}


void renderDisplay(const int currentEntry)
{ 
  static int old_Entry = 888;

  if (currentEntry == old_Entry) return;

  old_Entry = currentEntry;
  
  display.setTextSize(1);
  
  switch (currentEntry)
  {
    case 0: display.setTextColor(BLACK, WHITE);
            display.setCursor(5,0);
            display.print("Entry 1");
            break;
    case 1: display.setTextColor(BLACK, WHITE);
            display.setCursor(5,15);
            display.print("Entry 2");
            break;
    case 2: display.setTextColor(BLACK, WHITE);
            display.setCursor(5,30);
            display.print("Entry 3");
            break;
    case 3: display.setTextColor(BLACK, WHITE);
            display.setCursor(5,45);
            display.print("Entry 4");
            break;
    default: break;        
  }
  display.display();

}

Dann musst du dich nur um den Aufruf kümmern, der Wert wird durchgereicht.

update_Display (500, menuEntry);    // 500ms generelles Aufrufintervall und der Wert

Innerhalb der render Funktion wird nur abgearbeitet mit neuen Wert. Sodass durchaus auch einmal alles "quasi übersprungen" wird.