lauantai 23. huhtikuuta 2016

PID-säädin ja liukuluku

Koska kokonaisluvuilla tarkasti laskeminen on hiukan hankalaa, niin ratkaisu ongelmaan on liukuluvut (float). Analogiatulot ja –lähdöt käyttävät kokonaislukuja (tulo 10 bitin muunnos, lukualue 0 .. 1023 ja lähtö 8 bitin muunnos, lukualue 0 .. 255), mutta välissä olevassa signaalin ”jalostuksessa” voidaan käyttää liukulukuja, eli ottaa desimaalit mukaan.
Varsinkin teollisuudessa (erityisesti kemian) on paljon säätöpiirejä. Tuossa PID – termissä P tarkoittaa vahvistusta. Aluksi lasketaan asetusarvon ja mittausarvon erotus, tämä kerrotaan termillä P ja johdetaan säätöpiirin lähtöön. I tarkoittaa integroimista, eli ajan edetessä muutetaan pienin portain (jokin osa erotuksesta) muutetaan säätöpiirin lähtöarvoa joko kasvattaen tai vähentäen, aina tarpeen mukaan, kunnes asetusarvon ja mittausarvon erotus on nolla (0). Tällöin P:n vaikutus on nolla, sillä nolla kertaa jotain on nolla. D-termi tarkoittaa derivointia. Kun säätöpiirin laskenta havaitsee muutoksen erosignaalissa, aiheutetaan lähtöön hetkellinen muutos korjaavaan suuntaan. D-temi tulee kyseeseen lähinnä lämpötilansäädöissä, esim. uunien säädöissä, missä osa energiaa tarvitaan tuotteen lämmittämiseen ja osa itse uunin lämmittämiseen. D-termin vaikutus riippuu muutosnopeudesta ja muutoksen suuruudesta (ja tietysti myös viritysarvosta, eli millainen vaikutus sille on määritelty). Seuraavassa sovelluksessa D-termi jätetään pois.
Asetusarvo annetaan edelleen sarjaliikenteellä, mutta P-termi sekä I-termi syötetään analogiatulojen kautta trimmeripotentiometrien avulla. P-termin lukualue on 0 .. 10 ja I-termi vaikuttaa ajastimen kautta siihen, miten usein lähtöarvoa muutetaan. Jos vahvistus (P) on liian suuri tai integrointiaika (I) liian nopea, alkaa säätöpiiri värähdellä.


Edelliseen kytkinkaavaan tulee muutama lisäys, eli juuri nuo trimmerit.


Melko hyvä nyrkkisääntö säätöpiirin virittämiseksi on ensin saattaa piiri hiukan (siis juuri ja juuri) värähtelemään kasvattamalla vahvistusta. Tämä vahvistuksen arvo on lähtökohta lopulliselle arvolle. Tästä värähtelyn aiheuttamasta arvosta vähennetään 30% ja asetetaan se P:n arvoksi. Toinen talletettava tieto on värähtelyn jakson aika. Se puolitetaan. Jos esim. P saa arvoksi 2,7 on lopullinen arvo 0, 7 * 2,7 = 1,9. Jos värähtelyn jaksonaika on 2 sekuntia, on I:n arvo 1 sekunti, eli sekunnin välein korjataan lähtöä. Nämä säännöt ovat ainakin hyvä lähtökohta säätöpiirin virittämiseen.

Säätäessäni P-arvoa (=vahvistus * (asetusarvo - mittaus)), alkoi LEDi vilkkua noin 51 kohdalla. Tästä arvosta pois 30% antaa tulokseksi ja vahvistuskertoimeksi noin 36. Tosin LEDissä näkyy koko ajan pientä, nopeaa värähtelyä, koska mittausalue on nyt kapeampi; 600 .. 750, merkitsee 5mV A/D-muunnos jo noin 14% muutosta mittaustarkkuudessa. Laajempi värähtely oli kuitenkin selvästi havaittavissa. Värähtelytaajuudeksi arvioin noin 2s. Tuon ajan lyhyydestä johtuen, en ottanut integroinnin päivitysaikaa tarkasta ajastimesta (millis()), vaan muodostin päivitysajan suoraan ohjelmakierroksia laskemalla. Kierrosten lukumäärä 741 näytti antavan varsin hyvän säädön. Tarkkaa aikaa en täten tiedä, eikä sillä ole niin väliäkään, sillä säätämällä aikaa voi havainnoida säätöpiirin käyttäytymistä. Kun tietokoneelta antoi sarjaliikenteellä uuden asetusarvolukeman (SP-arvo = Set Point), muuttui LEDin valo nopeasti jälleen vakaaksi. Valaisin LEDiä myös taskulampulla, ja vastaavasti LEDin valo himmeni ja eroarvo palautui nopeasti lähelle nollaa. Tuossa lopullisessa ohjelmaversiossa olen poistanut tulostuksen (Bol_Tulosta = false), mutta jätin tulostusrivit aliohjelmaan kommenteiksi ja malliksi. Helpottaa tulostusta, kun tarpeettomat merkitsee kommenteiksi ja poistamalla kommenttimerkin (//), aktivoi vain kulloinkin tarvittavat rivit.

Ohjelma 15
/***************************************
 *  Ohjelma_15
 *  23.04.2016vv
 *  PI-säädin ja liukulukulaskenta
 **************************************/

// MÄÄRITTELYT:
// Kellon määrittely
unsigned long Ulo_MilliSek = 0;
unsigned long Ulo_UusiMilliSek = 0;
const long CoL_EroSekunti = 999;
int Int_Sekunti = 0;
boolean Bol_Tulosta = false; // Tulostusta ohjataan kellolla

// P-arvo, eli vahvistus
const int Con_P_Arvo = 1;
int Int_P_Arvo = 0;
float Flo_P_Arvo = 0.00;

// I-arvo, eli integrointitermi
const int Con_I_Arvo = 2;
int Int_I_Arvo = 0;
int Int_I_Ohjaus = 0;
int Int_I_Viive = 500;
int Int_Integraali = 0;

// LDR valovastus ja LEDi
const int Con_LDRTulo = 0;
const int Con_Valo = 3;
float Flo_Mittaus = 0;
int Int_LDRTulo = 0;

// Säätölaskenta
float Flo_EroArvo = 0;
int Int_Ohjaus = 0;
char Cha_Merkki = 48; // ASCII vastaa nollaa (0)
int Int_Numero = 0;
float Flo_Asetusarvo = 50;
const int Con_Hyster = 10;

// ASETUKSET:
void setup(){                 
 Serial.begin(9600);          
 pinMode(Con_Valo, OUTPUT);
}// Asetuksen loppu

// PÄÄLOOPPI Varsinainen suoritusosa. Jatkuva suoritus
void loop(){
// Sisäisen kellon käyttö
Ulo_MilliSek = millis();
  if(Ulo_MilliSek - Ulo_UusiMilliSek > CoL_EroSekunti){
    Ulo_UusiMilliSek = Ulo_MilliSek;
    Int_Sekunti++;
    if(Bol_Tulosta == true){Fun_Tulostus();}
      Int_Numero = 0; // Sarjasyötön nollaus!!
  }

// Sarjaliikenteen käsittely
if(Serial.available()){
 Cha_Merkki = Serial.read();
 if(isDigit(Cha_Merkki)){
  Int_Numero = (Int_Numero * 10) + (Cha_Merkki -'0');}
  Flo_Asetusarvo = Int_Numero; 
} // Serial.available loppu

// P-kertoimen käsittely
Int_P_Arvo = analogRead(Con_P_Arvo);
Flo_P_Arvo = map(Int_P_Arvo, 0, 1023, 0, 1000);
Flo_P_Arvo = Flo_P_Arvo / 10;

// I-ajan käsittely
Int_I_Arvo = analogRead(Con_I_Arvo);
Int_I_Viive--;
  if(Int_I_Viive == 0){
    Int_I_Ohjaus = Flo_EroArvo / 2;
    Int_Integraali = Int_Integraali + Int_I_Ohjaus;
    Int_I_Viive = Int_I_Arvo;
  }

// Analogiatulon käsittely
Int_LDRTulo = analogRead(Con_LDRTulo);
Flo_Mittaus = constrain(Int_LDRTulo, 600, 750);
Flo_Mittaus = (Int_LDRTulo - 600) / 1.5;

// Sätölaskenta ja ohjauslähtö
Flo_EroArvo = Flo_Asetusarvo - Flo_Mittaus;
Int_Ohjaus = Flo_EroArvo * Flo_P_Arvo + Int_Integraali;
Int_Ohjaus = constrain(Int_Ohjaus, 0, 255);
analogWrite(Con_Valo, Int_Ohjaus);

delay(1);
} // Pääohjelma LOPPU

// FUNKTIOT
void Fun_Tulostus(){
//Serial.print("Teksti :");  Serial.println(Muuttuja);
//Serial.print("Raaka P arvo :");  Serial.println(Int_P_Arvo);
//Serial.print("P arvo :");  Serial.println(Flo_P_Arvo);
//Serial.print("Mittaus :");  Serial.println(Flo_Mittaus);
//Serial.print("SetPoint :");  Serial.println(Flo_Asetusarvo);
//Serial.print("Eroarvo :");  Serial.println(Flo_EroArvo);
//Serial.print("P arvo :");  Serial.println(Flo_P_Arvo);
//Serial.print("Ohjaus :");  Serial.println(Int_Ohjaus);
//Serial.print("I-arvo :");  Serial.println(Int_I_Arvo);
 }

Muutama huomautus ohjelmasta. Tuossa sisäisen kellon osuudessa on lause Int_Numero = 0; // Sarjasyötön nollaus!! Sillä kerran sekunnissa nollataan se numerotieto, mikä lähetettiin sarjaliikenteellä edellisellä kerralla. Jos näin ei tehdä, kytkeytyy uusi syötetty luku edellisen perään. Mittausosassa mittaus rajoitetaan välille 600 .. 750. käsky constrain hyväksyy minkä tahansa numerotyypin. Toisin on käskyn map() kanssa. Se hyväksyy ainoastaan integerin. Siksi mittaus on skaalattu 0 .. 100% vähentämällä lukemasta 600 ja jakamalla sen jälkeen luvulla 1,5. Tämän jälkeen mittaus ja asetusarvo vastaavat lukualueeltaan toisiaan. Ohjausarvo rajoitetaan lähtöjen lukualueelle 0 .. 255 (8bit) ja varsinainen teho-ohjaus tapahtuu pulssinleveysmodulaationa (PWM).


Ei kommentteja:

Lähetä kommentti