PID controller: random spikes during it works

Hello to everyone, I have a problem about a PID controller. The gol of the project is to implement a PID voltage controller and a PI current controller. Seems that it works quite good but during the work there are casual spikes on the output characteristics that in this case is an output voltage of a three phase SCR.
Below you can find the code that I'm using and the image of the output characteristics with the spike.

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

     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); //Pin 8 e 9
      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;
  }
}

SCRN0162

Give you more details:

  • Beta is the variable that moves the pulse in order to turn on the SCR and this variable come from the output of the current PI.

  • The three TIMERS are used in order to generate the pulses and then set the "HIGH" and "LOW" state of the digital pins.

  • The interrupts pin 19, 3, 2 generates external interrupt, in particular they tell when the input sinewave cross the zero voltage.

Hope that I give you enough details. If you want more information, please tell me.

Thanks

Just a guess, but I'd try disabling TIMER0, the millis() timer.

I don't understand with "disable" what you mean... But i've implemented the pid without the TIMER0 using TIMER5 as an interrupt. Each 2ms the interrupt is generated and the function is recalled.. But the same is always present.

I tried to eliminate the PID. So driving the output directly with the potentiometer, in this case the value of Beta is proportional to the potentiometer. The problem is still there. Is it possible that the problem is generated by the mode in which the program elaborate the variable beta? Which type of variable i can write in OCRxA?

For disabling timer 0, I mean something like this

TIMSK0 = 0;

Maybe its interfering with your other interrupts?

...has to be volatile and protected by a critical section.

Hello, how can i do this protection?

https://gammon.com.au/interrupts

That’s a pretty tiny spike ? Looks like around 300mV from what I can see -although is the scope recording more ?

Check around with scope and see if it’s on the power supply or you have a wiring issue ( bad layout of 0v for example )