SOLVED Controlling constant speed of 3 motors using Rotary encoder and LCD

Hello all,

First time posting so give me a bit of slack.

My project and general goal:
I am trying to make three stepper motors rotate at different constant speeds (low RPMs in general) that I set for each motor. I have an LCD shield connected to an Arduino UNO. The LCD has 5 buttons on it and I have corresponded each button to each motor and then one for start and one for stop. In order to set the RPMs I have hooked up a rotary encoder and by turning it, the RPMs go up or down. e.x I want to set Motor1 at 6RPM, motor2 at 10 RPM and Motor3 at 1RPM (Motor3 will always have very low RPM). So I press button 1 on my LCD shield, it reads 0, using the rotary encoder, i take it to 6. -nothing runs yet-. I press button 2, set it to 10 using the rotary encoder, same with motor 3. Press button 4, which means START and all three motors start running with the preset RPM. Pressing button 5, makes everything stop.

What I’m using:

Hardware:

Libraries:

My problem:
In order to test if my code runs I’m not using all three stepper motors but only one. I have set it to correspond to the RPM of button 1. Also I still have not made it to run on RPMs but steps per minute. Thats an easy fix anyways. My LCD is doing what I’m telling it to do, meaning that there is a real time counter on the screen for the RPMs for each button I press (the first three) and the display is correct for the 4th (START) and 5th (STOP) buttons.
The thing is that the motors are not running at the desired RPM (or steps per minute), when I upload my code the motor is still as I want it to be. When I press button one and set some RPM, and then press button 4 (START) the motor moves just a tiny bit (constant speed), maybe at five microsteps per second. Also just mentioning that I have shortcircuted the stepper motor driver pins so that it does 1/16th step.

At first I had it so that the steps per minute were set from the Serial. And it was working. That was without the LCD and the Rotary encoder. If I run the previous code with the two last parts looked up, it still runs ok.

One initial version of my code, had the motor set up to digital pins 6, 7, but it was acting all strange and running full speed as soon as i pressed any button of the LCD shield. I realized that I have the shield set up on pins 2, 3, 4, 5, 6, 7, so there may be a conflict there. That’s why I changes the motor to pins 8, 9. My rotary encoder is set up on analog pins 2, 3.

I am thinking that my problem has to do with the code and specifically the AccelStepper library and functions. Maybe it does not have time to compute the speed I want it to? I don’t know, That’s where I need your help. Finally when looking at the code, at the moment only button 1 and START and STOP are coded to include the motors, the rest just refer to the LCD and the RotEnc.

My code:

#include <LiquidCrystal.h> //LCD lib
#include <RotaryEncoder.h> //RE lib
#include <AccelStepper.h> //SM lib


LiquidCrystal lcd(7, 6, 5, 4, 3, 2); //LCD pins
RotaryEncoder encoder(A2, A3); // rotary encoder pins

AccelStepper stepper2(AccelStepper::FULL2WIRE, 8, 9); //stepper2 pins


// Define constants and variables
const int inputPin = A0;  // buttons array analog input LCD
int buttonPressed = 0;    // LCD
uint16_t signalfromLCD = 0;   // value read from buttons array LCD  ??????
int previousPosition1 = 0;
int previousPosition2 = 0;
double previousPosition3 = 0;
int spd2;  //motor speeds

// Initialization


void setup() 
{
  Serial.begin(9600);

  stepper2.setMaxSpeed(100000); 
  stepper2.setSpeed(0);
  

  lcd.begin(16, 2);              // set up the LCD's number of columns and rows
  lcd.print("   DUVIC v0.1   ");  // Print a message on the LCD


  PCICR |= (1 << PCIE1);    // This enables Pin Change Interrupt 1 that covers the Analog input pins or Port C.
  PCMSK1 |= (1 << PCINT10) | (1 << PCINT11);  // This enables the interrupt for pin 2 and 3 of Port C.
}


ISR(PCINT1_vect) {
  encoder.tick(); // just call tick() to check the state.
}


// main loop
void loop() 
{

  if (inputValue() != buttonPressed && inputValue() != 0) 
    {
      buttonPressed = inputValue();                            //Update and store button value in button pressed, until another button is pressed
    }

      
  while (buttonPressed == 1)
  {
 
     stepper2.setSpeed(0);  
     stepper2.runSpeed();  
    for (int i = 0; i <= 1 && previousPosition1 != 0; i++) {

      encoder.setPosition(previousPosition1);
    }
      encoder.tick();
      lcd.setCursor(0, 0);
      lcd.print("Side Motor1 RPM:");
      lcd.setCursor(0, 1);
   
      lcd.print(encoder.getPosition());
      lcd.print("                ");  //to get rid of zeroes on the screen that remain when going backwards from 10 to 9
      previousPosition1 = encoder.getPosition();
      break;
    }
    
encoder.setPosition(0);

  while (buttonPressed == 2)
 { 
  for (int i = 0; i <= 1 && previousPosition2 != 0; i++) {

      encoder.setPosition(previousPosition2);
    }
      encoder.tick();
      lcd.setCursor(0, 0);
      lcd.print("Side Motor2 RPM:");
      lcd.setCursor(0, 1);

      lcd.print(encoder.getPosition());
      lcd.print("                ");
      previousPosition2 = encoder.getPosition();
      break;
    }

    encoder.setPosition(0);
    
      while (buttonPressed == 3)
 { 
  for (int i = 0; i <= 1 && previousPosition3 != 0; i++) {

      encoder.setPosition(previousPosition3);
    }
      encoder.tick();
      lcd.setCursor(0, 0);
      lcd.print("Main Motor RPM: ");
      lcd.setCursor(0, 1);

      lcd.print((encoder.getPosition())/100.0);
      lcd.print("                ");
      previousPosition3 = (encoder.getPosition());
      break;
    }

    encoder.setPosition(0);

     while (buttonPressed == 4)
 {
  

spd2 = previousPosition1; 

stepper2.setSpeed(spd2); //setting speed to that there is in memory from button1
 
stepper2.runSpeed(); 

  }
      lcd.setCursor(0, 0);
      lcd.print("M1:"); lcd.print(previousPosition1);lcd.print("   ");
      lcd.print("M2:"); lcd.print(previousPosition2);lcd.print("     ");

      lcd.setCursor(0, 1);
      lcd.print("    ");
      lcd.print("M3:"); lcd.print(previousPosition3/100.0);
   //   lcd.print("                ");

      break;
    }
    
    while (buttonPressed == 5)
{
 
    stepper2.setSpeed(0); //STOP motor
    stepper2.runSpeed();
  
  
      lcd.setCursor(0, 0);
      lcd.print("      STOP      ");
      lcd.setCursor(0,1);
      lcd.print("                 ");
      
      break;
    }
    stepper2.setSpeed(19900);
    stepper2.runSpeed();

} //loop

//-----------------------------------------------------------------------------------------------------------//
int inputValue() // LCD
{
  int signalfromLCD = analogRead(inputPin);
  if(signalfromLCD < 100 && signalfromLCD >= 0) return 1; 
  if(signalfromLCD < 250 && signalfromLCD > 150) return 2;
  if(signalfromLCD < 470 && signalfromLCD > 370) return 3;            //LCD function for buttons
  if(signalfromLCD < 670 && signalfromLCD > 570) return 4;
  if(signalfromLCD < 870 && signalfromLCD > 770) return 5;
  if(signalfromLCD <= 1023 && signalfromLCD > 950) return 0;
}

I don't understand the use of the encoder, when you have full control over the stepper motor. Is it required by the AccelStepper library?

Eventually you should change the motor speed only when it really should change, not continuously while a button is pressed.

You also may get ghost buttons presses from the LCD keypad. I'd add kind of debounce logic, that accepts a pressed button only if the voltage has stabilized, i.e. when multiple consecutive analogRead return the same button value.

I am using the encoder because I want to be able to change the speeds of the motors without using the Serial, or having to connect to a computer. Sorry when I said constant speed I meant not accelerating. So sometimes I ant the motor to rotate at 10 RPM, sometimes at 8.5, sometimes at 20 etc, so with the rotary encoder I will be able to do that easily, like using a knob to increase or decrease the RPM value. I hope that's clearer now.

Also, for your third point, if you look [u]at the bottom of my code[/u], I have written a function (inputValue()) that returns 0-5 when the voltage from the buttons is changing and [u]is within a range of values[/u] then I'm assigning this to ButtonPressed and this is what I'm feeding to my while loops that do what they do when a button is pressed and nothing else has been pressed afterwards. In my main loop, in the very beginning i have this "if statement" that checks to see if the the buttonPressed has changed. So I dont think any ghost buttons are being pressed since I would see that on my LCD, meaning what is being printed on the screen would flicker or change for no reason. But that's not the case, everything on the LCD is fine, it's just the stepper motor funtions that are not being executed properly, because (look at while buttonPressed == 4) I set the speed at what the the rotary encoder value was for butonPressed == 1 and then run it just under, but it doesn't run.

Moreover, for (ButtonPressed == 5) even if I explicitly set the motor speed (Stepps per minute) at 900 and then run it just below, It will still NOT run that speed. Thats what I mean:

.
.
.
while (buttonPressed == 4)
 {
  

//spd2 = previousPosition1; 

stepper2.setSpeed( [b]900[/b] ); //setting speed to that there is in memory from button1

stepper2.runSpeed(); 

  }
      lcd.setCursor(0, 0);
      lcd.print("M1:"); lcd.print(previousPosition1);lcd.print("   ");
      lcd.print("M2:"); lcd.print(previousPosition2);lcd.print("     ");

      lcd.setCursor(0, 1);
      lcd.print("    ");
      lcd.print("M3:"); lcd.print(previousPosition3/100.0);
   //   lcd.print("                ");

      break;
    }

Everything on the LCD is what I expect it to be, but the motor is not running, it is just pulsing very slowly (at 5μsteps per second maybe CCW).

Just to check out if the speed that I'm feeding is wrong, I tried calling "stepper2.speed()" function which returns the speed of the motor. I GET 900!!! But thats obviously not the case.

For your second point, you may be right, do you think incorporating the setSpeed() function in a nested for loop inside the while (for each button) that only runs once (when the button is pressed for the first time) and sets the speed, will do the trick?

Let me know if I'm making any sense here! thanks for your reply by the way!

Your while loops look incorrect. That will get it 'stuck' in that part of the program while the button is pressed.

You should think about the loop running continuously. It never stops and waits. If a button becomes pressed, then it can process that event but it should very quickly go back to reading all the other inputs. Look at the sticky thread "Planning and implementing an Arduino program" in the Programming forum.

If an action depends on a button being held down while you turn the encoder, then you should structure it with if statements, not while.

I get that it will run continuously, but thats the point exactly. As I am turning the rotary encoder, the RPMs on my screen go up and down in real time. I mean it has to be in a loop for that, and therefore check/update the displayed RPMS on the screen continuously. How could I do this with "ifs"?

vben92: I mean it has to be in a loop for that, and therefore check/update the displayed RPMS on the screen continuously.

That is why the IDE provides a function called "loop()". Properly written code cycles through that at incredible speed, looking for one thing after another to do.

vben92: How could I do this with "ifs"?

That is precisely what "ifs" are for. :astonished:

As the loop runs through, polling (checking) everything in turn - rotary encoders, switches, whether it is time (millis()) to do something else, if it determines an event (but only if), then it take care - instantly - of that event and moves on to the next task.

AHA! I understand what you are saying now! I tried it with ifs, and it's the same, I get the same issue. But I actually found out what is wrong.

Every time I use the lcd.print() function in the same block of code with any AccelStepper. h function it doesn't let the motor run smoothly. Even if I use ifs or whiles, its the same thing.

I commented out the lcd.print() functions and run it again without being able to see what I'm steps I'm using, and then pressed button 4 (:run it) and it works. Just by commenting those print lines out. Strange huh?

Here is another thing, even if I print the rotary encoder position on the serial, it still does not work properly. Also creating two ifs one for running the stepper and one for running the LCD still doesn't work.

Any ideas how to fix this?

Well the problem is of course, that the lcd.print() (or indeed, any "print" function) function is quite complex and contains a number of delays related to writing to the LCD controller.

So it will never be possible to use it in a "real-time" control system; it takes a holiday every time you use it.

It would absolutely be possible to write a "real-time" version of lcd.print() - or at least the subset of "print" functions that you need to display your data - which would work in the same way I have described - it would be yet another step in the main loop which used a "state" pointer to determine which incremental and immediate action (including waiting for a timer event - a specific target value of millis()) was appropriate in the tedious process of writing to the LCD on each pass.

I have not seen anyone describe such a module and have to say that I am not presently disposed to spend the time on it. :astonished:

Hm, I see. Anyways, your advice to use ifs was terrible after some trying out. :disappointed_relieved: Thanks for your precious time though.

I may not have the most well written code in the world but I fixed my issue. I went with whiles, they run faster since the program doesn’t go through the whole code.

Here is what I have so far for anyone out there who might be looking at something similar. Works alright too! see y’all.

#include <LiquidCrystal.h> //LCD lib
#include <RotaryEncoder.h> //RE lib
#include <AccelStepper.h> //SM lib


LiquidCrystal lcd(7, 6, 5, 4, 3, 2); //LCD pins
RotaryEncoder encoder(A2, A3); // rotary encoder pins

AccelStepper stepper1(AccelStepper::FULL2WIRE, 8, 9); //stepper2 pins
AccelStepper stepper2(AccelStepper::FULL2WIRE, 10, 11); //stepper2 pins
AccelStepper stepper3(AccelStepper::FULL2WIRE, 12, 13); //stepper2 pins

// Define constants and variables
const int inputPin = A0;  // buttons array analog input LCD
int buttonPressed = 0;    // LCD
uint16_t signalfromLCD = 0;   // value read from buttons array LCD  ??????

int previousPosition1 = 0;
int previousPosition2 = 0;       //Also what is displayed on the screen
float previousPosition3 = 0;

int spd1; int spd2; int spd3;  //motor speeds in steps per minute (200 or 400 steps per rev)

int count1 = 0;


// Initialization


void setup() 
{
  Serial.begin(9600);

  stepper1.setMaxSpeed(100000); 
  stepper1.setSpeed(0);
  stepper2.setMaxSpeed(100000); 
  stepper2.setSpeed(0);
  stepper3.setMaxSpeed(100000); 
  stepper3.setSpeed(0);
  

  lcd.begin(16, 2);              // set up the LCD's number of columns and rows
  lcd.print("   DUVIC v0.1   ");  // Print a message on the LCD


  PCICR |= (1 << PCIE1);    // This enables Pin Change Interrupt 1 that covers the Analog input pins or Port C.
  PCMSK1 |= (1 << PCINT10) | (1 << PCINT11);  // This enables the interrupt for pin 2 and 3 of Port C.
}


ISR(PCINT1_vect) {
  encoder.tick(); // just call tick() to check the state.
}


// main loop
void loop() 
{
  
buttonPressedF();  //check button 
 
  while (buttonPressed == 1)
  {
    if (count1++ == 0) {  // do once per button press
        for (int i = 0; i == 0 && previousPosition1 != 0; i++) {
            encoder.setPosition(previousPosition1);
    } //set previousPosition1 only when the button is pressed for the second time and there is a value other than zero in memory
      lcd.setCursor(0, 0);
      lcd.print("Side Motor1 RPM:");
 
    }
    
      lcd.setCursor(0, 1);
      encoder.tick();
      lcd.print(encoder.getPosition());
      lcd.print("                ");  //to get rid of zeroes on the screen that remain when going backwards from 10 to 9
      previousPosition1 = encoder.getPosition();
      buttonPressedF();  //Call function to check if any other button has been pressed
  
    }

    
encoder.setPosition(0);
count1 = 0;


  while (buttonPressed == 2)
  {
    if (count1++ == 0) {  // do once per button press
        for (int i = 0; i == 0 && previousPosition2 != 0; i++) {
            encoder.setPosition(previousPosition2);
    } //set previousPosition2 only when the button is pressed for the second time and there is a value other than zero in memory
      lcd.setCursor(0, 0);
      lcd.print("Side Motor2 RPM:");
 
    }
    
      lcd.setCursor(0, 1);
      encoder.tick();
      lcd.print(encoder.getPosition());
      lcd.print("                ");  //to get rid of zeroes on the screen that remain when going backwards from 10 to 9
      previousPosition2 = encoder.getPosition();
      buttonPressedF();  //Call function to check if any other button has been pressed
  
    }


encoder.setPosition(0);
count1 = 0;  

  
  while (buttonPressed == 3)
  {
    if (count1++ == 0) {  // do once per button press
        for (int i = 0; i == 0 && previousPosition3 != 0; i++) {
            encoder.setPosition(previousPosition3);
    } //set previousPosition3 only when the button is pressed for the second time and there is a value other than zero in memory
      lcd.setCursor(0, 0);
      lcd.print("Main Motor RPM: ");
 
    }
    
      lcd.setCursor(0, 1);
      encoder.tick();
      lcd.print(encoder.getPosition()/100.00);
      lcd.print("                ");  //to get rid of zeroes on the screen that remain when going backwards from 10 to 9
      previousPosition3 = encoder.getPosition();
      buttonPressedF();  //Call function to check if any other button has been pressed
  
    }


encoder.setPosition(0);
count1 = 0;


    while (buttonPressed == 4)
 {

       
    if (count1++ == 0) 
   {
     lcd.setCursor(0, 0);
     lcd.print("M1:"); lcd.print(previousPosition1);lcd.print("   ");
     lcd.print("M2:"); lcd.print(previousPosition2);lcd.print("     ");
     lcd.setCursor(0, 1);
     lcd.print("    ");
     lcd.print("M3:"); lcd.print(previousPosition3/100.0);
     lcd.print("                ");
      //----------------
     spd1 = previousPosition1 * 100; 
     stepper1.setSpeed(spd1); //setting speed to that there is in memory from button1

     spd2 = previousPosition2 * 100; 
     stepper2.setSpeed(spd2);

     spd3 = previousPosition3 * 100; 
     stepper3.setSpeed(spd3);
    }

stepper1.runSpeed();    
stepper2.runSpeed();
stepper3.runSpeed();

buttonPressedF();
 }


encoder.setPosition(0);
count1 = 0;

    
    while (buttonPressed == 5)
{
    if (count1++ == 0) 
   {
      lcd.setCursor(0, 0);
      lcd.print("      STOP      ");
      lcd.setCursor(0,1);
      lcd.print("                 ");

      stepper1.setSpeed(0);
      stepper2.setSpeed(0);
      stepper3.setSpeed(0);
   }

    stepper1.runSpeed();
    stepper2.runSpeed();
    stepper3.runSpeed();
  
    buttonPressedF();
}


encoder.setPosition(0);
count1 = 0;


} //loop

//-----------------------------------------------------------------------------------------------------------//
int buttonPressedF() // LCD
{

  int buttonBeingPressed;
  int signalfromLCD = analogRead(inputPin);
  
  if(signalfromLCD < 100 && signalfromLCD >= 0)  buttonBeingPressed = 1; 
  if(signalfromLCD < 250 && signalfromLCD > 150) buttonBeingPressed = 2;
  if(signalfromLCD < 470 && signalfromLCD > 370) buttonBeingPressed = 3;            //LCD function for buttons
  if(signalfromLCD < 670 && signalfromLCD > 570) buttonBeingPressed = 4;
  if(signalfromLCD < 870 && signalfromLCD > 770) buttonBeingPressed = 5;
  if(signalfromLCD <= 1023 && signalfromLCD > 950) buttonBeingPressed = 0;

 if (buttonBeingPressed != buttonPressed && buttonBeingPressed != 0) buttonPressed = buttonBeingPressed;
 return buttonPressed;
  
}