Keramik, Glas und andere Hochtemperaturanwendungen

Der chaotische Hauptfaden

Moderatoren: Heaterman, Finger, Sven, TDI, Marsupilami72, duese

Antworten
Benutzeravatar
Später Gast
Beiträge: 1699
Registriert: Di 5. Apr 2016, 22:03
Wohnort: Karlsruhe
Kontaktdaten:

Keramik, Glas und andere Hochtemperaturanwendungen

Beitrag von Später Gast »

N'Abend zusammen,

Hier kommen ja doch auch häufiger mal Ofen und Hochtemperaturthemen auf und da mich das Thema selbst brennend (SCNR :mrgreen: ) interessiert und auch Projekte in die Richtung immer wieder vorkommen mach ich mal nen Sammelfaden.

Konkret fertig hab ich grade ein dreikanaliges Thermometer, das von 0° bis 1000°C misst, und die Werte über serielle Schnittstelle oder SD-Karte loggen kann. Ist für den Pizzaofen. So sieht das aus:
thermomKL.jpg
Der Ofen hat schon eine dafür vorgesehene Mulde, ich brauche also nur noch ne Frontplatte dafür machen. Wie genau ich das löse bin ich mir noch nicht ganz sicher, weil das ja ganzjährig draußen ist, und ich die Ekeltronik gerne möglichst gut schützen möchte. An der Stelle wo das Thermometer nachher sitzt ist aber kein Platz, nen Regenschild o.ä. anzubringen. Muss ich noch n bisschen dran rumhirnen...
Auf den ersten drei 4er-Blöcken werden die drei Thermometer angezeigt, der vierte hat zur Wahl entweder die Uhrzeit, oder die Position eines Faders, mit dem ich behenfsmäßig den Rauchschieber mit in die Log-Datei reinschummle. muss halt dran denken, immer den Fader zu bewegen, wenn ich was am Rauchschieber mache. Der Fader ist grad noch nicht verbaut, der Code liest ihn aber schon ein.

Die Messwandler könnten wohl noch ne Spannungsstabilisierung brauchen, da ist etwas Rauschen, das hab ich mit ner Mittelwertberechnung rausgebügelt. Für den Pizzaofen gut genug.
Loggingintervall ist einstellbar zwischen 1 und 9999 Sekunden. Da müssen noch Edgecases abgefangen werden, und bis auf 9999 Sekunden in Einerschritten den Encoder hochzukurbeln macht auch sicher keinen Spaß. ;)
Kurze Bedienungsanleitung steht im Code.

Es braucht an Hardware:

1x Arduino Mega
1x RTC DS1307 (ich würde jetzt die ds3132 nehmen, weil da n über i2c beschreibbares EEPROM drauf ist, aber jetzt hab ich das verbaut. Für den ds1307 gibts aber ne DummyLib, die die Zeit dann aus Millis() oder so ableitet, die sollte auch genau genug sein)
1x µSD-Kartenleser mit Levelshifter für 5V Host
2x 8fach siebensegmentanzeigen an Max7219, kaskadiert
3x Max6675 für die Typ K Thermoelemente
3x Typ-K Thermoelement (d'uh)
1x Taster
1x Encoder

N Netzteil, das aus 12V Wackelsaft die 7V= für den Arduino macht gibts auch noch, aber das brauchts hier ja grad nicht.


und hier ist der Kot:

Code: Alles auswählen

/*-----------------------------------------------
 *  3-Kanaliges Thermometer mit Loggingfunktion
 * ----------------------------------------------
 * 
 * Getestet auf Arduino Mega pro mini
 * Braucht 3x Max6675 für die Typ_K Sensoren, eine DS1307 für die Uhrzeiten und einen SD-Slot mit Levelshifter für 5V->3,3v An SPI
 * SD-Karte mit bevorzugt FAT16 (32 soll auch gehen, ungetestet)
 * Außerdem einen Encoder und einen weiteren Eingabetaster für die Bedienung. 
 * Angezeigt wird über zwei in Serie gehängte 8x 7-Segment Anzeigen an Max7219
 * 
 * ---
 * 
 * Der Sketch geht davon aus, dass die RTC keine Batterie hat und verlangt daher nach jedem Powercycle ein Neueinstellen von Datum und Uhrzeit.
 * Dies geschieht über den Encoder, mit Druck auf den Encoder kommt man ins nächste Feld. 
 * Mit Button1 kommt man ein Feld zurück, wenn man aus dem ersten Feld Button1 drückt, kann man die Zeiteinstellung überspringen. 
 * Die Dezimalpunkte zeigen an, welches Feld gerade aktiv ist.
 * Die Reihenfolge ist: Tag - Monat - Jahr - Stunden - Minuten - Intervall
 * 
 * Mit Intervall lässt sich die Häufigkeit des Loggings einstellen, per Default ist sie auf 30s festgelegt. 
 * 
 * Um Das Logging zu aktivieren, muss man länger als 8 Sekunden Button1 gedrückt halten. währenddessen wird über die Dezimalpunkte im Display angezeigt, 
 * wie lange man den Knopf noch gedrückt halten muss. Bei aktiviertem Logging machen die Dezimalpunkte ein schnelles Lauflicht im entgegengesetzten Sinn.
 * Logging abschalten wie anschalten.
 * 
 * Das Loggingintervall (und auch die Zeit) kann auch im Betrieb geändert werden, dazu braucht es einen langen Druch auf den Encoder. (8Sekunden)
 * Dabei läuft ein langsames Lauflicht rückwärts. 
 * ->Änderung funktioniert nur bei abgeschaltetem Logging!
 * 
 */


//----------------------------------------------


#include "RTClib.h"
RTC_DS1307 rtc;

#include <Encoder.h>
Encoder myEnc(19, 18);
#include "max6675.h" 
#include <Adafruit_Sensor.h>
#include "LedControl.h"

//Sd-Karte
#include <SD.h>                 
File dataLog;
const int chipSelect = 53;        //Wg Mega, sonst 10
bool sd_ok = 0;

/*
 pin 12 is connected to the DataIn 
 pin 11 is connected to the CLK 
 pin 10 is connected to LOAD 
 We have two MAX7219.
 */
LedControl lc=LedControl(24,26,25,2);



long oldPosition  = 0;        //encoder Var

// Max6675_1
const byte thermo1D0 = 2; // so
const byte thermo1CS = 3; 
const byte thermo1CLK = 4; // sck
unsigned int temp1 = 0; 
MAX6675 thermocouple1(thermo1CLK, thermo1CS, thermo1D0); 


// Max6675_2
const byte thermo2D0 = 5; // so
const byte thermo2CS = 6; 
const byte thermo2CLK = 7; // sck
unsigned int temp2 = 0; 
MAX6675 thermocouple2(thermo2CLK, thermo2CS, thermo2D0); 


// Max6675_3
const byte thermo3D0 = 8; // so
const byte thermo3CS = 9; 
const byte thermo3CLK = 10; // sck
unsigned int temp3 = 0; 
MAX6675 thermocouple3(thermo3CLK, thermo3CS, thermo3D0);

//Fader
unsigned long       FadeWert=0;          //DummyWert für späteres Upgrade mit Feuchtigkeitssensor
int                 FadewAlt=0;

//IO 
const byte EButtonPin=22;           //Encoder Button
const byte Button1Pin=23;           //Button 1
const byte wasistderPin=A15;         //Fader-Pin für Erfassung der Rauchklappenposition

//Var
bool EncButton=true;
bool EncButtonpressed;
bool Button1=true;
bool Button1pressed=false;

unsigned long logTimer=0;         //für Logginintervallmessung
unsigned long DebTimer=0;         //für Button-Debounce
unsigned long PressTimer=0;       //für Lange Drücker
byte DpN=0;                       //für Anzeige von Dezimalpunkten (außer Datums- und Uhrzeiteinstellung)



int     Jahr=2020;        //Für Zeit einstellen/schreiben/lesen/loggen
byte    Monat=10;
byte    Tag=24;
byte    Stunde=10;
byte    Minute=0;
byte    Sekunde=0;


byte      EncTarget=1;           //Wohin zielt der Encoder, außerdem: wird gerade die Zeit eingestellt?
//bool      ZeitSetzen=true;       
bool      LoggingOn=false;       //wird gerade geloggt?

unsigned long       Intervall= 30;         //Logging intervall, (30 Sekunden) (wird mit 1000 multipliziert und braucht daher einen größeren Zahlenraum)
byte      kurzAlt=0;             //Hilfsvariable zur verkürzung des Intervalls Logintervall(30s) zu Mess- und Anzeigeintervall(1s)

bool      f4umschalt=false;      //displayfeld 4 umschalten zwischen Uhr und Feuchtigkeit(sensor noch nicht da)


//-------------------------
void setup(){
  Serial.begin(2000000);

  pinMode(Button1Pin, INPUT_PULLUP);
  /*
   The MAX72XX is in power-saving mode on startup,
   we have to do a wakeup call
   */
  lc.shutdown(0,false);      //Display 1, display 2, usw.
  lc.shutdown(1,false);
  /* Set the brightness to a medium values */
  lc.setIntensity(0,1);
  lc.setIntensity(1,1);
  /* and clear the display */
  lc.clearDisplay(0);
  lc.clearDisplay(1);

//---RTC---
  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    Serial.flush();
    abort();
  }

  if (! rtc.isrunning()) {
    Serial.println("RTC is NOT running, let's set the time!");
    // When time needs to be set on a new device, or after a power loss, the
    // following line sets the RTC to the date & time this sketch was compiled
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // This line sets the RTC with an explicit date & time, for example to set
    // January 21, 2014 at 3am you would call:
    // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
    // rtc.adjust(DateTime(Jahr, Monat, Tag, Stunden, Minuten, 0));
  }


// für DS logging: open serial communications and wait for port to open:
  Serial.print("Initialisiere SD Karte...");
 
  // initialize the SD card
  if ( !SD.begin(53) )
    Serial.println("Initialisierung fehlgeschlagen!");  // initialization error
 
    else{   // initialization OK
    sd_ok = 1;
    Serial.println("Initialisierung erledigt.");
    //if( SD.exists("Log.txt") == 0 ){               // test if file with name 'Log.txt' already exists
        // create a text file named 'Log.txt'
      Serial.print("\r\nerzeuge 'Log.txt' Datei ... ");
      dataLog = SD.open("Log.txt", FILE_WRITE);   // create (&open) file Log.txt
      if(dataLog) {                               // if the file opened okay, write to it:
        Serial.println("OK");
        // write some texts to 'Log.txt' file
        
        dataLog.println("    Datum    | Uhrzeit     | TEMP1  | TEMP2  | TEMP3 | KLAPPE    ");
        dataLog.println("(dd-mm-yyyy)|(hh:mm:ss)|");
        
        
        dataLog.close();   // close the file
      }
      /*else
        Serial.println("error creating file.");
       }*/
  }
  
  Serial.println("\r\n    Datum   | Uhrzeit  | TEMP1  | TEMP2  | TEMP3  | KLAPPE  ");
  Serial.println("(dd-mm-yyyy)|(hh:mm:ss)|");
  callRTC();                                                                                  //für weniger Einstellaufwand beim testen

if(!sd_ok){
            noSDWarnung();                                                                                           //Display Errormessage
          }

//Schnitt initialisieren

  temp1 = 8*thermocouple1.readCelsius() ;              //einlesen und multiplizieren, Ausgangsposition für schnittbildung
  temp2 = 8*thermocouple2.readCelsius();
  temp3 = 8*thermocouple3.readCelsius();
  FadeWert= 8*analogRead(wasistderPin);
          
}
// --- ende setup ---

void noSDWarnung(){
  //Warnung wenn keine SD-Karte da ist
for (int i = 0; i <= 8; i++) {
    write4ValTo7Segment( 8888,  8888, 8888, 8888, false, false, false, false, false, false, false, false);
    delay(125);
    
    lc.clearDisplay(0);         //nichts anzeigen
    lc.clearDisplay(1);
    delay(125);
    
    lc.setChar(0,0,'-',false);    //Max7219(0), Bindestriche anzeigen
    lc.setChar(0,1,'-',false);
    lc.setChar(0,2,'-',false);
    lc.setChar(0,3,'-',false);
    lc.setChar(0,4,'-',false);
    lc.setChar(0,5,'-',false);
    lc.setChar(0,6,'-',false);
    lc.setChar(0,7,'-',false);

    lc.setChar(1,0,'-',false);    //Max7219(1)
    lc.setChar(1,1,'-',false);
    lc.setChar(1,2,'-',false);
    lc.setChar(1,3,'-',false);
    lc.setChar(1,4,'-',false);
    lc.setChar(1,5,'-',false);
    lc.setChar(1,6,'-',false);
    lc.setChar(1,7,'-',false);
    delay(125);

    lc.clearDisplay(0);         //nichts anzeigen
    lc.clearDisplay(1);
    delay(125);
       }
  
}

void logSD(){
  char buffer1[34], buffer2[26];
       sprintf(buffer1, "%04u°C | %04u°C | %04u°C | %04umm ", temp1/8, temp2/8, temp3/8, FadewAlt/8);
               
       sprintf( buffer2, " %02u-%02u-%04u | %02u:%02u:%02u | ", Tag, Monat, Jahr,
                      Stunde, Minute, Sekunde);
                      
    // print data on PC serial monitor
          Serial.print(buffer2);
          Serial.println(buffer1);
    
    
    // write data to SD card
    if(sd_ok){
      // if the SD card was successfully initialized
      // open Log.txt file with write permission
      
      dataLog = SD.open("Log.txt", FILE_WRITE);
           
      dataLog.print( buffer2 );                   //Zeit
      dataLog.println( buffer1 );                 //Temps
      dataLog.close();   // close the file        */
      }
}

void Enc(){
  
  int newPosition = myEnc.read()/4 ;      //Liest pro Rastung 4 neue Werte ein, daher /4
  if (newPosition != oldPosition) {
      int AdVal=(oldPosition-newPosition);//Serial.println(AdVal);
      oldPosition=newPosition;    
      

        switch (EncTarget) {                          //Was soll mit dem Encoder gerade eingestellt werden?
          case 1:
            Tag=Tag+AdVal;                            //Tage einstellen
            Tag=constrain(Tag, 1,31);
              break;
              
          case 2:
            Monat=Monat+AdVal;                          //Monate einstellen
            Monat=constrain(Monat, 1,12);
              break;
              
          case 3:
            Jahr=Jahr+AdVal;                            //Jahre einstellen
            Jahr=constrain(Jahr, 2020, 3000);
              break;
              
          case 4:
            Stunde=Stunde+AdVal;                        //Stunde einstellen
            Stunde=constrain(Stunde, 0,23);             //compiler meckert, weil byte eh nie kleiner 0 wird. EGAL!
              break;
              
          case 5:
            Minute=Minute+AdVal;                        //Minute einstellen
            Minute=constrain(Minute, 0,59);             //compiler meckert, weil byte eh nie kleiner 0 wird. EGAL!
              break;
              
          case 6:
            if(Intervall<10){                             //Loggingintervall einstellen
            Intervall=Intervall+AdVal;
            goto escape;
            } 
                            
            if(Intervall>9 &&Intervall<60){                             //von 10-60 +- 10 Sekunden
            Intervall=Intervall+AdVal*10;
            goto escape;
            }
            
            if(Intervall>59 &&Intervall<9999){                             //von 60-3600 +- 60 Sekunden
            Intervall=Intervall+AdVal*60;
            goto escape;
            }

            escape:                        
            Intervall=constrain(Intervall, 1, 9960);
            break;    
         }
      }
}

void ButtonLogic(){
  //einlesen
  EncButton=digitalRead(EButtonPin);
  Button1=digitalRead(Button1Pin);

  //verlogeln

  
  if(EncTarget){                                        //on Startup, da (noch/dauerhaft?) keine Batterie
    if(!EncButton&&millis()-DebTimer>350){
      EncTarget++;
      if(EncTarget>6){   
        rtc.adjust(DateTime(Jahr, Monat, Tag, Stunde, Minute, 0));
        // Loggingintervall aus SD-Karte schreiben
          dataLog = SD.open("Log.txt", FILE_WRITE);   // create (&open) file Log.txt
          dataLog.print("Geloggt wird alle ");
          dataLog.print(Intervall);
          dataLog.println(" Sekunden, aktuelles Datum: ");
          dataLog.close();   // close the file
        // Loggingintervall über Serielle Schnittstelle bekanntgeben
          Serial.print("Geloggt wird alle ");
          Serial.print(Intervall);
          Serial.println(" Sekunden, aktuelles Datum: "); 
          logSD();                                                            //um das aktuelle Datum zu schreiben, ein einziges mal Log() ausführen.         
        EncTarget=0;                      //Einstellen fertig
          }
      DebTimer=millis();    
    }
    if(!Button1&& millis()-DebTimer>500 && EncTarget>0){                      //träger Taster, braucht Zeit zum Drücken, deswegen 500ms
      EncTarget--;
      DebTimer=millis();                             
      }
    }
  if(!EncTarget){                                                             //Umschalten zw Uhrzeit- u. Faderanzeuge in Feld 4
    if(!EncButton&&millis()-DebTimer>350){
      f4umschalt=!f4umschalt;
      DebTimer=millis();
    }
    
//---
//Langer Druck Button1 -> Logging an- und aussschalten

    if(!Button1&&!Button1pressed){   //wird gedrückt, timer setzen
      Button1pressed=true;
      PressTimer=millis();      //8s halten bis Beginn/Ende
      }
    if(!Button1&&Button1pressed&&  millis()-PressTimer<8000){    //wird gehalten, DpN visualisieren  
      DpN=(millis()-PressTimer)/1000+1;                   //Visualisierung Restzeit bis Loggingbeginn über Dezimalstellen
      }
    if(Button1&& Button1pressed&& millis()-PressTimer>7999){   //wird losgelassen, Aufgehts!
      LoggingOn=!LoggingOn;                               
      if(LoggingOn){
        Serial.println(" ");                              //Formatierung-Leerzeile, unten weiter im Einsatz
        Serial.println("Beginne Logging ...");
        if(!sd_ok){
          Serial.println("Warnung: SD-Karte fehlt/nicht erkannt!");
          noSDWarnung();                                  //
          }
        dataLog = SD.open("Log.txt", FILE_WRITE);         // create (&open) file Log.txt
          dataLog.println(" ");                           
          dataLog.println("Beginne Logging ...");
          dataLog.close();   // close the file
          }
      if(!LoggingOn){
        Serial.println("... Logging beendet.");
        Serial.println(" ");
        dataLog = SD.open("Log.txt", FILE_WRITE);         // create (&open) file Log.txt
          dataLog.println("... Logging beendet.");
          dataLog.println(" ");
          dataLog.close();   // close the file
          }
      }
    if(Button1&&Button1pressed){
      Button1pressed=false;
      DpN=0;
      }

//----Lange Drücke auf EncButton -------------------
if(!LoggingOn&&!EncButton&&!EncButtonpressed&&!EncTarget){    //wird gedrückt, timer setzen, Logging muss vorher deaktiviert werden
      EncButtonpressed=true;
      PressTimer=millis();                               //8s halten bis Beginn/Ende
      }
    if(!EncButton&&EncButtonpressed&&millis()-PressTimer<8000){    //wird gehalten, DpN visualisieren  
      DpN=7-(millis()-PressTimer)/1000+1;                     //Visualisierung Restzeit bis Loggingbeginn über Dezimalpunkte
      }
    if(EncButton&& EncButtonpressed&& millis()-PressTimer>7999){   //wird losgelassen, Aufgehts!
      EncTarget=6;                               
      }
    if(EncButton&&EncButtonpressed){
      EncButtonpressed=false;
      DpN=0;
      }  
//---------------------------------------      
  }
}

void callRTC() {
    DateTime now = rtc.now();
Tag=(now.day());
Monat=(now.month());
Jahr=(now.year());    
Stunde=(now.hour());
Minute=(now.minute());
Sekunde=(now.second());

}

void readTemps(){
  byte Faktor=7;                                      //schnitt vs aktuell
  int t1neu=8*thermocouple1.readCelsius();               //einlesen
  int t2neu=8*thermocouple2.readCelsius();
  int t3neu=8*thermocouple3.readCelsius();
  
            
  temp1 = ((temp1*Faktor)+t1neu) /(Faktor+1) ;          //Schnitt bilden  
  temp2 = ((temp2*Faktor)+t2neu) /(Faktor+1) ;          //Schnitt bilden
  temp3 = ((temp3*Faktor)+t3neu) /(Faktor+1) ;          //Schnitt bilden
  
}
  
void write4ValTo7Segment( int n1,  int n2, int n3, int n4, bool b1, bool b2, bool b3, bool b4, bool b5, bool b6, bool b7, bool b8){ 

// Für 2 gechainte max7219 mit 2x8 Siebensegment Anzeigen
int n;
  n1 = abs(n1);
  n2 = abs(n2);
  n3 = abs(n3);
  n4 = abs(n4);
/*bool b;
  Dezimalpunkte sind paarig zusammengeschaltet */

//max7219_0

  n=n2;
  lc.setDigit(0, 0, n%10, b4);  n/=10;        //!Reihenfolge beachten n=n# vertauscht, damit Displays von links nach rechts angeordnet sind, dito b#
  lc.setDigit(0, 1, n%10, b4);  n/=10;
  lc.setDigit(0, 2, n%10, b3);  n/=10; 
  lc.setDigit(0, 3, n%10, b3);  
  n=n1;
  lc.setDigit(0, 4, n%10, b2);  n/=10;
  lc.setDigit(0, 5, n%10, b2);  n/=10;
  lc.setDigit(0, 6, n%10, b1);  n/=10; 
  lc.setDigit(0, 7, n%10, b1);  

//max7219_1

  n=n4;
  lc.setDigit(1, 0, n%10, b8);  n/=10;
  lc.setDigit(1, 1, n%10, b8);  n/=10;
  lc.setDigit(1, 2, n%10, b7);  n/=10; 
  lc.setDigit(1, 3, n%10, b7);  
  n=n3;
  lc.setDigit(1, 4, n%10, b6);  n/=10;
  lc.setDigit(1, 5, n%10, b6);  n/=10;
  lc.setDigit(1, 6, n%10, b5);  n/=10; 
  lc.setDigit(1, 7, n%10, b5); 

}

//-----loop------
void loop(){ 

//Wird dauernd Gebraucht:    
    Enc();                  //Encoder einlesen
    ButtonLogic();          //Knöpfe einlesen und Programmablauf 
    int RhTumbN=analogRead(wasistderPin);               //Fader einlesen
    RhTumbN=8*RhTumbN;
    FadeWert= ((FadeWert*13)+RhTumbN)/14 ;      //Fader Schnitt bilden*/
    if((abs(FadeWert-FadewAlt))>40){            //nur Änderungen >4 anzeigen, aber werte sind verachtfacht um trunkierung beim Schnittbilden zu entschärfen
       FadewAlt=FadeWert;
        }
    
//zu Beginn ist Zeit noch nicht gesetzt, Einzustellendes Datum und Intervall werden angezeigt    
if(EncTarget){

//                    Zahlen1       Zahlen2      Zahlen3        Zahlen4      Punkt1(Tage)       Punkt2(Monat)   Punkt3(Jahr)      Punkt4(Jahr)     Punkt5(Stunde)   Punkt6(Minute)      Punkt7 + Punkt8(Intervall)
             
    write4ValTo7Segment((100*Tag+Monat),Jahr, (Stunde*100+Minute), Intervall, !(-1+EncTarget), !(-2+EncTarget), !(-3+EncTarget), !(-3+EncTarget), !(-4+EncTarget), !(-5+EncTarget),    !(-6+EncTarget), !(-6+EncTarget) );
        }
    
//nur wenn Zeit schon gesetzt ist, Temperaturen und Zeiten werden eingelesen und angezeigt, wenn erwünscht wird geloggt
if(!EncTarget){             
 //Logging ist an         
       if(LoggingOn){
           if(millis()-logTimer>Intervall*1000){
                logSD();                                      //Logzeilen raus nach SD & serial
                logTimer=millis();
                }
           if(millis()-PressTimer >100&&!Button1pressed){     //Signalisierung: Logging läuft! PressTimer wird recycelt
                DpN++;
                if(DpN>8){DpN=1;}                             //Lauflicht-Reset
                PressTimer=millis();    
                }
           byte kurzNeu=(millis()-logTimer)/1000;             //Temperaturen&Zeit sollen häufiger eingelesen und angezeigt werden, als Loggingintervall (1x/Sekunde)
           if (kurzNeu!=kurzAlt){                             //falls (millis()-logTimer)/1000 zu groß für byte ist, läuft byte halt über ist egal, weil nur die Veränderung wichtig ist für erfassung von Wechseln 
               readTemps();
               callRTC();
               kurzAlt=kurzNeu;
               }
            }

//Logging ist aus, trotzdem Zeit und Temperaturen messen            
       if(!LoggingOn && millis()-logTimer>1000){
        
           readTemps();
           callRTC();
           logTimer=millis();                          //es wird nicht geloggt, LogTimer kann also wiederverwendet werden, 1x /Sekunde
           }




  //Display beschicken
      
          int feld4;
          if (!f4umschalt){
            feld4=Stunde*100+Minute;  //Zeitanzeige
            }
          if (f4umschalt){
            feld4=FadewAlt/8;                                  //für Drosselklappenstgellung FeuchteSensor
            }
   write4ValTo7Segment(temp1/8,temp2/8, temp3/8,(feld4), !(-8+DpN), !(-7+DpN), !(-6+DpN), !(-5+DpN), !(-4+DpN), !(-3+DpN), !(-2+DpN), !(-1+DpN));   
        }

}
Die Logdatei sieht dann so aus:

Code: Alles auswählen

DATE    | TIME     | TEMP1  | TEMP2  | TEMP3  
(dd-mm-yyyy)|(hh:mm:ss)|
Geloggt wird alle30 Sekunden
 17-10-2020 | 14:14:19 | 0023°C | 0047°C | 0043°C | 9999X
 17-10-2020 | 14:14:49 | 0023°C | 0047°C | 0043°C | 9999X
 17-10-2020 | 14:15:19 | 0023°C | 0047°C | 0043°C | 9999X
 17-10-2020 | 14:15:49 | 0023°C | 0047°C | 0042°C | 9999X
 17-10-2020 | 14:16:19 | 0023°C | 0047°C | 0043°C | 9999X
 17-10-2020 | 14:16:49 | 0023°C | 0047°C | 0043°C | 9999X
 17-10-2020 | 14:17:19 | 0023°C | 0047°C | 0043°C | 9999X


Als nächstes kommt ne Ofensteuerung für Keramikbrände, hier wurde ja schon mal an dem Thema gearbeitet und über PID-Regelung nachgedacht. Hab heute mit RMK drüber PMs geschrieben und er ist auch schon Feuer und Flamme. :-P
Ich glaub PID würd ich der Einfachheit erstmal weglassen und fix mit ner Hysterese von 14°K arbeiten, bei 7 unter Soll ist Heizungsbeginn, bei 7 drüber schaltet er wieder aus und von vorne. Dann kann ich mir erstmal Gedanken über die Heizkurvenberechnung machen, und wenn PID dann noch relevante Vorteile verspricht, (weniger Schaltspiele, größere Genauigkeit) kann man sich das ja immernoch "antun". ;)
Problem beim Brennen von Keramiken ist ja, dass man bis zum Quarzsprung langsam (~85°K/h) heizen muss, weil einem das Zeug sonst futsch geht.

Für Keramikbrände möchte man eigentlich auch nochmal n besseres Thermoelement, ich glaub die heißen Typ-S, ich fürchte, das braucht dann auch andere Messwandler, der max6675 kann nur Typ-K.

Grüße
Moritz

edit: paar kleine Codeverbesserungen
Zuletzt geändert von Später Gast am Mo 15. Feb 2021, 16:41, insgesamt 1-mal geändert.
Benutzeravatar
Später Gast
Beiträge: 1699
Registriert: Di 5. Apr 2016, 22:03
Wohnort: Karlsruhe
Kontaktdaten:

Re: Keramik, Glas und andere Hochtemperaturanwendungen

Beitrag von Später Gast »

Okay, ich hab mal n bisschen was zusammengeschrieben, Bedienelemente, Display und Thermosensoren fehlen noch und getestet isses natürlich auch noch nicht, aber so stell ich mir den Programmablauf vor...

Variablen und Eingänge

Code: Alles auswählen

const byte relaisPin=9;                     // Ausgang für Relais

int eTemps[] = {575, 960, 0, 0, 0};         // Endtemperaturen in °C
byte hSpeed[] = {85, 255, 0, 0, 0};         // Heizgeschwindigkeiten in delta K/h
byte holdT[] = {10, 20, 0, 0, 0};             // Haltezeiten in Minuten

byte aStufe = 0;                            	  // Welche aktuelle Heizstufe haben wir? 

unsigned long brennbeginn=0;                // Wann hat der Brand angefangen
unsigned long schedTimer=0;                 // Wann wird was aufgerufen?
unsigned long holdTimer=0;                  // wird gerade gehalten und wie langen noch (dual use als Bool)


int tIst=20;                                //aktuelle Temperatur
int tSoll=0;                                //sollTemperatur
int tAlt=0;                                 //Temperatur zum Beginn der aktuellen Heizphase, für Berechnung der Heizrampe

bool Button1State=0;
bool burnRuns=0;
Für die einzelnen Heizsegmente gibt es 3 Arrays à je 5 Werte, also 5 mögliche Programmschritte. Für hSpeed kann man womöglich auch int nehmen, muss man halt erstmal rausfinden, wie schnell der Ofen überhaupt theoretisch aufheizen kann, außerdem, ob es Sinn macht, bei Aufheizgeschwindigkeiten oberhalb von 255°K/h noch zu begrenzen, oder ob man da nicht auch genausogut Vollgas geben kann. Für Keramik macht es imo keinen Sinn, daher erstmal byte.
Im setup steht noch nichts., hier der Loop:

Code: Alles auswählen

void loop() {

//Startpunkt setzen
if(!burnRuns && Button1State){
  brennbeginn=millis();
  tAlt= tIst;                                                           //
  burnRuns=1; 
  }

if(millis()-schedTimer>0&&burnRuns){                                    //1x/sekunde, Scheduling
  schedTimer=millis()+1000;
  readTemps();
  heizRampe();
  heizen();       
  }//ende scheduler
  
}

Es gibt einen fiktiven Button1, der den Brand startet, beim Starten wird der Brennbeginn festgelegt und die Ausgangstemperatur tAlt, die wir für die Rampe brauchen. Ab da wird ein Mal in der Sekunde nach dem Rechten geschaut. In readTemps() steht noch nichts großartiges drin, heizen() macht die Hysterese:

Code: Alles auswählen

void heizen()
{
  //Heizung mit Hysterese ein- und ausschalten    
    if (tIst<tSoll-7){
        digitalWrite(relaisPin,1);
         }
    if (tIst>tSoll+7){
        digitalWrite(relaisPin,0);
         }    
}

Und des Pudels Kern versteckt sich in heizRampe():

Code: Alles auswählen

void heizRampe(){
   //Endtemperatur aktueller Stufe ist noch nicht erreicht, Rampe berechnen,  Heizen:
   //   muss nur bechechnet werden wenn Rampe erwünscht (hSpeed[aStufe]!=0) und gerade nicht gehalten wird
  
  if(!holdTimer && hSpeed[aStufe]){
      if(tIst<tSoll){
        tSoll=tAlt+ (millis()-brennbeginn)/3600000*hSpeed[aStufe];         //   1°K/h  =  1°K/3600 000mS, für heizvorgänge
          }
      if(tIst>tSoll){
        tSoll=tAlt- (millis()-brennbeginn)/3600000*hSpeed[aStufe];         //   für Abkühlvorgänge
          }    
    }

  if(!hSpeed[aStufe]){                                                     // Wenn es keine Rampe gibt, kann Vollgas geheizt werden
    tSoll=eTemps[aStufe];  
  }


// --- tIst ist nahe genug (+-10) an eTemps[aStufe], daher wird jetzt auf Halten umgeschaltet          
  if(tIst==eTemps[aStufe] || (tIst+10>eTemps[aStufe]&&tIst<eTemps[aStufe]) || (tIst-10<eTemps[aStufe]&&tIst>eTemps[aStufe]) ){
    
        if(!holdTimer){                         //wenn holdTimer 0 ist, setzen.
                                  
           holdTimer=millis();
           if(holdTimer==0){
                holdTimer++;                                    //    holdTimer sollte nicht 0 sein, selbst wenn millis() 0 ist
                }
           tSoll=eTemps[aStufe];
           }
    }
  if(holdTimer){
     if((millis()-holdTimer)/60000 > holdT[aStufe]){
              holdTimer=0;
              aStufe++;                                   //einen Schritt weiter
              tAlt=tIst;                                  //neue Starttemperatur
              }        
    }

 if( !eTemps[aStufe] && hSpeed[aStufe] && !holdT[aStufe]){
    aStufe=5;
    } 
    
  if(aStufe>4){                                         //Brand ist durchgelaufen, abschalten!
   burnRuns=0;
   digitalWrite(relaisPin,0);                          //sicher ist sicher, Heizung ausschalten
   }
}
Zu Beginn eines jeden Heizsegments ist holdTimer==0, eine Rampe muss nur berechnet werden, wenn ich sie auch haben will.
Dann wird erstmal geschaut ob geheizt oder abgekühlt wird. Mit (millis()-brennbeginn)/3600000*hSpeed[aStufe] schauen wir, wieviele Stunden schon vergangen sind und multiplizieren das mit °K/Stunden. Stunden kürzen sich raus, Kelvin bleiben über, die werden auf tSoll addiert.
Wenn hSpeed[aStufe]==0 ist, brauchen wir keine Rampe und tSoll wird direkt auf den Endwert gesetzt.
Wenn wir bis auf 10°K an die aktuelle endTemperatur rangekommen sind wird auf "Halten" umgeschaltet. Die if Bedingung ist etwas undurchsichtig, weil man sich der Solltemperatur ja von oben und von unten annähern kann und deshalb ein simples < bzw > nicht ausreicht. Wenn die Zieltemperatur in etwa erreicht ist, wird der holdTimer gesetzt, damit beginnt die Haltezeit, (millis()-holdTimer)/60000 gibt Minuten seit deren Beginn aus. Sobald die erwünschte Dauer vorbei ist, wird der Timer wieder auf 0 gesetzt, außerdem geht das Heizprogramm einen Schritt weiter und damit die nächste Rampe auch mit der richtigen Temperatur rechnet, wird auch tAlt neu gesetzt, gleiches gilt für brennbeginn (Variablenname ist ab da nicht mehr ganz zutreffend). Wenn alle 3 Werte der aktuellen Heizstufe 0 sind, nimmt das Programm an, dass es fertig ist und springt auf Stufe 5, von dort wird es dann abgeschaltet.

Ich würde die drei Arrays gerne in mehrfacher Ausführung in einem EEPROM ablegen, damit man mehrere Programme direkt ohne Kurbelorgien zur Verfügung hat und nach Bedarf anpassen kann. Hab nur im Moment noch nicht wirklich verstanden, wie ich den at24c32 da richtig einsetze. Das kommt der gefürchteten Fußpilzebene gefährlich nah und ich muss tatsächlich mal was wirklich verstehen. :?

Grüße
ando
Beiträge: 2643
Registriert: Sa 31. Aug 2013, 23:30

Re: Keramik, Glas und andere Hochtemperaturanwendungen

Beitrag von ando »

Moin,

zum Thema Schutz der Elektronik durch Wettereinflüsse, schlage ich dir "Wet-Protect" Spray vor.

Das zieht ne Wasserabweisende Schutzschiht über die Elektronik. Nehm ich , wenn ich irgendwelche Funk Aktoren im Aussenbereich anbringe.

Ando
Benutzeravatar
Später Gast
Beiträge: 1699
Registriert: Di 5. Apr 2016, 22:03
Wohnort: Karlsruhe
Kontaktdaten:

Re: Keramik, Glas und andere Hochtemperaturanwendungen

Beitrag von Später Gast »

ando hat geschrieben: Sa 14. Nov 2020, 21:50 Moin,

zum Thema Schutz der Elektronik durch Wettereinflüsse, schlage ich dir "Wet-Protect" Spray vor.

Das zieht ne Wasserabweisende Schutzschiht über die Elektronik. Nehm ich , wenn ich irgendwelche Funk Aktoren im Aussenbereich anbringe.

Ando
Thx, ist das ölig, oder wie funzt das?

Ich hab derweil mal nach der Sensorik gekuckt. Zum Thermoelement einlesen wirds ein Max31856 werden. Der ist genauer (eigtl egal) und kann sowohl K- als auch S-Typen. Dann brauchts ein Typ-S Thermoelement. Da man ja an der Isolierung vorbei muss, sollte das nicht zu kurz sein. Ich probiers mal mit 15cm. Zum Schalten Relais.

Man könnte sich überlegen, mal in seinem Ofen, den man mit Steuerung ausstatten will nachzuschauen, wie die Heizspiralen/Elemente verschaltet sind. Vielleicht hat man Glück und es sind mehrere Parallel. Dann könnte man sie aufteilen (weniger Strom pro Relais) und zur besseren Dosierung der abgegebenen Heizleistung abwechselnd schalten.
j.o.e
Beiträge: 550
Registriert: Fr 29. Nov 2019, 01:15

Re: Keramik, Glas und andere Hochtemperaturanwendungen

Beitrag von j.o.e »

Später Gast hat geschrieben: Fr 13. Nov 2020, 19:50 sprintf(buffer1, "%04u°C | %04u°C | %04u°C | %04umm ", temp1/8, temp2/8, temp3/8, FadewAlt/8);
Serial.println(buffer1);
Ich sehe, du verwendest sprintf(), und vermeidest Arduinos String-Funktionen um buffer1 aufzubauen. Kannst (willst) Du zum Background deiner Vorgehensweise ein paar fundierte Worte sagen? Dass mit sprintf() der Code schöner und auch die Formatierung besser ausfällt, ist schon klar. Versuche hab ich noch keine gefahren, aber ich meine, der footprint im RAM müsste damit auch schmaler ausfallen.

Z.B. http://hacking.majenko.co.uk/the-evils- ... no-strings meint ja: "String is evil and should be avoided at all costs" - und begründet das sogar.

Edith meinte, dieser Link würde auch was taugen: https://hackingmajenkoblog.wordpress.co ... o-strings/
Benutzeravatar
Später Gast
Beiträge: 1699
Registriert: Di 5. Apr 2016, 22:03
Wohnort: Karlsruhe
Kontaktdaten:

Re: Keramik, Glas und andere Hochtemperaturanwendungen

Beitrag von Später Gast »

j.o.e hat geschrieben: Mo 15. Feb 2021, 22:52 ...
Ich sehe, du verwendest sprintf(), und vermeidest Arduinos String-Funktionen um buffer1 aufzubauen. Kannst (willst) Du zum Background deiner Vorgehensweise ein paar fundierte Worte sagen? Dass mit sprintf() der Code schöner und auch die Formatierung besser ausfällt, ist schon klar. Versuche hab ich noch keine gefahren, aber ich meine, der footprint im RAM müsste damit auch schmaler ausfallen.
... vermeidest string-Funktion, fundierte Worte...
Äh ... hüstel :oops:

Also wenn ich ehrlich bin, mit Arduino Strings kann ich nicht umgehen, ich habe die sprintf() Funktion aus nem Logger von Jemand anders rausgeklaut und für meine Zwecke angepasst.
Dort wurde das Wie und Warum auch nicht näher ausgeführt, ich war dabei einen Logger zu bauen und hab was gefunden, was eines der Probleme die ich hatte elegant löst. Wie die Syntax funktioniert hab ich dann mit Google und Try&Error rausgeknobelt. Dabei hab ich dann rausgefunden, dass das in der offiziellen Arduino Doku nicht drin ist.
Ich vermute meine Quelle hat den Code auch nur irgendwo herkopiert, er schreibt dazu nix, erklärt aber den Rest.

Auf den Majenko bin ich auch schon gestoßen, als ich versucht habe rauszufinden, wieviele Byte pro Character auf die sd-Karte geschrieben werden. (Habs nicht rausgefunden :P )

Viel fundierter wirds bei meinem Halbwissen leider nicht, bin aber immer daran interressiert noch mehr Halbwissen anzusammeln. ;)
Benutzeravatar
Fritzler
Beiträge: 12602
Registriert: So 11. Aug 2013, 19:42
Wohnort: D:/Berlin/Adlershof/Technologiepark
Kontaktdaten:

Re: Keramik, Glas und andere Hochtemperaturanwendungen

Beitrag von Fritzler »

Nehmt bitte snprintf.
Sonst lauert der Bufferoverflow hinter jeder Ecke :twisted:

Das begrenzt wieviele Zeichen printf in den Buffer schreibt.
j.o.e
Beiträge: 550
Registriert: Fr 29. Nov 2019, 01:15

Re: Keramik, Glas und andere Hochtemperaturanwendungen

Beitrag von j.o.e »

Fritzler hat geschrieben: Di 16. Feb 2021, 20:07 Nehmt bitte snprintf.
Sonst lauert der Bufferoverflow hinter jeder Ecke :twisted:

Das begrenzt wieviele Zeichen printf in den Buffer schreibt.
Hast recht, die 'n'-Funktionen verbessern die Sicherheit erheblich. Das gilt auch für strncpy(), strncat() und Konsorten.
Abklären wie groß der Puffer sein soll, muss man ja sowieso. Schließlich muss man den ja bereit stellen.

Zu sprintf/snprintf gibts noch noch was zu sagen. So funktionieren die float-Formate schlichtweg nicht. Das liegt an vprintf(), das die floats im Normalfall aus Platzgründen unterdrückt. Man kann das steuern, ist aber etwas tricky.

In z.B. https://www.e-tinkers.com/2020/01/do-yo ... ing-point/ findet sich mehr dazu.
Da ich aber eh kein float verwende ist mir das grad wurscht.
Benutzeravatar
Fritzler
Beiträge: 12602
Registriert: So 11. Aug 2013, 19:42
Wohnort: D:/Berlin/Adlershof/Technologiepark
Kontaktdaten:

Re: Keramik, Glas und andere Hochtemperaturanwendungen

Beitrag von Fritzler »

Das gilt wohl eher nur für Blödduino ;)
GCC auf nem ARM mit der newlib floatet dir alles aus.
Da gibts dann optional noch kleine libc bei denen sich das mit einem flag im makefile zu/ab- schalten lässt.
j.o.e
Beiträge: 550
Registriert: Fr 29. Nov 2019, 01:15

Re: Keramik, Glas und andere Hochtemperaturanwendungen

Beitrag von j.o.e »

Fritzler hat geschrieben: Mi 17. Feb 2021, 08:26 Das gilt wohl eher nur für Blödduino ;)
GCC auf nem ARM mit der newlib floatet dir alles aus.
Da gibts dann optional noch kleine libc bei denen sich das mit einem flag im makefile zu/ab- schalten lässt.
Auch hiermit hast Du natürlich recht.

Das Projekt läuft wohl auf einem "Arduino Mega pro mini"-Brett. Und ich ging stillschweigend davon aus, dass die Programmieroberfläche auch Arduino ist.
Richtigerweise hätte ich das wohl extra erwähnen sollen, alleine schon um klar zu machen, dass es außer Arduino auch noch andere Programmierumgebungen, sowie außer AVR auch noch andere uC-Plattformen gibt.

Im geposteten Link meines letzten Beitrags wird das float-Problem genauer beleuchtet, werden auch work-arounds geliefert: Henry Cheung erwähnt explizit, dass es am Aufruf des GCC klemme. Ein "-Wl,-u,vfprintf -lprintf_flt -lm" würde dem abhelfen, aber leider seien die Aufrufparameter in der Arduino-IDE hard-coded.

Ich persönlich finde die Arduino-IDE ganz brauchbar. Ich kenne auch viele Leute, die damit auf Anhieb klar gekommen sind und damit Projekte verwirklichen konnten, die anders nie realisiert worden wären.
Und ich finde auch, das muss man den Machern von Arduino sehr hoch anrechnen.
Benutzeravatar
Desinfector
Beiträge: 11010
Registriert: Mo 12. Aug 2013, 07:50
Wohnort: ___3,1415(...)___

Re: Keramik, Glas und andere Hochtemperaturanwendungen

Beitrag von Desinfector »

dazu glaube ich passt auch 'ne Frage die mir grad aufkommt.

Ich hatte mal Scherben von einem alten Waschbecken. darunter war eine, die noch 'ne schöne Mulde ergab und darin
wollte ich Metall zu nem entsprechend geformten Stück zusammenschmelzen.

In gewisser Voraussicht hatte ich das Ding erstmal ohne "Einlage" auf Temperatur bringen wollen.
und das auch recht moderat, in der Restglut von verbrannten Gartenabfällen.
Also das war noch ein ziemlicher Haufen davon als glühende Holzkohle in der Feuertonne.
In Grunde so wie ein Schmied ein Stück Eisen in die Kohle legt.

Das Ding ist mir in etliche Teile zersprungen.
Sowas kommt doch ehedem aus einem Brennofen, warum hält das dann keine Temperatur mehr aus?
darf die Hitze nicht nur von einer Seite kommen?
Benutzeravatar
barclay66
Beiträge: 1077
Registriert: Di 13. Aug 2013, 04:12
Wohnort: im Speckgürtel Münchens

Re: Keramik, Glas und andere Hochtemperaturanwendungen

Beitrag von barclay66 »

Keramik hat praktisch null Elastizität aber immer noch Wärmeausdehnung. Wenn also die Hitze nur punktuell oder von einer Seite kommt dehnt sich der erhitzte Teil aus und sprengt die umgebenden kühleren Areale.
Benutzeravatar
Später Gast
Beiträge: 1699
Registriert: Di 5. Apr 2016, 22:03
Wohnort: Karlsruhe
Kontaktdaten:

Re: Keramik, Glas und andere Hochtemperaturanwendungen

Beitrag von Später Gast »

Ergänzung zu punktuell/von der Seite: Faktor Zeit spielt auch ne wichtige Rolle. Auch wenn die Keramik gleichmäßig von allen Seiten aufgewärmt wird, kann bei zu schnellem Aufheizen der Kern noch kalt sein und das Teil damit zum Platzen bringen. Deswegen immer schön langsam. Braucht man ggf erheblich mehr Energie für, und die muss auch regulierbar sein.
Benutzeravatar
Bastelbruder
Beiträge: 11550
Registriert: Mi 14. Aug 2013, 18:28

Re: Keramik, Glas und andere Hochtemperaturanwendungen

Beitrag von Bastelbruder »

Glasierte Keramik ist sowieso nicht als ernsthafter Schmelztiegel geeignet weil die Glasur schon bei Rotglut schmilzt und "klebrig" wird.
Antworten