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


von Stefan G. (Gast)


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

von Sascha Rother (Gast)


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

von Stefan G. (Gast)


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

von Sascha Rother (Gast)


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.

von Stefan G. (Gast)


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;
}
}

von Sascha Rother (Gast)


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.

von Sascha Rother (Gast)


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

von Stefan G. (Gast)


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...

von Sascha Rother (Gast)


Lesenswert?

Hallo Stefan!

Da hast du natürlich Recht.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.