Regeln einer Stromquelle mit SCPI Befehlen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Mario.
User
Beiträge: 4
Registriert: Donnerstag 20. Mai 2021, 07:25

Hallo,
ich benötige etwas Hilfe bei der Umsetzung einer Leistungsregelung eines Netzteiles.

Ausgangslage:
Ich möchte mit einem Rhode & Schwarz HMP4040 Netzteil eine definierte Leistung -z.B. 20W- in ein Bauelement einbringen und mit einem Thermometer den Temperaturverlauf ermitteln.
Dazu ist bereits ein Python-Code vorhanden (im Internet gefunden und für meine Zwecke umgestaltet) mit dem ich die Temperatur eines Voltcraft Thermologgers auslese. Das funktioniert soweit. Momentan betreibe ich es so, dass ich anfangs einmalig Spannung & Strom am Netzteil einstelle und den Versuch starte. Durch die Erwärmung des Bauteils ändert sich jedoch dessen Widerstand, was im Konstant-Spannungs-Betrieb eine verminderten Stromfluss zur Folge hat. Die Leistungseinbringung (P=U*I) sinkt also.

Problem bei der Implementierung der Leistungsregelung:
Abseits der Grundlagen habe ich nur wenig Ahnung im Umgang mit jeglicher Programmiersprache, dazu zählt auch die Kommunikation mit Standardgeräten wie das HMP4040 Netzteil über eine RS232 Schnittstelle. Bei der Suche nach vergleichbaren Beispielen werden einem zwar allerlei Zusatzpackete vorgeschlagen:
-VISA, [ https://pyvisa.readthedocs.io/en/latest ... alues.html ]
-RSinstruments, [ https://pypi.org/project/RsInstrument/ ]
-Easy SCPI, [ https://pypi.org/project/easy-scpi/ ]
jedoch verwirren mich die Beispiele darin mehr als es mir hilft.

Mit dem Thermologger kommuniziere ich nur über das Modul "serial", funktioniert diese Methode auch bei dem Netzteil? Im Benutzerhandbuch sind die SCPI Befehle aufgeführt, ich bin aber noch nicht sicher ob ich die Struktur wie man die Befehle übersendet richtig verstehe.
Programierhandbuch PDF:
[ https://cdn.rohde-schwarz.com/pws/dl_do ... _en_01.pdf ]


Darin die für mich wichtigen Befehle:
-Werte auslesen
MEASure[:SCALar]:CURRent [:DC]?
MEASure[:SCALar][:VOLTage] [:DC]? #Wieso steht hier das VOLTage in eckigen Klammern? In eckigen Klammern oder geschweiften sind laut Handbuch optionale Parameter, erklärt auf Seite 9 .

-Werte setzten
[SOURce:]VOLTage[:LEVel][:IMMediate][:AMPLitude] {<voltage>| MIN | MAX}}
[SOURce:]CURRent[:LEVel][:IMMediate][:AMPLitude] {<current>| MIN | MAX}

-Bedienung sowohl über Python-Code, als auch über analoge Bedienknöpfe
SYSTem:MIX


Da ich das Netzteil nicht zuhause habe und jederzeit rumprobieren kann wollte ich zumindest die groben Code-Fehler im voraus vermeiden. Dazu mein Vorschlag wie ich es umsetzen würde:

Code: Alles auswählen

#Module importieren
import time
import serial

#Schnittstelle öffnen 
Serial = serial.Serial('COM6', 9600,timeout=1, parity=serial.PARITY_NONE)

#Anfangswerte über bekannten Anfangswiderstand ,zB. 5ohm festelgen, das geschieht außerhalb der Messschleife
# I=sqrt(P/R)
#U=P/I
Serial.write(b"CURR 2.000")
Serial.write(b"VOLT 10.000")


#Abrage des aktuellen Zustandes, Regelung der Strom bzw. Spannungswerte, das steht innerhalb der Messschleife und wird sekündlich ausgeführt
I_aktuell = Serial.write(b"MEAS CURR?")
U_aktuell= Serial.write(b"MEAS VOLT?")

"""
Festlegen des aktuellen Widerstandswertes, um die neuen Werte zu bestimmen. Ob es auf einen Konstant-Spannungsbetrieb oder Konstant-Strombetrieb 
hinausläuft muss ich am Netzteil probieren, und entsprechend den anderen Wert im Python-Code ändern. Hier gehe ich von einem Konstant-Spannungsbetrieb 
aus und ändere die Stromstärke.
"""
R_aktuell = U_aktuell / I_aktuell
I_neu = (20/R_aktuell)**0,5                    #Formel P=I^2 * R auf I umgestellt

#Neuen Stromwert an das Netzteil senden
Serial.write(b"CURR I_neu")


Nun die Frage: Funktioniert das so, bzw. gibt es hier grundsätzliche Fehler in der Syntax?
Vielen Dank im Voraus
Mario
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Fließkommazahlen schreibt man in eigentlich allen Programmiersprachen mit Punkt, nicht mit Komma. Dein Wurzelziehen ist also falsch. Es erzeugt eine Zahl hoch 0, also 1, und mit dem Komma wird ein sogenanntes Tupel draus.

Das fällt nicht weiter auf, weil dein Kommando danach diesen Wert gar nicht nutzt. Man muss den berechneten Wert in den String bekommen. Das Thema dazu in der Python Dokumentation das du durcharbeiten solltest heißt „String formatting“ und an Python 3.6 wird es deutlich einfacher mit f-strings.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Wenn Du irgendwelche Befehle über die serielle Schnittstelle schickst, müssen die Normalerweise mit einem Zeileende-Zeichen abgeschlossen werden.
write liefert die Anzahl der Bytes, die geschrieben wurden, das ist eher nicht I_aktuell bzw. U_aktuell. Du mußt die Antwort per serial.readline lesen und parsen.
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Nachtrag: der nächste Stolperstein sind die fehlenden Zeilenenden. Ich wette das Protokoll verlang die. Jede Instruktion muss mit \r\n abgeschlossen werden.
Mario.
User
Beiträge: 4
Registriert: Donnerstag 20. Mai 2021, 07:25

Vielen Dank für die Rückmeldungen. Ich habe die Zeichen für das Zeilenende hinzugefügt. Jetzt ist mir auch klarer, dass ich zwar die Befehle mit .write(b"SCPI_BEFEHL") an das Instrument sende und dieses die Befehle ausführt, dass Auslesen aber anscheinend extra mit .readline() stattfinden muss. Ich werde es bei Gelegenheit ausprobieren um zu schauen ob es so klappt. Es wäre echt einfacher wenn die Hersteller der Geräte mal ein Idiotensicheres Beispiel für solche Standardoperationen mitliefern würden.

Code: Alles auswählen


#Notwendige Module importieren
import serial

#Schnittstelle öffnen 
Serial = serial.Serial('COM6', 9600,timeout=1, parity=serial.PARITY_NONE)


"""
Anfangswerte über bekannten Anfangswiderstand ,zB. 5ohm festelgen,
das geschieht außerhalb der Messschleife.
I=wurzel(P/R) , U=P/I
"""
Serial.write(b"INST OUT1\r\n")      #Auswahl des Kanal 1 am Instrument
Serial.write(b"CURR 2.000\r\n")
Serial.write(b"VOLT 10.000\r\n")

"""
Abfrage des aktuellen Zustandes & Regelung der Strom bzw. Spannungswerte. 
Das steht innerhalb der Messschleife und wird sekündlich ausgeführt.
"""
Serial.write(b"MEAS CURR?\r\n")     #Instrument auffordern Strom auszulesen
I_aktuell = Serial.readline()       #Antwort des Instrumentes auslesen

                                                                                #Wird jetzt eine Zeitverzögerung benötigt?                                    

Serial.write(b"MEAS VOLT?\r\n")     #Instrument auffordern Spannung auszulesen
U_aktuell = Serial.readline()       #Antwort des Instrumentes auslesen


"""
Festlegen des aktuellen Widerstandswertes, um die neuen Werte zu bestimmen.
Ob es auf einen Konstant-Spannungsbetrieb oder Konstant-Strombetrieb 
hinausläuft muss ich am Netzteil probieren, und entsprechend den anderen Wert 
im Python-Code ändern. Hier gehe ich von einem Konstant-Spannungsbetrieb aus 
und ändere die Stromstärke.
"""
R_aktuell = U_aktuell / I_aktuell
I_neu = str((20/R_aktuell)**0.5)        #Formel P=I^2*R auf I umgestellt
                                        #Umwandlung in String
#Neuen Stromwert an das Netzteil senden
Serial.write(b"CURR I_neu\r\n")


__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Dieses SCIP ist eine absolute Katastrophe. Das scheint ein Relikt einer Konsolen-Nutzung zu sein, wo man für Menschen irgendwie gut lesbare Kommandos haben wollte, aber alles ist konfus und kacke. Wirklich schlecht. Naja. Dafür sind die Geräte teuer 🤪
Mario.
User
Beiträge: 4
Registriert: Donnerstag 20. Mai 2021, 07:25

Ich konnte es kürzlich probieren und dabei auch einige kleinere Fehler beseitigen. Nun lässt sich das Netzteil auslesen( Strom und Spannung) und ich kann auch manuell einen Befehl senden um die Spannung einzustellen. Damit läuft die Schleife (Leistungs"regelung" + Temperaturmessung) so wie sie soll durch. Das funktioniert aber eben nur wenn ich manuell einen Wert für die Spannung in den Python Code eingebe.

Zur Verdeutlichung, das funktioniert:
Ich gebe manuell 6.789 ein. Es wird der Wert 6,789 an das Netzteil gesendet und dieses stellt die Spannung 6,789V ein.

Code: Alles auswählen

Serial2.write(b"VOLT 6.789\r\n")
Folgendes klappt nicht:

Code: Alles auswählen

#Widerstand ermitteln, (Optional verwendbar, nur zur Information)
        R_aktuell = round(float(U_aktuell) / float(I_aktuell) , 3)
        print("R_aktuell= " + str(R_aktuell))
        
        # U=P/I , Umwandlung in String, Runden auf 3 Dezimalstellen
        U_neu = str(round(((Leistung_soll)/float(I_aktuell)), 3))                                        
        print("U_neu= " + (U_neu))
        
        #Neuen Spannungswert an das Netzteil senden
        Serial2.write(b"VOLT U_neu\r\n")
Wenn ich die Spannung von dem Netzteil auslese bekomme ich einen String. Daher dachte ich ich muss auch einen verwenden, wenn ich etwas sende.
Hier wird der berechnete Wert aber nicht übergeben. Nach dem Senden habe ich die Schnittstelle erneut ausgelesen und der Spannungswert ist noch immer der vorherige.

Es tritt sogar ein weiterer Effekt ein, der bei dem nächsten Schleifendurchgang einen Fehler erzeugt. Beim Start der nächsten Schleife ist der Strom I_aktuell auf einmal 0. Was bei der Widerstandsberechnung einen Fehler auslöst. Dieser Fehler (I_aktuell wird 0) tritt im oberen funktionierenden Beispiel nicht auf. Da wird die Schleife durchlaufen und wiederholt. Da aber der frühere Fehler schon in der Übergabe des Spannungswertes auftritt möchte ich diesen erstmal beseitigen, vielleicht wird so die Nulldivision gar nicht erst verursacht.


Nun Die Frage: Wie übergebe ich den berechneten Wert U_neu dem Netzteil? Als String funktioniert es nicht, als Float auch nicht.
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Die Frage wurde bereits beantwortet: string formatting. Also zb

Code: Alles auswählen

f"VOLT {volt}"
Zu dem Thema gibt es aber auch viele Einfuehrungen und Doku.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

U_neu wird in Deinem String nicht automatisch substituiert. Das musst Du schon selbst machen (wurde aber bereits erwähnt). Ich gehe mal davon aus, dass die Software des Netzteils nur mit ascii zurechtkommt. Ungetestet:

Code: Alles auswählen

volt = 3.7
message = f"VOLT {volt} \r\n"
Serial2.write(bytes(message, encoding='ascii'))
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

U_aktuell und I_aktuell werden bereits beim Einlesen in floats umgewandelt, und nicht bei jeder Anwendung.
Gerundet wird erst bei der Ausgabe, und zwar über Formatierungsangaben. Klammern um Ausdrücke tragen manchmal zur Lesbarkeit bei, hier sind es für meine Geschmack zu viele.

Code: Alles auswählen

U_aktuell = float(...)
I_aktuell = float(...)

R_aktuell = U_aktuell / I_aktuell
U_neu = Leistung_soll / I_aktuell

print(f"R_aktuell= {R_aktuell:.3f}")
print(f"U_neu= {U_neu:.3f}")
Serial2.write(f"VOLT {U_neu:.3f}\r\n".encode("ascii"))
Mario.
User
Beiträge: 4
Registriert: Donnerstag 20. Mai 2021, 07:25

Ok, vielen Dank. Ich dachte erst mit String formatting ist nur gemeint, dass man es irgendwie in einen String umwandeln soll. Das muss ich mir nochmal in Ruhe anschauen.
Ich werde am Mittwoch wieder die Gelegenheit haben es auszuprobieren.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

Das ist auch damit gemeint. Bei der String-Formatierung lässt sich allerdings noch mehr machen. In der Dokumentation des string-Moduls der Standard Library finden sich Informationen zur Formatierungs-Syntax.
Antworten