Measuring time between two pulses to calculate RPM of a motor (PID)

Hello there, I’m kinda new to programming with Arduino, so I’d like a little help.

I’ve been reading the forum and it helped me a lot with my code, specially with the use of Interrupts, but there’s a problem: once my PWM signal reaches 0, it doesn’t go up anymore because there are no more rotations from the 2-bladed fan (I’m using a proximity inductive sensor to measure the time between three pulses - to measure a full revolution - of the fan connected to the motor).

I’m trying to implement a PID controller on a motor so the speed measured from the sensor is the same as the speed set by the potentiometer (SetPoint).

//Declaração das portas usadas
int RelePin = 8;
int PWMPin = 9;
int SensorPin = 2;
int PotenciometroPin = A0;


//Tempos de processamento derivativo e integral
const long TMP_DER = 50;
const long TMP_INT = 50;


//Declaraçao de Variáveis
volatile long int pulsos; //Nestes casos é usado "volatile"
volatile long T1; //Devido às variáveis estarem
volatile long T2; //Dentro da função da interrupção
volatile boolean flag = 0;
unsigned long T3;


long rpm = 0.0;
float sample = 0.0;
float erro = 0.0; //Diferença entre a referência (pelo potenciômetro) e a resposta do sistema (dada pelo sensor)
float erroAnt = 0; //Erro anterior ao atual

//Controlador Proporcional
float Kp = 0.02;
float mProp = 0.0; //Saída do controlador proporcional
float m = 0; //Controle do PID

//Controlador Derivativo
float mDer = 0; //Saída do controlador derivativo
float Kd = 0.1;
float tmpDer = 0; //Instante no qual o valor da derivada deverá ser atualizado

//Controlador Integral
float mInt = 0; //Saída do controlador integral
float Ki = 0.04;
float tmpInt = 0; //Instante no qual o valor da integral deverá ser atualizado

//Estabelece os valores máximo e mínimo para o PWM
double minVal = 0;
double maxVal = 255;


void setup() {
 

Serial.begin(230400);
  
  pinMode(PWMPin, OUTPUT); //Coloca a porta PWM como uma porta de saída de dados

  pinMode(RelePin, OUTPUT); //Coloca a porta do Relé como uma porta de saída de dados

  pinMode(SensorPin, INPUT_PULLUP); //Coloca a porta do Sensor como uma porta de entrada de dados, com pullup

  pinMode(PotenciometroPin, INPUT); //Coloca a porta do Potenciômetro como uma porta de entrada de dados

  digitalWrite(RelePin, HIGH); //Coloca o nível HIGH no Relé a fim de colocar o motor em sua rotação máxima

  attachInterrupt(digitalPinToInterrupt(SensorPin), contagem, RISING); //Estabelece a interrupção e sua condição (ao receber um sinal de subida no pino do Sensor)

  long tmpAtual = millis();
  long tmpDer = tmpAtual + TMP_DER; //Atualiza o valor da derivada em tempoAtual+50ms
  long tmpInt = tmpAtual + TMP_INT; //Atualiza o valor da integral em tempoAtual+50ms

}

void loop() 
{
    
if(flag == 0) //Quando a flag vinda da ISR for 0, chama o loop
{
    detachInterrupt(SensorPin); //Tira a interrupção para que não haja problema no cálculo da linha abaixo
    T3 = T2 - T1; //Calcula o tempo entre o primeiro e o terceiro pulso de subida (o tempo de uma volta completa)
    if(T3>300000) T3 = 300000; //Estabelece um limite máximo para T3 para que a variável rpm não dê overflow
    rpm = 60000000/T3; //Cálculo da velocidade do motor, em RPM
    sample = map(analogRead(PotenciometroPin), 0, 1024, 0, 3000); //Coloca os valores do potenciômetro em uma escala de RPM
    //Estabelece limites inferior e superior
    if(sample > 3000)
    {
      sample = 3000;
    }
    if (sample < 0)
    {
      sample = 0;
    }
    if (rpm < 0)
    {
      rpm = 0;
    }
    if (rpm > 3000)
    {
      rpm = 3000;
    }
    erro = sample - rpm; //Cálculo do erro
    Serial.print(-3000); Serial.print(" ");
    Serial.print(3000); Serial.print(" ");
    Serial.println(erro);
    if(erro>3000) erro = 3000; //Estabelece limites máximo
    if(erro<-3000) erro = -3000; //E mínimo para o erro
    mProp = Kp*erro; //Cálculo do controlador proporcional
    mDer = controladorDer(erro); //Cálculo do controlador derivativo
    mInt = controladorInt(erro); //Cálculo do controlador integral
    m=mProp+mDer+mInt; //Cálculo final do PID
    //Estabelecendo limites para o PWM
    if(m>maxVal)
    {
      m = maxVal;
    }
    if(m<minVal)
    {   
      m = minVal;
    }
    if(m<25)
    {
      m = 25;
    }
    analogWrite(PWMPin, m); //Escreve o valor final do PID no pino PWM
    pulsos = 0; //Zera a contagem dos pulsos
    attachInterrupt(digitalPinToInterrupt(SensorPin), contagem, RISING); //Recoloca a interrupção para um novo cálculo dos tempos entre os pulsos
}
  
}


//Função para calcular a saída do controlador derivativo
float controladorDer(float erro)
{
  long tmpAux;
  float mAux;
  tmpAux = millis();
  if(tmpAux >= tmpDer)
  {
    mAux = Kd*(erro-erroAnt);
    tmpDer = tmpAux + TMP_DER;
    if(mAux > maxVal)
    {
      mAux = maxVal;
    }
    if(mAux < minVal)
    {
      mAux = minVal;
    }
    erroAnt = erro;

    return(mAux);
  }
  return(mDer);
}


//Função para calcular a saída do controlador integral
float controladorInt(float erro)
{
  long tmpAux;
  long mAux;
  tmpAux = millis();
  if(tmpAux >= tmpInt)
  {
    mAux = Ki*erro + mInt;
    tmpInt = tmpAux + TMP_INT;
    if(mAux > maxVal)
    {
      mAux = maxVal;
    }
    if(mAux < minVal)
    {
      mAux = minVal;
    }
    return mAux;
  }
  return mInt;
}


//Função de interrupção (ISR)
void contagem()
{
  pulsos = pulsos+1;

 if(pulsos==1)
 {
  T1 = micros();
  flag = 1;
 }
 if(pulsos==3)
 {
  T2 = micros();
  flag = 0;
 }
}

Thanks in advance!

Edit: I suspect it happens because the flag goes to 1 and stays there, freezing the motor. How could I change that?
If I try to make a single flag=1 on pulsos==3, it doesn’t go back to the ISR. :frowning:

If you detect that the fan is not rotating, then you have to choose some PWM value that will start it rotating again. Determine that value by experiment.

It appears that you are already doing that:

    if(m<25)
    {
      m = 25;
    }
    analogWrite(PWMPin, m); //Escreve o valor final do PID no pino PWM

jremington:
If you detect that the fan is not rotating, then you have to choose some PWM value that will start it rotating again.

Determine that value by experiment.

That's why I used the "m = 25" line, it's the one that keeps it moving. So technically there's no problem with that?

You should NOT be using attach/detachInterrupt in the loop() function, as that will cause you to miss interrupts.

Use attachInterrupt only once, in setup().

Better, don't use interrupts and just poll the input port, with timeouts.

jremington:
You should NOT be using attach/detachInterrupt in the loop() function, as that will cause you to miss interrupts.

Use attachInterrupt only once, in setup().

Better, don’t use interrupts and just poll the input port, with timeouts.

I’ll try to do that, thanks!
How do I implement it/where can I learn to implement it?

To poll a digital input for a HIGH, with timeout, you can do something like this:

unsigned long timeout=1000UL;  //one second, for example
unsigned long start_time=millis();
while ( (digitalRead(input_pin) == LOW) && (millis()-start_time < timeout) ); //wait here, with timeout
// here, check for timeout or HIGH as needed

Even at 3000 RPM, fans are very slow compared to Arduino.

I use interrupts for detecting the speed of a small DC motor with the code in this link.

My speed control code is not in the link. To detect a stalled motor (or the situation prior to first movement) I have code in loop() that checks the time since the most recent pulse and if it is very long I assume the motor is stalled and call a function that produces a PWM value to get the motor started. When the interval between pulses is in the normal range I pass control to my PID code.

...R