PID implementasyonu (kodlaması)
PID (Proportional, Integral, Derivative) oransal-integral-türevsel denetleyici PID kontrol döngüsü yöntemi, yaygın endüstriyel kontrol sistemlerinde kullanılan genel bir kontrol döngüsü geribildirim mekanizmasıdir.
Ayrık zamanda PID kontrolü, kararlılık söz konusu olduğunda, geniş kapsamlı incelenmesi ve analiz edilmesi gerekir. Bu yazıda transfer fonksiyonu bilinen bir devre için kapalı çevrim bir kontrol döngüsü tasarlamayacak ve kararlılık analizi yapmayacağız. Bunun yerine şansını denemek isteyenler için basit PID kontrol algoritmasının nasıl kodlanması gerektiğini ve katsayılarının nasıl ayarlaması gerektiğini anlatacağız. Ayrıca bir PID döngüsünde dikkat edilmesi gerekenlere de göz atacağız.
Örnekleme frekansı PID döngüsünün en önemli temellerindendir. Belirli zaman aralıklarında alınan örneklerle döngü beslenir. Burada dikkat edilmesi gereken en önemli şey; iki örnekleme zamanı arasında kontrol mekanizması için gereken örnekler alınmalı, PID hesabı bitirilmeli ve gerekli çıkış işlemi çoktan yapılmış olmalıdır. Herhangi bir şekilde yapılan işlemlerin bir sonraki örnekleme zamanının içine sarkması döngünün kararsız çalışmasına yol açacaktır. Mikroişlemci veya mikrokontrolör ile PID döngüsü gerçekleştiriyorsanız; bir timer ile örnekleme zamanının ayarlanması, timer kesme rutininin içerisinde PID ile ilgili tüm işlemlerin bitirilip çıkılması tavsiye edilir.
PID döngüsünde dikkat edilmesi gereken bir diğer kriter de doğrusal bölgede çalışmaktır. Yani örneklediğiniz işaretinde, çıkışı sürmek için kullandığınız değerlerinde sonucunda doğrusal bir sonuç elde edilmelidir. Giriş ve çıkışın y = ax + b şeklinde ifade edilebilir olması döngünün kararlılığı bakımından önemlidir. Doğrusal olmayan sistemin neden olmadığını şöyle açıklayabiliriz. Örneğin bir eğrinin tepe notasında kalmaya çalışan bir top için PID uyguladığımızı düşünelim. Top tepe noktasındayken aşağı düşmeye başladığında, olduğu noktaya geri yükselmek için sağa mı gitmeli sola mı gitmeli şeklinde iki farklı çözüm ortaya çıkacaktır, y = x^2 gibi. y nin 1 olması için x = 1 de olabilir -1 de. Burada bahsi geçen PID kodlaması bu işi çözemeyecek kadar ilkeldir. Bu yüzden, topu bir noktaya kadar dengede tutabilirken, bu noktanın ilerisinde dengeyi sağlamak için atılan adım aslında gerekenin tam tersi olduğundan, döngü topun dengede kalmaması için gereken her türlü adımı atacaktır.
Bir diğer önemli noktada PID döngüsü girdi ve çıktılarının birbirleri türünden olmaları gerekliliğidir. Yani armut girdisiyle elma kontrol edilmemelidir. Bu yüzden girişler öncelikle normalize edilir (-1, +1 aralığı gibi). Bu durumda PID döngüsü hata oranları cinsinden çalışır. PID çıkışı ise işlem sonucunda hata cinsinden kontrol edeceği birimin cinsine dönüştürülür. Örneğin girişler 0 ila 255 arasında değişiyorsa, bu aralık -1, +1 aralığına çekilir. PID nin tüm işlemleri bu değerler için yapılır. Çıkışta elde edilen sonuç, döngünün kontrol ettiği birimin cinsine yani aralığına dönüştürülür. Mesela PID çıkışının sürdüğü birimin alacağı değerler 200 ila 1000 arasında değişiyorsa, -1 +1 aralığı bu aralığa çevrilir.
PID kodlaması için gerçek sayılar kullanılması önerilir. Donanımsal olarak gerçek sayılar ile program yazma imkanı varsa derleyici için ek olarak bir ayar gerektirip gerektirmediğinden emin olun. Donanımsal olarak gerçek sayıların desteklenmediği durumda gerçek sayılarla çalışmak hesaplama süresini artıracaktır. Bu durumda iki örnekleme zamanı arasında tüm hesaplamanın bittiğinden ve çıkış birimini güncellediğinizden emin olun. Ayrıca gerçek sayıların desteklenmediği durumlarda tam sayılarda kullanılabilir fakat sistem kararsızlığı ihtimali yüksektir.
Yukarıda gösterilen klasik PID döngüsünün bir şemasıdır. Bu döngüyü gerçekleyen kod aşağıda verilmiştir.
#include <stdio.h> #include <stdlib.h> #include <string.h> //PID parametreleri #define MIN_ACTUATOR_VALUE 200 //PID sonucunu gercekleyecek birimin alacagi min deger #define MAX_ACTUATOR_VALUE 1000 //PID sonucunu gercekleyecek birimin alacagi max deger #define MIN_SETPOINT_VALUE 0 //ayar noktasinin alacagi min deger #define MAX_SETPOINT_VALUE 255 //ayar noktasinin alacagi max deger #define MIN_FEEDBACK_VALUE 0 //geribeslemenin alacagi min deger #define MAX_FEEDBACK_VALUE 255 //geribeslemenin alacagi max deger #define PID_SCALE_LOWER_LIMIT -3.0 //PID sonucunun alacagi min deger. PID limitlerine gore bu deger degisebilir #define PID_SCALE_UPPER_LIMIT +3.0 //PID sonucunun alacagi max deger. PID limitlerine gore bu deger degisebilir #define SET_POINT 85 //ayar noktasi void vPID(uint16 wInput); //her timer kesmesinde bu fonksiyonu cagirin. wInput degiskeni geribeslemeden aldiginiz degerdir. static float fNormalizeFeedback(uint16 wValue); static void vSetActuator(uint16 wToActuator); //PID sonucunu gerceklemek icin bu fonksiyonu cagirin //Asagidaki fonksiyonlarda degisiklik yapmayin static float fNormalizeSetPoint(uint16 wValue); static float fNormalize(uint16 wValue, float fPIDLowerLimit, float fPIDUpperLimit, uint16 wInputMin, uint16 wInputMax); static uint16 wPlantProcess(float fInput, float fPIDLowerLimit, float fPIDUpperLimit, uint16 wInputMin, uint16 wInputMax); void vPID(uint16 wInput) { float fError; static float fLastError = 0; uint16 wToActuator; float fSetPoint = fNormalizeSetPoint(SET_POINT); float fInput = fNormalizeFeedback(wInput); float fPIDOutput; float fProportional; float fDerivative ; static float fIntegral; //PID kazanc ve limitleri. Debug modunda belirlediginiz kazanc ve limit degerlerini, //programin son halinde sabit degiskeler olarak degistirebilirsiniz. static float fGainProportional = 1; //P kazanc static float fLimitProportional = 1; //P limit static float fGainIntegral = 0; //I kazanc static float fLimitIntegral = 1; //I limit static float fGainDifferential = 0; //D kazanc static float fLimitDerivative = 1; //D limit //Error fError = fSetPoint - fInput; //Proportional fProportional = fError * fGainProportional; if (fProportional > fLimitProportional) fProportional = fLimitProportional; else if (fProportional < -fLimitProportional) fProportional = -fLimitProportional; //Integral fIntegral += fError * fGainIntegral; if (fIntegral > fLimitIntegral) fIntegral = fLimitIntegral; else if (fIntegral < -fLimitIntegral) fIntegral = -fLimitIntegral; //Derivative fDerivative = (fError - fLastError) * fGainDifferential; fLastError = fError; if (fDerivative > fLimitDerivative ) fDerivative = fLimitDerivative ; else if (fDerivative < -fLimitDerivative ) fDerivative = -fLimitDerivative ; fPIDOutput = fProportional + fIntegral + fDerivative ; if (fPIDOutput > PID_SCALE_UPPER_LIMIT) fPIDOutput = PID_SCALE_UPPER_LIMIT; else if (fPIDOutput < PID_SCALE_LOWER_LIMIT) fPIDOutput = PID_SCALE_LOWER_LIMIT; wToActuator = wPlantProcess(fPIDOutput, PID_SCALE_LOWER_LIMIT, PID_SCALE_UPPER_LIMIT, MIN_ACTUATOR_VALUE, MAX_ACTUATOR_VALUE); vSetActuator(wToActuator); //Calisma aninda PID degerlerini gozlemlemek icin asagidaki satiri acabilirsiniz //printf("Set:%0.3f fError:%0.3f Act:%d P:%0.3f I:%0.3f D:%0.3f \n\r", fSetPoint, fError, wToActuator, fProportional, fIntegral, fDerivative); } //Bu fonksiyonu PID sonucunu gerceklemek icin kullanin static void vSetActuator(uint16 wToActuator) { //bu fonksiyonu PID sonucu ile etkilemek istediginiz //register i guncelleyin. mesela bir PWM modulunun darbe bosluk oranini //ayarlayan bir register gibi } //Bu satirin altinda kalanlari degistirmeyin static float fNormalize(uint16 wValue, float fPIDLowerLimit, float fPIDUpperLimit, uint16 wInputMin, uint16 wInputMax) { //function normalizes the input to PID limits //y = ax + b float a = (fPIDUpperLimit - fPIDLowerLimit)/(wInputMax - wInputMin); float b = (fPIDUpperLimit + fPIDLowerLimit - a*(wInputMax + wInputMin))/2.0; return a*wValue + b; } static float fNormalizeSetPoint(uint16 wValue) { return fNormalize(wValue, PID_SCALE_LOWER_LIMIT, PID_SCALE_UPPER_LIMIT, MIN_SETPOINT_VALUE, MAX_SETPOINT_VALUE); } static float fNormalizeFeedback(uint16 wValue) { return fNormalize(wValue, PID_SCALE_LOWER_LIMIT, PID_SCALE_UPPER_LIMIT, MIN_FEEDBACK_VALUE, MAX_FEEDBACK_VALUE); } static uint16 wPlantProcess(float fInput, float fPIDLowerLimit, float fPIDUpperLimit, uint16 wInputMin, uint16 wInputMax) { //function returns the real output //x = (y - b)/a float a = (fPIDUpperLimit - fPIDLowerLimit)/(wInputMax - wInputMin); float b = (fPIDUpperLimit + fPIDLowerLimit - a*(wInputMax + wInputMin))/2.0; return (uint16)((fInput - b)/a); }
PID katsayılarının ayarlanması istenilen kriterlere bağlı olup deneme yanılma ile oldukça zor bulunur. Ziegler Nichols metodu katsayıların kabaca ayarlanmasına yardımcı olacak bir metoddur. Metod, sistemi osilasyona götüren P değeri üzerinden katsayıları bulmaya dayanır. Fakat sistemi osilasyona götüren değeri bulduktan sonra sistemin osilasyon frekansını bulacak imkânınız yoksa pratik olarak bu değerin yarısını P kazancı olarak alıp kalan integral ve türev katsayılarını deneysel olarak bulmak mümkündür. Bu durumda printf gibi bir fonksiyonla anlık olarak döngü değişkenlerinin değerleri izlenebilir. Debug modunda çalışmak mümkünse anlık olarak katsayıları değiştirip kontrol döngüsünün katsayılarının daha hızlı bulunması da sağlanabilir. Daha öncede bahsettiğimiz gibi döngüyü ayarlamak için kullandığınız fonksiyonlarında zaman harcadığını unutmayın ve bir sonraki örnekleme zamanından önce herşeyin bittiğine emin olun.