Incremental encoder and "speed" of DigitalWrite

Hi @akilou23 ,

this was a fun exercise for me to write the code for this application.
My code makes use of the NewEncoder-library from user @gfvalvo
All the details to detect encoder-pin-changes are done inside the library

to install this library follow this tutorial

This is void loop() of the code

void loop() {
  DetectAndQueClicks();
  CreateForwardPulses();
  CreateBackwardPulses();
}

Pretty short. Because the code is well organised in functions.
To qoute the comment about loop()
void loop() iterates at high speed, because the timing is NON-blocking
this enables to increment a counter-variable for each encoder-click
this can be seen as some kind of queing up the incoming encoder-clicks

creating pulses is done independent from this queing up
you could say the clicks are dammed up
each time an encoder-click is detected the counter increments by 1 "++"
each time an output-pulse has finished the counter is decremented by 1 "--"

If you would turn the encoder into one direction for a long time the code would create pulses for a even longer time as the pulses must have a minimum-length

To avoid creating forward/backward-pulses in a mess inbetween each other
if you change encoder-rotationdirection qued up clicks of the opposite direction where decremented first until they are zero.

for demonstration purposes the constant pulseLength is set to 1000 milliseconds
reduce this pulseLength to the value you would like to have.

#include "Arduino.h"
#include "NewEncoder.h"

const byte EncChA_Pin = 2;
const byte EncChB_Pin = 3;
const int minVal = -20;
const int maxVal =  20;
const int startVal = 0;


const byte forwardPulsePin  = 8;
const byte backwardPulsePin = 9;

const unsigned long pulseLength = 1000;

// Pins 2 and 3 should work for many processors, including Uno. See README for meaning of constructor arguments.
// Use FULL_PULSE for encoders that produce one complete quadrature pulse per detnet, such as: https://www.adafruit.com/product/377
// Use HALF_PULSE for endoders that produce one complete quadrature pulse for every two detents, such as: https://www.mouser.com/ProductDetail/alps/ec11e15244g1/?qs=YMSFtX0bdJDiV4LBO61anw==&countrycode=US&currencycode=USD

NewEncoder myEncoderObject(EncChA_Pin, EncChB_Pin, minVal, maxVal, startVal, FULL_PULSE);

NewEncoder::EncoderState myCurrentEncoderState;

int16_t currentValue;
int16_t prevEncoderValue;

int forwardCount  = 0;
unsigned long forwartPulseStarted = 0;

int backwardCount = 0;
unsigned long backwardPulseStarted = 0;

// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - startOfPeriod >= TimePeriod ) {
    // more time than TimePeriod has elapsed since last time if-condition was true
    startOfPeriod = currentMillis; // a new period starts right here so set new starttime
    return true;
  }
  else return false;            // actual TimePeriod is NOT yet over
}

void setup() {
  digitalWrite(forwardPulsePin, HIGH);
  pinMode(forwardPulsePin, OUTPUT);

  digitalWrite(backwardPulsePin, HIGH);
  pinMode(backwardPulsePin, OUTPUT);
  // myEncState is a variable of type EncoderState
  // EncoderState is a structured variable that has two "simple" variables
  // .currentValue which is type int16_t
  // (16 bit signed integer valuerange -36767 to 36767)
  // currentValue counts up / down with each pulse created through rotating the encoder
  // and
  // .currentClick which is of type "EncoderClick"
  // the variable type "EncoderClick" can have just 3 values
  // NoClick, DownClick, UpClick where "click" means a "pulse" created through rotating the encoder
  NewEncoder::EncoderState myEncState;

  Serial.begin(115200);
  Serial.println( F("Setup-Start") );

  if (!myEncoderObject.begin()) {
    Serial.println("Encoder Failed to Start. Check pin assignments and available interrupts. Aborting.");
    while (1) {
      yield();
    }
  } else {
    // store values of currentValue and EncoderClick into variable myEncState
    myEncoderObject.getState(myEncState);
    Serial.print("Encoder Successfully Started at value = ");
    prevEncoderValue = myEncState.currentValue;
    Serial.println(prevEncoderValue);
  }
}

void DetectAndQueClicks() {
  myEncoderObject.getState(myCurrentEncoderState);

  switch (myCurrentEncoderState.currentClick) {

    case NewEncoder::UpClick: // UpClick is reset to NoClick on reading it
      Serial.println("UpClick");
      if (backwardCount == 0) { // if no backward-clicks are queued up
        forwardCount++;         // whenever an UpClick occures increment
      }
      else { // if backward-clicks are queued up decrement backwardCount
        if (backwardCount > 0) {
          backwardCount--;
          Serial.println("backwardCount--");
        }
      }
      break;

    case NewEncoder::DownClick: // DownClick is reset to NoClick on reading it
      Serial.println("DownClick");
      if (forwardCount == 0) {
        backwardCount++; // whenever an DownClick occures increment
      }
      else {
        if (forwardCount > 0) {
          forwardCount--;
          Serial.println("forwardCount--");
        }
      }
      break;
  }
}

void CreateForwardPulses() {
  static boolean CreatePulse;

  if (!CreatePulse && forwardCount > 0) { // as long as forward clicks have been occurred
    forwartPulseStarted = millis();  // store actual timestamp
    CreatePulse = true;              // set flag-variable
    digitalWrite(forwardPulsePin, LOW); // start low-pulse
  }

  if (CreatePulse) {
    if ( TimePeriodIsOver(forwartPulseStarted, pulseLength) ) { // check if the number of milliseconds stored in pulseLength have passed by
      // if the number of milliseconds stored in pulseLength have passed by
      forwardCount--;
      CreatePulse = false;    // reset flagvariable to false
      digitalWrite(forwardPulsePin, HIGH); // end low-pulse
      Serial.println( F("forward pulse finished") );
    }
  }
}

void CreateBackwardPulses() {
  static boolean CreatePulse;

  if (!CreatePulse && backwardCount > 0) { // as long as forward clicks have been occurred
    backwardPulseStarted = millis();  // store actual timestamp
    CreatePulse = true;              // set flag-variable
    digitalWrite(backwardPulsePin, LOW); // start low-pulse
  }

  if (CreatePulse) {
    if ( TimePeriodIsOver(backwardPulseStarted, pulseLength) ) { // check if the number of milliseconds stored in pulseLength have passed by
      // if the number of milliseconds stored in pulseLength have passed by
      backwardCount--;
      CreatePulse = false;    // reset flagvariable to false
      digitalWrite(backwardPulsePin, HIGH); // end low-pulse
      Serial.println( F("backward pulse finished") );
    }
  }
}

// void loop() iterates at high speed
//because the timing is NON-blocking
// this enables to increment a counter-variable for each encoder-click
// this can be seen as some kind of queing up the incoming encoder-clicks
// creating pulses is done independent from this queing up
// you could say the clicks are dammed up
// each time an encoder-click is detected the counter increments by 1
// each time an output-pulse has finished the counter is decremented by 1
void loop() {
  DetectAndQueClicks();
  CreateForwardPulses();
  CreateBackwardPulses();
}

best regards Stefan