How to add set_position() to Notch_Shaft_Encoder library

I have been asked to modify a project. The project used ADC and three potentiometers to set operating setpoints. Now the project is several years old, it would appear some temperature drift is occuring in the potentiometers. The pots are not touched but as the air temp changes, the resistance changes and therefore the voltage reading and in turn the setpoint changes.

The decision was made to change from 3 pots to 1 rotary encoder and use the integrated push button to index through the setpoints to change them.

With all of that aside I started with a blank Arduino Mega and the encoder. I tried the encoder library with the encoder and it returned random results when the shaft was turned one notch (sometimes it incremented 4, sometimes 5, sometimes 6). I changed to the Notched_Shaft_Encoder library and that has provided stable results.

When the program starts the encoder starts at zero. It would be best if I could set the starting position to the first setpoint and have the encoder drive it directly.

I added noticed there was a reset function and tried to add a set_position function:

void NSEncoder::set_position(int16_t new_pos)
{
    enc_old_step = new_pos; 
    enc_old_pulse = new_pos; 
    this->write(new_pos);//set the enocder counter to new_pos
}

I initialize sp [ x ] = {5, 20, 50};
That povided the results:

SP0: 5--
SP0: 1
SP0: 2
SP0: 1
SP0: 0
Change to SP1
SP1: 20--
SP1: 4
SP1: 3
SP1: 4
SP1: 5
Change to SP2
SP2: 50--
SP2: 12
SP2: 11
SP2: 12
#include <NSEncoder.h>

#define ENCODER_S1_PIN 5  //Define you encoder connection pin Here
#define ENCODER_S2_PIN 6
#define ENCODER_BTN_PIN 10

NSEncoder enc(ENCODER_S1_PIN, ENCODER_S2_PIN, 4);

#include <JC_Button.h>
Button Select_Btn(ENCODER_BTN_PIN);

uint8_t sp_idx=0;
int16_t sp[3] = {5, 20, 50};

int16_t enc_position;

void scan_buttons()
 {
  Select_Btn.read();

  if (Select_Btn.wasReleased())
   {
    sp_idx++;
    if (sp_idx>=3)
     { sp_idx = 0; }
    Serial.print("Change to SP");
    Serial.println(sp_idx);
    enc.set_position(sp[sp_idx]);
    enc.get_Position(sp[sp_idx]);
    Serial.print("SP");
    Serial.print(sp_idx);
    Serial.print(": ");
    Serial.print(sp[sp_idx]);
    Serial.println("--");
   }
 }

void setup()
 {
  Serial.begin(115200);
  Select_Btn.begin();

  enc.set_position(sp[sp_idx]);
  enc.get_Position(sp[sp_idx]);
  Serial.print("SP");
  Serial.print(sp_idx);
  Serial.print(": ");
  Serial.print(sp[sp_idx]);
  Serial.println("--");
 }

void loop()
 {
  scan_buttons();
  
  if(enc.get_Position(sp[sp_idx])) //If value is updated
   {
    Serial.print("SP");
    Serial.print(sp_idx);
    Serial.print(": ");
    Serial.println(sp[sp_idx]);
   }
 }

Plan A is the simplest solution, integrate a set position function so the setpoint is directly modified by the encoder position.
I have a plan B but arrays of setpoints are difficult to manage since you have to remember which index belongs to which setpoint while you are programming. There is also a plan C would be individual variables for each setpoint (far easier to read than Plan C) but requires several switch-case statements to get the encoder position to modify the initial value stored and loaded when the button is pressed and another to return it to the corresponding setpoint when the encoder is turned. Yeah, it is as clumsy as that sounds.

I'd like to get plan A to work if anyone has any information on how to add a set_position function to the Notched_Shaft_Encoder library, please let me know.

adwsystems:
Plan A is the simplest solution, integrate a set position function so the setpoint is directly modified by the encoder position.

Not sure I fully understand what you're trying to do never having heard the term 'notched shaft encoder' but, I'll relate what I did with a simple encoder/pushbutton/LCD menu feature.

Top level - encoder rotation moves through each selection (setpoints in this case) and displays selection name, current value and whatever else you deem pertinent. Pressing encoder button drops you to next level where the setpoint value for the current selection, retrieved from EEPROM at startup and stored in RAM, is manipulated by encoder rotation. When setpoint is as desired pressing button saves setpoint to EEPROM in case of power failure and returns you to top level.

The initial menu position is set in setup() or a declared variable and the menu display just uses that as a starting point.

I had a lot more selections so I did use an array of structures for text/initial values/etc.

I suspect the OP means an encoder with detents.
Paul

dougp:
Not sure I fully understand what you're trying to do never having heard the term 'notched shaft encoder' but, I'll relate what I did with a simple encoder/pushbutton/LCD menu feature.

Top level - encoder rotation moves through each selection (setpoints in this case) and displays selection name, current value and whatever else you deem pertinent. Pressing encoder button drops you to next level where the setpoint value for the current selection, retrieved from EEPROM at startup and stored in RAM, is manipulated by encoder rotation. When setpoint is as desired pressing button saves setpoint to EEPROM in case of power failure and returns you to top level.

The initial menu position is set in setup() or a declared variable and the menu display just uses that as a starting point.

I had a lot more selections so I did use an array of structures for text/initial values/etc.

Did your encoder 'click' through notches (see part in link) or was the rotation smooth? notched shaft aka detents.
I just tried provided libraries on the hope that one worked with little to no modification. The one I found in the Arduino IDE library list was call 'Notched Shaft Encoder'. I didn't name it. :smiley:

dougp:
Top level - encoder rotation moves through each selection (setpoints in this case) and displays selection name, current value and whatever else you deem pertinent. Pressing encoder button drops you to next level where the setpoint value for the current selection.

There is slight difference between our setups. I am planning to have the button press flip through the setpoints (press button-> next setpoint, circular buffer style).

dougp:
is manipulated by encoder rotation

That is the the heart of the post. How did you go about doing this?

The shortest way I figured would be to load the current setpoint into the encoder position when the button is pressed. Then turning the encoder results in the position equaling the setpoint directly.

Any other option, the inital setpoint must be retrieved and stored, the encoder position reset to 0, any time the encoder position is changed these two items added together and then returned to the setpoint variable. Lots more steps than plan A.

Take a look at the setValue() function in this encoder implementation: GitHub - gfvalvo/NewEncoder: Rotary Encoder Library

Paul_KD7HB:
I suspect the OP means an encoder with detents.
Paul

Yes, now I get it.

adwsystems:
Did your encoder 'click' through notches (see part in link) or was the rotation smooth? notched shaft aka detents.

It's the usual KY-040 type - not mounted on a breakout board. Twenty clicks/detents/notches per revolution IIRC.

Here's an extract of my menu code. Not a pro so some criticism/advice may be in order. I used Buxton's encoder library.

Lines like this: --textAndParms[activeScreen].parmVal ; or ++textAndParms[activeScreen].parmVal ; increment/decrement the particular value directly. textAndParms is the array of structures and activeScreen selects which parameter is being examined/modified. That is, the value manipulated here is the same one used elsewhere to control/influence some aspect of the machine. An example would be:

analogWrite(L298N_enablePin, textAndParms[menuRetractVel].parmVal);

Toggling back up to the top level does not save to EEPROM, it merely ends being able to modify that value. When returning to the main screen (16 X 2 LCD) a check is done and if any values changed a call to the 'save to EEPROM' function is triggered.

Hope this helps.

void menuNavigation(bool isDirectionUpDn) { // Received value (encoder PB toggle state) controls:
  //                                true = screen +-, false = parameter +-
  switch (isDirectionUpDn) {
    case true :  // process up/down action to change screen displayed and its values/options
      if (encoderTurned == DIR_CCW) { // CCW/left rotation = decrement screen no.
        if (--activeScreen < 0) activeScreen = menuItem_qty - 1;// wrap around to bottom of list
      }
      else { // increment screen no. (go down the menu)
        if (++activeScreen >= menuItem_qty) activeScreen = 0; // wrap around to first screen
      }
      break;

    case false : // UpDn false so process encoder value as parameter/function change
      bool isLeft2Right = DIR_CW;
      //
      // switch case structure handles parm chg
      //
      switch (activeScreen) {  // the active screen determines which parameter will be adjusted
        case 0: // 'Current Turret Position' active - adjust current position to match physical turret
          // this parameter only decreases then wraps to three
          if (encoderTurned == DIR_CCW) {
            --textAndParms[activeScreen].parmVal ;//
            if (textAndParms[activeScreen].parmVal < 0) textAndParms[activeScreen].parmVal =
                /* cont'd */ textAndParms[activeScreen].noOfParms - 3;
          }
          break;
        case 1: // 'Mode' screen active - selection of auto-indexed and non-indexed turret modes
          // this selection wraps in both directions
          if (encoderTurned == DIR_CCW) {
            turretStateNo = 0;  // Resetting this causes turret state machines to start at zero
            if (--textAndParms[activeScreen].parmVal < 0) {
              textAndParms[activeScreen].parmVal = textAndParms[activeScreen].noOfParms - 1; // parmval = 5 when 3-hole included
            }
          }
          else if (encoderTurned == DIR_CW) {
            turretStateNo = 0;  // Resetting this causes turret state machines to start at zero
            if (++textAndParms[activeScreen].parmVal > textAndParms[activeScreen].noOfParms - 1) //
              textAndParms[activeScreen].parmVal = 0;  // wrap around to beginning
          }
          if (textAndParms[activeScreen].parmVal > 1 && textAndParms[activeScreen].parmVal < 3)
            textAndParms[menuHome].parmVal = 5; // to display 'no indexing'?
          break;
        case 2 : // 'Calibrate Laser Top'
        /*  Since the three calibration points differ only in the
            activeScreen value, allowing Top and Load calibrations to
            fall through to Bottom saves two calls to laserCal().
        */
        case 3: // 'Calibrate Laser Load'
        case 4: // 'Calibrate Laser Bottom'
          textAndParms[activeScreen].parmVal = laserCal();// laser reading
          break;
        case 5 : // 'Extend time' adjustment - values are copied to retract time by default
          if (encoderTurned == DIR_CW) {        // initial value = 15
            textAndParms[activeScreen].parmVal++;
          }
          else textAndParms[activeScreen].parmVal--;
          textAndParms[activeScreen + 2].parmVal = textAndParms[activeScreen].parmVal;
          break;
        case 6 : // 'Extend Velocity' change -  initial value = 65
          if (encoderTurned == DIR_CW) {
            textAndParms[activeScreen].parmVal++;
          }
          else textAndParms[activeScreen].parmVal--;
          textAndParms[activeScreen].parmVal = constrain(textAndParms[activeScreen].parmVal, 0, 100);
          textAndParms[activeScreen + 2].parmVal = textAndParms[activeScreen].parmVal; // copy to retract velocity
          break;
        case 7 :  // 'Retract Time' inc/dec only    initial value = 15

dougp:
Here's an extract of my menu code. Not a pro so some criticism/advice may be in order. I used Buxton's encoder library.

I follow that. That library doesn't hold the position just which way the encoder was turned. That's why you didn't run into the issue I am (was) trying to solve.