Wetterstation- Projekt: Arduino Uno Speicher nahezu voll

Hallo zusammen,

ich habe ein Projekt mit einigen Breakout Boards realisiert:
1x RTC_DS1307
1x DHT11
1x BMP085
1 TFT Display inkl. SD Card

Nun sollen mir die Daten auf dem Display und der SD ausgegeben werden. Der Code funktioniert soweit. Jedoch ist der Speicher 31.072 Bytes (von einem Maximum von 32.256 Bytes) fast voll.

Was mache ich falsch? Kann ich noch irgendwie Speicher zurückgewinnen?

#include <TinyDHT.h>
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_ST7735.h>
#include <Adafruit_GFX.h>
#include <SD.h>
#include <Adafruit_BMP085.h>
//#include <DHT.h>
#include <RTClib.h>

#define file "log1.txt"

//Display
//mosi=11=grün, sclk=13=blau
#define tft_cs   10 //rot
#define tft_dc   9 //schwarz
#define tft_rst  8 //gelb
Adafruit_ST7735 tft = Adafruit_ST7735(tft_cs, tft_dc, tft_rst);

//SD
#define sd_cs 7
//RTC
RTC_DS1307 RTC;
//Sensoren
//DTH11
#define DHTTYPE DHT11   // DHT 11 
//#define DHTTYPE DHT22   // DHT 22  (AM2302)
//#define DHTTYPE DHT21   // DHT 21 (AM2301)
//DHT dht;
#define dhtPin 2

DHT dht(dhtPin, DHTTYPE);

//BMP085
Adafruit_BMP085 bmp;

void setup(){
//  Serial.begin(9600);
    // Initialisiere RTC
  RTC.begin();
  // Prüfen ob RTC läuft  
  if (! RTC.isrunning()) {
    
    // Aktuelles Datum und Zeit setzen, falls die Uhr noch nicht läuft
    RTC.adjust(DateTime(__DATE__, __TIME__));
    
//    Serial.println("Echtzeituhr wurde gestartet und auf Systemzeit gesetzt.");
  }
//  else Serial.println("Echtzeituhr laeuft bereits.");
  // DHT Sensor initialisieren
  dht.begin();
//  dht.setup(dhtPin);
  //BMP Sensor initialisieren
  bmp.begin();
  //SD Init
//  Serial.print("Initializing SD card...");
  pinMode(10, OUTPUT);
  if (!SD.begin(sd_cs)) {
//    Serial.println("initialization failed!");
    return;
  }
//  Serial.println("initialization done.");
  //TFT
  tft.initR(INITR_BLACKTAB);   // ST7735-Chip initialisieren
  tft.setRotation(1);
  tft.fillScreen(ST7735_BLUE);
  int y=0;
  tft.setCursor(0,y);
  tft.print("Temperatur S1   :");
  tft.setCursor(0,y+=10);
  tft.print("Temperatur S2   :");
  tft.setCursor(0,y+=10);
  tft.print("Luftfeuchtigkeit:");
  tft.setCursor(0,y+=10);
  tft.print("Barometer       :");
  tft.setCursor(0,y+=10);
  tft.print("Hoehe           :");
  tft.setCursor(0,y+=10);
  tft.print("Datum Uhrzeit   :");
}

void loop(){
  DateTime now=RTC.now();
  int8_t lDHT11 = dht.readHumidity();
  int16_t tDHT11 = dht.readTemperature(0);
//  float tDHT11 = dht.getTemperature();    // Auslesen der Luftfeuchtigkeit DHT11
//  float lDHT11 = dht.getHumidity(); // Auslesen der Temperatur DHT11
  float tBMP = bmp.readTemperature(); // Auslesen der Temperatur BMP085
  int32_t pBMP = bmp.readPressure(); // Auslesen des Drucks BMP085
  float aBMP = bmp.readAltitude(); // Auslesen der Hoehe BMP085
  
  //aktualisiere TFT
  displayTFT(tDHT11, lDHT11, tBMP, pBMP, aBMP, getTimestamp(now));
  writeToSD(tDHT11, lDHT11, tBMP, pBMP, aBMP, getTimestamp(now));
}

void displayTFT(int16_t tDHT11, int8_t lDHT11, float tBMP, int32_t pBMP, float aBMP, String timeStamp){
  int color = ST7735_WHITE;
  //DHT11Temp
  set_text(105,0,tDHT11,color,1);
  tft.write(247);tft.print("C");
  //DHT11Hum
  set_text(105,20,lDHT11,color,1);
  tft.print("%");
  //BMP085Temp
  set_text(105,10,tBMP,color,1);
  tft.write(247);tft.print("C");
  //BMP085Druck
  set_text(105,30,pBMP/100,color,1);
  tft.print("hPa");
  //BMP085Höhe
  set_text(105,40,aBMP,color,1);
  tft.print("m");
  //Datum UHrzeit
  set_text(0,60,timeStamp,color,1);
  
  //Delay und Reset
  delay(3000);
  color = ST7735_BLUE;
  set_text(105,0,tDHT11,color,1); 
  set_text(105,20,lDHT11,color,1);
  set_text(105,10,tBMP,color,1);
  set_text(105,30,pBMP/100,color,1);
  set_text(105,40,aBMP,color,1);
  set_text(0,60,timeStamp,color,1);
}
void set_text(int x,int y,String f,int color,int size){
  tft.setTextSize(size);
  tft.setCursor(x,y);
  tft.setTextColor(color);
  tft.print(f);
}
void set_text(int x,int y,float f,int color,int size){
  tft.setTextSize(size);
  tft.setCursor(x,y);
  tft.setTextColor(color);
  tft.print(f);
}
void set_text(int x,int y,int8_t f,int color,int size){
  tft.setTextSize(size);
  tft.setCursor(x,y);
  tft.setTextColor(color);
  tft.print(f);
}
void set_text(int x,int y,int16_t f,int color,int size){
  tft.setTextSize(size);
  tft.setCursor(x,y);
  tft.setTextColor(color);
  tft.print(f);
}
void set_text(int x,int y,int32_t f,int color,int size){
  tft.setTextSize(size);
  tft.setCursor(x,y);
  tft.setTextColor(color);
  tft.print(f);
}


void writeToSD(int16_t tDHT11, int8_t lDHT11, float tBMP, int32_t pBMP, float aBMP, String timeStamp){
  File myFile = SD.open(file, FILE_WRITE);

  // if the file opened okay, write to it:
  if (myFile) {
    String text = "Writing to " + String(file);
    Serial.print(text);
    myFile.print(timeStamp);
    myFile.print(";");
    myFile.print(tDHT11);
    myFile.print(";");
    myFile.print(lDHT11);
    myFile.print(";");
    myFile.print(tBMP);
    myFile.print(";");
    myFile.print(pBMP);
    myFile.print(";");
    myFile.print(aBMP);
    myFile.println();
    // close the file:
    myFile.close();
  } 
  else {
    // if the file didn't open, print an error:
//    Serial.println("error opening " + String(file));
  }
}

String getTimestamp(DateTime datetime){
  String stamp;
  if(datetime.day()<10)stamp += 0;
  stamp += datetime.day();
  stamp += ".";
  if(datetime.month()<10) stamp += 0;
  stamp += datetime.month();
  stamp += ".";
  stamp += datetime.year();
  stamp += " ";
  if(datetime.hour()<10)stamp +=0;
  stamp += datetime.hour();
  stamp += ":";
  if(datetime.minute()<10)stamp += 0;
  stamp += datetime.minute();
  stamp += ":";
  if(datetime.second()<10)stamp += 0;
  stamp += datetime.second();
  
  return stamp;
}

Hoi,
also mit INT anstelle von FLOAT kann man schon einiges sparen - steht irgendwo im "Handbuch"

Hab's noch nicht probiert, denn bei den Preisen für so ein Board kaufe ich eigentlich eh nur MEGAs :smiley:

INT statt FLOAT ist sicher eine Maßnahme.

Jedoch benötige ich 2 Nachkommastellen.

  void  FUNC_info(void)
  {
    LCDML.FuncInit();
     
    /* --------- LOOP ----------*/
    float readTemp;
    if(LCDML.Timer(g_timer_wait, 1000)) 
    {
      lcd.clear();
      lcd.setCursor(3,0);
      sprintf(lcdline,"%02d:%02d:%02d",hour(),minute(),second());
      lcd.print(lcdline);
      lcd.setCursor(0,1);
      lcd.print("T:");
      lcd.setCursor(2,1);
      readTemp=Temp;
      dtostrf(Temp,4,1,lcdline);
      lcd.print(lcdline);
      lcd.print("\337C");
      lcd.setCursor(10,1);
      lcd.print("L:");
      lcd.setCursor(12,1);
      readTemp=Temp2;
      dtostrf(Temp2,4,1,lcdline);
      lcd.print(lcdline);
      lcd.print("%");
    }
    
    /* --------- STOP  ---------*/
    
    if(LCDML.FuncEnd(0, 1, 0, 0, 0, 0)) 
    {
      
    }    
  }

@skorpi08

was für eine Variable ist "lcdline"?

  char lcdline[17];

Gibt an wieviel Zeilen das Display hat

Pace17881:
Was mache ich falsch? Kann ich noch irgendwie Speicher zurückgewinnen?

Deinen Speicher verbrätst Du im wesentlichen durch zwei eingebaute Dickschiffe, als da wären:

  • die Ansteuerung von Gigabyte-großen Massenspeichern mittels SD-Library
  • die Ansteuerung eines graphikfähigen Bildschirms mit einer Grafik-Library

Was da neben SD-Massenspeicher und Grafikmanipulation noch an Speicher einzusparen ist, ist vergleichsweise wenig.

Da läßt sich nur marginal etwas "zurückgewinnen", wenn Du auf beides nicht verzichten kannst.

Entweder benötigst Du einen größeren Controller mit mehr Speicher, z.B. einen MEGA statt eines UNO.

Oder Du lagerst die Ansteuerung eines der beiden Dickschiffe Massenspeicher und Grafik auf einen zweiten Controller aus, der dann mit dem Haupt-Controller kommuniziert. Das erfordert allerdings einen recht hohen Entwicklungsaufwand für die Programmierung effektiver Kommunikationsschnittstellen der Controller untereinander.

Wenn sich Deine Programmierkenntnisse darauf beschränken, jede Hardware ausschließlich mit fertigen Libraries anzusteuern, die andere geschrieben haben, und diese fremden Libraries bausteinartig zusammenzusetzen und mit ein wenig eigenem Code zu verbinden, bietet es sich an, auf den größeren Controller (MEGA) umzusteigen.

Pace17881:
INT statt FLOAT ist sicher eine Maßnahme.
Jedoch benötige ich 2 Nachkommastellen.

Die zwei Nachkommastellen wären nicht das Problem, aber deine bmp085 Library liefert schon float.
Zwei UNO sind sicher nicht so einfach wie ein MEGA 2560.

Der Code funktioniert soweit

Glückwunsch. D.h. sogar der RAM reicht ?!

EDIT:
Sehe gerade, dass dein Problem Flash und nicht RAM ist. Daher bringt PROGMEM erst mal nicht so viel. Habs mal wieder gelöscht, da das Problem alleine nicht löst.

Du verschwendest aber auch viel RAM durch die ganzen Strings und die Verwendung von String Objekten statt C Strings. Jedesmal wenn du da += machst wird im Hintergrund eines neues Objekt angelegt und das alte zerstört.
Das kann man optimieren, wenn man wo es geht die _P Versionen der C String Funktionen und das PSTR() Makro verwendet (damit die Strings nicht alle ins RAM kopiert werden), aber das löst nicht dein Flash Problem.

Der UNO ist für das alles einfach unterdimensioniert. Ich habe auf dem Mega mit TFT, DallasTemperature, SDFat und UTFT + Touch + Buttons locker 2kB RAM belegt. Und mit mehreren Fonts über 50kB Flash.

Hallo,

Du hast nicht geschrieben, welche IDE Du benutzt.
Ab IDE 1.. werden die Übersetzungen wesentlich größer als bei der IDE 0022.
Das macht 10-20 % aus.

Gruss Kalli

Es gibt im Forum eine kleine Funktion, die dir das freie RAM anzeigt:

int freeRam () {
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}

Damit kannst du deinen RAM-Verbrauch kontrollieren.

Hallo zusammen,

erst mal vielen Dank für die vielen Anregungen, die ich auch teilweise schon umgesetzt habe. Mittlerweile passt der Sketch sogar auf den Nano.

Hier ist der Sketch:

#include <TinyDHT.h>
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_ST7735.h>
#include <Adafruit_GFX.h>
#include <SD.h>
#include <Adafruit_BMP085.h>
#include <RTClib.h>

char values[6][18] = {{"Temperatur S1   :"},
                      {"Temperatur S2   :"},
                      {"Luftfeuchtigkeit:"},
                      {"Barometer       :"},
                      {"Hoehe           :"},
                      {"Datum Uhrzeit   :"}};

#define file "log1.txt"

//Display
//mosi=11=grün, sclk=13=blau
#define tft_cs   10 //rot
#define tft_dc   9 //schwarz
#define tft_rst  8 //gelb
Adafruit_ST7735 tft = Adafruit_ST7735(tft_cs, tft_dc, tft_rst);

//SD
#define sd_cs 7
//RTC
RTC_DS1307 RTC;
//Sensoren
//DTH11
#define DHTTYPE DHT11   // DHT 11 
//#define DHTTYPE DHT22   // DHT 22  (AM2302)
//#define DHTTYPE DHT21   // DHT 21 (AM2301)
//DHT dht;
#define dhtPin 2

DHT dht(dhtPin, DHTTYPE);

//BMP085
Adafruit_BMP085 bmp;

void setup(){
  //Serial.begin(9600);
    // Initialisiere RTC
  RTC.begin();
  // Prüfen ob RTC läuft  
  if (! RTC.isrunning()) {
    
    // Aktuelles Datum und Zeit setzen, falls die Uhr noch nicht läuft
    RTC.adjust(DateTime(__DATE__, __TIME__));
  }
  // DHT Sensor initialisieren
  dht.begin();
//  dht.setup(dhtPin);
  //BMP Sensor initialisieren
  bmp.begin();
  //SD Init
  pinMode(10, OUTPUT);
  if (!SD.begin(sd_cs)) {
//    Serial.println("initialization failed!");
    return;
  }
  //TFT
  tft.initR(INITR_BLACKTAB);   // ST7735-Chip initialisieren
  tft.setRotation(1);
  tft.fillScreen(ST7735_BLUE);
  //Bezeichnungen setzen
  for(int i=0;i<6;i++){
    tft.setCursor(0,i*10);
    for(int j=0;j<18;j++){
      tft.print(values[i][j]);
    }    
  }
}

void loop(){
  DateTime jetzt=RTC.now();
  int8_t lDHT11 = dht.readHumidity();
  int16_t tDHT11 = dht.readTemperature(0);
  float tBMP = bmp.readTemperature(); // Auslesen der Temperatur BMP085
  int32_t pBMP = bmp.readPressure(); // Auslesen des Drucks BMP085
  float aBMP = bmp.readAltitude(); // Auslesen der Hoehe BMP085
  
  //aktualisiere TFT
  char dt[18];
  sprintf(dt, "%02d.%02d.%d %02d:%02d:%02d", jetzt.day(), jetzt.month(), jetzt.year(), jetzt.hour(), jetzt.minute(), jetzt.second());
  displayTFT(tDHT11, lDHT11, tBMP, pBMP, aBMP, dt);
  writeToSD(tDHT11, lDHT11, tBMP, pBMP, aBMP, dt);
}

void displayTFT(int16_t tDHT11, int8_t lDHT11, float tBMP, int32_t pBMP, float aBMP, char* timeStamp){
  int color = ST7735_WHITE;
  //DHT11Temp
  set_text(105,0,tDHT11,color,1);
  tft.write(247);tft.print("C");
  //BMP085Temp
  set_text(105,10,tBMP,color,1);
  tft.write(247);tft.print("C");
  //DHT11Hum
  set_text(105,20,lDHT11,color,1);
  tft.print("%");
  //BMP085Druck
  set_text(105,30,pBMP/100,color,1);
  tft.print("hPa");
  //BMP085Höhe
  set_text(105,40,aBMP,color,1);
  tft.print("m");
  //Datum UHrzeit
  set_text(0,60,timeStamp,color,1);
  
  //Delay und Reset
  delay(3000);
  color = ST7735_BLUE;
  set_text(105,0,tDHT11,color,1); 
  set_text(105,20,lDHT11,color,1);
  set_text(105,10,tBMP,color,1);
  set_text(105,30,pBMP/100,color,1);
  set_text(105,40,aBMP,color,1);
  set_text(0,60,timeStamp,color,1);
}
void set_text(int x,int y,char* f,int color,int size){
  tft.setTextSize(size);
  tft.setCursor(x,y);
  tft.setTextColor(color);
  tft.print(f);
}
void set_text(int x,int y,float f,int color,int size){
  tft.setTextSize(size);
  tft.setCursor(x,y);
  tft.setTextColor(color);
  tft.print(f);
}
void set_text(int x,int y,int8_t f,int color,int size){
  tft.setTextSize(size);
  tft.setCursor(x,y);
  tft.setTextColor(color);
  tft.print(f);
}
void set_text(int x,int y,int16_t f,int color,int size){
  tft.setTextSize(size);
  tft.setCursor(x,y);
  tft.setTextColor(color);
  tft.print(f);
}
void set_text(int x,int y,int32_t f,int color,int size){
  tft.setTextSize(size);
  tft.setCursor(x,y);
  tft.setTextColor(color);
  tft.print(f);
}

void writeToSD(int16_t tDHT11, int8_t lDHT11, float tBMP, int32_t pBMP, float aBMP, char* timeStamp){
  File myFile = SD.open(file, FILE_WRITE);
  // if the file opened okay, write to it:
  if (myFile) {
    myFile.print(timeStamp);
    myFile.print(";");
    myFile.print(tDHT11);
    myFile.print(";");
    myFile.print(lDHT11);
    myFile.print(";");
    myFile.print(tBMP);
    myFile.print(";");
    myFile.print(pBMP);
    myFile.print(";");
    myFile.print(aBMP);
    myFile.println();
    // close the file:
    myFile.close();
  }
}

Im Grunde stehe ich noch am Anfang auf der Arduino und C Reise. Daher nehmt es mir bitte nicht übel wenn ich auf Libs anderer zurückgreife. So ganz möchte ich das Rad nicht neu erfinden. Dazu fehlt mir die Zeit dazu. Ich hab mir mal ein kleines Taschenbuch über C "C: Programmieren von Anfang an" bestellt. Damit hoffe ich noch besser mit C klar zu kommen.
Falls wer noch Lektüre empfehlen kann, bin ich schon jetzt dafür dankbar.

Nun zum Sketch. An welchen Stellen schaut er für euch noch nach Mist aus? Wo kann man noch optimieren?

Grüße
Sebastian

Nun zum Sketch. An welchen Stellen schaut er für euch noch nach Mist aus?

Bei der Frage "kann das denn funktionieren", ist mir aufgefallen, dass die eigentliche Hauptaufgabe von
displayTFT() das darin versteckte
** **delay(3000);** **
ist. :wink:

Nichts gegen delay() , wenn sonst nichts zu tun ist.
Aber so grundsätzliche Sachen in einer Funktion mit irreführendem Namen zu verstecken, ist "Mist".
( sorry, du hast gefragt, und ich erlaube mir mal, überspitzt zu fromulieren )

Die Uhr stellst du übrigens nie auf die aktelle Zeit, sondern höchstens auf die Zeit als der sketch das letzte Mal übersetzt und hochgeladen wurde.

Hast du Serial.begin(9600); auskommentiert, damit der Flash/Ram reicht ? Das wäre sehr schade :~

Hallo,

wofür ist die Funktion set_text mehrmals identisch im Sketch enthalten?

@michael_x
Wie würdest du es ändern ohne das der Speicherverbrauch steigt?

@Theseus
Zum überladen? Der Wert der aufs Display soll ist mal nen int8, int16, int32, float oder char*
Für ein und die selbe Aufgabe in der sich nur der anzuzeigende Typ ändert, hielt ich das für eine gute Lösung...
Muss man das anders machen?

Ich hatte die unterschiedlichen Typdeklarationen übersehen. Wäre es nicht sparsamer das tft.print aus der Unterfunktion herauszunehmen und direkt aufzurufen.

Ist das wirklich nötig? Normalerweise findet ein impliziter Cast statt wenn du z.B. einem uint32 einen uint8 übergibst. Eine Funktion die einen long als Parameter hat, sollte problemlos einen int oder ein byte schlucken. Lediglich float -> Integer geht nicht.

Hm.

Ich habe nun die Funktion mit Int8 und Int16 entfernt. Übrig bleibt Int32
Nun erhalte ich folgenden Compilerfehler

call of overloaded 'set_text(int, int, int16_t&, int&, int)' is ambiguous

Mit STRING einlesen, splitten in 2 x INT hilft Dir nicht ?
Vielleicht brauchst es nur zur Anzeige und nicht zum Rechnen...dann ginge das so.

Wie würdest du es ändern ohne das der Speicherverbrauch steigt?

Ob das delay(3000) innerhalb von displayTFT steckt, oder direkt in loop steht, damit man es sieht, sollte wohl am Speicherbedarf nichts ändern.
Ist halt die Frage, warum du dein dislpayTFT aus zwei Teilen ( vor und nach dem delay ) besteht.

Wenn der Flash-Speicher für Serial (oder weitere erforderliche Funktionen) nicht mehr reicht, würde ich SD.h durch was kleineres ersetzen.
Wenn du nur log-Dateien schreiben willst, und auch mit Fat16 auskommst, findest du Lösungen von Leuten die dieses Speicher-Problem auch schon hatten.

Was die anderen Libraries angeht, weiss ich nicht wie groß die sind und was du davon brauchst.
(Um eine RTC zu lesen brauchst du eigentlich kaum was, aber RTCLib ist auch nicht sehr groß... )