PT1-Regelstrecke mit Arduino programmieren

Hi Leute,

und zwar arbeite ich schon länger an einem Projekt.
Ich gebe an den Port A0 eine Spannung von 0-5V und möchte über eine Regelstrecke am Ausgang dann auch wieder 0-5V abgreifen nur über eine PT1-Strecke zeitverzögert.

Mein Problem ist allerdings, dass ich nicht genau weiß, wie ich diese Regelstrecke programmieren soll.

Hoffe ihr könnt mir helfen :wink:

Danke!

Strobelix:
und zwar arbeite ich schon länger an einem Projekt.
Ich gebe an den Port A0 eine Spannung von 0-5V und möchte über eine Regelstrecke am Ausgang dann auch wieder 0-5V abgreifen nur über eine PT1-Strecke zeitverzögert.

An welchen Ausgang möchtest du denn die Spannung ausgeben ?

Ist dir bekannt, dass der Arduino nur "High oder Low" also 5 Volt oder 0 Volt ausgeben kann.
Auf Wunsch geht das auch per PWM.

Alles andere musst du extern lösen.

Das Ausgangssignal gebe ich über ein PWM-Signal aus. Die externe Beschaltung habe ich alles.
Mir fehlt nur die Programmierung der Regelstrecke, dass mein Eingangssignal zeitverzögert hinten am Ausgang abgreifen kann.

Hast du denn eine Formel für das PT1 Glied?

Das kann man oft sehr leicht in code umsetzen

Es gibt eben viele Formeln für eine PT1-Strecke und da weiß ich eben nicht genau wie ich die alle umsetzen soll

Ein PT1-Glied ist - elektrisch gesehen - einfach ein Tiefpass 1.Ordnung.

Kann man z.B. so programmieren:

/*************************************************************************************************
** Funktion Filtern()  by GuntherB   2014				**
**************************************************************************************************
** Bildet einen Tiefpassfilter (RC-Glied) nach.							**
** Tau [ms] 							**
** Periode = Aufruf alle Periode [ms]								**
**  												**
**  											   	**
**  Input: FiltVal der gefilterte Wert, NewVal der neue gelesene Wert; FF Filterfaktor, Periode	**
**  Output:	FiltVal										**
**  genutzte Globale Variablen: 	keine							**
**************************************************************************************************/
void Filtern(float &FiltVal, int NewVal, unsigned long Periode, unsigned long Tau){
  static  unsigned long lastRun = 0;
  unsigned long FF = Tau / Periode;
  if (millis()-lastRun < Periode) return;
  lastRun = millis();
  FiltVal= ((FiltVal * FF) + NewVal) / (FF +1);  
}

Okay vielen Dank!

Und das soll mein Eingangssignal zeitverzögert am Ausgang wiedergeben?!

Nicht zeitverzögert!

Über ein PT1 Glied gefiltert. So wie gewünscht.

Das macht aus einem Signal mit vielen Störungen ein glatteres Signal. Oder aus einem Rechtecksprung einen sanften Anstieg.

Eine reine Zeitverzögerung geht anders, hat aber mit PT1 nichts zu tun.

Okay jetzt habe ich es geblickt :smiley:

Vielen Dank mal..ich versuche es mal so zu programmieren und melde mich dann wieder!

Eine reine Zeitverzögerung könntest du so machen:

const int AD_Pin = A3;
const int PWM_Pin = 13;
byte SigBuf[256];   //Signalspeicher (Ringspeicher mit 256 Speicherplätzen
byte Zeiger = 0;    //Zeiger auf Signalspeicher. Byte kann nur 0..255 annehmen, d.h. Überlaufbehandlung ist automatisch

void setup() {  
  Serial.begin(115200);
  Serial.print("IN\t"); Serial.print("OUT\t\n"); 
  for (int i = 0; i < 256; i++)SigBuf[i]=0;  // Signalbuffer auf Null initialisieren
}

void loop() { 
  static  unsigned long lastRun = 0;
  if (millis()-lastRun < 100) return; // Abtastung alle 100ms
  SigBuf[Zeiger] = analogRead(AD_Pin)/4;  // AD-Wert auf 256 Bit reduzieren, da PWM nur 256 WErte ausgeben kann
  analogWrite(PWM_Pin, SigBuf[Zeiger-20]);
  Serial.print(SigBuf[Zeiger]); Serial.print("\t");      // Serielle Ausgabe Eingangswert
  Serial.print(SigBuf[Zeiger-20]); Serial.print("\t\n"); // Serielle Ausgabe Wert von vor 20 Abtastungen (2Sek)
  Zeiger++;  
}

Okay..bei diesem Programm wird quasi mein Eingangssignal nur zeitverzögert am Ausgang als PWM-Signal ausgegeben?!

Für meine Anwendung hast du aber mit dem ersten Programm den richtigen Vorschlag gebracht.
Ich muss über ein PTx Glied mein Eingangssignal an den Ausgang geben. Da habe ich mich für eine PT1-Strecke entschieden, also passt glaub dein erster Vorschlag oder sehe ich das falsch?

Das siehst du richtig.

die zuerst vorgeschlagene Funktion stellt ein PT1-Glied dar.

digitale Filter leben davon, dass sie regelmäßig aufgerufen werden.
am besten du packst die AD-Scan mit in die Funktion rein, oder du nimmst den Teil “if (millis()-lastRun < Periode) return;” aus der Funktion raus und stellst Funktion und analogRead in eine Zeitscheibe.

Selbstredend, dass keinerlei delays verwendet werden dürfen.

Also brauche ich ja den ersten Vorschlag!

Wie meinst du das, mit Funktion und analogRead in eine Zeitscheibe?

Mit “Zeitscheibe” meint er, dass das Filtern um so “richtiger” ist, je regelmäßiger deas Ganze gerechnet wird.
Das macht bisher die Zeile
  if (millis()-lastRun < Periode) return false; Wenn zu früh, erstmal nichts machen.
Hab mir mal erlaubt, das Messen in das Filtern zu integrieren:

/**********************************************************
** Funktion MessenUndFiltern()  (c) GuntherB   2014 - 2016 
***********************************************************
** Bildet einen Tiefpassfilter (RC-Glied) nach.  
** 
** Kanal: der Messkanal ( A0 ... ) 
** Tau  :[ms]   FilterFaktor            
** Periode:[ms] neuer Wert alle Periode   
**  
** FiltVal (In/Out) der gefilterte Wert 
*   
**  return: true wenn nach Ablauf der Periode FiltVal einen neuen Wert hat  
************************************************************/
bool PT1Messung(float &FiltVal, byte Kanal, unsigned long Periode, unsigned long Tau){
  static  unsigned long lastRun = 0;
  unsigned long FF = Tau / Periode;
  unsigned int neuWert = analogRead(Kanal);
  if (millis()-lastRun < Periode) return false;
  if (lastRun == 0) FiltVal = neuWert; 
  FiltVal= ((FiltVal * FF) + neuWert) / (FF+1); 

  lastRun = millis();
  return true;
}
void setup() { }

const byte PWMOut = 9;
const byte AnalogIn = A0;

void loop() {
  static float FiltVal;
  if (PT1Messung(FiltVal, AnalogIn, 10, 1000) )
     analogWrite(PWMOut, (int)FiltVal / 4) ; // PWM auf 0 .. 255 skalieren  
}

nur compiliert, nicht getestet.

Eigentlich braucht es das if in loop nicht, denn den gleichen Wert nochmal überschreiben sollte bei
analogWrite nicht viel ausmachen…

Okay…ich bin grad nicht ganz auf der Höhe sorry :smiley:

hilfreich wäre es wenn ich wüsste was hier jede Programmzeile genau macht, weil hier Sachen dabei sind, die mir gerade noch ein wenig fremd sind! Würde das gehen, dass du mir das dahinter schreibst?! :slight_smile:
Sorry…

So etwa?

  FiltVal= ((FiltVal * FF) + neuWert) / (FF+1); // Tiefpass-Filter

Wie in c generell programmiert wird und was Variable und Datentypen sind, ist dir ungefähr klar?

Falls ja, kannst du z.B. anhand von Zahlenwerten nachvollziehen, was hier gerechnet wird?

Falls nein:
= ist eine Zuweisung des Ergebnisses des Ausdrucks rechts an die Variable links
/ ist das Divisionszeichen ...
(sorry, kann schlecht abschätzen, wie tief deine Frage zu verstehen ist... )

Sonst such erstmal in der Referenz Informationen zu den verwendeten Funktionen

millis()
analogRead()
analogWrite()

Kurz das wichtigste erklärt:

Es wird eine Funktion PT1Messung() definiert.
in der wird alle Periode millisekunden ein Wert aus dem AD-Pin Kanal gelesen und mit einem Tiefpass (PT1-Glied) mit der Grenzfrequenz 1/Tau gefiltert und in der Variablen FiltVal gespeichert.

if (millis()-lastRun < Periode) return false; Hier wird geprüft, ob die Zeit bis zur nächsten Berechnung schon gekommen ist. Wenn nicht, wird die Funktion sofort verlassen und false zurückgegeben.

FiltVal= ((FiltVal * FF) + neuWert) / (FF+1);
Das ist das PT1-Glied.
z.B.: Tau = 990ms; Periode = 10ms; => Filterfaktor FF = 99
Formel:
Neuer Filterwert = (alter Filterwert * 99+ neuer Messwert) / 100
Bei jeder Messung nehme ich 99% des alten Wertes und addiere den neuen dazu.
Das sorgt für eine starke Filterung, weil sich “ausreisser” nicht so stark bemerkbar machen.

/**********************************************************
** Funktion PT1Messung()  
***********************************************************
** Bildet einen Tiefpassfilter (RC-Glied) nach. 
**
** Kanal: der Messkanal ( A0 ... )
** Tau  :[ms]   Tau des Tiefpasses           
** Periode:[ms] neuer Wert alle Periode ms  (Tau >> Periode!!)  
** 
** FiltVal (In/Out) der gefilterte Wert
*   
**  return: true wenn nach Ablauf der Periode FiltVal einen neuen Wert hat 
************************************************************/
bool PT1Messung(float &FiltVal, byte Kanal, unsigned long Periode, unsigned long Tau){
    static  unsigned long lastRun = 0;
    unsigned long FF = Tau / Periode;  // 
  if (millis()-lastRun < Periode) return false;  
  unsigned int neuWert = analogRead(Kanal);
  FiltVal= ((FiltVal * FF) + neuWert) / (FF+1);

  lastRun = millis();
  return true;
} // ende Funktion PT1Messung

//*******  Pindefinitionen   **************
  const byte PWMOut = 9;
  const byte AnalogIn = A0;

void setup() {
   pinMode(PWMOut, OUTPUT); 
}

void loop() {
  static float FiltVal;
  if (PT1Messung(FiltVal, AnalogIn, 10, 1000) )
     analogWrite(PWMOut, (int)FiltVal / 4) ; // PWM auf 0 .. 255 skalieren 
}

Wenn diese Erklärung zu speziell sein sollte, dann solltest du dich erst mal in die Grundlagen einarbeiten.

@Michael_X:
Die Zeile " if (lastRun == 0) FiltVal = neuWert; " funktioniert nicht, weil lastrun nur nach dem Reset Null ist.

So, und ich mach jetzt Feierabend.

Also mir sind Variablen und Datentypen schon bekannt keine Sorge :smiley:
Bin nur nicht ganz dahinter gekommen, wie genau das Programm arbeitet aber durch die Erklärung ist das klar!

Vielen Dank euch beiden..ich versuche mal das ganze umzusetzen und gebe dann natürlich Rückmeldung!

Schönen Feierabend und nochmal Danke!!! :wink:

@GuntherB:

Die Zeile " if (lastRun == 0) FiltVal = neuWert; " funktioniert nicht, weil lastrun nur nach dem Reset Null ist.

Nur dafür ist diese Zeile gedacht. Bei Start soll der Anfangswert sofort übernommen werden. Und nicht langsam ab 0 hochgefiltert werden.
Da filtVal auch ausserhalb der Funktion existiert, kann man natürlich auch andere "Reset"-Möglichkeiten realisieren. Hier gibt es z.Zt. nur den Reset-Button.

Schönen Feierabend

Das Programm läuft, alles gut nur möchte ich versuchen es über die konkreten Formeln einer PT1 Regelung zu programmieren. Es gibt ja verschiedene Formeln für die zeitdiskrete Programmierung, ich habe diese hier mal gefunden, die für mich auch erklärbar ist :smiley:

X(n) = X(n-1) * (1 - Ta/T) + Y(n) * Ta/(T*V)

X(n) = neuer Ausgangswert
X(n-1) = vorhergehender Wert
Y(n) = Eingangswert
Ta = Abtastzeit
T = Zeitkonstante
V = Verstärkung

oder gibt es wie für den PID-Regler auch für den PT1 Regler eine Bibliothek?

Hoffe ihr könnt mir nochmal weiterhelfen :wink: