Adaption an Multi-MCU - Neuprogrammierung MATLAB® GUI: Unterschied zwischen den Versionen

Aus Technische Beeinflussbarkeit der Geschmacksache Kaffee
Zur Navigation springen Zur Suche springen
Zeile 44: Zeile 44:
= Melina Scherf, 24.05.2023 =
= Melina Scherf, 24.05.2023 =
Die neu erstellten Platinen und die geschriebene Software wurden erstmals zusammengeführt. Die Initialisierung erfolgt in den meisten Fällen problemlos. Um die Fehlerfälle zu beseitigen wurde in die Initialisierungsfunktion eine Abfrage nach dem Verbindungsstatus eingefügt. Hierbei wird am Ende der Initialisierung die connected()-Funktion aufgerufen, die lediglich das Wort "connected" bei Ausführung rücksendet. Wird diese Wort von der GUI erkannt wird der Verbindungsstatus auf 1 gesetzt. Sollte diese Antwort nicht erkannt werden, wird die Initialisierungsroutine nochmals abgerufen. <br>
Die neu erstellten Platinen und die geschriebene Software wurden erstmals zusammengeführt. Die Initialisierung erfolgt in den meisten Fällen problemlos. Um die Fehlerfälle zu beseitigen wurde in die Initialisierungsfunktion eine Abfrage nach dem Verbindungsstatus eingefügt. Hierbei wird am Ende der Initialisierung die connected()-Funktion aufgerufen, die lediglich das Wort "connected" bei Ausführung rücksendet. Wird diese Wort von der GUI erkannt wird der Verbindungsstatus auf 1 gesetzt. Sollte diese Antwort nicht erkannt werden, wird die Initialisierungsroutine nochmals abgerufen. <br>
  if app.ssr_connected == 0
Initalisierungszeug
                        writeline(app.ssr_platine, 'import verbunden');
                        writeline(app.ssr_platine, 'verbunden.connected()');
                        if contains(app.zeile_char, 'connected')
                            app.ssr_connected = 1;
                            app.Verbinden_Button.Enable = 0;
                            app.SSR_Status.Text = ('SSR-Platine Initialiserung abgeschlossen');
                           
                        end
end
Die Funktionen zum Stellen der Pumpe, Heizung und Dosierventil wurden angepasst und werden wie zuvor über den SOLL/IST-Vergleich getriggert. <br>
Die Funktionen zum Stellen der Pumpe, Heizung und Dosierventil wurden angepasst und werden wie zuvor über den SOLL/IST-Vergleich getriggert. <br>
* Pumpe: Der Pumpe wird lediglich der Sollwert übergeben:
* Pumpe: Der Pumpe wird lediglich der Sollwert übergeben:

Version vom 24. Juli 2023, 23:25 Uhr

Melina Scherf, 14.06.2023

Der MicroPython-Code für die zuvor beschriebene Funktionsweise der Ventilschaltung wurde erstellt und auf der Seite zum Schalten Magnetventile SSR-Platine Multi-MCU erklärt.

Melina Scherf, 07.06.2023

Um die Schaltung der Aktoren übersichtlicher zu gestalten, sollen direkt in den Programmen (automatischer Modus) oder bei Wertänderung (manueller Modus) Funktionen aufgerufen werden.
Die Schaltung der Ventile erfolgt aktuell zunächst über die Festlegung der Sollwerte:

    app.ventileSollIntern=[0 1 0 1 0 0 0 0 0 0]; 

Dieser Vektor wird anschließend im SOLL/IST-Vergleich verarbeitet. Dies ist in der Handhabung sehr umständlich, da zu einem die Zuordnung der Ventile schwer fällt und zum anderen die Position nicht mehr zum aktuellen Stand der Maschine passt, was zukünftig verhindert werden soll. Aktuelle Schaltung: [Y01, Y03, Y04, Y05, Y13, Y06, Y07, Y08, Y09, Y00]
Daher wurde die neue Funktion ventile_stellen() programmiert, die eine unbekannte Anzahl an Eingaben verarbeiten kann. Ein möglicher Aufruf könnte wie folgt aussehen:

    app.ventile_stellen("Y01", "Y13", "Y07");

Die Eingaben werden in der Funktion in dem Vektor zu_schaltende_ventile gespeichert. Im automatischen Modus werden nun alle Ventile, die in dieser Funktion genannt werden, auf 1 gesetzt, alle nicht genannten auf 0. Dazu werden die zu schaltenden Ventile mit der Funktion ventile_schalten_automatisch() an die SSR-Platine übergeben:

    writeline(app.ssr_platine, sprintf(['ventile_schalten_automatisch(', repmat('%s, ', 1, numel(app.zu_schaltende_ventile)-1), '%s)'], app.zu_schaltende_ventile));

Im automatischen Modus werden nur die Werte der eingegebenen Ventile invertiert (ON -> OFF oder OFF -> ON) und dafür in der Funktion ventile_schalten_manuell() übergeben:

    writeline(app.ssr_platine, sprintf(['ventile_schalten_manuell(', repmat('%s, ', 1, numel(app.zu_schaltende_ventile)-1), '%s)'], app.zu_schaltende_ventile));

Theoretisch würde es im manuellen Modus reichen nur einen Wert zu übergeben, denn manuell kann bisher nur ein Ventil gleichzeitig geschaltet werden. Um aber zukünftigen Komplikationen aus dem Weg zu gehen und für zukünftige Bearbeiter eine bessere Übersichtlichkeit und Anpassungsmöglichkeit zu garantieren, wurde sich für den Vektor entschieden.

Für Dosierventil, Pumpe und Heizung sollen die bestehenden Funktionen zusammengelegt werden, sodass die Visualisierung und Stellung der Aktoren in je einer Funktion erfolgt. (noch zu machen)

Madita vom Stein, 06.06.2023

Beschreibung Prozessbild manueller Modus der GUI

Prozessbild manueller Modus der GUI
gerahmt

Auf dem Prozessbild sind die wesentlichen Zusammenhänge der im Mai 2023 aktuellen Labormaschine dargestellt.

Das Magnetventil Y01 ist das Ventil für den Festwasseranschluss.
Es wird eine Fluid-O-tech Pumpe verwendet. Parallel gibt es einen steuerbaren Bypass, der über ein Drosselventil mit Schrittmotor hergestellt wird.
Über das Magnetventil Y03 (Entschichtungsventil) wird der Boiler durch Umpumpen entschichtet oder durch Abpumpen entleert. Schmutzwasser kann über Y05 (Entwässerungsventil) abgeführt werden und über Y04 (Boilerbefüllung) wird der Boiler befüllt.
Der Kaffee- bzw. Teewasserbezug wird über Y06 (Bezugsventil) geschaltet. Es führt ein ungedrosselter Wasserstrang durch die Wasserwendel im Boiler zum Mischer. Ein weiterer Wasserstrang, der durch ein Dosierventil gedrosselt wird, führt ebenfalls zum Mischer. Der Mischer selbst ist derzeit als T-Stück realisiert.
Das 3/2-Wegeventil Y07 (Mischventil) führt im nicht-geschalteten Zustand in die Abtropfwanne.
Das Magnetventil Y09 (Umschaltventil) schaltet zwischen Kaffeebezug und Teewasserbezug um. Es steht im nicht-geschalteten Zustand auf Kaffeebezug.
Vor der Brühgruppe befindet sich eine Drossel mit Schrittmotor.
Über das Ventil Y08 (Rückspülventil) wird die halbautomatisierte Rückspülreinigung der Brühgruppe realisiert.
Der Dampfhahn ist als 3/2-Wege-Magnetventil Y13 ausgeführt. Im nicht-geschalteten Zustand wird das Kondenswasser zwischen Dampfhahn und -lanze in die Abtropfschale abgeleitet.
Der Stahlboiler der Labormaschine weist 3,6 Liter Gesamtvolumen auf und wird auf 2,4 Liter befüllt. Mit einem 1800 Watt leistungsgeregeltem Heizelement wurden 12 Minuten Aufheizzeit ermittelt.

Melina Scherf, 25.05.2023

Die Schaltung der Ventile wurde zusätzlich angepasst. Zuvor wurde eine Abfrage der IST- und SOLL-Stellwerte der Ventile durchgeführt und bei Abweichungen ein Vektor an die STM32 geschickt, die die Ventile bei 1 auf ON und bei 0 auf OFF geschaltet hat.
Der Vergleich bleibt vorläufig bestehen, nun wird aber bei abweichenden Werten direkt eine Funktion "Yxx__on" bzw. "Yxx_off" aufgerufen. In dieser erfolgt die Ansprache der Ventile auf der Multi-MCU über einen writeline-Befehl, der die Funktionen ventil_on(pin) bzw. ventil_off(pin) auf den Platinen aufruft. Die Definition der Pins wurde zuvor in der Initialisierung in einem Vektor gespeichert, dessen Werte über Indizes händisch zugeordnet und aufgerufen werden:

    writeline(app.ssr_platine, 'a = ventil_schalten.init()'); (Initialisierung)
    writeline(app.ssr_platine, 'ventil_schalten.ventil_on(a[0])'); (Funktionsaufruf auf MCU)

Je nach Stellung des Ventils wird das Icon in der Oberfläche angepasst.
Beachtet werden muss jedoch, dass bisher die ON-Funktion die Ventile OFF schaltet und umgekehrt. Dieses Problem soll zukünftig über eine Neuprogrammierung der Ventilschaltung behoben werden.

Melina Scherf, 24.05.2023

Die neu erstellten Platinen und die geschriebene Software wurden erstmals zusammengeführt. Die Initialisierung erfolgt in den meisten Fällen problemlos. Um die Fehlerfälle zu beseitigen wurde in die Initialisierungsfunktion eine Abfrage nach dem Verbindungsstatus eingefügt. Hierbei wird am Ende der Initialisierung die connected()-Funktion aufgerufen, die lediglich das Wort "connected" bei Ausführung rücksendet. Wird diese Wort von der GUI erkannt wird der Verbindungsstatus auf 1 gesetzt. Sollte diese Antwort nicht erkannt werden, wird die Initialisierungsroutine nochmals abgerufen.

 if app.ssr_connected == 0

Initalisierungszeug

writeline(app.ssr_platine, 'import verbunden'); writeline(app.ssr_platine, 'verbunden.connected()');


if contains(app.zeile_char, 'connected') app.ssr_connected = 1; app.Verbinden_Button.Enable = 0; app.SSR_Status.Text = ('SSR-Platine Initialiserung abgeschlossen');

end


end


Die Funktionen zum Stellen der Pumpe, Heizung und Dosierventil wurden angepasst und werden wie zuvor über den SOLL/IST-Vergleich getriggert.

  • Pumpe: Der Pumpe wird lediglich der Sollwert übergeben:
    writeline(app.bas_platine, sprintf('pumpe(%d)', app.pumpeSollIntern));
  • Heizung: Ein- und Ausschalten ähnlich Ventilschaltung:
    writeline(app.ssr_platine, 'ventil_on(3)'); (Einschalten)
    writeline(app.ssr_platine, 'ventil_off(4)'); (Ausschalten)
  • Dosierventil:
    writeline(app.ssr_platine, ['dosierventil_pos = schrittmotor.forwardStep(' num2str(dosierventilStellwert*2) ', dosierventil_pos, seq, dosierventil, "endDosier")']); (Vorwärts)
    writeline(app.ssr_platine, ['dosierventil_pos = schrittmotor.backwardStep(' num2str(dosierventilStellwert*(-2)) ', dosierventil_pos, seq, dosierventil, "endDosier")']); (Rückwärts)

Melina Scherf, 20.05.2023

Die RPI-Platine entfällt, sodass deren Funktionen zur Schrittmotorsteuerung auf die BAS- und SSR-Platine verteilt werden.

  • Dosierventil -> SSR
  • Drossel -> BAS
  • Bypass -> BAS

Dazu wurden die Initialisierungsfunktionen der beiden Platinen um die Schrittmotorinitialisierung erweitert wurden, die jedoch zu großen Teilen aus der bestehenden GUI übernommen wurden.
Es wurden zudem kleinere Anpassungen in den writeline-befehlen vorgenommen, diese betreffen jedoch nur Groß- und Kleinschreibung.

SSR function ssr_initalisieren(app, ~,~)

           % Initalisierungsfunktion der SSR-Platine
           % Import aller benötigten Pythonprogamme + mechanische Initalisierung
           % (aufgerufen aus init_mcus())
         
             % SCHRITTMOTOREN
           
                   app.zeile_char_ssr = char('import Schrittmotor');           % Anzeige des Befehls in der GUI-Oberfläche (Statusfeld SSR)
                   writeline(app.ssr_platine, 'import Schrittmotor');          % Import des Schrittmotor-Python-Programmes
                       
                   app.zeile_char_ssr = char('seq = Schrittmotor.sequenz()');  % Anzeige des Befehls in der GUI-Oberfläche
                   writeline(app.ssr_platine, 'seq = Schrittmotor.sequenz()'); % Steuersequenz für die Schrittmotoren festlegen


                 %Dosierventil 
                   app.zeile_char_ssr = char('pins = Schrittmotor.Dosierventil_Pins()');  
                   writeline(app.ssr_platine, 'pins = Schrittmotor.Dosierventil_Pins()'); % Festlegung der benötigten Pins des Dosierventils
   
                   app.zeile_char_ssr = char('dosierventil = Schrittmotor.setup(pins)'); 
                   writeline(app.ssr_platine, 'dosierventil = Schrittmotor.setup(pins)'); % Festlegung der Ansprache des Dosierventiles
   
                   app.zeile_char_ssr = char('dosierventil_pos = -1'); 
                   writeline(app.ssr_platine, 'dosierventil_pos = -1'); % Initialisierungsposition in der Schrittmotorsequenz


                 %mechanische Initialisierung
                   writeline(app.ssr_platine, 'dosierventil_pos = Schrittmotor.forwardStep(800, dosierventil_pos, seq, dosierventil, "endDosier")');
                   pause(1);
                   writeline(app.ssr_platine, 'dosierventil_pos = Schrittmotor.backwardStep(800, dosierventil_pos, seq, dosierventil, "endDosier")');
                   pause(1);
                   % Befehle zum Vorwärts- und Rückwärtsstellen des Dosierventils
                   % in diesem Fall 800 vorwärts sowie 800 rückwärts Halbschritte
                   % Rückgabe des Signalwortes "endDosier" nach Abschluss des Vorganges 
             % VENTILE
                   
                 % ALT (getestet)
                   writeline(app.ssr_platine, 'import ventil_schalten');     % Import des Python-Programmes
                   writeline(app.ssr_platine, 'a = ventil_schalten.init()'); % Speicherung eines Vektors mit den Pins der Ventile
                   app.Y01_off();                                            % Aufruf der Funktionen zur Schließung aller Ventile -> sicherer Zustand
                   app.Y03_off();
                   app.Y04_off();
                   app.Y05_off();
                   app.Y13_off();
                   app.Y06_off();
                   app.Y07_off();
                   app.Y08_off();
                   app.Y09_off();


BAS

           % SCHRITTMOTOREN
               app.zeile_char_bas = char('import Schrittmotor');           % Anzeige des Befehls in der GUI-Oberfläche (Statusfeld BAS)
               writeline(app.bas_platine, 'import Schrittmotor');          % Import des Schrittmotor-Python-Programmes   
               app.zeile_char_bas = char('seq = Schrittmotor.sequenz()');  % Anzeige des Befehls in der GUI-Oberfläche
               writeline(app.bas_platine, 'seq = Schrittmotor.sequenz()'); % Steuersequenz für die Schrittmotoren festlegen 


               %Brühgruppendrossel
               app.zeile_char_bas = char('pins = Schrittmotor.Bruehgruppendrossel_Pins()'); 
               writeline(app.bas_platine, 'pins = Schrittmotor.Bruehgruppendrossel_Pins()'); % Festlegung der benötigten Pins der Brühgruppendrossel
               app.zeile_char_bas = char('drossel = Schrittmotor.setup(pins)');           
               writeline(app.bas_platine, 'drossel = Schrittmotor.setup(pins)'); % Festlegung der Ansprache des Dosierventiles      
               app.zeile_char_bas = char('drossel_pos = -1'); 
               writeline(app.bas_platine, 'drossel_pos = -1');  % Initialisierungsposition in der Schrittmotorsequenz
               %Bypass
               app.zeile_char_bas = char('pins = Schrittmotor.Bypass_Pins()'); 
               writeline(app.bas_platine, 'pins = Schrittmotor.Bypass_Pins()');  % Festlegung der benötigten Pins des Bypasses
               app.zeile_char_bas = char('Bypass = Schrittmotor.setup(pins)'); 
               writeline(app.bas_platine, 'Bypass = Schrittmotor.setup(pins)');  % Festlegung der Ansprache des Bypasses
               app.zeile_char_bas = char('Bypass_pos = -1'); 
               writeline(app.bas_platine, 'Bypass_pos = -1');  % Initialisierungsposition in der Schrittmotorsequenz
               %mechanische Initialisierung
               writeline(app.bas_platine, 'drossel_pos = Schrittmotor.forwardStep(800, drossel_pos, seq, drossel, endDrossel)');
               pause(1);
               writeline(app.bas_platine, 'drossel_pos = Schrittmotor.backwardStep(800, drossel_pos, seq, drossel, endDrossel)');
               pause(1);
               writeline(app.bas_platine, 'Bypass_pos = Schrittmotor.forwardStep(800, Bypass_pos, seq, Bypass, "endBypass")');
               pause(1);
               writeline(app.bas_platine, 'Bypass_pos = Schrittmotor.backwardStep(800, Bypass_pos, seq, Bypass, "endBypass")');
               pause(1);
              % Befehle zum Vorwärts- und Rückwärtsstellen des Bypasses/ der Brühgruppedrossel
                   % in diesem Fall 800 vorwärts sowie 800 rückwärts Halbschritte
                   % Rückgabe des Signalwortes "endDrossel" bzw. "endBypass" nach Abschluss des Vorganges 
            
            %PUMPE
               app.zeile_char_bas = char('import Pumpensteuerung'); 
               writeline(app.bas_platine, 'import Pumpensteuerung'); %Import des Python-Programms zur Pumpensteuerung


MWP

           app.zeile_char_mwp = char('import Messung');   % Anzeige des Befehls in der GUI-Oberfläche (Statusfeld MWP)    
           writeline(app.mwp_platine, 'import Messung');  % Import des Messung-Python-Programmes

Melina Scherf, 18.05.2023

Die simulierte Messwerterfassung wurde in gleicher Weise wie für die MWP für die BAS-Platine erstellt. Beide Platinen-Verarbeitungsfunktionen greifen dabei auf dieselbe Messwert-Verarbeitungsfunktion zu, speichern die Messwerte aber zunächst in unterschiedlichen lokalen Vektoren. Hinzu kommt, dass einkommende Messwerte der BAS-Platine zusätzlich an einem "F!" zu erkennen sind.
Um dies in der Praxis zu testen wurde je die letzte Zeile des von Armin Rohnen erstellten Programm zur simulierten Messdatenerfassung angepasst:
MWP:

  print('M!',T_Boiler, T_Eingang, T_Mischer, P_Boiler, Leitwert, T_Bruehgruppe, P_Bruehgruppe) 

BAS:

  print('F!', Fuellstand , Durchfluss, Pulse) 

So werden von der Messwertplatine sieben und von der Basisplatine drei Messwerte gesendet, die nach der festgelegten Reihenfolge (s.u.) in der GUI gespeichert werden. Um diese auch sichtbar zu machen, wurde die neu erstellte Oberfläche integriert, welche automatische jede Sekunde die aktualisierten Messwerte an den zugeordneten Positionen anzeigt.
Es wurde zudem die automatische (nach vorgegebener Zeit in der Oberfläche) und die manuelle Datensicherung (per Knopfdruck) programmiert. Die Dateien enthalten den aktuellen Messwertpuffer und werden mit Zeitstempel und dem Zusatz "automatisch" oder "manuell" angelegt. Bei jedem Schließen der App wird eine Sicherung mit dem Namen "last_data" erstellt, die bei jedem Neustart geladen wird.


Melina Scherf, 14.05.2023

Die Messwerte der Messwertplatine werden nun im Messwert-Puffer gespeichert und können von anderen functions verarbeitet werden.
Dazu werden die Messwerte der MWP in einem lokalen Vektor gespeichert und dieser anhand der Indizes dem globalen Messwertpuffer zusammen mit dem Zeitstempel der Messung zugeordnet und umgerechnet. Die Zuordnung erfolgt auf Basis der bestehenden Logik, wobei es allerdings zu Unregelmäßigkeiten kommt, diese könnten zukünftig aufgearbeitet werden:

  1. Boilertemperatur -> MWP
  2. Frischwassertemperatur -> MWP
  3. Mischwassertemp -> MWP
  4. Boilerdruck -> MWP
  5. Wasserleitfähigkeit -> MWP
  6. Füllstand -> BAS
  7. Durchfluss -> BAS
  8. Pulse Flowmeter -> BAS
  9. Temperatur hinter der Wasserwendel/ vor der Brühgruppe -> MWP
  10. Druck in der Brühgruppe -> MWP
  11. Pumpensteuerspannung
  12. Bypass-Stellung
  13. Dosierventilstellung
  14. Brühgruppendrossel
  15. Heizleistung PWM


           if app.verarbeitung_aktiv == 0
               % wenn gerade keine Verarbeitungsroutine durchgeführt wird, kann eine neue gestartet werden
               app.verarbeitung_aktiv = 1; % Variable auf 1 setzen, um erneute Verarbeitung zu verhindern
               messwerte_mwp = [0,0,0,0,0,0,0,0]; % Anlegen von lokalen Vektoren mit 0 als Messwerte
               messwerte_bas = [0,0,0,0,0,0];
               % im Fehlerfall von einer Platine werden nur Nullen an den entsprechenden Stellen der Oberfläche angezeigt
               % ohne diese Vektoren, keine Verarbeitung über eine Verarbeitungsfunktion, wenn eine Platine Fehlerfall hat, möglich
               if strcmp(app.zeile_char_mwp(1:1),'M')
                   % wenn gesendete Antwort der MWP an erster Stelle Signalwort enthielt (kein Fehlerfall auf MWP)
                  messwerte_mwp =str2num(app.zeile_char_mwp_neu); % 0-Vekor mit gesendeten Messwerten überschreiben 
               end   
               if strcmp(app.zeile_char_bas(1:1),'B')
                   % wenn gesendete Antwort der BAS an erster Stelle Signalwort enthielt (kein Fehlerfall auf BAS)
                  messwerte_bas =str2num(app.zeile_char_bas_neu); % 0-Vekor mit gesendeten Messwerten überschreiben 
               end


               %ERSTELLUNG DER ZEITPUNKTE
               aktuelle_uhrzeit = datestr(now,'HH:MM:SS.FFF');    % Aktuelle Uhrzeit ermitteln
               uhrzeit_sekunden = str2num(aktuelle_uhrzeit(1:2))*3600 + str2num(aktuelle_uhrzeit(4:5))*60 + str2num(aktuelle_uhrzeit(7:end)); % Umrechnung in Sekunden
                                                          
               if app.uhrzeit_erste_messung == 0                  % Erkennung der erstmalige Messwertverarbeitung
                   app.uhrzeit_erste_messung = uhrzeit_sekunden;  % Abspeichern des Startzeitpunktes der Messung
                   app.zeitstempel_messung(1,1) = 0;              % Startzeitpunkt an erste Stelle des Vktor mit den Zeitstempel der Messungen speichern
               else
                   app.zeitstempel_messung(end+1,1) = uhrzeit_sekunden - app.uhrzeit_erste_messung; %Hinzufügen eines weiteren Zeitstempels am Ende des Vektors            
                                                                                                    % vergangene Zeit zwischen aktueller und erster Messung
               end
               
               %VERARBEITUNG MESSWERTE
               pos_zeitstempel = length(app.zeitstempel_messung); % Anzahl der durchgeführten Messungen ermitteln = letztes Element
               
               % Abspeichern der Messwerte in letzter Zeile des Messwert-Puffer (pos_zeitstempel) und in zugewiesener Spalte 
               %1. Boilertemperatur
                   app.messwert_puffer(pos_zeitstempel,1) = app.boilerkalibrierkurve(round(messwerte_mwp(1)*3300/4096)); 
                   % Umwandlung des Messwertes zu einem Boilertemperaturwert, durch Extraktion eines Spannungswert aus dem ADC-Wert und dann nachschauen in einer Tabelle.      
               % 2. Frischwassertemperatur
                   app.messwert_puffer(pos_zeitstempel,2)=(messwerte_mwp(2)*1.00416-1.68)*140/4096; 
                   % Umwandlung des Messwertes zu einer Frischwassertemperatur inkl. linearer Kalibrierkurve (ADC)
               % 3. Mischwassertemp
                   app.messwert_puffer(pos_zeitstempel,3)=app.mischerkalibrierkurve(round(messwerte_mwp(3)*3300/4096)); 
                   % Umwandlung des Messwertes zur Mischwassertemperatur,durch extraktion eines Spannungswert aus dem ADC-Wert und dann nachschauen in einer Tabelle.   
               % 4. Boilerdruck
                   app.messwert_puffer(pos_zeitstempel,4)=4500*((messwerte_mwp(4)*1.00605-4.72)/4096)-500; 
                   % Umwandlung des Messwertes zum Boilerdruck. Dieser hat einen 0-Offset von 500mV zzgl. linearer Kalibrierkurve (ADC).
               % 5. Wasserleitfähigkeit
                   app.messwert_puffer(pos_zeitstempel,5)=((messwerte_mwp(5)*1.00382-1.78)*19.8/4096)+0.2;  
                   % Umwandlung des Messwertes zur Wasserleitfähigkeit, dieser hat einen 0-Offset von 0.2mS/cm zzgl. linearer Kalibrierkurve (ADC)
               % 6. Füllstand -> BAS
                   app.messwert_puffer(pos_zeitstempel,6)=messwerte_bas(1);
               % 7. Durchfluss
                   app.messwert_puffer(pos_zeitstempel,7)=messwerte_bas(5);
               % 8. Pulse Flowmeter -> BAS
                   app.messwert_puffer(pos_zeitstempel,8)=messwerte_bas(6);
                   app.flowCounter = app.flowCounter + messwerte_bas(6); 
                   % Kumulierung der Interruptmenge zur bestimmung der Bezugsmenge
               % 9. Temperatur hinter der Wasserwendel/ vor der Brühgruppen
                   app.messwert_puffer(pos_zeitstempel,9)=app.mischerkalibrierkurve(round(messwerte_mwp(6)*3300/4096));
                   % Umwandlung des Messwertes zu einer Brühgruppen oder Wasserwendeltemperatur je nach Einbau       
                   app.messPuffer(end,10)=20000*((werte(10))/4096)-2000; % gültig für AVS Römer Drucksensor
               % 10. Druck in der Brühgruppe.
                   app.messwert_puffer(pos_zeitstempel,10)=15000*((messwerte_mwp(7))/4096)-1500; 
                   % gültig für SEEED Drucksensor, Umwandlung des Messwertes zum Brühgruppendruck. Dieser hat einen 0-Offset von 500mV und es wird nur der Bereich bis 4,5V verwendet
               % 11. Brühgruppentemperatur (neu??) ANPASSEN FRAGE
                   % app.messwert_puffer(pos_zeitstempel,11)= messwerte_mwp(7); 
               % 11. Pumpensteuerspannung -> BAS
                   app.messwert_puffer(pos_zeitstempel,11) = app.Pumpenleistung.Value;
               % 12. Bypass-Stellung -> RPI
                   app.messwert_puffer(pos_zeitstempel,12) = app.Pumpenbypass.Value; 
               % 13. Dosierventilstellung -> RPI
                   app.messwert_puffer(pos_zeitstempel,13) = app.Dosierventil.Value;
               % 14. Brühgruppendrossel -> RPI
                   app.messwert_puffer(pos_zeitstempel,14) = app.Bruehgruppendrossel.Value;
               % 15. Heizleistung PWM
                   app.messwert_puffer(pos_zeitstempel,15) = 0; 
  
   
               if length(app.zeitstempel_messung)>app.puffer_laenge 
                   % wenn Anzahl die Messungen die definierte Pufferlänge übersteigt
                   % Kürzung auf puffer_laenge
                   app.messwert_puffer = app.messwert_puffer(end - app.puffer_laenge:end,:);         % Löschen aller Zeilen von messwert_puffer bis auf die letzten "puffer_laenge"-Zeilen
                   app.zeitstempel_messung = app.zeitstempel_messung(end - app.puffer_laenge:end,1); % Löschen aller Zeilen von zeitstempel_messung bis auf die letzten "puffer_laenge"-Zeilen
               end
               app.messwerte_anzeigen();       % Funktionsaufruf zur Visualisierung der Messwerte in der GUI-Oberfläche      
               app.reglerundfunktionsaufruf(); % Funktionsaufruf, die Regler und Funktionen bearbeitet  
   
               if app.zeitpunkt_letzte_sicherung == 0
                   % wenn noch keine Sicherung erfolgte 
                   app.datensicherung(app);                           % Aufruf der Funktion zur Datensicherung
                   app.zeitpunkt_letzte_sicherung = uhrzeit_sekunden; % Zeitpunkt der letzten Sicherung auf aktuellen Zeitpunkt
               elseif (uhrzeit_sekunden - app.zeitpunkt_letzte_sicherung) > app.Datensicherung.Value
                   %wenn die vergangene Zeit zwischen letzter Sicherung und aktuellem Zeitpunkt eingegebenen Wert im Start-Reiter überschreitet
                   app.automatische_datensicherung = 1;               % Variable, die anzeigt, dass es sich um eine automatische Datensicherung handelt 
                   app.datensicherung(app);                           % Aufruf der Funktion zur Datensicherung
                   app.zeitpunkt_letzte_sicherung = uhrzeit_sekunden; % Zeitpunkt der letzten Sicherung auf aktuellen Zeitpunkt
                   
               end
               app.verarbeitung_aktiv = 0; % Verarbeitung abgeschlossen, neue Verarbeitungsroutine kann beginnen
           end

Melina Scherf, 13.05.2023

In der MATLAB® GUI wurde die Verarbeitungsfunktion der Messwert-Platine erstellt. Diese kann einkommende Messwerte (Erkennungszeichen "M!") von Fehlern und Codezeilen unterscheiden. Werden Messwerte erkannt wird eine weitere Funktion zur Verarbeitung der Messwerte aufgerufen.
Der Benutzer kann anhand der Error-Lampe sicherstellen, ob es zu Fehlern kommt, diese würde in diesem Fall rot leuchten.

                   zeile = readline(app.mwp_platine);        % Einlesen der Antwort der MWP in lokale Variable
                   app.zeile_char_mwp = char(zeile);         % Abspeicherung der Antwort in globale Variable
                   app.MWP_Status.Text = app.zeile_char_mwp; % Anzeige der Antwort in Statusfeld der MWP
                       
                    app.verarbeitung_moeglich_mwp = 0; % lokale Variable, mit Information, ob Verarbeitung möglich ist / kein Fehler aufgetreten ist (1 -> möglich)
                    lenstr = strlength(app.zeile_char_mwp); % Ermittlung der Länge der gesendeten Antwort
                    %Abfrage der Fehler / Aufbereitung der Antwort
                        % alle in einzelnen if-Abfrage, da auf eine Antwort mehrere Fälle zutreffen können                    
                    if lenstr>2
                          if strcmp(app.zeile_char_mwp(1:3),'>>>') 
                              % enthält die Antwort an den ersten drei Stellen ">>>"
                               app.zeile_char_mwp = app.zeile_char_mwp(5:end); % Entfernung der ">>>" 
                               lenstr = lenstr-3;                              % Anpassung der Länge der Antwort    
                          end                                                                       
                    end            
                    if lenstr > 1
                        if strcmp(app.zeile_char_mwp(1:1),'M') 
                            % wenn die Antwort (evtl. bereits um ">>>" gekürzt) an den ersten beiden Stellen das Identifikations-Kennzeichen M enthält
                                                                   
                           app.verarbeitung_moeglich_mwp = 1;                 % eine fehlerfreie Verarbeitung ist möglich
                           app.zeile_char_mwp_neu =app.zeile_char_mwp(3:end); % Entfernung des Identifikations-Kennzeichen M
                                                                              % Abspeicherung in neuer Variable, um Überschreibung durch neu einkommende Messwerte zu vermeiden 
                        end
                    end
                    % Sollte die Antwort anders aussehen, als bereits abgefragt -> Fehlerfall, verarbeitung_moeglich_bas bleibt 0
                    
                    if app.verarbeitung_moeglich_mwp == 1 
                        % wenn der korrekte Fall erkannt wurde
          
                            if (app.ssr_init == 1 && app.bas_init == 1 && app.mwp_init == 1)
                                % wenn alle Platinen bereits initalisiert
                               app.messwerte_verarbeiten(); % Aufruf der Verarbeitungsfunktion der Messwerte
                               app.Error_Lampe.Color = 'g'; % Lampe wird grün
       
                            end
                    else
                        % in allen anderen Fällen
                            app.Error_Lampe.Color = 'r'; % Lampe wid rot und zeigt optisch Fehler an
                    end

Melina Scherf, 07.05.2023

Das von Armin Rohnen erstellte Programm zur simulierten Messdatenerfassung wurde eingepflegt und kann über einen Knopf gestartet und beendet werden. So können die Verarbeitungsfunktionen der BAS und MWP zukünftig implementiert und getestet werden. ANPASSEN

              writeline(app.mwp_platine, "from machine import Timer");        
              writeline(app.mwp_platine, "from messwerte import messwerte");
              writeline(app.mwp_platine, "messwert_timer = Timer(mode=Timer.PERIODIC, freq=2, callback=messwerte)");
              zeile = readline(app.mwp_platine);
              app.zeile_char_mwp = char(zeile);
              app.MWP_Status.Text = app.zeile_char_mwp;     
              writeline(app.bas_platine, "from machine import Timer");
              writeline(app.bas_platine, "from messwerte import messwerte");
              writeline(app.bas_platine, "messwert_timer = Timer(mode=Timer.PERIODIC, freq=2, callback=messwerte)");
              zeile = readline(app.bas_platine);
              app.zeile_char_bas = char(zeile);
              app.MWP_Status.Text = app.zeile_char_bas;

Melina Scherf, 06.05.2023

Die Initialisierung aller vier MCUs ist nun möglich.
Dazu wird nach Betätigen des Verbinden-Buttons eine Callback-Funktion ausgelöst. In dieser wird zunächst überprüft, ob zuvor noch keine Verbindung aufgebaut wurde (die Variable verbindungsstatus den Wert 0 besitzt). Sollte dies der Fall sein, wird der Variable verbindungsstatus der Wert 1 zugewiesen und es kann mit der eigentlichen Verbindung begonnen werden. Dem Benutzer wird dies durch eine grün werdende Lampe sowie einen Informationstext in der allgemeinen Statuszeile angezeigt.

  if app.verbindungsstatus == 0
       app.verbindungsstatus = 1;   
       app.Error_Lampe.Color = 'g';  
       app.Allgemeiner_Status.Text = char('MCUs werden initalisiert...');

Es wird eine Liste mit allen belegten Ports angelegt und der erste Port ausgewählt. Für diesen Port wird eine allgemeine, unidentifizierte MCU erstellt, inklusive Terminator (Zeilenumbruch), Verbindungsgeschwindigkeit und Verarbeitungsfunktion.

  app.ports = serialportlist("available");                  
  app.port_nummer = 1;                                      
  app.mcu = serialport(app.ports(app.port_nummer), 115200); 
  configureTerminator(app.mcu, "CR/LF");                    
  configureCallback(app.mcu, "terminator", @app.init_mcus);

Um die am ersten Port angelegte MCU zu identifizieren wird mittels eines writeline Befehls die ident()-Funktion importiert und ausgeführt.

  writeline(app.mcu, "import ident");                       
  writeline(app.mcu, "ident.ident()");

Die zugehörige Python-Funktion sendet das Identifikationskürzel der Platine als Antwort an MATLAB® zurück:

ANPASSEN: PYTHON_CODE

Der Zeilenumbruch am Ende der Antwort triggert als Terminator den Aufruf der init_mcus()-Funktion. In dieser wird zunächst die gesendete Antwort eingelesen und abgespeichert.

  zeile = readline(app.mcu);           
  app.zeile_char = char(zeile);

Mittels einer if-Abfrage wird überprüft, welches der zuvor definierten Kürzel (ssr, bas, mwp) die Antwort enthält und ob diese Platine nicht bereits initialisiert wurde. Die eigentliche Initialisierung wird gestartet, welches dem Benutzer an einer Meldung in der Statusleiste signalisiert wird. (Im folgenden wird dies am Beispiel der SSR dokumentiert. BAS- und MWP-Platine sind analog angelegt.

  if contains(app.zeile_char,'ssr') && app.ssr_init == 0 
       app.SSR_Status.Text = ('SSR-Platine wird konfiguriert');

Die zuvor angelegte allgemeine MCU wird nun als ssr_platine abgespeichert und ihr eine neue, SSR-spezifische Verarbeitungsfunktion zugewiesen.

  app.ssr_platine = app.mcu;
  configureCallback(app.ssr_platine, "terminator", @app.ssr_verarbeitung)

Anschließend wird die, der Übersichtlichkeit halber, separate Initialiserungsfunktion aufgerufen, in der sämtliche Python-Programme importiert und die Schrittmotoren sowie Magnetventile mechanisch initialisiert werden. Nach Abschluss der Initialisierung der Platine kann der Initialisierungsstatus auf den Wert 1 gesetzt werde, sodass eine erneute Initialisierung verhindert wird und dem Benutzer eine entsprechende Nachricht angezeigt werden.

  app.ssr_initalisieren();                                              
  app.ssr_init = 1;                                                      
  app.SSR_Status.Text = ('SSR-Platine Initalisierung abgeschlossen'); 

Abschließend wird die Variable hochzaehlen_erlauben auf den Wert 1 gesetzt, um gegebenenfalls die Bearbeitung des nächsten Ports zuzulassen. Diese Variable stellt sicher, dass der aktuelle Port erst nach abgeschlossener Bearbeitung gewechselt werden kann. Dazu wird am Ende der init_mcus()-Funktion die hochzaehlen()-Funktion aufgerufen.
In dieser wird nun überprüft, ob es noch weitere zu bearbeitende Ports gibt, indem die aktuelle Portnummer mit der Länge der Liste mit allen verfügbaren Ports verglichen wird. Sind noch weitere Ports zu bearbeiten und besitzt die Variable zur Erlaubnis zum Hochzählen den Wert 1, kann der nächste Port angewählt werden.

  if app.port_nummer < length(app.ports) && app.next == 1 

In der hochzaehlen()-Funktion wird nun der nächste Port ausgewählt und erneut eine allgemeine MCU inklusive Terminator (Zeilenumbruch), Verarbeitungsgeschwindigkeit und -funktion erstellt. Durch Aufruf der ident()-Funktion wird erneut die init_mcus()-Funktion aufgerufen.

  app.port_nummer = app.port_nummer +1;                 
  app.mcu = serialport(app.ports(app.port_nummer), 115200);
  configureTerminator(app.mcu, "CR/LF");                   
  configureCallback(app.mcu, "terminator", @app.init_mcus);
  writeline(app.mcu, "import ident");                       
  writeline(app.mcu, "ident.ident()"); 

Sind alle belegten Ports abgearbeitet, also der Initialisierungsstatus aller Platinen auf dem Wert 1, wird dies dem Benutzer durch eine Information in der Statuszeile signalisiert.

  if (app.ssr_init == 1 && app.bas_init == 1 && app.mwp_init == 1) 
       app.Allgemeiner_Status.Text = char('Alle MCUs initalisert');

Dieses Verfahren hat den Vorteil, dass die Platinen in einer unbekannten Reihenfolge durch einmaligen Knopfdruck auf den Verbinden-Button verbunden und initialisiert werden können.

Melina Scherf, 28.04.2023

Während der Neuprogrammierung der MATLAB® GUI ist so vorzugehen, dass jeder hinzugefügte Codeabschnitt zunächst mit Versuchs-Microcontrollern getestet wird. Dazu wurden Melina Scherf 4 Versuchs-Microcontroller übergeben.
Um die Kommunikation zwischen MATLAB® und MicroPython zu ermöglichen, müssen die MCUs initialisiert werden. Dazu wurde mit Armin Rohnen eine Workshop durchgeführt, in welchem das Vorgehen zur Initialisierung vorgestellt wurde. Anschließend wurde von Melina Scherf diese in den neuen Code eingepflegt. Die MCUs heißen im Code nun ssr_platine, bas_platine, mwp_platine, rpi_platine nach zuvor festgelegten Definition der Benennungen. So wird die Ansprache der alten STM32 zukünftig auf unterschiedliche MCUs aufgeteilt werden.

Melina Scherf, 16.04.2023

Als Vereinfachungspotential wurde zum einen die generelle Übersichtlichkeit des Codes festgestellt. Um dies zu verbessern, wird mit größeren Einzügen gearbeitet, um die übergeordneten Funktionen auf einen Blick zu erkennen. Die Reihenfolge der functions wird zudem thematisch sinnvoll gegliedert, vorläufig in:

  • Programme
  • Regler
  • Kommunikation mit MCU
  • Aktoren

Dazu wurde am Anfang der methods im Code ein Inhaltsverzeichnis und über den jeweiligen Abschnitten Überschriften eingefügt.
Das Programm des Mischwasserbezugs besteht im vorhanden Code aus 8 Funktionen, diese sollen zusammengekürzt und (sofern möglich) in einer Funktion zusammengefasst werden.

Melina Scherf, 01.04.2023

Die Neuprogrammierung der MATLAB® GUI wird von Madita vom Stein und Melina Scherf durchgeführt.
Dabei wird Madita vom Stein die graphische Oberfläche der App erstellen. Es soll versucht werden eine übersichtlichere und ansprechendere Ansicht insbesondere des manuellen Modus' zu erarbeiten.
Melina Scherf wird sich in den bereits bestehenden Code einarbeiten und Vereinfachungs- und Verbesserungspotentiale identifizieren. Diese sollen in die neue MATLAB® GUI einfließen.

Melina Scherf, 31.03.2023

Um den aktuellen Projektteilnehmern einen besseren Überblick zu gewähren, wurde beschlossen die MATLAB® GUI neu zu programmieren. Dies soll dazu beitragen die Lesbarkeit des Codes zu erhöhen.

Melina Scherf, 24.03.2023

Ein Einführungsworkshop wurde durch Armin Rohnen durchgeführt. Dieser diente dazu, den Projektteilnehmern einen ersten Einblick in die Programmierung von MATLAB® in Kombination mit MicroPython zu geben.
Es wurde die generelle Funktion des MATLAB® App Designers vorgestellt und dafür eine erste Testoberfläche erstellt, welche einen Raspberry Pi initialisiert und eine Lampe per Schalter an- und ausschaltet.
Der dabei entstandene Code wurde von Melina Scherf kommentiert, um den anderen Projektteilnehmern, die zuvor noch nicht mit (Micro)Python gearbeitet hatten, eine eigenständige Nacharbeitung zu ermöglichen

Armin Rohnen, 16.02.2023

Für die Inbetriebnahme der Multi-MCU-Elektronik muss der Programmcode der MATLAB®-GUI entsprechend angepasst werden. Die bisherige Datenverarbeitung der STM32-MCU wird dabei auf drei Datenverarbeitungen aufgeteilt. Die angeschlossenen MCUs müssen dabei eindeutig identifiziert werden.