Anzeigefehler mit einem SPI Display mit ILI9488 Driver sowie ESP32

Hallo liebes Forum.

Aktuell habe ich das Problem das bei meiner kürzlich gebauten Wetterstation mit ESP32 und einem 4" SPI-Display mit ILI9488_DRIVER nach längerer Nutzung (Erst nach mehreren Tagen) ziemlich wilde Probleme mit der Anzeige bekomme. Meistens passiert das, wenn der ESP32 die Daten der Außenwetterstation empfängt gesendet und Empfangen wird mit einem HC-12 Modul, die Außenwetterstation läuft mit einem Attiny85. Das kuriose ist, wenn ich den ESP32 führ mehrere Sekunden vom Strom nehme und anschließend wieder einstecke verschwinden die Anzeigefehler nicht mehr. Hat da jemand eine Idee, wo noch der Wurm drinnen liegt?

Die Anzeige Fehler:

Der Code der Sendestation:

#include <Arduino.h>
#include <avr/wdt.h>
#include <avr/sleep.h>
#include <SoftwareSerial.h>
#include <dht22.h>

constexpr byte DHTPIN {2};
constexpr byte UNUSEDPINS[] {0, 1, 4};

SoftwareSerial hc12(4, 3);   // TX = 4, RX = 3
dht22 dht;

constexpr byte WATCHDOG_WAKEUPS_TARGET {37};   // 8 * 7 = 56 seconds between each data collection


// watchdog ISR
ISR(WDT_vect) 
{
  // nothing to do here, just wake up
}

void enableWatchdog() 
{
  cli();
  MCUSR &= ~(1 << WDRF);
  WDTCR |= (1 << WDCE) | (1 << WDE);
  WDTCR = 1 << WDP0 | 1 << WDP3;
  WDTCR |= (1 << WDIE);
  sei();
}

// function to go to sleep
void enterSleep(void) {
  ADCSRA &= ~(1 << ADEN);              // switch off ADC -320µA
  set_sleep_mode(SLEEP_MODE_PWR_DOWN); // SLEEP_MODE_PWR_DOWN for lowest power consumption. 
  sleep_enable();
  sleep_mode();
  sleep_disable();
}

void initADC0() {
  // Read 1.1V reference as input with reference operating voltage vcc
  // Reference Vcc and analog input = internal reference 1.1V

 // Initialise ADC with REFS[2:0] is 0 = VCC as Ref,  MUX[3:0] 1100 = Vbg as Input,
  ADMUX = _BV(MUX3) | _BV(MUX2);
  bitSet(ADCSRA, ADEN);   // Enable
  delay(100);              // Wait until the reference has stabilized
  
  // After activating the ADC, a "dummy readout" is recommended.
  // In other words, a value is read and discarded to allow the ADC to "warm up"
  ADCSRA |= _BV(ADSC);                 // Start conversion
  while ((ADCSRA & _BV(ADSC))) { ; }   // measure
  (void)ADCW;                          // Discard dummy readout..
}

constexpr uint16_t INTERN {1120};   // determined per IC
constexpr uint32_t INTERNxRESOLUTION {INTERN * 1024UL};

float measurementVCC() 
{
  initADC0();
  ADCSRA |= _BV(ADSC);                 // Start conversion
  while ((ADCSRA & _BV(ADSC))) { ; }   // measure
  return static_cast<float>(INTERNxRESOLUTION / ADCW) / 1000;
}

void setup() {
  hc12.begin(4800);
  dht_init(&dht, DHTPIN);
  // Set unused pins to INPUT_PULLUP to save power.
  for (auto pin : UNUSEDPINS) { pinMode(pin, INPUT_PULLUP); }

  // switch off ADC -320µA
  ADCSRA &= ~(1 << ADEN);
  delay(2000);

  // enable the watchdog
  enableWatchdog();
}

void loop() 
{
  float Temperatur;
  float Luftfeuchtigkeit;
  float vcc = measurementVCC();

  char toSend[40];  // Increased buffer size to accommodate VCC data
  if (dht_read_data(&dht, &Temperatur, &Luftfeuchtigkeit) == 1) {
    char tempData[5];
    char humData[5];
    char vccData[5];
    char trenner[] = ",";

    // 4 is mininum width, 1 is precision; float value is copied onto buff
    dtostrf(Temperatur, 4, 1, tempData);
    dtostrf(Luftfeuchtigkeit, 2, 0, humData);
    dtostrf(vcc, 1, 2, vccData);  // Adjust precision as needed

    strcpy(toSend, tempData);
    strcat(toSend, trenner);
    strcat(toSend, humData);
    strcat(toSend, trenner);
    strcat(toSend, vccData);
    
  } else {
    strcpy(toSend, "99.9,99,");   // Error
  }
  hc12.print(toSend);   // Transmitting data with the HC12 transmitter
  // deep sleep
  for (uint8_t i = 0; i < WATCHDOG_WAKEUPS_TARGET; i++) { enterSleep(); }
}

Der Code der Basisstation:

/*****************************************************************************************
HC12 ---------------------------> G26>RX // G27>TX 
RTC DS3231 ---------------------> G21>SDA // G22>SCL
TFT ILI9488 --------------------> G23>MOSI // G18>SCK // G4>RESET // G2>DC // G32>CS // G15>LED         
DHT22 --------------------------> G25>DHT
BEWEGUNGSSENSOR ----------------> G12>PIR
******************************************************************************************/


#include <Wire.h>
#include "DHT.h"
#include <SoftwareSerial.h>
SoftwareSerial HC12Serial(26, 27); 

#define DHTPIN 25
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);

#include "RTClib.h"
RTC_DS3231 rtc; 

#include <TFT_eSPI.h>
#include <SPI.h>
TFT_eSPI tft = TFT_eSPI(); 
#include "GfxUi.h"
GfxUi ui = GfxUi(&tft);

#define AA_FONT_FONT1 "fonts/NirmalaUI-Bold-45"
#define AA_FONT_FONT2 "fonts/Calibri-Light-25" 
#define AA_FONT_FONT3 "fonts/Calibri-25" 

const int pirSensorPin = 33;  // Pin, an dem der PIR-Sensor angeschlossen ist
const int ledPin = 15;       // Pin für die LED
bool motionDetected = false;
unsigned long motionTimestamp = 0;
const unsigned long motionDuration = 15000; // 15 Sekunden

unsigned long lastSensorUpdateTime = 0;
const unsigned long sensorUpdateInterval = 120000; // 300000 = 5 minutes in milliseconds





void setup()
{
    SPIFFS.begin();
    HC12Serial.begin(4800);
    Serial.begin(115200);
    Wire.begin(SDA, SCL);
    dht.begin();
    rtc.begin();
    
    pinMode(pirSensorPin, INPUT);  // PIR-Sensor als Eingang konfigurieren
    pinMode(ledPin, OUTPUT); 
           
    tft.init(); 
    tft.setRotation(3
    ); 
    tft.fillScreen(TFT_WHITE);
    tft.fillRect(6, 6, 468, 308, TFT_GOLD);       // Darstellungsfläche
    tft.fillRect(6, 102, 468, 6, TFT_WHITE);      // Erster Querteiler
    tft.fillRect(6, 204, 468, 6, TFT_WHITE);      // Zweiter Querteiler
    tft.fillRect(234, 102, 6, 234, TFT_WHITE);    // Längsteiler
                                                  
    tft.setTextColor(TFT_BLACK);                  
    tft.loadFont(AA_FONT_FONT3);                  
    tft.drawString("Feuchtigkeit", 307, 123);     // Außen
    tft.drawString("Temperatur", 71, 123);        // Außen 
    tft.drawString("Temperatur", 71, 225);        // Innen
    tft.drawString("Feuchtigkeit", 307, 225);     // Innen
                                                  
    if (SPIFFS.exists("/bilder/OUT.jpg") == true) 
                                                      
    ui.drawJpeg("/bilder/IN.jpg",244, 214+0);     // Außen Feuchtigkeit
    ui.drawJpeg("/bilder/OUT.jpg",244, 112+0);    // Außen Feuchtigkeit
    ui.drawJpeg("/bilder/OUT.jpg",11, 112+0);     // Außen Temperatur
    ui.drawJpeg("/bilder/IN.jpg",11, 212+0);      // Innen Temperatur

    lastSensorUpdateTime = millis(); // Initialize the update time
    readAndPrintSensorData(); // Initial display of DHT sensor values
}





void loop()
{
    if (HC12Serial.available())
  {
    String receivedData = HC12Serial.readString();
    teileDaten(receivedData);
  }
    unsigned long currentMillis = millis();
    int motionState = digitalRead(pirSensorPin);  // PIR-Sensor abfragen
    if (motionState == HIGH && !motionDetected)
  {
    digitalWrite(ledPin, HIGH);    // LED einschalten  // Bewegung erkannt
    motionDetected = true;          // Bewegung gesetzt
    motionTimestamp = millis();     // Zeitstempel für die Bewegung speichern
  }
    if (motionDetected && (currentMillis - motionTimestamp >= motionDuration))
  {
    digitalWrite(ledPin, LOW);     // LED ausschalten     // Überprüfen, ob die Bewegungsdauer abgelaufen ist
    motionDetected = false;         // Zurücksetzen der Bewegungserkennung
  }
    if (currentMillis - lastSensorUpdateTime >= sensorUpdateInterval)   // Sensordaten aktualisieren
    readAndPrintSensorData(), lastSensorUpdateTime = currentMillis;
    readAndPrintRTC();
  }
    void motionInterrupt()
  {
    motionDetected = true;
  }







void teileDaten(String daten) 
{
    char *daten2 = new char[daten.length() + 1];
    strcpy(daten2, daten.c_str());
    char *Part0 = strtok(daten2, ",");  // Für Temp
    char *Part1 = strtok(NULL, ",");    // Für Humidity
    char *Part2 = strtok(NULL, ",");    // Für VCC

    // Überprüfung, ob alle Teile vorhanden sind
    if (Part0 == NULL || Part1 == NULL || Part2 == NULL) {
        Serial.println("Fehlerhafte Daten empfangen");
        delete[] daten2;
        return;
    }

    // Überprüfung der Temperatur auf unrealistische Werte
    float tempValue = atof(Part0);
    if (tempValue < -50 || tempValue > 50) {
        Serial.println("Ungültige Temperatur empfangen");
        delete[] daten2;
        return;
    }

    // Überprüfung der Luftfeuchtigkeit auf unrealistische Werte
    int humidityValue = atoi(Part1);
    if (humidityValue < 0 || humidityValue > 100) {
        Serial.println("Ungültige Luftfeuchtigkeit empfangen");
        delete[] daten2;
        return;
    }

    // Überprüfung des VCC-Werts auf unrealistische Werte
    float vccValue = atof(Part2);
    if (vccValue < 3 || vccValue > 5.0) {
       Serial.println("Ungültiger VCC-Wert empfangen");
       delete[] daten2;
       return;
    }

    // Weitere Verarbeitung der Daten, da sie gültig sind
    float maxVcc = 4.86;  // Maximaler VCC-Wert
    float minVcc = 3.3;   // Minimaler VCC-Wert
  
    int vccPercentage = int(((vccValue - minVcc) / (maxVcc - minVcc)) * 100); // Umrechnung des VCC-Werts in Prozent mit Berücksichtigung des Minimalwerts
    vccPercentage = max(0, min(100, vccPercentage));  // Begrenzen des Prozentwert auf den Bereich von 0 bis 100

    tft.loadFont(AA_FONT_FONT1);
    tft.setTextColor(TFT_BLACK, TFT_WHITE);
    tft.setTextPadding(tft.textWidth("88.8.88")); 

    String tempString = String(Part0);
    if (tempString.toFloat() < 0) {
        tempString = "-" + tempString.substring(1);
    }
    tft.drawString(tempString + " °C", 75, 157); // OUT Temp
    tft.setTextPadding(tft.textWidth("888.88")); 
    tft.drawString(String(Part1) + " %", 327, 157); // OUT Humidity
    tft.loadFont(AA_FONT_FONT2);
      
    if (vccValue < 3.3) {
    tft.setTextPadding(tft.textWidth("88888888888888.")); 
    tft.setTextColor(TFT_BLACK, TFT_GOLD);  // Text in Rot einfärben
    tft.drawString("Battery is low:", 85, 70);
    } else {
    tft.setTextColor(TFT_BLACK, TFT_GOLD);
    tft.setTextPadding(tft.textWidth("888888888888888.")); 
    tft.drawString("Battery Voltage:", 85, 70);
    }
    tft.setTextColor(TFT_BLACK, TFT_GOLD);
    tft.setTextPadding(tft.textWidth("8.88.8.888888")); 
    tft.drawString(String(Part2) + " V, " + String(vccPercentage) + "% ", 260, 70); // Beispielposition für VCC-Anzeige, anpassen nach Bedarf

    tft.unloadFont();
    tft.setTextPadding(0);
    // Aufräumen
    delete[] daten2;
}








void readAndPrintSensorData() 
{
    float Temperatur = dht.readTemperature();
    float Luftfeuchtigkeit = dht.readHumidity();

    tft.loadFont(AA_FONT_FONT1);
    tft.setTextColor(TFT_BLACK, TFT_WHITE);
    tft.setTextPadding(tft.textWidth("88.8.88")); 
    tft.drawString(String(Temperatur, 1) + " °C", 75, 265); // IN Temp

    tft.setTextPadding(tft.textWidth("888.88")); 
    tft.drawString(String((int)Luftfeuchtigkeit) + " %", 327, 265); // IN Humidity
  
    tft.unloadFont();
    tft.setTextPadding(0);
}






void readAndPrintRTC() 
{
    DateTime now = rtc.now();
    char dateDisplay[15];
    char timeDisplay[15];
    
    sprintf(dateDisplay, "%.1d.%.02d.%.2d", now.day(), now.month(), now.year());

    if (millis() % 1000 < 500)   // Uhrzeit Doppelpunkt im Sekundentakt blinken lassen
    {
    sprintf(timeDisplay, "%.02d:%02d", now.hour(), now.minute());
    } 
    else 
    {
    sprintf(timeDisplay, "%.02d %02d", now.hour(), now.minute());
    }
  
    tft.loadFont(AA_FONT_FONT1);                    // Datum 
    tft.setTextColor(TFT_BLACK, TFT_GOLD);
    tft.setTextPadding(tft.textWidth("88.88.88")); 
    tft.drawString(String(dateDisplay), 200, 20);

    tft.setTextColor(TFT_BLACK, TFT_GOLD);
    tft.setTextPadding(tft.textWidth("88 88"));      // Uhrzeit
    tft.drawString(String(timeDisplay), 47, 20);
 
    tft.unloadFont(); // Font entladen und TextPadding zurücksetzen
    tft.setTextPadding(0);
}

Vielen dank für eure unterstützung :slight_smile:

habe Dir damals schon geschrieben, ist man mit dem Font fertig soll sofort
tft.unloadFont(); kommen

   tft.loadFont(AA_FONT_FONT1);
   tft.setTextColor(TFT_BLACK, TFT_WHITE);
   tft.setTextPadding(tft.textWidth("88.8.88")); 

   String tempString = String(Part0);
   if (tempString.toFloat() < 0) {
       tempString = "-" + tempString.substring(1);
   }
   tft.drawString(tempString + " °C", 75, 157); // OUT Temp
   tft.setTextPadding(tft.textWidth("888.88")); 
   tft.drawString(String(Part1) + " %", 327, 157); // OUT Humidity
   tft.unloadFont(); 

   tft.loadFont(AA_FONT_FONT2);

also noch mall vor jedem wechseln der Schriftart die alte wegwerfen,
Bei meiner Wetterstation habe auch drei Schriftarten und noch mehr Schreiberei, ohne das mir was schmiert, ok muss nicht so viel gerechnet werden aber trotz dem.
Das sind vermutlich 100KB was du nur ein mal am ende der Loop aus dem Speicher wirfst.
So ist meine Vermutung.

Sowohl in der void readAndPrintRTC() und der void readAndPrintSensorData() als auch der void teileDaten(String daten) Funktion ist schon ein tft.unloadFont(); vorhanden immer am ende der Funktion.

    tft.drawString(String(timeDisplay), 47, 20);
    
    tft.unloadFont(); // Font entladen und TextPadding zurücksetzen
    tft.setTextPadding(0);
}
   tft.drawString(String((int)Luftfeuchtigkeit) + " %", 327, 265); // IN Humidity
  
    tft.unloadFont();
    tft.setTextPadding(0);
}
    tft.drawString(String(Part2) + " V, " + String(vccPercentage) + "% ", 260, 70); // Beispielposition für VCC-Anzeige, anpassen nach Bedarf

    tft.unloadFont();
    tft.setTextPadding(0);
    // Aufräumen
    delete[] daten2;
}

Ups :wink: ja , habe das nicht gesehen :thinking: was man könnte noch machen die Serial.print kommentieren die brauchst nur zum Debuggen wen am PC hängt.
Den das scheint mir nicht am Defektem Display liegen

Sollte der HC-12 aus Grunden, Bullshit senden könnte das auch durchaus ein grund sein, deshalb wollte ich das mit dem Part minimiren, das wenn fehlerhaftes gelesen wird nicht gezeigt wird:

 // Überprüfung, ob alle Teile vorhanden sind
    if (Part0 == NULL || Part1 == NULL || Part2 == NULL) {
        Serial.println("Fehlerhafte Daten empfangen");
        delete[] daten2;
        return;
    }

    // Überprüfung der Temperatur auf unrealistische Werte
    float tempValue = atof(Part0);
    if (tempValue < -50 || tempValue > 50) {
        Serial.println("Ungültige Temperatur empfangen");
        delete[] daten2;
        return;
    }

    // Überprüfung der Luftfeuchtigkeit auf unrealistische Werte
    int humidityValue = atoi(Part1);
    if (humidityValue < 0 || humidityValue > 100) {
        Serial.println("Ungültige Luftfeuchtigkeit empfangen");
        delete[] daten2;
        return;
    }

    // Überprüfung des VCC-Werts auf unrealistische Werte
    float vccValue = atof(Part2);
    if (vccValue < 3 || vccValue > 5.0) {
       Serial.println("Ungültiger VCC-Wert empfangen");
       delete[] daten2;
       return;
    }

Ob das ausreichend ist...hm

Du kannst auch dir im SerMon anzeigen wie viel Speicher der bei durchlauf frei hat, wie das war Frag nicht ist aus dem Kopf.
Wie viel ist belegt nach Kompilieren über 50% ?

385069 Bytes (29%)

Der Sketch verwendet 385069 Bytes (29%) des Programmspeicherplatzes. Das Maximum sind 1310720 Bytes.
Globale Variablen verwenden 25312 Bytes (7%) des dynamischen Speichers, 302368 Bytes für lokale Variablen verbleiben. Das Maximum sind 327680 Bytes.
C:\Users\Migel\AppData\Local\Arduino15\packages\esp32\tools\esptool_py\4.5.1/esptool.exe --chip esp32 --port COM3 --baud 921600 --before default_reset --after hard_reset write_flash -e -z --flash_mode dio --flash_freq 80m --flash_size 4MB 0x1000 C:\Users\Migel\AppData\Local\Temp\arduino_build_261485/OLD_V3.ino.bootloader.bin 0x8000 C:\Users\Migel\AppData\Local\Temp\arduino_build_261485/OLD_V3.ino.partitions.bin 0xe000 C:\Users\Migel\AppData\Local\Arduino15\packages\esp32\hardware\esp32\2.0.11/tools/partitions/boot_app0.bin 0x10000 C:\Users\Migel\AppData\Local\Temp\arduino_build_261485/OLD_V3.ino.bin 
esptool.py v4.5.1
Serial port COM3
Connecting....

Bei

Normal sollte das nicht passieren ich bin über 40%

Oh okay, sehr komisch

Habe nur mal in den Code der Basisstation geschaut.
Meine Vermutung: Die Zeichenketten vom HC12 enthalten nicht das was Du erwartest.

Vorschlag zur Fehlersuche:

  1. Nach Zeile 92 die komplette empfangene Nachricht ausgeben. Falls da schon kaputt im Sender weitersuchen
  2. In teileDaten() nach Zeile 129 die Teilstrings ausgeben lassen

Frage:
Für die Fehlerüberpüfung liest Du aus Part0, -1 und -2 ja schon die Werte aus (139-160). Das gibt - wenn der String länger als erwartet ist - u.U. nur den gültigen ersten Wert.
Warum nimmst Du dann für die Ausgabe nicht diese Werte, sondern wieder die Strings? Wenn die länger sind, gibst Du alles aus und hast den Salat.

ja genau

ich kann mir die Zeilen nicht anzeigen lasssen was genau meinst du in der der Funktion
void teileDaten(String daten)?

Ach so. Ich habe mir Deinen Code in den Notepad++ geladen und da die Zeilennummern verwendet (kann die IDE übrigens auch).

void loop()
{
    if (HC12Serial.available())
  {
    String receivedData = HC12Serial.readString();
    teileDaten(receivedData);
  }

Zeile 92 ist die erste in der Klammer wo Du vom HC liest.

void teileDaten(String daten) 
{
    char *daten2 = new char[daten.length() + 1];
    strcpy(daten2, daten.c_str());
    char *Part0 = strtok(daten2, ",");  // Für Temp
    char *Part1 = strtok(NULL, ",");    // Für Humidity
    char *Part2 = strtok(NULL, ",");    // Für VCC

Danach hast Du Deine drei Teil-Strings - die solltest Du ausgeben.

Ach so, verstehe. Also kann ich dass einfach herraus löschen

Habs grad herrausgefunden, gut zu wissen das die IDE das auch kann

Nein, nicht löschen. EIne Zeile zur Ausgabe des empfangenen Strings hinzufügen:

if (HC12Serial.available())
  {
    String receivedData = HC12Serial.readString();
    Serial.println(receivedData);
    teileDaten(receivedData);
  }

Wenn das nicht funktionieren sollte:

    Serial.println(receivedData.c_str());

Bei den Parts weiter unten analog verfahren.

Serial.println verwende ich eigendlich gar nicht? da ich das auf dem Display darstelle

Ja, am Ende ist das so; da sind die Serial.print entbehrlich.
Du weißt aber noch nicht, wo es schief geht, sprich was den "Anzeigefehler" verursacht.

Da kann es helfen (hilft es mir meistens) die Daten als reinen Text auf dem seriellen Monitor anzuschauen. Da kannst Du Dir jeden einzelnen Verarbeitungsschritt anzeigen lassen und damit sehen was rein und was rausgeht.

Ach so ich verstehe, macht sinn. Das problem ist die Anzeigefehler passieren nicht oft und wenn ganz unvorhergesehen, da müsste ich den ESP32 24/7 Am Ser Monitor laufen lassen.
ich Würde warscheinlich gar nicht so weit kommen da der ESP dann eh abstürtzt (nicht immer) wenn er die Empfangenden Daten nicht verarbeiten kann

Hmm. Das macht es nicht leichter.

Dann vielleicht anders ansetzen:
Du hast ja für Deine Sensordaten-Strings auch minimale und maximale Längen.
Wenn Du Part 0 bis 2 nun zusätzlich daraufhin überprüfst ob diese Randbedingungen eingehalten werden?
Im Fehlerfall dann genauso handeln wie bei den schon existierenden Prüfungen.

Gehe jetzt in die Heia, mehr dann vielleicht morgen.
Gute Nacht!

Gute Nacht