HOW TO IMPLEMENT INTERRUPT STEPPER MOTOR - SOLAR TRACKER

Hello, everyone!!

I have been developing a code to use in a Solar Tracker Project. So I would like to understand how Interrupt works. I’m using a switch to avoid solar panel going through an certain angle value (maximum angle). For each axis (two axis in total), we have two reedswitches to set the maximum range of movement. All of them are connected to the same digital pin and to know which pin was activated, we have to know which axis was in motion and which direction was the last (clockwise or anticlockwise - HIGH OR LOW).
Another thing is that the code runs in two modes on the loop: tracking (rastreamento) and positioning (posicionamento).

What I want is:

  • Switch is activated
  • Calls the function sensordefimdecurso.
  • Depending on the mode, we have to do:
  1. Tracking mode:
  • Set the motor to move to another direction
  • Moving 90° degrees to another direction
  • Return to the loop
  1. Positioning mode:
  • Set the motor to move to another direction
  • Execute the movement until the other side (until activate the other switch) ------> this step
  • Moving 90° to the other direction and stops
  • Return to the loop

To explain:
The sensordefimdecurso function aims to set what will be done when the switch is activated. There are two situations: this function is activated while the code executes the tracking mode or this function is called while runs the positioning mode.

In the first case (tracking mode), when the sensordefimdecurso is triggered, it will call the function calibracaorastreamento, for calibrate the angle value of the switch, by comparing it with a certain value set.
The second case (position mode), when the sensordefimdecurso is triggered, it will call the function Homeposition, for calibrate the angle value of the switch, by comparing it with a certain value set.

Summarize, I want to know how Interrupt works in a function in my case and if my code is right in terms of logic.

And one doubt more: when I activate the switch again in this step (Execute the movement to another side, until activate the other switch), the code returns to the beginning of the function called by Interrupt, but I would like to execute the following step (Moving 90° to the other direction and stops). How I do this?

The code is attached.

sketch_mar03a.ino (32.9 KB)

Couple of things. First, an interrupt has no relation to a function. All your code in the Arduino is in one function or another. An interrupt can occur at any time, in any piece of code. When the interrupt code has completed, the processor returns to the next instruction in line when the interrupt occurred.

Interrupt code must be as short and quick as possible to the remaining program timing is not disrupted.

Paul

Paul_KD7HB:
Couple of things. First, an interrupt has no relation to a function. All your code in the Arduino is in one function or another. An interrupt can occur at any time, in any piece of code. When the interrupt code has completed, the processor returns to the next instruction in line when the interrupt occurred.

Interrupt code must be as short and quick as possible to the remaining program timing is not disrupted.

Paul

But is it possible, in my case, to use noInterrupts, to manage this step I have mentioned? And one doubt more: when I activate the switch again in this step (Execute the movement to another side, until activate the other switch), the code returns to the beginning of the function called by Interrupt, but I would like to execute the following step (Moving 90° to the other direction and stops) ?

You are just digging a deeper hole which you will not get out of.

There is NO need for interrupts in your program.

Moreover, your switch’s contacts are probably bouncing and giving multiple closures.

Paul

Paul_KD7HB:
You are just digging a deeper hole which you will not get out of.

There is NO need for interrupts in your program.

Moreover, your switch's contacts are probably bouncing and giving multiple closures.

Paul

So, my friend, Paul_KD7HB! What is the best option for my case? What I should do when the switch is pressed or activated, according to my needs that I have explained ?

Other question: Is it important to use debounce in this case? So, for add one information: I'm using a reedswitch to do it!

luciocr:
What I should do when the switch is pressed or activated, according to my needs that I have explained ?

It will be much easier for you to examine the problem and for us to help you if you write a short program that does nothing except move a motor and detect the limit switches. Leave out everything else.

Solar tracking is a very slow process so detecting a switch by polling (using digitalRead() in every iteration of loop() ). You need to detect when the switch state changes from not-pressed to pressed. Use code like this

void loop() {
   lastSwitchState = switchState;
   switchState = digitalRead(switchPin);
   if (switchState == HIGH and lastSwitchState == LOW) { // assumes LOW when pressed
       // switch has just been triggered so do stuff
   }
}

Quite separately, stepper motors are very inefficient and might be consuming all the power that the solar panel can produce.

...R

@Robin2 and @Paul_KD7HB how short is the code supposed to be? Can my code's size be considered long?

If my motor needs to run a certain number of steps, what should I do if I know that it's not advisable to use delay and millis within an ISR? Could I use micros and expect to be incremented the its value to mark the time from repetitive task (in this case, run the steps)?

Here is my piece of the code that I would like to implement interrupt!

volatile boolean portaA = LOW;
volatile boolean portaB = LOW;
volatile int indicacaodefuncao;
volatile int eixoatual; // marcador para número do eixo atual, 0= Vertical, 1=Horizontal
volatile int potValue;

volatile boolean swLeitura  = false; //estado atual da chave de fim de curso, True = fechado; False = aberto
volatile boolean dir[2] = {true, true}; //variável indicadora de direção para cada eixo - {vertical,horizontal} - HIGH ou LOW

//Pinos Switch para chave de fim de curso - digital 
int  pinoswitch = 2;

//Pino leitura Potenciometro {Pot1, Pot2]/ eixos{vertical,horizontal}- analogico
int PinoPotEixo[2] = {5, 4};

// Pinos de saída Motor de passo {stepper pin, direction pin, Reset Pin, Mode 0 pin, Mode 1 pin, Mode 2 pin}-digital
int pinoDrive[6] = {9,10,11,12,7,8};




void setup() {

    pinMode(pinoswitch,INPUT_PULLUP);
    attachInterrupt(digitalPinToInterrupt(pinoswitch),sensorfimcurso,FALLING);
    }

void loop  {

doing something until pinoswitch change from HIGH TO LOW.

}


void sensorfimcurso(){//função para dizer o que deve ser feito quando o pino digital do sensor de fim de curso é acionado (serve para limitar amplitude de movimentação da estrutura)
    
    digitalWrite(pinoDrive[2], LOW);

boolean portaA = digitalRead(sinalportaA);
boolean portaB = digitalRead(sinalportaB);

sinalporta =String(portaA) + String(portaB);

 if (indicacaodefuncao = 0){ // in the case of tracking 
calibracaorastreamento(eixoatual);
  } 
}
if (indicacaodefuncao = 1){// in the case of positioning
 Homeposition (eixoatual); 
}


void Homeposition (){
 
  potValue = analogRead(PinoPotEixo[eixoatual]); //faz a leitura inicial do potenciometro
  swLeitura  = digitalRead(pinoswitch); //faz a leitura do pino switch 
   float difleiturapot = abs(potValue - leituraswitchextremo[eixoatual][dir(eixoatual)]); //calcula a diferença entre a leitura atual do pino switch e o valor pré-determinado para cada eixo (vertical e horizontal) e para cada lado (esquerdo e direito)
  
 
  if(difleiturapot>3){ ///caso a diferença entre as leituras for maior do que 3 bytes (valor calculado para o erro de 1º)
  int contador;
  contador ++;
do something;
  }
   }
  if(contador>1){
    contador = 0;
  }
  
/////////////////////////////////////////////////////////////////////////////////////////////
void funcaoacionamentopassos (){
 do {
  digitalWrite(pinoDrive[0], HIGH);
  delay(stepDelayVH[eixoatual]);
  digitalWrite(pinoDrive[0],LOW);
  delay(stepDelayVH[eixoatual]);
}while(pinoswitch  == HIGH)
}

luciocr:
@Robin2 and @Paul_KD7HB how short is the code supposed to be? Can my code's size be considered long?

I'm certainly not going to try to get my head around 32k of code.

1k or 2k would be much better.

...R

All program code should be long enough to get the job done.

The most critical thing for your design is to stop movement if one of the limit switches is set. The best plan is to NOT do the movement in that direction if the switch is set.

So, the very first thing to do is to make two booleans, set to false, that will tell your program the status of both switches.

The logic is to set both booleans to “FALSE” and then test each switch and if set, make it’s boolean TRUE. Do this as the very first instructions in your “loop”.

Then the rest of the code can decide what to do based on the switch booleans. This way, no interrupt is required.

Paul

Paul_KD7HB:
All program code should be long enough to get the job done.

The comment about length arises from my Reply #5 in which I suggested that it would be easier to help with a short test program that only deals with the problem at hand.

…R

Robin2:
The comment about length arises from my Reply #5 in which I suggested that it would be easier to help with a short test program that only deals with the problem at hand.

...R

Certainly true. Much easier to find errors when they are not hiding in the weeds!

Paul

Paul_KD7HB:
All program code should be long enough to get the job done.

The most critical thing for your design is to stop movement if one of the limit switches is set. The best plan is to NOT do the movement in that direction if the switch is set.

So, the very first thing to do is to make two booleans, set to false, that will tell your program the status of both switches.

The logic is to set both booleans to "FALSE" and then test each switch and if set, make it's boolean TRUE. Do this as the very first instructions in your "loop".

Then the rest of the code can decide what to do based on the switch booleans. This way, no interrupt is required.

Paul

Paul, will this work if I have only one output to Arduino? All of the switches are connected to the same output. So, how can I do what you are saying? I think my big issue is that I need to do two different things for the same situation. For example, at the first time when I hit the switch, I need to change the direction of my motor and move until hit the switch that is on the other side. Now, this time, I need to move only 90° degrees (this way I don't want to hit the switch again)! How can I do this?
I have thought these days about this problem and come to conclusion that it isn't possible to do this by using Interrupts, because each time that the switch is hit, the function connected to Interrupt is called again and I don't want to do the same thing in the second time.

The reason you are using stepper motors is so you can count the steps necessary to get from one place to the other If you place your limit switches at the x=0 and y=0 positions, you can move the stepper motors the number of steps necessary to get into position. If you count 100 motor steps and measure the actual distance moved, you can compute the distance each step moves and come pretty close to your desired end point in your software.

Yes, you do have to keep track of where you are from the 0,, 0 point by adding steps for one direction and subtracting for the opposite direction.

Fundamental CNC design and programming.

Paul

luciocr:
I have thought these days about this problem and come to conclusion that it isn’t possible to do this by using Interrupts, because each time that the switch is hit, the function connected to Interrupt is called again and I don’t want to do the same thing in the second time.

I suspect you are thinking about this the wrong way.

The switch should not directly control the hardware. All the switch should do is cause the value of a variable (let’s call it currentAction) to change. For example the first time you press the switch it might change currentAction from ‘S’ for stopped to ‘R’ for move to the right. The next switch press will cause the code to check the value of currentAction and if it is ‘R’ it will change it to ‘N’ for ninety degrees. (By the way the names and values are just things I made up - you should use things that make sense for you).

Then the code that controls the motor will check the value of currentAction to decide what to do. You may also need to have another variable that keeps track of when the motor has finished a move if you don’t want the value to change from ‘R’ to ‘N’ until the right move is complete.

Read up about the concept of a State Machine.

…R

Paul_KD7HB:
The reason you are using stepper motors is so you can count the steps necessary to get from one place to the other If you place your limit switches at the x=0 and y=0 positions, you can move the stepper motors the number of steps necessary to get into position. If you count 100 motor steps and measure the actual distance moved, you can compute the distance each step moves and come pretty close to your desired end point in your software.

Yes, you do have to keep track of where you are from the 0,, 0 point by adding steps for one direction and subtracting for the opposite direction.

Fundamental CNC design and programming.

Paul

Robin, good afternoon! I think that only counting the steps made by the stepper motor isn't so accurate, because sometimes, we might have some steps missed during the movement due to some error of the motor.

My aim is to assure that the movement doesn't go beyond the switch. The switch will be used to set a homeposition to stepper motor. How can I accomplish it, in terms of state machine in Arduino?

You are SOOOOO wrong about stepper motor accuracy. I owned a CNC soldering machine for 11 years of almost daily use. It was accurate to 0.0001 inch in three dimensions. Once programmed, it NEVER missed getting to the correct position to solder a pin.

When the system was turned on it zeroed each axes independently and ran from that location by counting steps.

Paul

luciocr:
because sometimes, we might have some steps missed during the movement due to some error of the motor.

The error would be with the person who chose a motor with inadequate torque

...R

So, people! Here is my code, after using State Machine concept. However, I have two doubts:
How can replace the delay function used within for statement and use millis, in my case?
And for state machine function (StateMachine_s1(eixoatual)), will it be executed in the order that it appears in the loop? This can be a problem for me, because I don’t want it runs every time on the loop, but only when the limit switch is activated. But there is a problem, because the other switch of the same axis will be activated during this function and it may cause that the function restarts again (and I want to move only 90°).
How can I solve this?

void loop() {


  sinalportaA = digitalRead(portaDatalogger[0]);//leitura de cada uma das portas de comunicação com o datalogger
  sinalportaB = digitalRead(portaDatalogger[1]);
  sinalportaC = digitalRead(portaDatalogger[2]);//porta para acionamento do Modo, High = desligado; Low = Acionado/fechado

  if (sinalportaC = LOW){

    if (sinalportaA = LOW && sinalportaB = LOW){
      eixoatual=0;
     posicionamento (eixoatual);
     stateMachine_S1(eixoatual);
     eixoatual =1;
     rastreamento (eixoatual);
     
    }

    if (sinalportaA = HIGH && sinalportaB = LOW){
  }
 another combination similar to condition above;

}



void posicionamento (eixoatual){

  execute something;
}

void rastreamento (eixoatual){
  execute something;
}
  
void StateMachine_s1(eixoatual) {
  
  swLeitura = digitalRead(pinoswitch); //faz a leitura do pino switch 
  state_prev_s1 = state_s1;

  //State Machine Section
  switch (state_s1) {
    case 0: //RESET!
      //Catch all "home base" for the State Machine
      state_s1 = 1;
    break;

    case 1: //READ THE STATE OF THE SWITCH
      if (swLeitura == LOW) {state_s1 = 2;}
    break;

    case 2: //CALCULATE THE DIFERENCE BETWEEN SET READING PREVIOUSLY AND THE CURRENT VALUE OF POTVALUE WHEN IT HITS THE SWITCH
      potValue = analogRead(PinoPotEixo[eixoatual]);
      int difleiturapot = abs(potValue - leituraswitchextremo[eixoatual][dir(eixoatual)]);
      if (difleiturapot >3) 
      { leituraswitchextremo[eixoatual][dir[eixoatual]] = potValue;
        state_s1 = 3;}
      }
         else { state_s1 = 0;}
    break;

    case 3: //CHANGE THE DIRECTION OF THE MOVEMENT 

    digitalWrite(pinoDrive[2], LOW); // turn off Reset pin
    dir[eixoatual]= !dir[eixoatual]; // change the direction of current axis
    digitalWrite(pinoDrive[1],dir[eixoatual]); //write this change in Dir Pin
    digitalWrite(pinoDrive[2], HIGH); //turn on Reset pin
    state_s1 = 4;
    break;

    case 4: //EXECUTE THE STEPS TO THE DIRECTION THAT IT WAS CHANGED IN THE LAST STEP (CASE 3) UNTIL HITS AGAIN (AT THIS TIME, THE OTHER SWITCH IS ACTIVATED)
      funcaoacionamentopassos();
      if(swLeitura== LOW){ state_s1 = 5;}
      
    break;

    case 5: //CHANGE THE DIRECTION OF MOVEMENT
    
    digitalWrite(pinoDrive[2], LOW); // turn off Reset pin
    dir[eixoatual]= !dir[eixoatual]; // change the direction of current axis
    digitalWrite(pinoDrive[1],dir[eixoatual]);//write this change in Dir Pin
    digitalWrite(pinoDrive[2], HIGH); ////turn on Reset pin
    state_s1=6;

   
    break;

    case 6: // MOVE 90°DEGREES AND RETURNS TO THE BEGINNING
     
      float passospara90 = 90*passo_grausEixoVH[eixoatual] //calculate the number of steps needed to move 90°degrees
      for (int i = 1; i <= passospara90; i++) {
      digitalWrite(pinoDrive[0], HIGH);  // turn on the Step Pin//
      delayMicroseconds(stepDelayMotorVH[eixoatual]);
      digitalWrite(pinoDrive[0], LOW); // turn off the Step Pin//
      delayMicroseconds(stepDelayMotorVH[eixoatual]);
      }
      state_s1 = 0;
    break;

  }


  void funcaoacionamentodepassos() // FUNCTION CREATED TO GENERATE THE STEPS
{
  unsigned long currentMillis = millis();

do {
  if((pinoDrive[0]== HIGH) && (currentMillis - previousMillis >= stepDelayVH[nEixo]))
  {
    leitura_driver = LOW;  // Turn it off
    previousMillis = currentMillis;  // Remember the time
    digitalWrite(pinoDrive[0], leitura_driver);  // Update the actual STEP READING (HIGH OR LOW)
  }
  else if ((pinoDrive[0]== LOW) && (currentMillis - previousMillis >= stepDelayVH[nEixo]))
  {
    leitura_driver = HIGH;  // turn it on
    previousMillis = currentMillis;   // Remember the time
    digitalWrite(pinoDrive[0],leitura_driver );    // Update the actual LED
  }
}while(pinoswitch == HIGH)}

luciocr:
So, people! Here is my code, after using State Machine concept.

It is only part of the program. Always post the complete program. Does it compile? If not correct any errors. If it does compile then tell us in detail what it actually does and what you want it to do that is different.

How can replace the delay function used within for statement and use millis, in my case?

Have a look at the second example in this Simple Stepper Code

I strongly recommend that you identify the states in your state machine with characters that are meaningful abbreviations (for example 'S' for stop) rather than numbers which are meaningless to a human. Using ENUMs allows you to use proper words. See this ENUM example.

...R

Hi,
WHY have you connected ALL your limit switches to ONE digital input?
WHY do you need to know using stepper steps where the motors are?

The LDRs will tell you what position the steppers are in with respect to the sun, thats ALL you need to know.
When it is night, you just return the PV array to the East position and await sunrise.

You don’t need to calculate how many steps to move!

Just look at the LDRs work out which way it needs to move the stepper, move it, say one step.
Check limit switches.
Read the LDRs again and move one step in the appropriate direction.
Check limit switches.

Us it as an iterative process.
What do you store in the EEPROM and why?

Tom… :slight_smile: