Ciao a tutti, ho un problema riguardo i controllori PID. Il progetto prevede l'implementazione di un controllore PID per la tensione e un controllore PI per la corrente. Sembra quasi che tutto funzioni ma quando il programma gira ho degli spike casuali sulla caratteristica di uscita che in questo caso è la tensione presente all'uscita di un ponte trifase SCR.
Di seguito vi allego il codice che sto utilizzando e il segnale di uscita
unsigned int Beta = 0;
int Beta1 = 0;
float Curr_ref = 0;
int Potenziometro = 0; //lettura potenziometro (riferimento)
int Feedback = 0; //tensione di feedback proveniente dal ponte
int Feedbackcurr = 0; //tensione di feedback proveniente dal loop di corrente
int lastFeedback = 0; //valore di feedback i-1, serve per il PID di tensione
float lasterror = 0; //valore di errore i-1, serve per il PID di tensione
float lasterror_c = 0; //valore di errore i-1, serve per il PID di tensione
int iia = 0; //variabili utilizzate per entrare nei "case" quando genero il periodo
int iib = 0;
int iic = 0;
int i = 0; //variabile utilizzata per migliore lettura degli ingressi analogici
float Kp_volt = 12;
float Ki_volt = 200;
float Kd_volt = 0.075;
float Kp_curr = 0.1;
float Ki_curr = 0;
float Kd_curr = 0;
float error_v = 0; //errori dati dalla differenza tra feedback e riferimento
float error_c = 0; //errori dati dalla differenza tra feedback di corrente e corrente di riferimento
float Total_error = 0; //errore totale dato dagli errori singoli del PID
float Prev_error_v = 0; //errore al controllo i-1
float Error_volt_P = 0; //inizializzo gli errori a 0 per il PID di tensione
float Error_volt_I = 0;
float Error_volt_D = 0;
float Error_curr_P = 0; //inizializzo gli errori a 0 per il PID di corrente
float Error_curr_I = 0;
float Error_curr_D = 0;
float Delta_T_volt = 0; //variabile di tempo dichiarata...Ogni quanto faccio agire il PID (2ms)
float Delta_T_curr = 0; //variabile di tempo dichiarata...Ogni quanto faccio agire il PID (1m)
//double actual_time = 0;
//double last_time = 0;
int SampleTimeVolt = 2000;
int SampleTimeCurr = 1000;
unsigned long PastTimeVolt = 0;
unsigned long PastTimeCurr = 0;
void setup() {
// inizializzo le variabili
Serial.begin(2000000);
cli(); //stop interrupts
DDRB = (1 << DDB7) | (1 << DDB6) | (1 << DDB5) | (1 << DDB4);
DDRH = (1 << DDH6) | (1 << DDH5);
PORTB = (0 << PB7) | (0 << PB6) | (0 << PB5) | (0 << PB4);
PORTH = (0 << PH6) | (0 << PH5);
pinMode(19, INPUT_PULLUP); //definisco i pin utili per gli interrupt esterni
pinMode(2, INPUT_PULLUP);
pinMode(3, INPUT_PULLUP);
TCCR1A = 0; //TIMER UTILIZZATO PER LA FASE S (PER SINCRONISMO)
TCCR1B = (0 << CS12) | (1 << CS11) | (0 << CS10); //Prescaler nullo, fermo il contatore
TIMSK1 = (1 << OCIE1C) | (1 << OCIE1B) | (1 << OCIE1A); //abilitazione interrupt
TIFR1 = (0 << OCF1C) | (0 << OCF1B) | (0 << OCF1A); //flag azzerato raggiungimento valore scelto
TCNT1 = 0; ///azzero contatore a 16 bit
TCCR3A = 0; //TIMER UTILIZZATO PER LA FASE R (PER SINCRONISMO)
TCCR3B = (0 << CS32) | (1 << CS31) | (0 << CS30); //prescaler nullo, fermo il contatore
TIMSK3 = (1 << OCIE3C) | (1 << OCIE3B) | (1 << OCIE3A); //abilitazione interrupt
TIFR3 = (0 << OCF3C) | (0 << OCF3B) | (0 << OCF3A); //flag azzerato raggiungimento valore scelto
TCNT3 = 0; ///azzero contatore a 16 bit
TCCR4A = 0; //TIMER UTILIZZATO PER LA FASE T (PER SINCRONISMO)
TCCR4B = (0 << CS42) | (1 << CS41) | (0 << CS40); //Prescaler nullo, fermo il contatore
TIMSK4 = (1 << OCIE4C) | (1 << OCIE4B) | (1 << OCIE4A); //abilitazione interrupt
TIFR4 = (0 << OCF4C) | (0 << OCF4B) | (0 << OCF4A); //flag azzerato raggiungimento valore scelto
TCNT4 = 0; ///azzero contatore a 10 bit
attachInterrupt(digitalPinToInterrupt(19), sincronismo_RS, RISING); //richiamo l'interrupt quando la tensione AB, collegata sul pin digitale 0 diventa positiva, sincronismo_AB è il nome della funzione
attachInterrupt(digitalPinToInterrupt(2), sincronismo_ST, RISING); //richiamo l'interrupt quando la tensione BC, collegata sul pin digitale 1 diventa positiva, sincronismo_BC è il nome della funzione
attachInterrupt(digitalPinToInterrupt(3), sincronismo_TR, RISING); //richiamo l'interrupt quando la tensione CA, collegata sul pin digitale 2 diventa positiva, sincronismo_CA è il nome della funzione
sei(); //Enable interrupt
}
void loop()
{
Feedback = analogRead(A7);
Potenziometro = analogRead(A5);
Feedbackcurr = analogRead(A14);
Curr_ref = MyPID(Potenziometro, Feedback, Kp_volt, Ki_volt, Kd_volt);
Beta = MyPID2(Curr_ref, Feedbackcurr, Kp_curr, Ki_curr);
// Serial.println(Total_error);
}
void sincronismo_RS()
{
TCNT3 = 0; //azzeramento del contatore quando trovo il fronte di salita
OCR3A = 18500 - Beta; //primo valore di uguaglianza per il primo impulso
}
void sincronismo_ST()
{
TCNT1 = 0;
OCR1A = 18500 - Beta; //primo valore di uguaglianza per il primo impulso
}
void sincronismo_TR()
{
TCNT4 = 0;
OCR4A = 18500 - Beta; //primo valore di uguaglianza per il primo impulso
}
float MyPID( int Potenziometro, int Feedback, float Kp_volt, float Ki_volt, float Kd_volt)
{
unsigned long ActualTimeVolt = micros();
int Delta_T_volt = ActualTimeVolt - PastTimeVolt;
if (Delta_T_volt >= SampleTimeVolt)
{
error_v = Potenziometro - Feedback;
Error_volt_P = error_v * Kp_volt;
Error_volt_I = Error_volt_I + (error_v + lasterror) * 0.5 * Ki_volt * (Delta_T_volt);
if (Error_volt_I >= 5)
{
Error_volt_I = 5;
}
else if (Error_volt_I <= -5)
{
Error_volt_I = -5;
}
else
{
Error_volt_I = Error_volt_I;
}
Error_volt_D = (error_v - lasterror) * Kd_volt / (Delta_T_volt);
Total_error = Error_volt_P + Error_volt_I + Error_volt_D;
lasterror = error_v;
PastTimeVolt = ActualTimeVolt;
}
return Total_error;
}
int MyPID2(float Curr_ref, int Feedbackcurr, float Kp_curr, float Ki_curr)
{
unsigned long ActualTimeCurr = micros();
int Delta_T_curr = ActualTimeCurr - PastTimeCurr;
if (Delta_T_curr >= SampleTimeCurr)
{
error_c = Curr_ref - Feedbackcurr;
Error_curr_P = error_c * Kp_curr;
Error_curr_I = Error_curr_I + (error_c + lasterror_c) * 0.5 * Ki_curr * (Delta_T_curr);
if (Error_curr_I >= 5)
{
Error_curr_I = 5;
}
else if (Error_curr_I <= -5)
{
Error_curr_I = -5;
}
else
{
Error_curr_I = Error_curr_I;
}
Error_curr_D = (error_c - lasterror_c) * Kd_curr / (Delta_T_curr);
Beta1 += Error_curr_P + Error_curr_I + Error_curr_D;
if (Beta1 >= 15166)
{
Beta1 = 15166; //15166 è il numero che porta l'angolo di conduzione alpha a 0°
}
else if (Beta1 <= 0)
{
Beta1 = 0;
}
else
{
Beta1 = Beta1;
}
lasterror_c = error_c;
PastTimeCurr = ActualTimeCurr;
}
return Beta1;
}
ISR(TIMER3_COMPA_vect)
{
iia++;
switch (iia)
{
case 1:
OCR3A += 1000;
PORTH = (1 << PH6) | (1 << PH5);
attachInterrupt(digitalPinToInterrupt(19), sincronismo_RS, FALLING);
break;
case 2:
PORTH = (0 << PH6) | (0 << PH5);
break;
case 3:
OCR3A += 1000;
PORTB = (1 << PB7) | (1 << PB4);
attachInterrupt(digitalPinToInterrupt(19), sincronismo_RS, RISING);
break;
case 4:
PORTB = (0 << PB7) | (0 << PB4);
iia = 0;
break;
}
}
ISR(TIMER1_COMPA_vect)
{
iib++;
switch (iib)
{
case 1:
OCR1A += 1000;
PORTB = (1 << PB7) | (1 << PB6);
attachInterrupt(digitalPinToInterrupt(2), sincronismo_ST, FALLING);
break;
case 2:
PORTB = (0 << PB7) | (0 << PB6);
break;
case 3:
OCR1A += 1000;
PORTB = (1 << PB5);
PORTH = (1 << PH5);
attachInterrupt(digitalPinToInterrupt(2), sincronismo_ST, RISING);
break;
case 4:
PORTB = (0 << PB5);
PORTH = (0 << PH5);
iib = 0;
break;
}
}
ISR(TIMER4_COMPA_vect)
{
iic++;
switch (iic)
{
case 1:
OCR4A += 1000;
PORTB = (1 << PB5) | (1 << PB4);
attachInterrupt(digitalPinToInterrupt(3), sincronismo_TR, FALLING);
break;
case 2:
PORTB = (0 << PB5) | (0 << PB4);
break;
case 3:
OCR4A += 1000;
PORTB = (1 << PB6);
PORTH = (1 << PH6);
attachInterrupt(digitalPinToInterrupt(3), sincronismo_TR, RISING);
break;
case 4:
PORTB = (0 << PB6);
PORTH = (0 << PH6);
iic = 0;
break;
}
}
Per darvi più dettagli:
-
Beta è la variabile che muove degli impulsi utili ad azionare gli SCR del ponte e proviene
dall'uscita del PI di corrente. -
I tre TIMER vengono utilizzati per generare gli impulsi e quindi impostare "HIGH" o "LOW" dei pin digitali
-
Gli interrupt dei pin 19, 3, 2 generano interrupt esterni, in particolare ognuno di essi mi dice quando le tre sinusoidi passano lo zero.
Spero di essere stato abbastanza dettagliato. Se manca qualcosa fatemelo pure notare.
Grazie