Using rotary encoder and serial monitor

Hi, thanks for letting me join, I'm hooping someone eon here can help me with a couple of issues i am having getting a project working for my local model club.
I'm building Arduino controlled stepper driven turntable assemblies for two layouts at my local railway club.
I have the mechanical setup working with Arduino Uni, A4988 driver and Nema 17 stepper motor fitted directly to turntables and a hall effect sensor and magnet correctly locating a home starting position each time the system is turned on.

And I have 2 buttons that when pressed move the turntable 180' and then back home again respectively.
The issue I am having now is I need to provide the ability for the users of the layout to program 3 different track settings for the loco sheds, these will be stored in the Arduinos eeprom and i have currently worked out the programming and reading code for this on one instance at the moment, but I have now introduced a rotary encoder so the user can turn the turntable to the desired location manually and then store its position in the memory. the issue is when running the encoder loop to turn the stepper motor the serial monitor does not record the current or new position of the stepper motor, this means i cant read it and store it. also my stepper is bouncing randomly when turning the encoder is this some thing i can tune out?

really hope someone can help as this had been a fun project and learning exercise and I feel I'm pretty close just missing a trick somewhere.
thanks my code is below.

#include <AccelStepper.h>
#include <EEPROM.h>

// Rotary Encoder Module connections
const int PinCLK = 2; // Generating interrupts using CLK signal
const int PinDT = 3; // Reading DT signal
const int PinSW = 4; // Reading Push Button switch


// A4988 Driver connections
#define motorInterfaceType 1
#define dirPin 8
#define stepPin 9
#define MS1 10 // Pin 10 connected to MS1 pin
#define MS2 11 // Pin 11 connected to MS2 pin
//#define SLEEP 12 // Pin 12 connected to SLEEP pin

// Hall Sensor connections
#define HALL_SENSOR 7


// Stepper Home & Travel Variables
long TravelX;  // Used to store the X value entered in the Serial Monitor
int move_finished = 1; // Used to check if move is completed
long initial_homing = -1; // Used to Home Stepper at startup

// Create a new instance of the AccelStepper class:
AccelStepper stepper = AccelStepper(motorInterfaceType, stepPin, dirPin);

int Position1 = 0;
int Position2 = 0;
int a = 1;
int value;

volatile boolean TurnDetected; // to detect rotation of Rotary Encoder
volatile boolean rotationdirection; // CW or CCW rotation
volatile boolean rswitch = 1; // Variable to store rotary encoder switch state
volatile boolean led_speed_change; // to detect change of speed
volatile int StepsToTake = 2; // Controls the speed of the Stepper per Rotary click

int direction; // Variable to set Rotation (CW-CCW) of stepper

//define rotary switch pin
//const int switchPin = 9;

//define push button pins
const int buttonPin = 12;
//create a variable to store a counter and set it to 0
int counter = 0;
unsigned long last_button_time = 0;   //for saving last millis button 1

const int buttonPin1 = 13;
//create a variable to store a counter and set it to 0
int counter1 = 0;
unsigned long last_button_time1 = 0;   //for saving last millis button 1


// Interrupt routine runs if CLK goes from HIGH to LOW
void rotarydetect () {
  delay(4); // delay for Debouncing Rotary Encoder

  if (rswitch == 1) {
    if (digitalRead(PinCLK)) {
      if (digitalRead(PinDT)) {
        if (StepsToTake > 2) {
          StepsToTake = StepsToTake - 1;
        }
      }
      if (!digitalRead(PinDT)) {
        if (StepsToTake < 9) {
          StepsToTake = StepsToTake + 1;
        }
      }
      led_speed_change = true;
    }
  }

  else {
    if (digitalRead(PinCLK))
      rotationdirection = digitalRead(PinDT);
    else
      rotationdirection = !digitalRead(PinDT);
    TurnDetected = true;
  }
}

void setup()
{ // initialize the serial port:
  Serial.begin(9600);
  pinMode(HALL_SENSOR, INPUT);
  delay(5);
  // Set the maximum speed and acceleration:
  stepper.setMaxSpeed(60);
  stepper.setAcceleration(100);
  //initialise pins as inputs
  pinMode(MS1, OUTPUT);
  pinMode(MS2, OUTPUT);
  pinMode(buttonPin, INPUT);
  digitalWrite(buttonPin, HIGH);
  pinMode(buttonPin1, INPUT);
  digitalWrite(buttonPin1, HIGH);


  value = EEPROM.read(a);
  digitalWrite(MS1, HIGH); // Configures to Full Steps
  digitalWrite(MS2, HIGH); // Configures to Full Steps

  // Start Homing procedure of Stepper Motor at startup
  Serial.print("Stepper is Homing . . . . . . . . . . . ");
  while (digitalRead(HALL_SENSOR)) {  // Make the Stepper move CCW until the switch is activated
    stepper.moveTo(initial_homing);  // Set the position to move to
    initial_homing--;  // Decrease by 1 for next move if needed
    stepper.run();  // Start moving the stepper
    delay(5);
  }

  stepper.setCurrentPosition(0);  // Set the current position as zero for now
  stepper.setMaxSpeed(60.0);      // Set Max Speed of Stepper (Slower to get better accuracy)
  stepper.setAcceleration(100.0);  // Set Acceleration of Stepper
  initial_homing = 1;

  while (!digitalRead(HALL_SENSOR)) { // Make the Stepper move CW until the switch is deactivated
    stepper.moveTo(initial_homing);
    stepper.run();
    initial_homing++;
    delay(5);
  }
  stepper.setCurrentPosition(0);
  Serial.println("Homing Completed");
  Serial.println("");
  Serial.print (stepper.currentPosition());
  Serial.println ();

  //digitalWrite(MS1, HIGH); // Configures to Full Steps
  //digitalWrite(MS2, LOW); // Configures to Full Steps

  pinMode(PinCLK, INPUT); // Set Pins to Inputfor rotary encoder
  pinMode(PinDT, INPUT);
  pinMode(PinSW, INPUT);
  digitalWrite(PinSW, INPUT_PULLUP); // use internal resistor of UNO for Rotary Encoder switch
  attachInterrupt (0, rotarydetect, FALLING); // interrupt 0 always connected to pin 2 on Arduino UNO

}



void loop()
{
  if (!(digitalRead(PinSW))) { // Do if Rotary Encoder switch is pressed
    delay(250); // debounce switch
    if (rswitch == 0) {
      rswitch = 1;
    }
    else {
      rswitch = 0;
    }
  }

  // Runs if Rotary Encoder switch is pressed
  if (rswitch == 1) {


  }

  // Runs if rotation was detected
  if (TurnDetected) {
    TurnDetected = false; // do NOT repeat IF loop until new rotation detected

    // Which direction to move Stepper motor
    if (rotationdirection) { // Move motor CCW
      digitalWrite(dirPin, HIGH); // (HIGH = anti-clockwise / LOW = clockwise)
      for (int x = 1; x < StepsToTake; x++) {
        digitalWrite(stepPin, HIGH);
        delay(1);
        digitalWrite(stepPin, LOW);
        delay(1);

      }
      //   Serial.print (stepper.currentPosition()); // inserted to try and view steps taken when using rotary encoder
      //   Serial.println ();
    }

    if (!rotationdirection) { // Move motor CW
      digitalWrite(dirPin, LOW); // (HIGH = anti-clockwise / LOW = clockwise)
      for (int x = 1; x < StepsToTake; x++) {
        digitalWrite(stepPin, HIGH);
        delay(1);
        digitalWrite(stepPin, LOW);
        delay(1);

      }
      //   Serial.print (stepper.currentPosition());  // inserted to try and view steps taken when using rotary encoder
      //   Serial.println ();
    }
  }

  //if rotary switch not pushed these functions will run in the loop

  fullSwing();
  returnHome();
}


void fullSwing()
{
  bool buttonState;   // local variable to hold the pushbutton states
  //read the digital state of buttonPin with digitalRead() function and store the value in buttonState variable
  buttonState = digitalRead(buttonPin);
  //if the button is pressed increment counter and wait a tiny bit to give us some time to release the button
  if (buttonState == LOW && millis() - last_button_time > 200 ) // checks to see if button has been pressed and time since last button press is greater than 200ms
  {
    last_button_time = millis();
    counter++;



    {
      // Set the maximum speed and acceleration:
      stepper.setMaxSpeed(50);
      stepper.setAcceleration(20);
      stepper.moveTo(value * 8); //(801)180 Spin
      stepper.runToPosition();

    }
  }
  //Position1 = stepper.currentPosition();  // inserted to view end position of stepper motor
  //Serial.print (Position1);
  //Serial.println ();

  {
    // EEPROM.write(0, Position1/8);  //currently not used but will be used to store the position of the stepper motor to a memory location.


  }
}

void returnHome()
{
  bool buttonState1;   // local variable to hold the pushbutton states
  //read the digital state of buttonPin with digitalRead() function and store the value in buttonState variable
  buttonState1 = digitalRead(buttonPin1);
  //if the button is pressed increment counter and wait a tiny bit to give us some time to release the button
  if (buttonState1 == LOW && millis() - last_button_time1 > 200 ) // checks to see if button has been pressed and time since last button press is greater than 200ms
  {
    last_button_time = millis();
    counter++;



    {
      // Set the maximum speed and acceleration:
      stepper.setMaxSpeed(50);
      stepper.setAcceleration(20);
      stepper.moveTo(0);
      stepper.runToPosition();

    }
  }
  // Position2 = stepper.currentPosition(); // inserted to view end position of stepper motor
  // Serial.print (Position2);
  // Serial.println ();

  {
    // EEPROM.write(5, Position2/8);  //currently not used but will be used to store the position of the stepper motor to a memory location.

  }
}

don't use delay() in an ISR...

You should use a library such as the encoder library to make your life easier.

You could also use a button library such as Button from easyRun or OneButton (or one of many other) to simplify handling the rotary switch

And don't use an ISR for rotary encoders unless it's absolutely inevitable, which it most likely isn't.

Nah. Read up on using millis() to handle this kind of thing, or use a library that handles button inputs for you. Also, 250ms for debouncing is waaaaaay long. Try something between 10 and 50.

I suspect that the problem is that when using the rotary encoder you do not use the library to move the stepper so subsequently using

stepper.currentPosition();

is not going to work because the library has no idea that the stepper has moved

if that helps, here is small test code using a basic rotary encoder with built in switch that you find in most kits

it's using the two libraries I mentioned above:

Encoder ➜ Encoder Library, for Measuring Quadarature Encoded Position or Rotation Signals
easyRun ➜ GitHub - bricoleau/easyRun: Doing sereval things at the same time becomes so easy

the encoder is on pin 2 and 3, the switch on pin 4

#include <Encoder.h>  // https://www.pjrc.com/teensy/td_libs_Encoder.html
#include <easyRun.h>  // https://github.com/bricoleau/easyRun

const byte encoderDTPin = 2;     // encoder encoder DT
const byte encoderCLKPin = 3;    // encoder encoder CLK
Encoder encoder(encoderDTPin, encoderCLKPin);

const byte encoderSWPin = 4;     // encoder encoder SW
button encoderSwitch(encoderSWPin);

long currentValue = 0;

bool checkEncoder() {
  long newValue = encoder.read() >> 1;// my encoder generates 2 ticks per click so I divide by 2
  if (newValue != currentValue) {
    currentValue = newValue;
    Serial.println(currentValue);
    return true;
  }
  return false;
}

bool checkSwitch() {
  easyRun();
  if (encoderSwitch) {
    Serial.println("click");
    return true;
  }
  return false;
}
void setup() {
  Serial.begin(115200); Serial.println();
  encoder.write(currentValue << 1); // my encoder generates 2 ticks per click so I multiply by 2
  Serial.println(F("Ready"));
}

void loop() {
  checkEncoder();
  checkSwitch();
}

You can see that the libraries make the code super easy. The only non obvious part is the multiply/divide by 2 thingy when I read or set the rotary encoder value because the library counts the number of ticks and I've 2 ticks per rotary click on my encoder. Yours might have only 1 (so no need for >> 1 or << 1) or 4 in which case you'll need >> 2 and << 2 instead of 1.

open the serial monitor at 115200 bauds and turn the rotary or press the button and you should see something happening

(turning up, then down, then click twice, then turning up again)

Ready
1
2
3
2
1
0
click
click
1
2
3

thanks hadn't thought about an encode library will have a look at the link thnaks,

ah hadn't thought of that the encoder code i found is using on off pulses to move the motor, can you point me in the right direction to determine how do I use the accelstepper library to move it 1 step each time the encoder is turned 1 step?

look at the code I just posted.

when you detect the encoder has changed you can issue an accelStepper command to go to the new location

great thanks, ill try a new window and run the code scenario you have above and see if I can interpret how to get it to run in my situation with the code i have already, really appreciate the responses and assistance, cheers.

Hi ya, the library is asking for a teensy board to be present on one my usb ports? I'm not using one, i only have a nano, a A4988 and my stepper motor and encoder connected at present. do i need to use another board to use the encoder library>?

:thinking:

the library works with a nano, uno (and teensy too) etc... nothing to add. (download and install from GitHub - PaulStoffregen/Encoder: Quadrature Encoder Library for Arduino)

where did you see it was waiting for a teensy board?

when i i try to upload the code to the uno it verifys it and then it opens up a window as below,

did you download teensy's IDE TeensyDuino ? there is no need as you don't use a Teensy board. Just use the normal IDE you get from Arduino ➜ https://www.arduino.cc/en/software

i went to the link you had in your post and then downloaded the windows version of the teensy libraries. where would i find it otherwise>

Please do not post pictures of code and output

Please follow the advice given in the link below when posting code, in particular the section entitled 'Posting code and common code problems'

Use code tags (the </> icon above the compose window) to make it easier to read and copy for examination

to be fair that was to show where he was getting the ask for a teensy board.
image

so made sense

Ok. that was not needed

you can download it in the normal IDE using the library manager

or go to GitHub - PaulStoffregen/Encoder: Quadrature Encoder Library for Arduino to get the source code and install the library manually

1 Like

Hi got it to work as per your example, sorry i forgot to swtich back to the uno board (it defaulted to a teensy!!!)
so now i can see the movement of the rotary encoder which is great, the bit i need to work out now is how to control the stepper and move it 1 step for every positive or negative value of the encoder , preferably in real time s o i can work out where the turntable is and save the position. is there a accelstepper 1 step routine i can use as a base and then adjust accordngly to my requirements from somewhere and incorporate into the routine?

try something like this

#include <Encoder.h>  // https://www.pjrc.com/teensy/td_libs_Encoder.html
#include <easyRun.h>  // https://github.com/bricoleau/easyRun
#include <AccelStepper.h>

const byte encoderDTPin = 2;     // encoder encoder DT
const byte encoderCLKPin = 3;    // encoder encoder CLK
Encoder encoder(encoderDTPin, encoderCLKPin);

const byte encoderSWPin = 4;     // encoder encoder SW
button encoderSwitch(encoderSWPin);

// A4988 Driver connections
#define motorInterfaceType 1
#define dirPin 8
#define stepPin 9
#define MS1 10 // Pin 10 connected to MS1 pin
#define MS2 11 // Pin 11 connected to MS2 pin

// Create a new instance of the AccelStepper class:
AccelStepper stepper = AccelStepper(motorInterfaceType, stepPin, dirPin);

long currentPosition = 0;

bool encoderChanged() {
  long newPosition = encoder.read() >> 1;// my encoder generates 2 ticks per click so I divide by 2
  if (newPosition != currentPosition) {
    currentPosition = newPosition;
    Serial.println(currentPosition);
    return true;
  }
  return false;
}

bool switchPressed() {
  easyRun();
  if (encoderSwitch) {
    Serial.println("click");
    return true;
  }
  return false;
}
void setup() {
  Serial.begin(115200); Serial.println();
  encoder.write(currentPosition << 1); // my encoder generates 2 ticks per click so I multiply by 2
  Serial.println(F("Ready"));

  stepper.setSpeed(100);

  //Configures to Full Steps (???)
  pinMode(MS1, OUTPUT);
  pinMode(MS2, OUTPUT);
  digitalWrite(MS1, HIGH);
  digitalWrite(MS2, HIGH);

  stepper.setCurrentPosition(currentPosition);  // Set the current position as currentPosition
}

void loop() {
  stepper.runSpeed();

  if (encoderChanged()) {
    stepper.moveTo(currentPosition);
  }

  if (switchPressed()) {
    Serial.println(F("Here I should save the position in EEPROM"));
  }

}

(totally untested - I took pieces from your code for accelStepper)

thanks it kind of works, the stepper moves, in either direction depending on the rotation of the encoder, but doesn't stop when it gets to what should be the position chosen, just keeps moving, and if i set the encoder back to 0 it doesn't return to where it started or stop which would be preferable. very confusing.