Problems with keeping two different values for encoder value

I am using a library for encoder which saves it's value to a variable, and i then have setTemperature and setMotorSpeed variables which i change with the encoder, the problem is since the encoder values are saved to a single variable when i press the button to switch to choosing motor speed, it automatically writtes encoder value to it. So i tried writting a code that saves last value of set temperature and motor speed and then when i switch back to choosing them it automatically sets value from the last saved value, the problem is the code doesn't work, i tried everything already, but just can't figure it out.
Would really appreciate some help.
I have code that toggles state of swcount when i press it.


  if (SW_Count == 1)
  {
    EncoderValueTemp = (myEnc.read() / 4); // divide by 4 because the encoder makes 4 pulses per turn
    if (EncoderValueTemp != EncoderValueTempLastValue)
    {
      setTemperature = EncoderValueTempLastValue;
    }
    EncoderValueTempLastValue = EncoderValueTemp;
  }

  else if (SW_Count == 0)
  {
    EncoderValueRPM = (myEnc.read() / 4);
    if (EncoderValueRPM != EncoderValueRPMLastValue)
    {
      setMotorSpeed = EncoderValueRPMLastValue;
    }
    EncoderValueRPMLastValue = EncoderValueRPM;
  }

With "Code doesn't work" what exactly do you mean. Also, SW_Count is always 0 or 1? Is this running perpetually at 0 or 1? If so wouldn't this "block" the encoder value from changing or am I misunderstanding the logic?

Yes it's 1 or 0, (when it's 0 i can set the value of the temperature and when it's 1 i can set motor speed) i have a code that toggles the state when i press the button. Yeah but myEnc.read() command reads when encoder is turned and saves it to a variable that is defined in the library.
So basically i would like the code to save last set temperature value and motor speed, so when i toggle between these settings it's doesn't adjust the variables based on the encoder value because that's shared.
I hope you understand.

You need to maintain two different counts from the encoder for the two different things it is controlling, if I understand correctly.

Thus you need offsets for each count which are set whenever switching the encoder to controlling a different thing.

So the code should use these offsets to maintain setpoints, and set them when switching mode, something like this:

int temperature_offset = 0 ;
int temperature_setpoint = 20 ;
int speed_offset = 0 ;
int speed_setpoint = 20 ;

void read_encoder()
{
  if (speed_mode)
    speed_setpoint = (myEnc.read() + speed_offset ) / 4;
  else
    temperature_setpoint = (myEnc.read() + temperature_offset) / 4 ;
}

void switch_to_speed_mode ()
{
  speed_offset = speed_setpoint*4 - myEnc.read() ;
  speed_mode = true ;
}

void switch_to_temperature_mode()
{
  temperature_offset = temperature_setpoint*4 - myEnc.read()  ;
  speed_mode = false ;
}

The support to you could be much better if you would post the complete sketch from very first line to the very last line. And if you would provide a link to that encoder-library that you are using.

There are mimium 5 encoder-libraries online which all work different. Me personally I use NewEncoder because

  • it works relyably even with the most bounciest mechanical switch encoders
  • it has a function to set the encoder-variable to a new value
  • you can setup limits for min and max of the encoder-variable.
    The logic of the library itselfs limits the value in between the min and max.

best regards Stefan

1 Like

I use this encoder library

So over the past few days i tried a few different things, to save last set temperature value and then when i switch back to setting temperature it writes last set temperature to encoder value, but of course it doesn't work. The whole code is pretty long and it's for display, measuring temperature, protection... Only part of the code that would be useful to show is switch toggle state which i included. I also included a video so you can see what the problem is. I also added comments to code.
code for switch toggle button (when it's zero i can set the rpm of the motor, when 1 i can set temperature)

if ((millis() - LastDebounceTimeSW) > DebounceDelay)
  {

    SW_Button_State = digitalRead(SW); // reads button state and saves it in a variable

    if (SW_Button_State != SW_Button_Last_State)
    {
      if (SW_Button_State == HIGH)
      {
        if (SW_Count == 0)
        {
          SW_Count = 1;
        }
        else
        {
          SW_Count = 0;
        }
      }
      SW_Button_Last_State = SW_Button_State;
    }
EncoderValue = myEnc.read(); // reads encoder value and saves it to EncoderValue variable

  if (SW_Count == 1)
  {
    EncoderValue = EncoderValueTempLastValue;          // set EncoderValue to last set temperature value
    EncoderValueTemp = EncoderValue;                   // set temperature value is equal to encoder value
    if (EncoderValueTemp != EncoderValueTempLastValue) // if set temperature value is different then last set temperature value
    {
      EncoderValueTempLastValue = EncoderValueTemp; // update last set temperature value
      setTemperature = EncoderValueTempLastValue;   // set temperature last set temperature value
    }
    setTemperature = (myEnc.read() / 4);        // read for pulses on the encoder
    EncoderValueTempLastValue = setTemperature; // update last set temperature value
  }

  else if (SW_Count == 0)
  {
    EncoderValue = EncoderValueRPMLastValue;
    EncoderValueRPM = EncoderValue;
    if (EncoderValueRPM != EncoderValueRPMLastValue)
    {
      EncoderValueRPMLastValue = EncoderValueRPM;
      setMotorSpeed = EncoderValueRPMLastValue;
    }
    setMotorSpeed = (myEnc.read() / 4);
    EncoderValueRPMLastValue = setMotorSpeed;
  }

On the video you can see i first set the rpm of the motor, then i swithc to setting the temperature, but when i do so the value of the rpm also sets the temperature and vice versa.
clbuzgecowe

really really believe me.
Even if your code is 10.000 lines long:
the easiest way to support is to have the full sketch.
If you start cutting down the code you as the world-best-coding-expert has of course te ovierview which part can be left out and which part can be kept to analyse the problem. The declaration of variables is important. The scope of variables is important. So please just post the complete sketch by using this method:

There is an automatic function for doing this in the Arduino-IDE
just three steps

  1. press Ctrl-T for autoformatting your code
  2. do a rightclick with the mouse and choose "copy for forum"
  3. paste clipboard into write-window of a posting

If your code inlcude some access-keys or passwords delete them. All the rest should stay in the code.

best regards Stefan

1 Like

Your are using this very old unreliable encoder-lib that is delivered in the Arduino-IDE
@Arduino-maintaining-Team: throw out this crappy PaulStoffregen-library!
Paul Stoffregen is a famous and very good software-developper but this library sucks!

With encoders with mechanical switches it works unreliably! this library is unable to count up/down correctly if you change direction.

one very good encoder-library is the NewEncoder-library from gfvalvo
With this library you can set the encoder-value new.

Anyway
in your code yuo are reading in the encoder-value

EncoderValue = myEnc.read();

he rest of the code is some logic to set some variables but you

never

change the value of the encoder itself. With not changing this value it is indeed not possible except:

If you stay with this crappy PaulStoffregen-encoder-library
you have to code a new counter you can control yourself by detecting just the changes of myEnc.read() but without using the value of myEnc.read()

or much much more comfortable use the NewEncoder-library where you can do

newSettings(myNewMin_Value, myNewMaxValue, myNewEncoderValue, myEncState);

which even allows to switch between different min/max-values for temperature and motor-rpm

This library needs the encoder-channels to be connected to interrupt-able IO-pins.
So which exact type of microcontroller are you using and what is connected to which IO-pin?

best regards Stefan

1 Like

For now i am using atmega328, pin 2 and 3.
I tried the library and it's pretty good, but i don't understand some of the code in it.
Also i don't know what to put for arguments in new settings command for the last one

bool NewEncoder::newSettings(int16_t newMin, int16_t newMax, int16_t newCurrent, NewEncoder::EncoderState &state)

if (SW_Count == 1)
  {
    NewEncoder::EncoderState EncoderValueTemp;
    //encoder.newSettings(0, 300, setTemperature, EncoderValueTemp);
    if (encoder.getState(EncoderValueTemp))
    {
      setTemperature = EncoderValueTemp.currentValue;
    }
    if (setTemperature != EncoderValueTempLastValue)
    {
      EncoderValueTempLastValue = setTemperature;
    }
  }

  else if (SW_Count == 0)
  {
    
    //encoder.newSettings(0, 150, setMotorSpeed, );
    if (encoder.getState(EncoderValueRPM))
    {
      setMotorSpeed = EncoderValueRPM.currentValue;
    }
    if (setMotorSpeed != EncoderValueRPMLastValue)
    {
      EncoderValueRPMLastValue = setMotorSpeed;
    }
  }

so

    NewEncoder::EncoderState EncoderValueTemp;

sets to what variable to save encoder state?

I'm working on a demo-code for this but it is not yet finished
best regards Stefan

1 Like

so after quite a long time of trying to understand how the NewEncoder-library works I have a demo-code that show changing between two modes where each mode can have its own value.

The code is divided in serveral functions to make it easier to understand what happens inside loop

@gfvalvo : the command NewSettings does ## not work as described in the documentation the third parameter is **not** used to update the currentValue the min and max-values **don't** get updated

So I guess there is something wrong in the code or your documentation is misunderstandable

updated the NewEncoder-library and now it works like expected

// start of macros dbg and dbgi
#define dbg(myFixedText, variableName) \
  Serial.print( F(#myFixedText " "  #variableName"=") ); \
  Serial.println(variableName);
// usage: dbg("1:my fixed text",myVariable);
// myVariable can be any variable or expression that is defined in scope

#define dbgi(myFixedText, variableName,timeInterval) \
  do { \
    static unsigned long intervalStartTime; \
    if ( millis() - intervalStartTime >= timeInterval ){ \
      intervalStartTime = millis(); \
      Serial.print( F(#myFixedText " "  #variableName"=") ); \
      Serial.println(variableName); \
    } \
  } while (false);
// usage: dbgi("2:my fixed text",myVariable,1000);
// myVariable can be any variable or expression that is defined in scope
// third parameter is the time in milliseconds that must pass by until the next time a
// Serial.print is executed
// end of macros dbg and dbgi

#include "Arduino.h"

void PrintFileNameDateTime() {
  Serial.println( F("Code running comes from file ") );
  Serial.println( F(__FILE__));
  Serial.print( F("  compiled ") );
  Serial.print(F(__DATE__));
  Serial.print( F(" ") );
  Serial.println(F(__TIME__));
}


boolean TimePeriodIsOver (unsigned long &periodStartTime, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - periodStartTime >= TimePeriod )
  {
    periodStartTime = currentMillis; // set new expireTime
    return true;                // more time than TimePeriod) has elapsed since last time if-condition was true
  }
  else return false;            // not expired
}

unsigned long MyTestTimer =  0;                   // variables MUST be of type unsigned long
const byte    OnBoard_LED = 13;

unsigned long buttonReleaseTimer;

void BlinkHeartBeatLED(int IO_Pin, int BlinkPeriod) {
  static unsigned long MyBlinkTimer;
  pinMode(IO_Pin, OUTPUT);

  if ( TimePeriodIsOver(MyBlinkTimer, BlinkPeriod) ) {
    digitalWrite(IO_Pin, !digitalRead(IO_Pin) );
  }
}


#include "NewEncoder.h"

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

// 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);
// 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;
NewEncoder::EncoderState myDummyState;

int16_t currentValue;
int16_t prevEncoderValue;

int16_t tempMin = 10;
int16_t tempMax = 30;
int16_t actualTemperature;
int16_t storedTemperature;

int16_t rpmMin = 40;
int16_t rpmMax = 50;
int16_t actualRpm;
int16_t storedRpm;


const byte EncBtnPin = 4;

const byte TEMPERATURE_MODE = 0;
const byte RPM_MODE = 1;

byte myMode = TEMPERATURE_MODE;
byte myLastMode = myMode;


void setup() {
  pinMode(EncBtnPin, INPUT_PULLUP);

  Serial.begin(115200);
  PrintFileNameDateTime(); 

  Serial.println("Starting");

  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 pushBtnTogglesMode(byte p_IO_Pin, byte &p_mode) {

  static int buttonState;             // the current reading from the input pin
  static int lastButtonState = LOW;   // the previous reading from the input pin

  // the following variables are unsigned longs because the time, measured in
  // milliseconds, will quickly become a bigger number than can be stored in an int.
  static unsigned long lastDebounceTime = 0;  // the last time the output pin was toggled
  unsigned long debounceDelay = 50;    // the debounce time; increase if the output flickers

  // read the state of the switch into a local variable:
  int reading = digitalRead(p_IO_Pin);

  // check to see if you just pressed the button
  // (i.e. the input went from LOW to HIGH), and you've waited long enough
  // since the last press to ignore any noise:

  // If the switch changed, due to noise or pressing:
  if (reading != lastButtonState) {
    // reset the debouncing timer
    lastDebounceTime = millis();
  }

  if ((millis() - lastDebounceTime) > debounceDelay) {
    // whatever the reading is at, it's been there for longer than the debounce
    // delay, so take it as the actual current state:

    // if the button state has changed:
    if (reading != buttonState) {
      buttonState = reading;

      // only toggle the mode if the new button state is HIGH
      if (buttonState == HIGH) {
        if (p_mode == 1) {
          p_mode = 0;
        }
        else {
          p_mode = 1;
        }
      }
    }
  }
  // save the reading. Next time through the loop, it'll be the lastButtonState:
  lastButtonState = reading;  
}

void CheckForModeChange() {
  if (myMode != myLastMode) {
    myLastMode = myMode;
    Serial.print("mode changed new mode ");
    
    if (myMode == TEMPERATURE_MODE) {
      Serial.println("Temperature");      
    }
    if (myMode == RPM_MODE) {
      Serial.println("rpm");      
    }

    if (myMode == RPM_MODE) {
      storedTemperature = actualTemperature; // store actual temperature for later RE-storing
      myEncoderObject.newSettings(rpmMin, rpmMax, 22, myEncState); // setup encoder for rpm-mode
      myEncoderObject.getAndSet(storedRpm,myDummyState,myEncState);
      actualRpm = myEncState.currentValue;
      dbg("RPM-Mode active", actualRpm);
      Serial.println();
    }

    if (myMode == TEMPERATURE_MODE) {
      storedRpm = actualRpm;                  // store actual RPM for later RE-storing
      actualTemperature = storedTemperature;  // restore temperature as we are changing to temperature-mode
      myEncoderObject.newSettings(tempMin, tempMax, actualTemperature, myEncState); // setup encoder for temperature-mode
      myEncoderObject.getAndSet(storedTemperature,myDummyState,myEncState);
      actualTemperature = myEncState.currentValue;
      dbg("TemperatureMode active", actualTemperature);
      Serial.println();
    }
  }
}

void PrintEncoderValue() {
  // very important ! this stores actual values into variable myEncState
  if (myEncoderObject.getState(myEncState)) { //<<<== very important
    Serial.print("Encoder: ");
    if (myMode == TEMPERATURE_MODE) {
      Serial.print("Temperature=");      
    }
    if (myMode == RPM_MODE) {
      Serial.print("rpm=");      
    }
    currentValue = myEncState.currentValue;

    // if currentValue has REALLY changed print new currentValue
    if (currentValue != prevEncoderValue) {
      Serial.println(currentValue);
      prevEncoderValue = currentValue;

      // if currentValue stayed the same because the number is at upper/lower limit
      // check if encoder was rotated by using the UpClick / DownClick-values
    } else
      switch (myEncState.currentClick) {
        case NewEncoder::UpClick:
          Serial.println("at upper limit.");
          break;

        case NewEncoder::DownClick:
          Serial.println("at lower limit.");
          break;

        default:
          break;
      }
  }
}  


void loop() {
  pushBtnTogglesMode(EncBtnPin, myMode); // check if button was pressed if Yes change value of variable myMode

  CheckForModeChange();

  PrintEncoderValue();
  
  // update value according to active mode
  if (myMode == TEMPERATURE_MODE) {
    actualTemperature = myEncState.currentValue;
  }
  else {
    actualRpm = myEncState.currentValue;
  }
}

as a general hint:

This kind of code is what I define as an easy to understand example for the given functionality.

This functionality is pretty complex. This means the code is not understandable as the second exercise for somebody who just started learning coding from zero.
But it is caused by the complexity of the functionality.

Writing down minimal examples that don't use functions but instead use too similar and too generalised names like "encoder" and leaving out serial output that makes visible what is happending this kind of code is hard to understand

best regards Stefan

1 Like

The newSettings() function works exactly as documented in the GitHub README. Example usage here. The code fix mentioned in that post was applied to the GitHub version 3 months ago.

1 Like

Thanks a lot for taking the time to reply to my questions, i will try to add this code to my project tommorow.