Anzeigefehler mit einem SPI Display mit ILI9488 Driver sowie ESP32

Du kannst dir die Infos auch im LittleFS als "Log" anzeigen lassen.
Anstatt die auf den seriellen Monitor zu schreiben.
Dann muss der nicht am Monitor hängen.

Kurzes Update:
Der ESP32 wurde für längere Zeit vom Strom genommen. Anzeige sieht seidem halbwegs in Ordnung aus. Was mir aber gestern entfallen ist, ist das die Anzeige für die VCC Anzeige der Außensendestation ziemlich oft kleinere Anzeigefehler hat (siehe Bild) das was für Temp und Feuchtigkeit empfangen wird macht ganz unvorhergesehen Anzeigefehler.

EDIT:
Das auslessen und anzeigen der RTC sowie der des Innensensors verursacht NIE Probleme, lediglich das was über den HC-12 Empfangen wird: VCC Spannung, Temp und Feuchtigkeit.

Nur wirst du damit nicht den Fehler beseitigt haben.
Was Funk betrifft, vermute ich fehlerhafte Zeichen in der Übertragung. Die können entstehen, wenn die "alten" Daten nicht gelöscht werden, sondern einfach nur überschrieben werden. Auf der Senderseite und auch Empfängerseite.

Das mit den "Alten Daten Löschen" werde ich mir nochmals anschauen (ist ein guter Tipp). ich hab zusätzlich noch was gebastelt das den Datenstring auf die richtige länge prüft, dann wird geprüft ob der Datenstring Part0, Part1 und Part2 die richtige Länge hat. Zusätzlich wird darauf geachtet das der Datenstring nur Ziffern enthält sowohl Positive als auch Negative und auch Trennzeichen, außerdem wird noch geprüft ob die Werte realistisch sind, der Code:

bool isNumeric(const char *str) 
{
    if (str[0] == '\0') 
    {
        // Überprüft, ob der String leer ist. Wenn ja, gibt die Funktion false zurück, da ein leerer String nicht numerisch ist.
        return false; 
    }
    bool hasDigit = false; // Flag für Ziffern
    bool hasDotCommaDash = false; // Flag für Dezimalpunkt, Komma und Minuszeichen
    for (int i = 0; str[i] != '\0'; i++) 
    {
        if (str[i] == '-' || str[i] == '.' || str[i] == ',') 
        {
            hasDotCommaDash = true; // Setze das Flag für Dezimalpunkt, Komma oder Minuszeichen
        } 
        else if (!isdigit(str[i])) 
        {
            return false;  // Andere Zeichen sind nicht erlaubt
        }
        else
        {
            hasDigit = true; // Setze das Flag für Ziffern
        }
    }
    return hasDigit && hasDotCommaDash; // Es muss mindestens eine Ziffer und ein erlaubtes Zeichen vorhanden sein
}







void teileDaten(String daten) 
{
    char *daten2 = new char[daten.length() + 1];
    strcpy(daten2, daten.c_str());
    if (daten.length() > 17) // Überprüfung der maximalen Länge des Datenstrings
{
        delete[] daten2;
        return;
}
    char *Part0 = strtok(daten2, ",");  // Für Temp
    char *Part1 = strtok(NULL, ",");    // Für Humidity
    char *Part2 = strtok(NULL, ",");    // Für VCC
    
    if (strlen(Part0) > 6 || strlen(Part1) > 4 || strlen(Part2) > 5) // Überprüfung der Längen der Teile
    {
        delete[] daten2;
        return;
    }
    if (!isNumeric(Part0) || !isNumeric(Part1) || !isNumeric(Part2)) // Weitere Überprüfungen für die numerischen Werte
    {
        delete[] daten2;
        return;
    }
    float tempValue = atof(Part0); // Weitere Überprüfungen für die numerischen Werte
    if (tempValue < -15.0 || tempValue > 40.0) 
    {
        delete[] daten2;
        return;
    }
    int humidityValue = atoi(Part1);
    if (humidityValue < 0 || humidityValue > 100) 
    {
        delete[] daten2;
        return;
    }
    float vccValue = atof(Part2);
    if (vccValue < 3.0 || vccValue > 5.0) 
    {
        delete[] daten2;
        return;
    }

Ob es das tut wird sich zeigen müssen

EDIT:
bei einer sache bin ich mir nicht sicher, bricht die verabeitung ab wenn der datenstring größer als 18 ist oder bricht die verarbeitung auch ab wenn der datenstring kleiner als 18 ist?

if (daten.length() != 18) {
    delete[] daten2;
    return;
}

Der bricht ab, wenn es nicht genau 18 ist ( != ist tatsächlich ungleich).

Wirf bitte nochmal einen Blick in Deinen Sendesketch.
Ich glaube, dass die Länge 5 bei der Temperatur ggf. nicht ausreicht: -14.5 wären schon fünf Zeichen, man braucht aber eins mehr für die abschließende \0.

Dort wird tatsächlich mit 5 zeichen gesendet überall:

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

Wobei -99.9 und +99.9 auch 5 Zeichen sind

Aber....

char toSend[40];  

ist doch recht viel.

Temperatur: -99.9 = 5 Zeichen
Feuchtigkeit: 100 = 3 Zeichen
VCC-Spannung: 4.99 = 5 Zeichen
Das „%“ und „˚C“ Zeichen werden nicht gesendet, das wird im Empfänger hinzugefügt
+2 Zeichen für die trenner

Also 15 Zeichen wenn ich kein denkfehler habe

Das sind zwar fünf Zeichen, aber die brauchen sechs Byte.

Beispiel:

char tempData[5];
char humData[5];

Diese Vereinbarungen geben Dir Platz für Zeichenketten von jeweils nur vier Zeichen.

        --- tempData ---------| --- humData -----------|
Index    0    1    2    3    4    0    1    2    3    4     
Inhalt  '9'  '8'  '.'  '7'   0   '5'  '5'   0             // passt: Temperatur 98.7°C, Luft 55%

        '-'  '1'  '2'  '.'  '3'   0                       // passt nicht Temp: -12.3°C
                                 '5'  '5'   0             // Luft: 55%

strcpy() und strcat() kopieren alle Zeichen bis zum nächsten Nullbyte im Speicher - wo auch immer das stehen mag. Damit kann es passieren, dass Du mehr sendest (und am Ende auf das Display schreibst) als erwartet.

(s.a. hier)

2 Likes

Das heisst das ich als Endbit eine "0" mir dazu denken muss um das Ende der Zeichenkette anzuzeigen

Genauer als Endbyte eine binäre 0, also als Zeichen '\0'.
"0" ist eine Zeichenkette, also 2 Zeichen. Die '0' und das '\0'

Gruß Tommy

Das heisst für die Feuchtigkeit "100" +/0 sind das 5 bytes

Das sind 4 Bytes: '1', '0','0','\0'
'\0' ist ein Byte mit dem binären Wert 0.
'0' ist 0x30 oder dezimal 48
ASCII-Tabelle.

Gruß Tommy

Okay danke, da habe ich wieder was dazu gelernt :grin:
Da die 2 trenner auch je ein byte benötigen wären das insgesammt 17 bytes.

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

Ja - für die Zwischenvariablen (nur ein Trenner!) und nein für die zu sendende Zeichenkette toSend.

Allerdings kann es passieren, das nicht alle davon genutzt werden - z.B. bei der Temperatur:

'-' '1' '0'  '.' '3' '\0'    // 6 Byte  -10.2°C  heute
'2' '.' '2' '\0'             // 4 Byte    2.2°C  nächste Woche
'3' '2' '.' '1' '\0'         // 5 Byte   32.1°C  nächsten Sommer

Nur die benutzten Teile landen am Ende im Puffer toSend, weil nach strcpy() und den strcat()-Aufrufen die '\0' innerhalb nicht mehr da sind, nur noch eines am Ende.

Worst case (längste zu sendende Zeichenkette) sind 15 Byte unter den Annahmen

  • -100 < Temperatur < 100
  • 0 <= Luftfeuchtigkeit <= 100
  • 0 <= Spannung < 10
    strcpy(toSend, tempData);    // max. 5 Byte   '-' 9' '9' '.' '9'
    strcat(toSend, trenner);     // ein Byte      ','
    strcat(toSend, humData);     // max. 3 Byte   '1' '0' '0'
    strcat(toSend, trenner);     // ein Byte      ','
    strcat(toSend, vccData);     // max. 4 Byte   '9' '.' '9' '9'
                                 // ein Byte      '\0'

Ach so, ich bin davon ausgegangen das, dass Abschluss bit "\0" nach jedem Part kommt. Aber das Abschluss bit "\0" scheint wohl nur ganz am ende des Datenstrings zu kommen

Darf ich nochmal auf #29 und #31 von @Tommy56 verweisen?

  • Bit kann 0 oder 1 sein
  • Byte ist eine Ansammlung aus 8 Bit
  • "A" oder "irgendwas" sind (C-)-Strings und werden abgeschlossen durch ein Nullbyte ("eins mehr")
  • 'A', '3' oder '\0' sind einzelne Zeichen, die in der Regel ein Byte belegen

Wir haben es bei Softwareentwicklung mit eigentlich recht dummen Maschinen zu tun, deshalb lohnt es sich, auch in diesen Details genau zu sein - damit die Dinger das tun, was wir von ihnen wollen.

UPDATE:

Ich habe den Empfänger Sketch nochmals angepasst und für das Empfangende ein Spamfilter sozusagen eingebaut:
Wie gut das funktioniert wird sich in den nächsten Tagen der inbetriebnahme zeigen.

/*****************************************************************************************
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>
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

boolean isSummer; // Globale Variable deklarieren





void adjustForDST(DateTime &d) 
{
  if (isSummer) 
  {
      d = d + TimeSpan(0,1,0,0);
  }
}




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.bmp") == true) 
                                                      
    ui.drawBmp("/bilder/IN.bmp",244, 214+0);     // Außen Feuchtigkeit
    ui.drawBmp("/bilder/OUT.bmp",244, 112+0);    // Außen Feuchtigkeit
    ui.drawBmp("/bilder/OUT.bmp",11, 112+0);     // Außen Temperatur
    ui.drawBmp("/bilder/IN.bmp",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;
  }





bool isNumeric(const char *str) 
{
    if (str[0] == '\0') 
    {
        // Überprüft, ob der String leer ist. Wenn ja, gibt die Funktion false zurück, da ein leerer String nicht numerisch ist.
        return false; 
    }
    bool hasDigit = false; // Flag für Ziffern
    for (int i = 0; str[i] != '\0'; i++) 
    {
        if (!isdigit(str[i]) && str[i] != '-' && str[i] != '.' && str[i] != ',') 
        {
            return false;  // Andere Zeichen sind nicht erlaubt
        }
        else if (isdigit(str[i]))
        {
            hasDigit = true; // Setze das Flag für Ziffern
        }
    }
    return hasDigit; // Es muss mindestens eine Ziffer vorhanden sein
}








void teileDaten(String daten) 
{
    char *daten2 = new char[daten.length() + 1];
    strcpy(daten2, daten.c_str());
    if (daten.length() > 17) // Überprüfung der maximalen Länge des Datenstrings
{
        delete[] daten2;
        return;
}
    char *Part0 = strtok(daten2, ",");  // Für Temp
    char *Part1 = strtok(NULL, ",");    // Für Humidity
    char *Part2 = strtok(NULL, ",");    // Für VCC
    
    if (strlen(Part0) > 6 || strlen(Part1) > 4 || strlen(Part2) > 5) // Überprüfung der Längen der Teile
    {
        delete[] daten2;
        return;
    }
    if (!isNumeric(Part0) || !isNumeric(Part1) || !isNumeric(Part2)) // Weitere Überprüfungen für die numerischen Werte
    {
        delete[] daten2;
        return;
    }
    float tempValue = atof(Part0); // Weitere Überprüfungen für die numerischen Werte
    if (tempValue < -15.0 || tempValue > 40.0) 
    {
        delete[] daten2;
        return;
    }
    int humidityValue = atoi(Part1);
    if (humidityValue < 0 || humidityValue > 100) 
    {
        delete[] daten2;
        return;
    }
    float vccValue = atof(Part2);
    if (vccValue < 3.0 || vccValue > 5.0) 
    {
        delete[] daten2;
        return;
    }
    
    // Weitere Verarbeitung der Daten, da sie gültig sind
    float maxVcc = 4.87;  // 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(); // Aufräumen
    tft.setTextPadding(0);
    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 check_SummerTime() 
{
    DateTime now = rtc.now();
    isSummer = summertime_EU(now.year(), now.month(), now.day(), now.hour(), 1);
}
    boolean summertime_EU(int year, byte month, byte day, byte hour, byte tzHours) 
{
    if (month < 3 || month > 10) return false;
    if (month > 3 && month < 10) return true;
    if (month == 3 && (hour + 24 * day) >= (1 + tzHours + 24 * (31 - (5 * year / 4 + 4) % 7)) || month == 10 && (hour + 24 * day) < (1 + tzHours + 24 * (31 - (5 * year / 4 + 1) % 7)))
    return true;
    else
    return false;
}


void readAndPrintRTC() 
{
    DateTime now = rtc.now();
    check_SummerTime(); // Aufruf der Funktion zur Überprüfung der Sommerzeit
    adjustForDST(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);  
}

Überprüft ob der String nur Zahlen mit nachkommastellen sowie Negative Zahlen enthällt.

bool isNumeric(const char *str) 
{
    if (str[0] == '\0') 
    {
        // Überprüft, ob der String leer ist. Wenn ja, gibt die Funktion false zurück, da ein leerer String nicht numerisch ist.
        return false; 
    }
    bool hasDigit = false; // Flag für Ziffern
    for (int i = 0; str[i] != '\0'; i++) 
    {
        if (!isdigit(str[i]) && str[i] != '-' && str[i] != '.' && str[i] != ',') 
        {
            return false;  // Andere Zeichen sind nicht erlaubt
        }
        else if (isdigit(str[i]))
        {
            hasDigit = true; // Setze das Flag für Ziffern
        }
    }
    return hasDigit; // Es muss mindestens eine Ziffer vorhanden sein
}

Überprüft die Länge des Datenstrring und die der Teildatenstringe

    if (daten.length() > 17) // Überprüfung der maximalen Länge des Datenstrings
.......
    if (strlen(Part0) > 6 || strlen(Part1) > 4 || strlen(Part2) > 5) // Überprüfung der Längen der Teile

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