Adaption an Multi-MCU - Neuprogrammierung MATLAB® GUI: Unterschied zwischen den Versionen
Keine Bearbeitungszusammenfassung |
|||
Zeile 309: | Zeile 309: | ||
app.Allgemeiner_Status.Text = char('Datensicherung abgeschlossen'); % Anzeige im allgemeinen Statusfeld | app.Allgemeiner_Status.Text = char('Datensicherung abgeschlossen'); % Anzeige im allgemeinen Statusfeld | ||
= Melina Scherf, 15.05.2023, | = Melina Scherf, 15.05.2023, Datensicherung= | ||
function datensicherung(app, ~, ~) | |||
% Sicherung aller Messwerte und Eingaben der Oberfläche im Start-Reiter | |||
% automatische und manuelle Datensicherung können beide mittels dieser Funktion erfolgen | |||
app.Allgemeiner_Status.Text = char('Datensicherung gestartet'); % Anzeige im allgemeinen Statusfeld | |||
% Abspeicherung der Eingaben aus dem Start-Reiter | |||
meta.Datum = app.Datum.Value; | |||
meta.Bearbeiter = app.Bearbeiter.Value; | |||
meta.Messzyklus = app.Messzyklus.Value; | |||
meta.Bemerkung1 = app.Bemerkung1.Value; | |||
meta.Bemerkung2 = app.Bemerkung2.Value; | |||
meta.Bemerkung3 = app.Bemerkung3.Value; | |||
meta.Kaffeesorte = app.Kaffeesorte.Value; | |||
meta.Mahlgrad = app.Mahlgrad.Value; | |||
meta.Kaffeemenge = app.Kaffeemenge.Value; | |||
meta.Tamperdruck = app.Tamperdruck.Value; | |||
% Überschriften der jeweiligen Messwerte | |||
meta.messpunkt(1).Value = 'Boilertemperaturwert'; | |||
meta.messpunkt(2).Value = 'Frischwassertemperatur'; | |||
meta.messpunkt(3).Value = 'Mischwassertemperatur'; | |||
meta.messpunkt(4).Value = 'Boilerdruck'; | |||
meta.messpunkt(5).Value = 'Leitfähigkeit'; | |||
meta.messpunkt(6).Value = 'Füllstand'; | |||
meta.messpunkt(7).Value = 'Durchfluss'; | |||
meta.messpunkt(8).Value = 'Pulse vom Flowmeter'; | |||
meta.messpunkt(9).Value = 'Brühgruppenwassertemperatur'; | |||
meta.messpunkt(10).Value = 'Brühgruppendruck'; | |||
meta.messpunkt(11).Value = 'Pumpenansteuerung'; | |||
meta.messpunkt(12).Value = 'Pumpenbypass'; | |||
meta.messpunkt(13).Value = 'Dosierventilstellung'; | |||
meta.messpunkt(14).Value = 'Brühgruppendrossel'; | |||
meta.messpunkt(15).Value = 'Heizleistung PWM'; | |||
% Speicherung der Messwerte mit zugehörigem Zeitstempel | |||
messdaten.data = app.messwert_puffer; | |||
messdaten.time = app.zeitstempel_messung; | |||
% es soll unterschieden werden, ob es sich um eine manuelle oder automatische Datensicherung handelt (Kennzeichnung im Dateinamen) | |||
% dazu wurden die Variablen automatische_datensicherung und manuelle_datensicherung angelegt, die bei den Aurufen der Funktion auf 1 gesetzt werden | |||
% sollte weder eine automatische noch manuelle Datensicherung erwünscht sein, die Funkion aber aufgerufen wird, handelt es sich um die Sicherung bei | |||
% Schließung der App | |||
if app.automatische_datensicherung == 1 | |||
% wenn es sich um eine automatische Datensicherung handelt | |||
app.automatische_datensicherung = 0; % Rücksetzen auf 0 | |||
t = char(datetime); % Abspeichern der aktuellen Uhrzeit | |||
datei = [t(8:11) t(4:6) t(1:2) '_Time' t(13:14) '.' t(16:17) '.' t(19:20) '_automatisch']; %Festlegen des Dateinamens als "YYYYMMDD_Timehh.mm.ss_automatisch" | |||
% Achtung im Dateinamen kein ':' möglich! | |||
folder = 'C:\Users\Rohnen\Desktop\Labormaschine\application\Test GUI Multi-MCU\MCU_Test\20230525\Datensicherung'; % Festlegen des Speicherortes | |||
save_data = fullfile(folder, [datei '.mat']); % Erstellung des vollständigen Speicherpfades (inklusive Dateiname) | |||
save([save_data], 'meta', 'messdaten'); % Abspeicherung der Eingaben und Messwerte an erstelltem Speicherort | |||
elseif app.manuelle_datensicherung == 1 | |||
% wenn es sich um eine manuelle Datensicherung handelt | |||
app.manuelle_datensicherung = 0; % Rücksetzen auf 0 | |||
t = char(datetime); % Abspeichern der aktuellen Uhrzeit | |||
datei = [t(8:11) t(4:6) t(1:2) '_Time' t(13:14) '.' t(16:17) '.' t(19:20) '_manuell']; %Festlegen des Dateinamens als "YYYYMMDD_Timehh.mm.ss_manuell" | |||
%Achtung im Dateinamen kein ':' möglich! | |||
folder = 'C:\Users\Rohnen\Desktop\Labormaschine\application\Test GUI Multi-MCU\MCU_Test\20230525\Datensicherung'; %Festlegen des Speicherortes | |||
save_data = fullfile(folder, [datei '.mat']); % Erstellung des vollständigen Speicherpfades (inklusive Dateiname) | |||
save([save_data], 'meta', 'messdaten'); % Abspeicherung der Eingaben und Messwerte an erstelltem Speicherort | |||
else | |||
% Schließung der App | |||
% Die Eingaben im Reiter Start, die hier gespeichert wurden, werden beim nächsten Start der App wieder geladen | |||
folder = 'C:\Users\Rohnen\Desktop\Labormaschine\application\Test GUI Multi-MCU\MCU_Test\20230525\last_data'; % Festlegen des Speicherortes | |||
datei = ['last_data']; % Festlegen des Dateinamens | |||
save_data = fullfile(folder, [datei '.mat']); % Erstellung des vollständigen Speicherpfades (inklusive Dateiname) | |||
save([save_data], 'meta', 'messdaten'); % Abspeicherung der Eingaben und Messwerte an erstelltem Speicherort | |||
end | |||
app.Allgemeiner_Status.Text = char('Datensicherung abgeschlossen'); % Anzeige im allgemeinen Statusfeld | |||
= Melina Scherf, 14.05.2023, Messwertverarbeitung= | = Melina Scherf, 14.05.2023, Messwertverarbeitung= |
Version vom 24. Juli 2023, 23:34 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
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, Verarbeitungsfunktion BAS (Messwerte)
Verarbeitungsfunktion BAS Messwerte
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)
BAS Verarbeitung Schrittmotoren
%SCHRITTMOTOREN if contains(app.zeile_char_bas,'endDrossel') % wenn diese Antwort das Signalwort "endDrossel" enthält % dieses wird nach Beendigung des Schrittmotor-Stellens gesendet writeline(app.bas_platine, 'Schrittmotor.setStepperOff(drossel)'); % Stromlos-Schaltung des Schrittmotors app.zeile_char_bas = char('Schrittmotor.setStepperOff(drossel)'); end if contains(app.zeile_char_bas,'endBypass') % wenn diese Antwort das Signalwort "endBypass" enthält % dieses wird nach Beendigung des Schrittmotor-Stellens gesendet writeline(app.bas_platine, 'Schrittmotor.setStepperOff(Bypass)'); % Stromlos-Schaltung des Schrittmotors app.zeile_char_ssr = char('Schrittmotor.setStepperOff(Bypass)'); end
Datenspeicherung
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.
% Sicherung aller Messwerte und Eingaben der Oberfläche im Start-Reiter % automatische und manuelle Datensicherung können beide mittels dieser Funktion erfolgen app.Allgemeiner_Status.Text = char('Datensicherung gestartet'); % Anzeige im allgemeinen Statusfeld % Abspeicherung der Eingaben aus dem Start-Reiter meta.Datum = app.Datum.Value; meta.Bearbeiter = app.Bearbeiter.Value; meta.Messzyklus = app.Messzyklus.Value; meta.Bemerkung1 = app.Bemerkung1.Value; meta.Bemerkung2 = app.Bemerkung2.Value; meta.Bemerkung3 = app.Bemerkung3.Value; meta.Kaffeesorte = app.Kaffeesorte.Value; meta.Mahlgrad = app.Mahlgrad.Value; meta.Kaffeemenge = app.Kaffeemenge.Value; meta.Tamperdruck = app.Tamperdruck.Value; % Überschriften der jeweiligen Messwerte meta.messpunkt(1).Value = 'Boilertemperaturwert'; meta.messpunkt(2).Value = 'Frischwassertemperatur'; meta.messpunkt(3).Value = 'Mischwassertemperatur'; meta.messpunkt(4).Value = 'Boilerdruck'; meta.messpunkt(5).Value = 'Leitfähigkeit'; meta.messpunkt(6).Value = 'Füllstand'; meta.messpunkt(7).Value = 'Durchfluss'; meta.messpunkt(8).Value = 'Pulse vom Flowmeter'; meta.messpunkt(9).Value = 'Brühgruppenwassertemperatur'; meta.messpunkt(10).Value = 'Brühgruppendruck'; meta.messpunkt(11).Value = 'Pumpenansteuerung'; meta.messpunkt(12).Value = 'Pumpenbypass'; meta.messpunkt(13).Value = 'Dosierventilstellung'; meta.messpunkt(14).Value = 'Brühgruppendrossel'; meta.messpunkt(15).Value = 'Heizleistung PWM'; % Speicherung der Messwerte mit zugehörigem Zeitstempel messdaten.data = app.messwert_puffer; messdaten.time = app.zeitstempel_messung; % es soll unterschieden werden, ob es sich um eine manuelle oder automatische Datensicherung handelt (Kennzeichnung im Dateinamen) % dazu wurden die Variablen automatische_datensicherung und manuelle_datensicherung angelegt, die bei den Aurufen der Funktion auf 1 gesetzt werden % sollte weder eine automatische noch manuelle Datensicherung erwünscht sein, die Funkion aber aufgerufen wird, handelt es sich um die Sicherung bei % Schließung der App if app.automatische_datensicherung == 1 % wenn es sich um eine automatische Datensicherung handelt
app.automatische_datensicherung = 0; % Rücksetzen auf 0 t = char(datetime); % Abspeichern der aktuellen Uhrzeit datei = [t(8:11) t(4:6) t(1:2) '_Time' t(13:14) '.' t(16:17) '.' t(19:20) '_automatisch']; %Festlegen des Dateinamens als "YYYYMMDD_Timehh.mm.ss_automatisch" % Achtung im Dateinamen kein ':' möglich! folder = 'C:\Users\Rohnen\Desktop\Labormaschine\application\Test GUI Multi-MCU\MCU_Test\20230525\Datensicherung'; % Festlegen des Speicherortes save_data = fullfile(folder, [datei '.mat']); % Erstellung des vollständigen Speicherpfades (inklusive Dateiname) save([save_data], 'meta', 'messdaten'); % Abspeicherung der Eingaben und Messwerte an erstelltem Speicherort elseif app.manuelle_datensicherung == 1 % wenn es sich um eine manuelle Datensicherung handelt
app.manuelle_datensicherung = 0; % Rücksetzen auf 0 t = char(datetime); % Abspeichern der aktuellen Uhrzeit datei = [t(8:11) t(4:6) t(1:2) '_Time' t(13:14) '.' t(16:17) '.' t(19:20) '_manuell']; %Festlegen des Dateinamens als "YYYYMMDD_Timehh.mm.ss_manuell" %Achtung im Dateinamen kein ':' möglich! folder = 'C:\Users\Rohnen\Desktop\Labormaschine\application\Test GUI Multi-MCU\MCU_Test\20230525\Datensicherung'; %Festlegen des Speicherortes save_data = fullfile(folder, [datei '.mat']); % Erstellung des vollständigen Speicherpfades (inklusive Dateiname) save([save_data], 'meta', 'messdaten'); % Abspeicherung der Eingaben und Messwerte an erstelltem Speicherort
else % Schließung der App % Die Eingaben im Reiter Start, die hier gespeichert wurden, werden beim nächsten Start der App wieder geladen
folder = 'C:\Users\Rohnen\Desktop\Labormaschine\application\Test GUI Multi-MCU\MCU_Test\20230525\last_data'; % Festlegen des Speicherortes datei = ['last_data']; % Festlegen des Dateinamens save_data = fullfile(folder, [datei '.mat']); % Erstellung des vollständigen Speicherpfades (inklusive Dateiname) save([save_data], 'meta', 'messdaten'); % Abspeicherung der Eingaben und Messwerte an erstelltem Speicherort
end app.Allgemeiner_Status.Text = char('Datensicherung abgeschlossen'); % Anzeige im allgemeinen Statusfeld
Melina Scherf, 15.05.2023, Datensicherung
function datensicherung(app, ~, ~) % Sicherung aller Messwerte und Eingaben der Oberfläche im Start-Reiter % automatische und manuelle Datensicherung können beide mittels dieser Funktion erfolgen app.Allgemeiner_Status.Text = char('Datensicherung gestartet'); % Anzeige im allgemeinen Statusfeld % Abspeicherung der Eingaben aus dem Start-Reiter meta.Datum = app.Datum.Value; meta.Bearbeiter = app.Bearbeiter.Value; meta.Messzyklus = app.Messzyklus.Value; meta.Bemerkung1 = app.Bemerkung1.Value; meta.Bemerkung2 = app.Bemerkung2.Value; meta.Bemerkung3 = app.Bemerkung3.Value; meta.Kaffeesorte = app.Kaffeesorte.Value; meta.Mahlgrad = app.Mahlgrad.Value; meta.Kaffeemenge = app.Kaffeemenge.Value; meta.Tamperdruck = app.Tamperdruck.Value; % Überschriften der jeweiligen Messwerte meta.messpunkt(1).Value = 'Boilertemperaturwert'; meta.messpunkt(2).Value = 'Frischwassertemperatur'; meta.messpunkt(3).Value = 'Mischwassertemperatur'; meta.messpunkt(4).Value = 'Boilerdruck'; meta.messpunkt(5).Value = 'Leitfähigkeit'; meta.messpunkt(6).Value = 'Füllstand'; meta.messpunkt(7).Value = 'Durchfluss'; meta.messpunkt(8).Value = 'Pulse vom Flowmeter'; meta.messpunkt(9).Value = 'Brühgruppenwassertemperatur'; meta.messpunkt(10).Value = 'Brühgruppendruck'; meta.messpunkt(11).Value = 'Pumpenansteuerung'; meta.messpunkt(12).Value = 'Pumpenbypass'; meta.messpunkt(13).Value = 'Dosierventilstellung'; meta.messpunkt(14).Value = 'Brühgruppendrossel'; meta.messpunkt(15).Value = 'Heizleistung PWM'; % Speicherung der Messwerte mit zugehörigem Zeitstempel messdaten.data = app.messwert_puffer; messdaten.time = app.zeitstempel_messung; % es soll unterschieden werden, ob es sich um eine manuelle oder automatische Datensicherung handelt (Kennzeichnung im Dateinamen) % dazu wurden die Variablen automatische_datensicherung und manuelle_datensicherung angelegt, die bei den Aurufen der Funktion auf 1 gesetzt werden % sollte weder eine automatische noch manuelle Datensicherung erwünscht sein, die Funkion aber aufgerufen wird, handelt es sich um die Sicherung bei % Schließung der App if app.automatische_datensicherung == 1 % wenn es sich um eine automatische Datensicherung handelt
app.automatische_datensicherung = 0; % Rücksetzen auf 0 t = char(datetime); % Abspeichern der aktuellen Uhrzeit datei = [t(8:11) t(4:6) t(1:2) '_Time' t(13:14) '.' t(16:17) '.' t(19:20) '_automatisch']; %Festlegen des Dateinamens als "YYYYMMDD_Timehh.mm.ss_automatisch" % Achtung im Dateinamen kein ':' möglich! folder = 'C:\Users\Rohnen\Desktop\Labormaschine\application\Test GUI Multi-MCU\MCU_Test\20230525\Datensicherung'; % Festlegen des Speicherortes save_data = fullfile(folder, [datei '.mat']); % Erstellung des vollständigen Speicherpfades (inklusive Dateiname) save([save_data], 'meta', 'messdaten'); % Abspeicherung der Eingaben und Messwerte an erstelltem Speicherort elseif app.manuelle_datensicherung == 1 % wenn es sich um eine manuelle Datensicherung handelt
app.manuelle_datensicherung = 0; % Rücksetzen auf 0 t = char(datetime); % Abspeichern der aktuellen Uhrzeit datei = [t(8:11) t(4:6) t(1:2) '_Time' t(13:14) '.' t(16:17) '.' t(19:20) '_manuell']; %Festlegen des Dateinamens als "YYYYMMDD_Timehh.mm.ss_manuell" %Achtung im Dateinamen kein ':' möglich! folder = 'C:\Users\Rohnen\Desktop\Labormaschine\application\Test GUI Multi-MCU\MCU_Test\20230525\Datensicherung'; %Festlegen des Speicherortes save_data = fullfile(folder, [datei '.mat']); % Erstellung des vollständigen Speicherpfades (inklusive Dateiname) save([save_data], 'meta', 'messdaten'); % Abspeicherung der Eingaben und Messwerte an erstelltem Speicherort
else % Schließung der App % Die Eingaben im Reiter Start, die hier gespeichert wurden, werden beim nächsten Start der App wieder geladen
folder = 'C:\Users\Rohnen\Desktop\Labormaschine\application\Test GUI Multi-MCU\MCU_Test\20230525\last_data'; % Festlegen des Speicherortes datei = ['last_data']; % Festlegen des Dateinamens save_data = fullfile(folder, [datei '.mat']); % Erstellung des vollständigen Speicherpfades (inklusive Dateiname) save([save_data], 'meta', 'messdaten'); % Abspeicherung der Eingaben und Messwerte an erstelltem Speicherort
end app.Allgemeiner_Status.Text = char('Datensicherung abgeschlossen'); % Anzeige im allgemeinen Statusfeld
Melina Scherf, 14.05.2023, Messwertverarbeitung
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:
- Boilertemperatur -> MWP
- Frischwassertemperatur -> MWP
- Mischwassertemp -> MWP
- Boilerdruck -> MWP
- Wasserleitfähigkeit -> MWP
- Füllstand -> BAS
- Durchfluss -> BAS
- Pulse Flowmeter -> BAS
- Temperatur hinter der Wasserwendel/ vor der Brühgruppe -> MWP
- Druck in der Brühgruppe -> MWP
- Pumpensteuerspannung
- Bypass-Stellung
- Dosierventilstellung
- Brühgruppendrossel
- 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, Verarbeitungsfunktion MWP
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, simulierte Messwerte starten
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, Verbinden
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.