Rewriting an encoder/stepper sketch to work with interrupts

This is the datasheet of the stepper we use:

This is the encoder we use.

This is the code:

#include <Bounce2.h>
#include <Stepper.h>

/* 
 DESCRIPTION
 ====================
 Reads the 2 switches in an encoder, 
 determines direction,
 updates counter.
 No interrupts.
 Switches debounced, didn't test first, just did it anyway.
 */


const byte ENCODER_PINA= 2;
//const byte LED_PINA= 12;
const byte ENCODER_PINB= 3;
//const byte LED_PINB= 11;

const int stepsPerRevolution = 64;

Stepper myStepper(stepsPerRevolution, 7, 4, 5, 6);

int valueA; //debounced encoder switch reads
int valueB;

bool motionDetected = false;
int grossCounter = 0; // total steps
int nettCounter = 0;  // cw-ccw
int fullRevolutions = 0;
int surplusSteps = 0; //part revs
bool CW;

byte cyclesPerRev =20;  //check encoder datasheet

// Instantiate 2 Bounce object
Bounce debouncerA = Bounce(); 
Bounce debouncerB = Bounce(); 

// setup ********************************************
void setup() {
  Serial.begin(115200);
  Serial.println("Setup");
  // Setup the buttons
  pinMode(ENCODER_PINA,INPUT_PULLUP);
  pinMode(ENCODER_PINB,INPUT_PULLUP);


  // After setting up the button, setup debouncer
  debouncerA.attach(ENCODER_PINA);
  debouncerA.interval(5);
  debouncerB.attach(ENCODER_PINB);
  debouncerB.interval(5);

  myStepper.setSpeed(300);
  
  Serial.println("Setup done");
}

// loop *****************************************
void loop() {

  // Update the debouncers
  doDebounce();

  // Read the encoder switches
  doEncoderRead();

    //determine direction and update counter

  updateCounter();

} //loop

// my functions **************************************************
void doDebounce()
{
  debouncerA.update();
  debouncerB.update();
} //doDebounce

void doEncoderRead()
{
  valueA = debouncerA.read();
  valueB = debouncerB.read();
} //doEncoderRead

void updateCounter()
{

  /*
  the possibilites are:
   
   AB: in a detent
   if just arrived, update counter, clear motiondetected
   otherwise do nothing
   Ab: start of CW or end of CCW
   if start, set CW bool and set motionDetected
   if at end (know becasue motionDetected already set), do nothing
   aB: start of CCW or end of CW
   if start, clear CW bool and set motionDetected
   if at end (know becasue motionDetected already set), do nothing
   ab: in middle of either CW or CCW, do nothing
   */

  if (valueA && valueB && motionDetected ) //in a detent and just arrived
  {
    if (CW)
    {
      grossCounter= grossCounter + 1;
      nettCounter= nettCounter + 1;
      
      myStepper.step(80);
    }
    else //CCW
    {
      grossCounter= grossCounter + 1;
      nettCounter= nettCounter - 1;
      
      myStepper.step(-80);
    }
    motionDetected = false;
    Serial.print("grossCounter: ");
    Serial.println(grossCounter);
    Serial.print("nettCounter: ");
    Serial.println(nettCounter);
    
    fullRevolutions = nettCounter / cyclesPerRev; 
    surplusSteps = nettCounter % cyclesPerRev;
    
    Serial.print("Nett position: ");
    Serial.print(fullRevolutions);
    Serial.print(" + ");
    Serial.println(surplusSteps);
    Serial.println(" ");
    
    


  }

  if (valueA && !valueB && !motionDetected ) // just started CW
  {
    CW= true;
    motionDetected=true;
    Serial.println("CW");

  }

  if (!valueA && valueB && !motionDetected )  //just started CCW
  {
    CW= false;
    motionDetected=true;
    Serial.println("CCW");

  }
} //updateCounter

Our code works perfectly for our purposes when we use myStepper.step(1);.

The problem is in the myStepper.step(80);, because when we use that, the serial monitor (and the stepper) mess up clockwise and counterclockwise. For example, when we use step(1) the output would be:
cw
cw
cw
cw
cw
cw
but with step(80) and doing the same, the output is e.g.
cw
cw
ccw
cw
ccw
ccw
cw

We think that is because when the stepper is turning 80 steps, that takes time and in that time the code misses pulses from the encoder.

We want 80 steps at once because that corresponds with the amount of degrees when you turn the encoder one step.

We think we could solve that by using interrupts, because that would 'interrupt' the code to read out the encoder, and we wanted to try that but didn't know where to start.

We have some example codes for the encoder that work with interrupts, but when we tried to implement the code for the stepper in those, well, we don't really know what we're doing anyway.

We think that is because when the stepper is turning 80 steps, that takes time and in that time the code misses pulses from the encoder.

So, why do it that way? Each time the encoder changes, you have a new target position - 80 steps more or 80 steps less than the current target.

You also have an actual position. On each pass through loop, move one step closer to the target position, if you are not there. It won't matter if the target changes.

PaulS:
So, why do it that way? Each time the encoder changes, you have a new target position - 80 steps more or 80 steps less than the current target.

You also have an actual position. On each pass through loop, move one step closer to the target position, if you are not there. It won't matter if the target changes.

It works! We changed the code so it works with a target variable.

We added i = i + 85; in the function, and made the loop look like this:

void loop() {

  // Update the debouncers
  doDebounce();

  // Read the encoder switches
  doEncoderRead();

    //determine direction and update counter

  updateCounter();
  
  while (i != 0){
    if (i < 0){
      myStepper.step(-1);
      i++;
    } else {
      myStepper.step(1);
      i--;
    }
    
    doDebounce();
    doEncoderRead();
    updateCounter();
    
      }

} //loop

That solved it for us!

Full code now:

#include <Bounce2.h>
#include <Stepper.h>

/* 
 DESCRIPTION
 ====================
 Reads the 2 switches in an encoder, 
 determines direction,
 updates counter.
 No interrupts.
 Switches debounced, didn't test first, just did it anyway.
 */


const byte ENCODER_PINA= 2;
//const byte LED_PINA= 12;
const byte ENCODER_PINB= 3;
//const byte LED_PINB= 11;

const int stepsPerRevolution = 64;

Stepper myStepper(stepsPerRevolution, 7, 4, 5, 6);

int valueA; //debounced encoder switch reads
int valueB;

int i = 0;

bool motionDetected = false;
int grossCounter = 0; // total steps
int nettCounter = 0;  // cw-ccw
int fullRevolutions = 0;
int surplusSteps = 0; //part revs
bool CW;

byte cyclesPerRev =20;  //check encoder datasheet

// Instantiate 2 Bounce object
Bounce debouncerA = Bounce(); 
Bounce debouncerB = Bounce(); 

// setup ********************************************
void setup() {
  Serial.begin(115200);
  Serial.println("Setup");
  // Setup the buttons
  pinMode(ENCODER_PINA,INPUT_PULLUP);
  pinMode(ENCODER_PINB,INPUT_PULLUP);


  // After setting up the button, setup debouncer
  debouncerA.attach(ENCODER_PINA);
  debouncerA.interval(5);
  debouncerB.attach(ENCODER_PINB);
  debouncerB.interval(5);

  myStepper.setSpeed(300);
  
  Serial.println("Setup done");
}

// loop *****************************************
void loop() {

  // Update the debouncers
  doDebounce();

  // Read the encoder switches
  doEncoderRead();

    //determine direction and update counter

  updateCounter();
  
  while (i != 0){
    if (i < 0){
      myStepper.step(-1);
      i++;
    } else {
      myStepper.step(1);
      i--;
    }
    
    doDebounce();
    doEncoderRead();
    updateCounter();
    
   
  }

} //loop

// my functions **************************************************
void doDebounce()
{
  debouncerA.update();
  debouncerB.update();
} //doDebounce

void doEncoderRead()
{
  valueA = debouncerA.read();
  valueB = debouncerB.read();
} //doEncoderRead

void updateCounter()
{

  /*
  the possibilites are:
   
   AB: in a detent
   if just arrived, update counter, clear motiondetected
   otherwise do nothing
   Ab: start of CW or end of CCW
   if start, set CW bool and set motionDetected
   if at end (know becasue motionDetected already set), do nothing
   aB: start of CCW or end of CW
   if start, clear CW bool and set motionDetected
   if at end (know becasue motionDetected already set), do nothing
   ab: in middle of either CW or CCW, do nothing
   */

  if (valueA && valueB && motionDetected ) //in a detent and just arrived
  {
    if (CW)
    {
      grossCounter= grossCounter + 1;
      nettCounter= nettCounter + 1;
      
      i = i + 85;
    }
    else //CCW
    {
      grossCounter= grossCounter + 1;
      nettCounter= nettCounter - 1;
      
      i = i - 85;
    }
    motionDetected = false;
    Serial.print("grossCounter: ");
    Serial.println(grossCounter);
    Serial.print("nettCounter: ");
    Serial.println(nettCounter);
    
    fullRevolutions = nettCounter / cyclesPerRev; 
    surplusSteps = nettCounter % cyclesPerRev;
    
    Serial.print("Nett position: ");
    Serial.print(fullRevolutions);
    Serial.print(" + ");
    Serial.println(surplusSteps);
    Serial.println(" ");
    
    


  }

  if (valueA && !valueB && !motionDetected ) // just started CW
  {
    CW= true;
    motionDetected=true;
    Serial.println("CW");

  }

  if (!valueA && valueB && !motionDetected )  //just started CCW
  {
    CW= false;
    motionDetected=true;
    Serial.println("CCW");

  }
} //updateCounter