Εισαγωγή
Ο Ελεγκτής PID ή αλλιώς ελεγκτής τριών όρων, είναι ένα αναλογικό – ολοκληρωτικό – παράγωγο (Proportional–Integral–Derivative ) σύστημα ελέγχου. Είναι ένας μηχανισμός βρόχου ελέγχου που χρησιμοποιεί ανάδραση. Χρησιμοποιείται ευρέως σε βιομηχανικά συστήματα ελέγχου και σε μια ποικιλία άλλων εφαρμογών που απαιτούν συνεχώς διαμορφωμένο έλεγχο. Δηλαδή ένας ελεγκτής PID υπολογίζει συνεχώς μια τιμή σφάλματος e(t) ως τη διαφορά μεταξύ ενός επιθυμητού σημείου ρύθμισης (SetPoint -SP) και της τρέχουσας τιμής μιας μετρούμενης μεταβλητής διεργασίας (Process Variable PV) και εφαρμόζει μια διόρθωση που βασίζεται σε αναλογικό, ολοκληρωτικό και παράγωγο όρο (που συμβολίζονται με P, I και D αντίστοιχα), από όπου πήρε και το όνομα.
Πρακτικά, εφαρμόζει αυτόματα μια ακριβή και ανταποκρινόμενη διόρθωση σε μια λειτουργία ελέγχου. Ένα καθημερινό παράδειγμα είναι το cruise control σε ένα σύγχρονο αυτοκίνητο. Η ανάβαση σε ένα λόφο θα μείωνε την ταχύτητα του αυτοκινήτου εάν υπήρχε μόνο σταθερή ισχύς κινητήρα. Ο αλγόριθμος PID του ελεγκτή επαναφέρει τη μετρούμενη ταχύτητα στην επιθυμητή ταχύτητα με ελάχιστη καθυστέρηση και υπέρβαση αυξάνοντας την ισχύ εξόδου του κινητήρα με ελεγχόμενο τρόπο.
Λειτουργία του ελεγκτή
Το χαρακτηριστικό γνώρισμα του ελεγκτή PID είναι η δυνατότητα χρήσης των τριών όρων ελέγχου της αναλογικής, της ολοκληρωτικής και της παράγωγης επιρροής στην έξοδο του ελεγκτή για την εφαρμογή ακριβούς και βέλτιστου ελέγχου. Το μπλοκ διάγραμμα του ελεγκτή που φαίνεται πιο κάτω, δείχνει τις αρχές του τρόπου με τον οποίο δημιουργούνται και εφαρμόζονται αυτοί οι όροι. Εμφανίζει έναν ελεγκτή PID, ο οποίος υπολογίζει συνεχώς μια τιμή σφάλματος e(t) ως τη διαφορά μεταξύ ενός επιθυμητού σημείου ρύθμισης SP=r(t) και μια μετρημένη μεταβλητή διαδικασίας PV=y(t) e(t)=r(t)-y(t) και εφαρμόζει μια διόρθωση που βασίζεται σε αναλογικό, ολοκληρωτικό και παράγωγο όρο. Ο ελεγκτής προσπαθεί να ελαχιστοποιήσει το σφάλμα με την πάροδο του χρόνου προσαρμόζοντας μια μεταβλητή ελέγχου u(t), όπως π.χ. το άνοιγμα μιας βαλβίδας ελέγχου, σε μια νέα τιμή που καθορίζεται από ένα σταθμισμένο άθροισμα των όρων ελέγχου .

Πώς δουλεύει ο PID ελεγκτής
Η συνολική έξοδος του ελεγκτή PID είναι ένας συνδυασμός των τριών όρων όπως φαίνονται στο παραπάνω σχήμα, των : P, I και D
Ο όρος P είναι ανάλογος με την τρέχουσα τιμή του σφάλματος SP−PV = e(t).

Για παράδειγμα, εάν το σφάλμα είναι μεγάλο και θετικό, η έξοδος ελέγχου θα είναι αναλογικά μεγάλη και θετική, λαμβάνοντας υπόψη τον παράγοντα απολαβής Kp. Μόνον ο P έλεγχος (Proportional) είναι αρκετός και λειτουργεί καλά ένα σύστημα όταν:
- Το σύστημα είναι απλό και ανταποκρίνεται γρήγορα.
- Δεν απαιτείται απόλυτη ακρίβεια στο τελικό αποτέλεσμα.
- Είναι αποδεκτό να υπάρχει λίγο σφάλμα στο steady-state (τελική κατάσταση).
- Δεν έχεις πρόβλημα με κάποια σταθερή απόκλιση από το setpoint.
Έτσι, όταν σε ένα σύστημα λειτουργεί μόνο P έλεγχος υπάρχουν περιορισμοί:
- Μόνιμο σφάλμα (steady-state error):
- Ο P έλεγχος δεν μπορεί να εξαφανίσει τελείως το σφάλμα.
- Όσο πλησιάζεις την επιθυμητή τιμή, το σφάλμα μικραίνει → άρα και η διόρθωση μικραίνει → και σταματά πριν φτάσεις ακριβώς το setpoint.
- Περιορισμός στην ισχύ:
- Αν κάνεις πολύ μεγάλο τον αναλογικό συντελεστή Kp, για να “πιέσει” πιο πολύ, τότε:
- Το σύστημα γίνεται ασταθές.
- Ίσως έχεις ταλαντώσεις ή υπερβολική απόκριση (overshoot).
- Αν κάνεις πολύ μεγάλο τον αναλογικό συντελεστή Kp, για να “πιέσει” πιο πολύ, τότε:
- Κακή συμπεριφορά σε αργά ή πολύπλοκα συστήματα:
- Σε συστήματα με αδράνεια (όπως θερμικά ή μηχανικά), ο P έλεγχος μόνος του δεν προλαβαίνει ή καθυστερεί να αντιδράσει σωστά.
Για παράδειγμα ας πούμε ότι ελέγχεις έναν κινητήρα για να φτάσει σε συγκεκριμένη ταχύτητα.
Με P μόνο, μπορεί να φτάσει κοντά στον στόχο, αλλά:
- Μπορεί να σταματήσει στο 98% του στόχου και να μη πάει ποτέ ακριβώς στο 100%.
- Αν αυξήσεις τον Kp, μπορεί να ταλαντώνεται γύρω από το 100% (overshoot → undershoot → overshoot…).
Ο όρος I λαμβάνει υπόψη τις προηγούμενες τιμές του σφάλματος SP−PV

και τις ενσωματώνει με την πάροδο του χρόνου για να παράγει τον όρο I. Για παράδειγμα, εάν υπάρχει ένα υπολειπόμενο σφάλμα SP−PV μετά την εφαρμογή του αναλογικού ελέγχου, ο ολοκληρωτικός όρος επιδιώκει να εξαλείψει το υπολειπόμενο σφάλμα προσθέτοντας ένα αποτέλεσμα ελέγχου, με βαρύτητα Ki , λόγω της ιστορικής αθροιστικής τιμής του σφάλματος. Όταν εξαλειφθεί το σφάλμα, ο ολοκληρωτικός όρος θα πάψει να αυξάνεται. Αυτό θα έχει ως αποτέλεσμα τη μείωση του αναλογικού αποτελέσματος καθώς μειώνεται το σφάλμα, αλλά αυτό αντισταθμίζεται από το αυξανόμενο ολοκληρωτικό αποτέλεσμα.
Ο όρος D είναι η καλύτερη εκτίμηση της μελλοντικής τάσης του σφάλματος SP−PV,

με βάση τον τρέχοντα ρυθμό μεταβολής του. Μερικές φορές αποκαλείται «προληπτικός έλεγχος», καθώς επιδιώκει ουσιαστικά να μειώσει την επίδραση του σφάλματος SP−PV ασκώντας μια επιρροή ελέγχου που δημιουργείται από τον ρυθμό μεταβολής του σφάλματος. Όσο πιο γρήγορη είναι η αλλαγή, τόσο μεγαλύτερο είναι το αποτέλεσμα ελέγχου ή απόσβεσης. Σημαντικός είναι ο ρόλος της σταθεράς Kd στον υπολογισμό της διόρθωσης D.
Εφαρμογή ελεγκτή PID στον Κώδικα
Για να εφαρμόσετε έναν ελεγκτή PID σε κώδικα ή σε ένα πρόγραμμα Arduino, πρέπει να είναι γνωστές πέντε παράμετροι:
- αναλογική σταθερά Kp
- ολοκληρωτική σταθερά Ki
- και παράγωγη σταθερά Kd
- τιμή εισόδου (PV)
- και τιμή σημείου ρύθμισης (SP)
Ο υπολογισμός PID πρέπει να βρίσκεται μέσα σε μια συνάρτηση βρόχου. Το πρώτο μέρος της συνάρτησης θα πρέπει να είναι ο καθορισμός του χρόνου που έχει παρέλθει.
Στο Arduino, η τρέχουσα ώρα μπορεί να προσδιοριστεί με τη συνάρτηση millis() και ο χρόνος που έχει παρέλθει είναι απλώς:
currentTime = millis();
elapsedTime = currentTime – previousTime;
Στη συνέχεια, πρέπει να προσδιοριστεί το σφάλμα:
error = setPoint – input;
Προγραμματίζοντας σε Arduino οι υπολογισμοί απλοποιούνται πάρα πολύ με τη χρήση της βιβλιοθήκης PID_v1. Στο παράδειγμα που σας παραθέτω έκανα χρήση της βιβλιοθήκης PID_v1.
Σε κάθε περίπτωση είναι αναγκαίος ο προσδιορισμός για τις τρεις σταθερές P, I, D. Αυτός ο προσδιορισμός γίνεται συνήθως πειραματικά και είναι μια επίπονη διαδικασία.
Κατασκευή
Η κατασκευή αυτή είναι συνέχεια της προηγούμενης που έκανα με τους δύο σερβοκινητήρες. Σε αυτή την κατασκευή έχω προσθέσει τον αισθητήρα MPU-9250 ο οποίος θα “οδηγεί” τους σερβοκινητήρες. Βλέπε σχετικά εδώ.
Το σημαντικότερο για την κατασκευή είναι το λογισμικό που θα γράψουμε έτσι ώστε η κίνηση στους σερβοκινητήρες να ελέγχεται πλέον από τον αισθητήρα και να γίνεται ομαλά, χωρίς ταλαντώσεις και να φέρνει το σύστημα στην επιθυμητή (οριζόντια) θέση.
Τον κώδικα της κατασκευής για Arduino UNO τον βλέπετε στη συνέχεια και μπορείται να το κατεβάσετε από εδώ.
/********************************************************
* Author : Manolis Aristovoulidis
* Το πρόγραμμα αυτό χρησιμοποιεί έναν αισθητήρα κίνησης MPU-9250,
* δύο σερβοκινητήρες και έλεγχο PID στην κίνηση των σερβοκινητήρων.
* Με τη λειτουργία του προγράμματος οισερβοκινητήρες κρατουν πάντα σε
* οριζόντια θέση τον αισθητήρα όπως και να περιστρέψουμε τη βάση της κατασκευής
*
* This program uses an MPU-9250 motion sensor,
* two servomotors and PID control in servomotors.
* With the operation of the program, motors always keep on
* position the sensor horizontally as well as rotate the base of the construction
* Date : 27/11/2021
*
********************************************************/
#include <Wire.h>
#include <MPU9250.h>
#include <Servo.h>
#include <PID_v1.h>
Servo servo_x; // δημιουργία ενός servo object για τον έλεγχο του servo
Servo servo_y; // creating a servo object to control the servo
int val_x = 100; // Αρχική γωνια για το servo_x Home corner for servo_x
int val_y = 100; // Αρχική γωνια για το servo_y Home corner for servo_y
// an MPU9250 object with the MPU-9250 sensor on I2C bus 0 with address 0x68
MPU9250 IMU(Wire,0x68);
int status;
//Acc Variables
float rad_to_deg = 180/3.141592654; //Αυτή η τιμή είναι για τη μετατροπή ακτινίων σε μοίρες
float Acc_rawX, Acc_rawY, Acc_rawZ; //Εδώ αποθηκεύουμε ανεπεξέργαστα δεδομένα από το επιταχυνσιόμετρο
float Acc_angle_x, Acc_angle_y; //Εδώ αποθηκεύουμε τις τιμές γωνίας που υπολογίσαμε για το επιταχυνσιόμετρο
float SUp_Acc_angle_x, SUp_Acc_angle_y; //SetUp angles
float Gyr_rawX, Gyr_rawY, Gyr_rawZ; //Εδώ αποθηκεύουμε ανεπεξέργαστα δεδομένα από το γυροσκόπιο
float Gyro_angle_x, Gyro_angle_y; //Εδώ αποθηκεύουμε τις τιμές γωνίας που υπολογίσαμε για το γυροσκόπιο
float Total_angle_x, Total_angle_y; //Εδώ αποθηκεύουμε τις τελικες ολικές γωνίες
float elapsedTime, timeNow, timePrev; //Μεταβλητές για τον έλεγχο χρόνου
//Καθορίζουμε τις μεταβλητές που θα συνδεθούν με τα αντικείμενα PID_x και PID_y
double Setpoint_x, Input_x, Output_x;
double Setpoint_y, Input_y, Output_y;
//Καθορίζουμε τις αρχικές παραμέτρους συντονισμού
double x_Kp=2.0, x_Ki=0.5, x_Kd=0.01;
double y_Kp=2.0, y_Ki=0.5, y_Kd=0.01;
//Δημιουργούμε τα αντικείμενα PID
PID PID_x(&Input_x, &Output_x, &Setpoint_x, x_Kp, x_Ki, x_Kd, DIRECT);
PID PID_y(&Input_y, &Output_y, &Setpoint_y, y_Kp, y_Ki, y_Kd, DIRECT);
void setup()
{
Serial.begin(115200);
while(!Serial) {}
servo_x.attach(4); //κάνει προσάρτηση του servo στο pin 4 του servo object // attachs the servo to pin 4 of the servo object
servo_y.attach(5); //κάνει προσάρτηση του servo στο pin 5 του servo object // attachs the servo to pin 5 of the servo object
//περιστοφή των σερβοκινητήρων
servo_x.write(val_x); //Μετά από πειραματισμούς αυτή είναι η //After experimentation this is
servo_y.write(val_y); //οριζόντια θέση για τη βάση του MPU9250 // horizontal position for the MPU9250 base
delay(1000);
// start communication with IMU
status = IMU.begin();
if (status < 0) {
Serial.println("IMU initialization unsuccessful");
Serial.println("Check IMU wiring or try cycling power");
Serial.print("Status: ");
Serial.println(status);
while(1) {}
}
/* Default values of MPU9250
* accel range to 16G as default
* gyro range to 2000DPS as default
* bandwidth to 184Hz as default
* sample rate divider to 0 as default
*
*/
delay(3000);
IMU.readSensor();
Acc_rawX = IMU.getAccelX_mss();
Acc_rawY = IMU.getAccelY_mss();
Acc_rawZ = IMU.getAccelZ_mss();
/*---Y---*/
SUp_Acc_angle_x = (atan((Acc_rawY)/sqrt(pow((Acc_rawX),2) + pow((Acc_rawZ),2)))*rad_to_deg) ;
/*---Y---*/
SUp_Acc_angle_y = (atan(-1*(Acc_rawX)/sqrt(pow((Acc_rawY),2) + pow((Acc_rawZ),2)))*rad_to_deg) ;
//Input_x = 0.0;
Input_x = SUp_Acc_angle_x;
Setpoint_x = SUp_Acc_angle_x;
//Setpoint_x = 0.0;
//Input_y = 0.0;
//Setpoint_y = 0.0;
Input_y = SUp_Acc_angle_y;
Setpoint_y = SUp_Acc_angle_y;
double mini = -90;
double maxi = 90;
PID_x.SetOutputLimits(mini, maxi); //ορίζουμε τα όρια ελάχιστης - μέγιστης τιμής που
PID_y.SetOutputLimits(mini, maxi); //θα μας επιστρέφει ο μηχανισμός ελέγχου PID
//turn the PID on
PID_x.SetMode(AUTOMATIC);
PID_y.SetMode(AUTOMATIC);
}
void loop()
{
timePrev = timeNow; // η προηγούμενη ώρα αποθηκεύεται πριν διαβάσουμε την πραγματική ώρα
timeNow = millis(); // Ανάγνωση πραγματικής ώρας
elapsedTime = (timeNow - timePrev) / 1000;
IMU.readSensor();
Acc_rawX = IMU.getAccelX_mss();
Acc_rawY = IMU.getAccelY_mss();
Acc_rawZ = IMU.getAccelZ_mss();
Gyr_rawX=IMU.getGyroX_rads();
Gyr_rawY=IMU.getGyroY_rads();
/*---Y---*/
Acc_angle_x = (atan((Acc_rawY)/sqrt(pow((Acc_rawX),2) + pow((Acc_rawZ),2)))*rad_to_deg) ;
/*---Y---*/
Acc_angle_y = (atan(-1*(Acc_rawX)/sqrt(pow((Acc_rawY),2) + pow((Acc_rawZ),2)))*rad_to_deg) ;
Gyro_angle_x = Gyr_rawX*elapsedTime;
/*---X---*/
Gyro_angle_y = Gyr_rawY*elapsedTime;
Total_angle_x = 0.98 *(Total_angle_x + Gyro_angle_x) + 0.02*Acc_angle_x;
Total_angle_y = 0.98 *(Total_angle_y + Gyro_angle_y) + 0.02*Acc_angle_y;
Input_x = Total_angle_x;
Input_y = Total_angle_y;
PID_x.Compute();
PID_y.Compute();
servo_x.write(Output_x + val_x ); // ρυθμίζει τη θέση του servo στην τιμή της κλίμακας
servo_y.write(val_y - Output_y);
//Serial.print(Input_x); // εκτυπώσεις για την παρακολούθηση του μηχανισμού σε λειτουργία
//Serial.print(",");
//Serial.println(Input_y);
delay(40);
}