mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik Problem mit PI-Regelung.


Autor: Stefan G. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,
ich ärgere mich immernoch mit meiner Regelung meines mittels PWM 
angesteuerten Ventils rum. Ich hatte dazu nochmal den Thread 
"Algorithmus fuer PID (Heizungsregelung)" gelesen, und bin schon etwas 
schlauer geworden. Mit meiner Problematik komme ich jedoch noch nicht 
richtig vorwärts.
Ich habe zunächst einmal eine Sprungantwort meiner Regelstrecke 
(bestehend aus Ventil und Durchflussmesser) aufgenommen, und mir so mit 
der Ziegler Nichols Methode die Parameter k_p (Verstärkung) und t_i 
(Verzugszeit) berechnet.
Nun habe ich folgende Regelroutine in meinem Programm:

// Regelabweichung e_k
      e_k = v_soll - v_ist;
// Differenzengleichung zur Berechnung der Stellgröße:
      u_k = u_k_alt + k_p*(e_k*(1+t_0/t_i)-e_k_alt);
      u_k_alt = u_k;
      e_k_alt = e_k;

zu den einzelnen Paramtern:
v_soll: Sollspannung die der Durchflussensor anzeigen soll (1-5V)
v_ist: Istspannung die der Durchflussensor anzeigt (1-5V)
t_0: Abtastzeit

Das größte Problem ist nun, daß ich nicht genau weiß, was ich mit der 
Stellgröße u_k anfangen soll...
Normalerweise müßte das doch die Größe sein, die ich auf meine Strecke 
(also auf mein Ventil) raufgebe, oder? Nun steuere ich mein Ventil ja 
mittels PWM an, muß ich dann den u_k Wert in eine entsprechende 
Pulsbreite übersetzen? D.h. wenn ich ein u_k von 5 Volt rausbekäme, 
entspräche das einer Pulsbreite von 100 %? In welchem Rahmen bewegt sich 
denn überhaupt u_k? Das ist mir nicht wirklich klar.
Die zweite Sache ist die Abtastzeit t_0. Was stellt die dar? Ist das die 
Zeit, wie oft ich v_ist Werte mit meinem A/D Wandler einlese?
Über ein par Tipps wäre ich sehr dankbar,
Gruß,
Stefan

Autor: Sascha Rother (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Stefan!

u_k ist wie du schon sagtest die Stellgröße. Innerhalb deines Programmes 
ist dies natürlich nur irgendeine Zahl vom Typ BYTE oder int oder was 
auch immer. Mit diesem Wert musst du jetzt deine PWM ansteuern. In der 
Regel (hier: Atmel) bedeutet nun ein Wert von 0 0% PWM-Ausgang und 255 
100% PWM-Ausgang. Wichtig ist, daß du die Stellgröße u_k begrenzt, so 
dass sie nicht kleiner als 0 und größer als 255 wird. Wenn dein Sollwert 
nicht erreicht werden aknn, so musst du zusätzlich die Integration 
unterbinden. Mit einem geeigneten Faktor und einem Offset kannst du 
natürlich dein u_k so skalieren, dass dein PWM-Ausgangssignal das mit 
dem Ventil macht, was du willst (Arbeitspunkt und Verstärkung 
sozusagen).
Bezüglich t_0 hast du Recht: es ist die Zeit, mit der du deine Werte 
einliest. Damit deine DZGL stimmt, musst du dafür sorgen, das diese 
Abtastzeit konstant ist, d.h. am besten mit Timer-Interrupts arbeiten. 
So hast du die Möglichkeit, t_0 exakt zu bestimmen und in deine 
Gleichung einfließen zu lassen.

Gruß, Sascha

Autor: Stefan G. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Sascha, erstmal danke für die Antwort. Die Sache ist mir nun schon 
etwas klarer.

>Mit einem geeigneten Faktor und einem Offset kannst du >natürlich dein u_k so 
skalieren, dass dein PWM->Ausgangssignal das mit dem Ventil macht, was du willst
>(Arbeitspunkt und Verstärkung sozusagen).

Was genau meinst du mit "Offset"?
Gruß,
Stefan

Autor: Sascha Rother (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Mit dem Offset könntest du den Arbeitspunkt deines Ventils bestimmen, 
z.B. wenn du beim Reglerausgang=0 das Ventil trotzdem etwas geöffnet 
haben musst, addierst du einfach einen Offset, bevor du den Wert an 
deine PWM übergibst.

Autor: Stefan G. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Alles klar, jetzt weiß ich was du mit Offset meintest. Ich habe das 
Programm nun mal fertiggeschrieben (benutze übrigens CodevisionAVR und 
ATmega323), jedoch klappt es noch nicht. Egal was für einen Sollwert ich 
vorgebe, die Pulsbreite wird immer auf 100% gesetzt und ich bekomme 
vollen Durchfluss (-> Ventil ganz auf -> Durchflussmesser zeigt 5 V an).
Ich poste hier mal das Programm mit einigen Erläuterungen (der 
interessante Teil kommt ganz unten). Vielleicht findet jemand einen 
Fehler:

#include <mega323.h>

// Standard Input/Output functions
#include <stdio.h>


#define ADC_VREF_TYPE 0x00
// Read the AD conversion result
unsigned int read_adc(unsigned char adc_input)
{
ADMUX=adc_input|ADC_VREF_TYPE;
// Start the AD conversion
ADCSR|=0x40;
// Wait for the AD conversion to complete
while ((ADCSR & 0x10)==0);
ADCSR|=0x10;
return ADCW;
}

// Variablendeklaration für die Ist-Spannung:
float v_ist;

/* Timer 2 overflow interrupt service routine
Bei jedem Overflow des Timers wird das A/D Ergebnis //eingelesen und 
v_ist berechnet (10 bit A/D Ergebnis auf 5 Volt "normiert"): */
interrupt [TIM2_OVF] void timer2_ovf_isr(void)
{
  v_ist = (5.0/1023)*read_adc(0);
}


void main(void)
{
// Declare your local variables here
// Die Regelparamter wurden von mir vorher mit Hilfe der
// Sprungantwort berechnet:

float e_k;    // Regeldifferenz
float e_k_alt;
float k_p = 0.723;  // Verstärkungsfaktor
float t_i = 7.343;  // Verzugszeit
float t_0 = 8.85;  // Abtastzeit
float u_k;        // Stellgröße
float u_k_alt;
char v_soll;
int stell;



// Input/Output Ports initialization
// Port A initialization
// Func0=In Func1=In Func2=In Func3=In Func4=In Func5=In Func6=In 
Func7=In
// State0=T State1=T State2=T State3=T State4=T State5=T State6=T 
State7=T
PORTA=0x00;
DDRA=0x00;

// Port B initialization
// Func0=In Func1=In Func2=In Func3=Out Func4=In Func5=In Func6=In 
Func7=In
// State0=T State1=T State2=T State3=1 State4=T State5=T State6=T 
State7=T
PORTB=0x08;
DDRB=0x08;

// Port C initialization
// Func0=In Func1=In Func2=In Func3=In Func4=In Func5=In Func6=In 
Func7=In
// State0=T State1=T State2=T State3=T State4=T State5=T State6=T 
State7=T
PORTC=0x00;
DDRC=0x00;

// Port D initialization
// Func0=In Func1=In Func2=In Func3=In Func4=In Func5=In Func6=In 
Func7=In
// State0=T State1=T State2=T State3=T State4=T State5=T State6=T 
State7=T
PORTD=0x00;
DDRD=0x00;

// Timer/Counter 0 initialization
// Clock source: System Clock
// Clock value: 460,800 kHz
// Mode: Phase correct PWM top=FFh
// OC0 output: Non-Inverted PWM
TCCR0=0x62;
TCNT0=0x00;
OCR0=0x00;

// Timer/Counter 1 initialization
// Clock source: System Clock
// Clock value: Timer 1 Stopped
// Mode: Normal top=FFFFh
// OC1A output: Discon.
// OC1B output: Discon.
// Noise Canceler: Off
// Input Capture on Falling Edge
TCCR1A=0x00;
TCCR1B=0x00;
TCNT1H=0x00;
TCNT1L=0x00;
OCR1AH=0x00;
OCR1AL=0x00;
OCR1BH=0x00;
OCR1BL=0x00;

// Timer/Counter 2 initialization
// Clock source: System Clock
// Clock value: TCCR2=0x07: 3600 Hz
//    TCCR2=0x06; 14400 Hz
//    TCCR2=0x05; 28800 Hz -> t_0 = 1/(28800/255) = 8,85 ms
// Mode: Normal top=FFh
// OC2 output: Disconnected
ASSR=0x00;
TCCR2=0x05;
TCNT2=0x00;
OCR2=0x00;

// External Interrupt(s) initialization
// INT0: Off
// INT1: Off
// INT2: Off
GICR|=0x00;
MCUCR=0x00;
MCUCSR=0x00;

// Timer(s)/Counter(s) Interrupt(s) initialization
TIMSK=0x00;

// USART initialization
// Communication Parameters: 8 Data, 1 Stop, No Parity
// USART Receiver: On
// USART Transmitter: On
// USART Mode: Asynchronous
// USART Baud rate: 115200
UCSRA=0x00;
UCSRB=0x18;
UCSRC=0x86;
UBRRH=0x00;
UBRRL=0x01;

// Analog Comparator initialization
// Analog Comparator: Off
// Analog Comparator Input Capture by Timer/Counter 1: Off
// Analog Comparator Output: Off
ACSR=0x80;
SFIOR=0x00;

// ADC initialization
// ADC Clock frequency: 115,200 kHz
// ADC Voltage Reference: AREF pin
ADMUX=ADC_VREF_TYPE;
ADCSR=0x85;

// Global enable interrupts
#asm("sei")

// Sollspannung über Terminal einlesen (48 entspricht dabei dem Offset 
zwischen ASCII und dezimal):
v_soll = (int) getchar()-48;

// Endlosschleife mit Regelroutine (was besseres ist mir nicht
// eingefallen):
while(1)
{

      // Regelabweichung e_k berechnen:
      e_k = v_soll - v_ist;

      // Stellgröße u_k berechnen:
      u_k = u_k_alt + k_p*(e_k*(1+t_0/t_i)-e_k_alt);

      // Stellgrößenbegrenzung:
      if (u_k > 5.0) u_k = 5.0;
      else if (u_k < 0.0) u_k = 0.0;

      // Stellgröße in entsprechenden PWM Wert umrechnen
      // (falls die benötigte Stellgröße 5 Volt beträgt,
      // wird OCR0 auf 255 gesetzt -> Ventil ganz auf):
      stell = (int) (255/5.0)*u_k;

      // OCR0 Register -> Pulsbreite setzen:
      OCR0 = stell;

      u_k_alt = u_k;
      e_k_alt = e_k;
}
}

Autor: Sascha Rother (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich hab jetzt dein Programm nicht durchgesehen, aber kann es sein, dass 
du keinen negativen Wirksinn hast? Wenn die Soll-Istwert-Abweichung 
positiv ist, muss natürlích dein Ventil weiter schließen.

Autor: Sascha Rother (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Was mit auffällt:

wenn t_0=8,85ms ist, dann musst du natürlich 0.00885 zuweisen und nicht 
8.85!!

die Regelroutine in der while(1) Schleife muss in die Interrupt-Routine 
timer2_ovf_isr, damit du einbe konstante Abtastzeit hast. So wird es 
nicht funktionieren!

Gruß, Sascha

Autor: Stefan G. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Sascha,
die Sache mit den Millisekunden und der while (1) Schleife habe ich 
jetzt verbessert. Aber das mit dem Wirksinn sehe ich genau andersrum: 
Wenn ich eine positive Regelabweichung habe (mein Sollwert also höher 
ist als mein Istwert), dann fließt ja noch zuwenig Gas durchs Ventil, es 
muß sich also weiter öffnen statt schließen...

Autor: Sascha Rother (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Stefan!

Da hast du natürlich Recht.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.