Arduino hängt sich auf? I2C Oled

Hallo, ich habe ein Projekt, bei dem ein Oled Display, eine RTC und ein Temperatur-Sensor per i2c an einem Arduino Nano angeschlossen sind. Außerdem misst der µC mit einem Voltage-Divider mit 2 Widerständen eine Spannung. Ich möchte das ich mit einem Knopf, der an Pin 10 hängt, eine Art Menü durchgeschalten werden kann. Dazu benutze ich die switch case Funktion. Bei allen Menüeinträgen soll eine Schleife den Code im jeweiligen Menü wiederholen. Das heißt, wenn ich zum Beispiel auf Seite 1 bin, soll sich die Uhrzeit jede Sekunde aktualisieren und so angezeigt werden: 10:11 -> nach einer Sekunde 10 11. Sobald der Knopf gedrückt wird, soll auf Seite 2 geschalten werden, wo das Datum angezeigt werden soll. Das funktioniert so semi, weil nach ein Paar Sekunden sich der µC komplett aufhängt und auf dem Oled komische Sachen in der rechten unteren Ecke angezeigt werden. Ich hab schon nach einer Lösung gesucht und ich habe gelesen, das es an der while Schleife liegen kann.

Wie kann ich meinen Code optimieren und den µC vom Aufhängen bewahren. Ausserdem wird der Luftdruck garnicht angezeigt (wird auch mit dem Temp Sensor gemessen).

Schonmal vielen dank für euer bemühen!!!

Code:

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_BMP280.h>
#include <RTClib.h>

// RTC
RTC_DS1307 rtc;
//

// Oled
#define OLED_RESET  4
Adafruit_SSD1306 display(OLED_RESET);         // initialize Adafruit OLED display library
const int next_button = 10;                   // Menü Knopf
int menu = 1;
//

//Temp Sensor
#define BMP280_I2C_ADDRESS  0x76
Adafruit_BMP280  bmp280;                      // initialize Adafruit BMP280 library
//

// VoltMeter
int VoltIn_a = 0;      //Analog Input
float Vout_a = 0.0;    //Voltage In after voltage divider
float V_a = 0.0;       //Actual voltage after calculation
float R1 = 100000.0;  //R1 100K
float R2 = 10000.0;   //R2 10K
//Timing
unsigned long previousMillis = 0;
const long interval = 200;          //Interval to read voltages
//

void setup() {
  pinMode(next_button, INPUT_PULLUP);
  pinMode(A0, INPUT);
  pinMode(A1, INPUT);                       // VoltMeter Pin
  Wire.begin();
  // initialize the SSD1306 OLED display with I2C address = 0x3C
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  // clear the display buffer.
  display.clearDisplay();
  updateMenu();
  display.display();

  // initialize the BMP280 sensor
  if ( bmp280.begin(BMP280_I2C_ADDRESS) == 0x76 )
  { // connection error or device address wrong!
    display.setTextSize(1);
    display.setCursor(5, 17);
    display.print("Connection");
    display.setCursor(35, 37);
    display.print("Error");
    display.display();        // update the display
    while (1); // stay here
  }
}

char _buffer[9];  // OLED TEMP

void loop() {
  if (!digitalRead(next_button)) {
    if (menu >= 6) {                       // Wenn Seite 6 erreicht wurde
      menu = 0;                           // Gehe zurück zu Seite 1
    }
    menu++;
    updateMenu();
    delay(100);
    while (!digitalRead(next_button));
  }
}

void updateMenu() {
  DateTime now = rtc.now();
  char buf1[] = "hh:mm";
  char buf12[] = "hh mm";
  char buf2[] = "DD.MM";
  char buf3[] = "DDD MMM";
  switch (menu) {
    case 0:
      menu = 1;
      break;
    case 1:                                     // Uhrzeit
      while ( menu = 1)  {
        display.clearDisplay();
        display.setTextSize(1);
        display.setTextColor(WHITE, BLACK);
        display.setCursor(0, 0);   // (x, y)
        display.print("Uhrzeit");
        display.setTextSize(3);
        display.setCursor(0, 10);   // (x, y)
        display.print(now.toString(buf1));
        display.display();
        if (!digitalRead(next_button)) {
          menu++;
          updateMenu();
        }
        delay(1000);
        display.clearDisplay();
        display.setTextSize(1);
        display.setTextColor(WHITE, BLACK);
        display.setCursor(0, 0);   // (x, y)
        display.print("Uhrzeit");
        display.setTextSize(3);
        display.setCursor(0, 10);   // (x, y)
        display.print(now.toString(buf12));
        display.display();
        if (!digitalRead(next_button)) {
          menu++;
          updateMenu();
        }
        delay(1000);
        updateMenu();
        break;
      }
      break;
    case 2:                                     // Datum
      while ( menu = 2)  {
        display.clearDisplay();
        display.setTextSize(1);
        display.setTextColor(WHITE, BLACK);
        display.setCursor(0, 0);   // (x, y)
        display.print("Datum");
        display.setTextSize(3);
        display.setCursor(0, 10);   // (x, y)
        display.print(now.toString(buf2));
        display.display();
        break;
      }
      break;
    case 3:                                     // Datum 2
      while ( menu = 3)  {
        display.clearDisplay();
        display.setTextSize(1);
        display.setTextColor(WHITE, BLACK);
        display.setCursor(0, 0);   // (x, y)
        display.print("Datum");
        display.setTextSize(3);
        display.setCursor(0, 10);   // (x, y)
        display.print(now.toString(buf3));
        display.display();
        break;
      }
      break;
    case 4:                                    // Bordspannung
      display.clearDisplay();
      display.setTextSize(1);
      display.setTextColor(WHITE, BLACK);
      display.setCursor(0, 0);   // (x, y)
      display.print("Bordspannung");
      display.display();
      display.setTextColor(WHITE, BLACK);
      display.setTextSize(3);
      display.setCursor(0, 10);   // (x, y)
      VoltIn_a = analogRead(A0);              //Read analog values
      Vout_a = (VoltIn_a * 5.0) / 1024.0;     //Convert 10bit input to an actual voltage
      V_a = Vout_a / (R2 / (R1 + R2));        //Using the voltage divider formula, work out the input voltage
      display.print(V_a);
      display.display();
      if (!digitalRead(next_button)) {
        menu++;
        updateMenu();
      }
      delay(5000);
      updateMenu();
      break;
    case 5:                                        // AussenTemp
      //    float temp     = bmp280.readTemperature();   // get temperature
      //    float pressure = bmp280.readPressure();      // get pressure
      display.clearDisplay();
      display.setTextSize(1);
      display.setTextColor(WHITE, BLACK);
      display.setCursor(0, 0);   // (x, y)
      display.print("Aussen-Temperatur");
      display.setCursor(0, 5);   // (x, y)
      display.setTextSize(2);
      /*   if (temp < 0)
           sprintf(_buffer, "-%02u.%02u C", (int)abs(temp), (int)(abs(temp) * 100) % 100 );
         else
           sprintf(_buffer, " %02u.%02u C", (int)temp, (int)(temp * 100) % 100 );
         display.print(_buffer);
         display.drawCircle(78, 5, 2, WHITE);  // ° Zeichen
         display.display();
         if (!digitalRead(next_button)) {
           menu++;
           updateMenu();
         }
      */  delay(1000);
      updateMenu();
      break;
    case 6:                                    // Luftdruck
      while ( menu = 6)  {
        display.clearDisplay();
        display.setTextSize(1);
        display.setTextColor(WHITE, BLACK);
        display.setCursor(0, 0);   // (x, y)
        display.print("Uhrzeit");
        display.setTextSize(3);
        display.setCursor(0, 10);   // (x, y)
        display.print(now.toString(buf1));
        display.display();
        if (!digitalRead(next_button)) {
          menu++;
          updateMenu();
        }
        delay(1000);
        display.clearDisplay();
        display.setTextSize(1);
        display.setTextColor(WHITE, BLACK);
        display.setCursor(0, 0);   // (x, y)
        display.print("Uhrzeit");
        display.setTextSize(3);
        display.setCursor(0, 10);   // (x, y)
        display.print(now.toString(buf12));
        display.display();
        if (!digitalRead(next_button)) {
          menu++;
          updateMenu();
        }
        delay(1000);
        updateMenu();
        break;
      }
      break;
    case 7:
      menu = 1;
      break;
  }
}

was reported der Kompiler bezüglich RAM und Programmspeicherbedarf? kopier das mal rein...

Der Sketch verwendet 22820 Bytes (74%) des Programmspeicherplatzes. Das Maximum sind 30720 Bytes.
Globale Variablen verwenden 692 Bytes (33%) des dynamischen Speichers, 1356 Bytes für lokale Variablen verbleiben. Das Maximum sind 2048 Bytes.

Sollte doch passen oder?

könnte man meinen.

aber das

while ( menu = 2) {

ist sicher nicht was du machen willst oder?

== ist nicht gleich =

stimmt, darauf hab ich garnicht geachtet... Aber gibt es noch eine alternative zur while Schleife? Weil ohne die Schleife läuft der Inhalt von switch case einmal durch und das wars....

Philipp_Gretscher:
Wie kann ich meinen Code optimieren

void loop() {

if (!digitalRead(next_button)) {
   if (menu >= 6) {                       // Wenn Seite 6 erreicht wurde
     menu = 0;                           // Gehe zurück zu Seite 1
   }
   menu++;
   updateMenu();
   delay(100);
   while (!digitalRead(next_button));
 }
}

switch (menu) {
   case 6:                                    // Luftdruck
     while ( menu = 6)  {

if (!digitalRead(next_button)) {
         menu++;
       }
       delay(1000);
       updateMenu();
       break;
     }
     break;
   case 7:
     menu = 1;
     break;
 }
}

Deine ganzen delays sind nicht zielführend.
Ich versteh auch nicht, was Deine while()s da sollen.
menu ändert sich erst mit einem weiteren Tastendruck. Auswertung mit switch/case.

Du wertest jetzt immer in jedem switch - case - while() - break die Taste aus. Das kannst Du vollständig weg lassen.

Für das switch/case Konstrukt fehlt Dir eine letzte Bedingung:
default - menu=1 - break.
Wenn, aus welchen Gründen auch immer, menu >=8 wird, gibt es keinen definierten Zustand.

Naja, wenn zum beispiel case 2 ausgewählt wurde, dann läuft dort der code einmal durch und dann bleibt er quasi stehen, bis sich das case ändert, ich will allerdings, das er quasi nen loop in jedem case hat, der mit dem knopfdruck unterbrochen wird und sich das case um 1 erhöht...

Du hast doch bereits eine Schleife: loop
Mehr brauchst Du nicht.

Gruß Tommy

Aber das Menü läuft nicht im Loop, hab es schon probiert, aber geht nur in dieser extra funktion

Philipp_Gretscher:
Naja, wenn zum beispiel case 2 ausgewählt wurde, dann läuft dort der code einmal durch und dann bleibt er quasi stehen, bis sich das case ändert,

Dein Verständnis ist falsch. Der Code läuft durch loop() von oben nach unten - im Normalfall kommt nach unten wieder oben.
loop() ist dein while(1).

Es bleibt also nach dem Umschalten bei menu++ und der Auswahl via switch/case nichts stehen.
Mit jedem Umlauf wird genau der selbe case wieder aufgerufen.

Und genau da denke ich, ist Dein Problem.
Allein Dein : der in der Uhr blinken soll, macht Dir schon Schwierigkeiten.
Dafür löscht Du in jedem case den Display komplett und beschreibst ihn ebenso komplett neu.

Ich würde den Display nur löschen, wenn die Taste gedrückt wurde.
Im Case beschreiben
Und nach dem Durchlauf des des switch/case-Konstrukt display.display().

Wenn Du nicht hinterher kommst, sags einfach.

Meinst du dann das blinken komplett rauslassen, oder meinst du einfach das clear Display rauslassen und zum blinken einfach ohne clear display laufen lassen?? wenn ich das while allerdings weglasse, läuft es nicht im loop, hab ich da noch was übersehen? Ich probier es nochmal komplett ohne while schleife. Schonmal vielen dank also läuft das menü mit im loop, da updateMenu(); im Loop ausgeführt wird?

Philipp_Gretscher:
Meinst du dann das blinken komplett rauslassen, oder meinst du einfach das clear Display rauslassen und zum blinken einfach ohne clear display laufen lassen??

Genau das.
Um das blinken hinzubekommen, musst Du ja nur wissen, ob sich die Sekunde geändert hat.
Dazu merkst Du Dir die aktuelle Sekunde und prüfst auf Änderung.
Dann schreibst Du an die Position des : entweder ein Leerzeichen oder einen :

Das display.display() machst Du am Ende der Funktion.
Die Taste geprüft wird nur im loop - Bei tastendruck wird display.clear ausgeführt und dem switch/case-Konstrukt eine neue menu-Zahl übergeben. Damit wird das Display neu beschrieben - fertig.

Ich hab mal kurz was zusammengestellt.#
Um Dir zu zeigen, was ich meine.
Der Code geht nur mit Uhr und seriellem Monitor.

#include "RTClib.h"
RTC_DS3231 rtc;
DateTime now;

void setup()
  {
  Serial.begin(115200);
  Serial.println(F("Start..."));
  rtc.begin();
  }

void loop()
  {
  funktion_Ausgabe();
  }

void funktion_Ausgabe()
  {
  now = rtc.now();
  static uint8_t lastsecond = 61;
  if (now.second() != lastsecond)
    { 
    lastsecond = now.second();
    if (lastsecond%2)
    Serial.println(":");
    else
    Serial.println("-");
    }
  }

Ich habe an Stelle des Leerzeichen einen - genommen.
Die Uhr musst Du noch auf Deine umstellen.

(deleted)

Hallo Peter, meinst du, du könntest deinen code mir genauer erklären, weil ich in der Form noch nichts programmiert hab.... Und wo und wie muss ich jetzt meinen vorhandenen Code einfügen?

Liebe Grüße
Philipp

my_xy_projekt:
Genau das.
Um das blinken hinzubekommen, musst Du ja nur wissen, ob sich die Sekunde geändert hat.
Dazu merkst Du Dir die aktuelle Sekunde und prüfst auf Änderung.
Dann schreibst Du an die Position des : entweder ein Leerzeichen oder einen :

Das display.display() machst Du am Ende der Funktion.
Die Taste geprüft wird nur im loop - Bei tastendruck wird display.clear ausgeführt und dem switch/case-Konstrukt eine neue menu-Zahl übergeben. Damit wird das Display neu beschrieben - fertig.

Ich hab mal kurz was zusammengestellt.#
Um Dir zu zeigen, was ich meine.
Der Code geht nur mit Uhr und seriellem Monitor.

#include "RTClib.h"

RTC_DS3231 rtc;
DateTime now;

void setup()
 {
 Serial.begin(115200);
 Serial.println(F("Start..."));
 rtc.begin();
 }

void loop()
 {
 funktion_Ausgabe();
 }

void funktion_Ausgabe()
 {
 now = rtc.now();
 static uint8_t lastsecond = 61;
 if (now.second() != lastsecond)
   {
   lastsecond = now.second();
   if (lastsecond%2)
   Serial.println(":");
   else
   Serial.println("-");
   }
 }




Ich habe an Stelle des Leerzeichen einen - genommen.
Die Uhr musst Du noch auf Deine umstellen.

Aber das Funktioniert nicht mit einer Switch Case Anwendung oder?

Philipp_Gretscher:
Aber das Funktioniert nicht mit einer Switch Case Anwendung oder?

Ist das jetzt eine Frage?
Hast Du es ausprobiert?
Wenn ja, mit welchem Ergebnis - Wenn nein, warum nicht?

Ich hab das von gerstern mal erweitert:

// Forumsketch https://forum.arduino.cc/index.php?topic=701740.msg4717629#msg4717629

#include "RTClib.h"
RTC_DS3231 rtc;
DateTime now;
int case_zaehler = 0;

void setup()
{
  Serial.begin (115200);
  Serial.println (F ("Start..."));
  rtc.begin();
}

void loop()
{
  funktion_zaehler();
  funktion_Ausgabe();
}

void funktion_zaehler()
{
  if (millis() % 10000 == 1)
  {
    case_zaehler++;
    if (case_zaehler >= 3)
    {
      case_zaehler = 0;
    }
  }
}

void funktion_Ausgabe()
{
  now = rtc.now();
  static uint8_t lastsecond = 61;
  switch (case_zaehler)
  {
    case 0:
      if (now.second() != lastsecond)
      {
        lastsecond = now.second();
        if (lastsecond % 2)
          Serial.println (":");
        else
          Serial.println ("-");
      }
      break;
    case 1:
      Serial.println (" Case 1 ");
      break;
    case 2:
      Serial.println (" Case 2 ");
      break;
    default:
      break;
  }
}

Las das einfach mal laufen - und sag mir, was Du da erkennst.

my_xy_projekt:
Ist das jetzt eine Frage?
Hast Du es ausprobiert?
Wenn ja, mit welchem Ergebnis - Wenn nein, warum nicht?

Ich hab das von gerstern mal erweitert:

// Forumsketch https://forum.arduino.cc/index.php?topic=701740.msg4717629#msg4717629

#include "RTClib.h"
RTC_DS3231 rtc;
DateTime now;
int case_zaehler = 0;

void setup()
{
  Serial.begin (115200);
  Serial.println (F ("Start..."));
  rtc.begin();
}

void loop()
{
  funktion_zaehler();
  funktion_Ausgabe();
}

void funktion_zaehler()
{
  if (millis() % 10000 == 1)
  {
    case_zaehler++;
    if (case_zaehler >= 3)
    {
      case_zaehler = 0;
    }
  }
}

void funktion_Ausgabe()
{
  now = rtc.now();
  static uint8_t lastsecond = 61;
  switch (case_zaehler)
  {
    case 0:
      if (now.second() != lastsecond)
      {
        lastsecond = now.second();
        if (lastsecond % 2)
          Serial.println (":");
        else
          Serial.println ("-");
      }
      break;
    case 1:
      Serial.println (" Case 1 ");
      break;
    case 2:
      Serial.println (" Case 2 ");
      break;
    default:
      break;
  }
}




Las das einfach mal laufen - und sag mir, was Du da erkennst.

Also erst kommt relativ lange Case 1 ganz oft und nach ner Zeit das gleiche mit Case 2 und danach kommt jede Sekunde : dann - und so weiter und dann das ganze wieder von vorne

Philipp_Gretscher:
Also erst kommt relativ lange Case 1 ganz oft und nach ner Zeit das gleiche mit Case 2 und danach kommt jede Sekunde : dann - und so weiter und dann das ganze wieder von vorne

Naja, nicht ganz, aber das erste Mal siehst Du das -:-:- nicht.
Hast Du den Code verstanden?
Was sagt Dir das?

Ist damit Deine Frage beantwortet?

Kleine Hilfe: In der funktion_zaehler() emuliere ich einen Tastendruck im 10-Sekundenintervall. Das hat wiederrum Auswirkungen auf das, was dann die nächsten 10 Sekunden gemacht wird.

my_xy_projekt:
Naja, nicht ganz, aber das erste Mal siehst Du das -:-:- nicht.
Hast Du den Code verstanden?
Was sagt Dir das?

Ist damit Deine Frage beantwortet?

Kleine Hilfe: In der funktion_zaehler() emuliere ich einen Tastendruck im 10-Sekundenintervall. Das hat wiederrum Auswirkungen auf das, was dann die nächsten 10 Sekunden gemacht wird.

Ich versteh das mit dem millis nowsecond lastsecond und das % nicht, also aus dem Beispielsketch blinkwithout delay hab ich verstanden, das damit gezählt werden kann, aber vielleicht kannst dus ja nochmal erklären

Philipp_Gretscher:
Ich versteh das mit dem millis nowsecond lastsecond und das % nicht, also aus dem Beispielsketch blinkwithout delay hab ich verstanden, das damit gezählt werden kann, aber vielleicht kannst dus ja nochmal erklären

Na dann:

Die Ausgangsfrage war:
Wie bekommst Du das blinken hin - ohne delay und ohne while im case.
Ich wollte Dir den Sketch nicht komplett umschreiben -- das musst Du allein machen :wink:
Darum gebe ich den Wechsel von Doppelpunkt und Leerzeichen auf dem seriellen Monitor aus. Damit das Leerzeichen auch zu sehen ist, hab ich das mit einem Minus gekennzeichent.

In der funktion_Ausgabe() ist folgendes Konstrukt enthalten:

 static uint8_t lastsecond = 61;
 switch (case_zaehler)
 {
   case 0:
     if (now.second() != lastsecond)
     {
       lastsecond = now.second();
       if (lastsecond % 2)
         Serial.println (":");
       else
         Serial.println ("-");
     }
     break;

lastsecond ist ein Merker für die Sekunde, in der eine (die letzte) Aktion durchgeführt wurde.
das static sorgt dafür, das der Wert nicht verloren geht, wenn die Funktion verlassen wird.
Man könnte lastsecond auch oben vor dem setup() festmachen, aber dann wäre die Variable im gesamten Sketch änderbar - und hätte Auswirkungen auf Deinen Doppelpunkt.
Initialisiert wird sie mit 61, damit schon beim ersten Aufruf die Bedingung aktuelle Sekunde != gemerkte Sekunde erfüllt ist.
Dann wird lastsecond mit dem Wert der aktuellen Sekunde gefüllt.
Das %2 bedeutet "modulo 2" , das immer dann, wenn letzte Sekunde geteilt durch 2 keinen Rest ergibt der : geschrieben wird.
Merke: Der Wert letzte Sekunde ändert sich jede Sekunde einmal, die Bedingung dazu wird also auch nur einmal jede Sekunde durchlaufen, damit wird auch nur einmal jede Sekunde entweder ein : oder ein Leerzeichen ausgegeben.

Du musst jetzt errechnen (von mir aus auch auszählen), an welcher Stelle sich Dein blinkender : befindet.
Dann setzt Du an Stelle meiner seriellen Ausgabe den Cursor an die Position und schreibst nur dieses eine Zeichen.

Und das macht das Programm solange, wie die switch/case Bedingung erfüllt ist. - bei mir, wenn Menu 0

Hintergrund des Sketches war Dir zu zeigen, das die Änderung der Variable menu nur einmal erfolgt und das nicht im switch case, der case-zweig dauerhaft, wiederholt und ohne Bremse durchlaufen wird. (case 1 / case 2) aber mit einer zusätzlichen Bedingung nur dann eine Ausgabe erfolgt wenn man das wirklich will - nämlich, wenn eine Änderung eintritt. (second())

Lad Dir mal das .pdf runter und lies mal - da ist wirklich viels schön ausführlich beschrieben.
https://www.arduinoforum.de/code-referenz