State Machine?

Beginner here.

Equipent:
Arduino MEGA 2560
SyRen50 motor controller
5V Wall Wart for Arduino
eTopxizu 12V, 30A regulated supply for SyRen/Motor (this may be undersized (12V, 900W tarp motor) but it seems to be holding up for the moment.
Generic Incremental encoder
Generic 16character LCD

I am trying to run a dc motor with encoder feedback. Right now, my motor runs fine in both directions, ramping power up and down to minimize wear and tear. My encoder reads properly as confirmed via serialprint and printing to my lcd. The problem is that when the motor is running, the encoder stops counting, or at least printing. This will be a problem when I want to stop/start the motor based on it's current position as measured with the encoder.

I suspect what is happening is that the program is getting 'stuck' in the loop of the motor control code and won't update the encoder position until the motor loop is complete. I understand I may need to create state machines (going way back to college prgramming....) but I am unsure how to start that with my current application. Any help is greatly appreciated!

Full code here below. I have attached the atypical library file though I don't think it will be relavent.

#include <SyRenSimplified.h>
#include <LiquidCrystal.h>
// Connections:
// rs (LCD pin 4) to Arduino pin 12
// rw (LCD pin 5) to Arduino pin 11
// enable (LCD pin 6) to Arduino pin 10
// LCD pin 15 to Arduino pin 13
// LCD pins d4, d5, d6, d7 to Arduino pins 5, 4, 33, 32
LiquidCrystal lcd(12, 11, 10, 5, 4, 33, 32);

int backLight = 13;
int curPos = 0;
int tgtPos = 225;

SyRenSimplified SR;

const int BUTTON1 = 52; //ccw direction button
const int BUTTON2 = 53; //cw direction button
const int LED1 = 22;    //input confirmation light for B1
const int LED2 = 23;    //input confirmation light for B2
int BUTTONstate1 = 0;
int BUTTONstate2 = 0;

int power = 0;

volatile long temp, counter = 0; //This variable will increase or decrease depending on the rotation of encoder

void setup()
{
  pinMode(BUTTON1, INPUT); //ccw button
  pinMode(BUTTON2, INPUT); //cw button
  pinMode(LED1, OUTPUT);  //input confirmation light for B1
  pinMode(LED2, OUTPUT);  //input confirmation light for B2
  SyRenTXPinSerial.begin(9600);

  pinMode(backLight, OUTPUT);
  digitalWrite(backLight, HIGH); // turn backlight on. Replace 'HIGH' with 'LOW' to turn it off.
  lcd.begin(16, 2);             // columns, rows.  use 16,2 for a 16x2 LCD, etc.
  lcd.clear();                  // start with a blank screen
  lcd.setCursor(0, 0);          // set cursor to column 0, row 0 (the first row)
  lcd.print("Tgt Pos:");    // set base text for screen begin
  lcd.setCursor(13, 0);
  lcd.print("DEG");
  lcd.setCursor(0, 1);
  lcd.print("Cur Pos:");
  lcd.setCursor(13, 1);
  lcd.print("DEG");         // set base text for screen end

  pinMode(2, INPUT_PULLUP); // internal pullup input pin 2 //encoder input 1
  pinMode(3, INPUT_PULLUP); // internal pullup input pin 3 //encoder input 2
  //Setting up interrupt
  attachInterrupt(0, ai0, RISING);  //A rising pulse from encodenren activated ai0().
  attachInterrupt(1, ai1, RISING);   //B rising pulse from encodenren activated ai1().
}

void loop()

{

  digitalWrite(LED1, LOW);
  digitalWrite(LED2, LOW);

  // Send the value of counter as input from encoder
  if ( counter != temp ) {
    temp = counter;
    lcd.setCursor (9, 1);
    lcd.print(counter);
  }

  //Run motor ccw while button is pressed, ramping to max speed and ramping down to 0 when released.
  while (digitalRead(BUTTON1) == HIGH && digitalRead(BUTTON2) == LOW) //ramp up in positive direction with B1 pressed
  { //but return speed to zero if conflicting inputs
    digitalWrite(LED1, HIGH);
    if (power <= 100) {
      power++;
    }
    SR.motor(power);
    delay(40);
  }

  while (power > 0 && digitalRead(BUTTON1) == LOW || power > 0 && digitalRead(BUTTON1) == HIGH && digitalRead(BUTTON2) == HIGH)
  {
    power--;
    SR.motor(power);
    delay(40);
  }

  //Run motor cw while button is pressed, ramping to max negative speed and ramping up to 0 when released.
  while (digitalRead(BUTTON2) == HIGH && digitalRead(BUTTON1) == LOW) //ramp up in negatvie direction with B2 pressed
  { //but return speed to zero if conflicting inputs
    digitalWrite(LED2, HIGH);
    if (power >= -100) {
      power--;
    }
    SR.motor(power);
    delay(40);
  }

  while (power < 0 && digitalRead(BUTTON2) == LOW || power < 0 && digitalRead(BUTTON1) == HIGH && digitalRead(BUTTON2) == HIGH)
  {
    power++;
    SR.motor(power);
    delay(40);
  }


  //display target position on LCD, will eventually be an input from another system and when target position and current position are different, will drive motor accordingly
  lcd.setCursor (9, 0);
  lcd.print(tgtPos);

}

void ai0() {
  // ai0 is activated if DigitalPin nr 2 is going from LOW to HIGH
  // Check pin 3 to determine the direction
  if (digitalRead(3) == LOW) {
    counter++;
  } else {
    counter--;
  }
}

void ai1() {
  // ai0 is activated if DigitalPin nr 3 is going from LOW to HIGH
  // Check with pin 2 to determine the direction
  if (digitalRead(2) == LOW) {
    counter--;
  } else {
    c

SabertoothArduinoLibraries.zip (382 KB)

Here is a tutorial on state machines:

State Machine

Basically, you need to get rid of the while loops and let the loop() function do your looping. That way you can read the encoder at the beginning of loop() and that data will be available for processing the current state you are in.

Basically, you need to get rid of the while loops

And the delays.

Here is an example of a state machine with all timing done with millis. Button is read while flashing LED. No delays, no blocking code.

Not a substitute for the tutorial but I hope it gives you some ideas. Feel free to ask questions if you don't understand anything.

/* Demonstration of a state machine */
/* Uses built in LED */
/* Requires a push button wired between ground and digital input 2 */
/* Tested on Nano Every */

const uint8_t buttonPin = 2;

void setup() {
  Serial.begin(9600);
  pinMode(buttonPin, INPUT_PULLUP);
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  stateMachine();
}

void stateMachine() {
  const uint32_t debounceTime = 20;
  const uint32_t slowFlash = 1500;
  const uint32_t mediumFlash = 750;
  const uint32_t fastFlash = 50;
  enum states {flashSlow, debounceToMedium, flashMedium, debounceToFast, flashFast, debounceToSlow};
  static uint8_t state;
  static uint32_t lastMillis;
  uint32_t currentMillis = millis();
  bool buttonState = !digitalRead(buttonPin);     // buttonState true if button pressed
  
  switch (state) {
    
    case flashSlow:
      if (buttonState == true) {
        state = debounceToMedium;
        lastMillis = currentMillis;               // Start debounce timer
      } else {
        if (currentMillis - lastMillis >= slowFlash) {
          lastMillis = currentMillis;
          digitalWrite (LED_BUILTIN, !(digitalRead(LED_BUILTIN)));
        }
      }
      break;
    
    case debounceToMedium:
      if (buttonState == true) {
        lastMillis = currentMillis;
      } else if (currentMillis - lastMillis >= debounceTime) {
        state = flashMedium;
      }
      break;

    case flashMedium:
      if (buttonState == true) {
        state = debounceToFast;
        lastMillis = currentMillis;               // Start debounce timer
      } else {
        if (currentMillis - lastMillis >= mediumFlash) {
          lastMillis = currentMillis;
          digitalWrite (LED_BUILTIN, !(digitalRead(LED_BUILTIN)));
        }
      }
      break;

    case debounceToFast:
      if (buttonState == true) {
        lastMillis = currentMillis;
      } else if (currentMillis - lastMillis >= debounceTime) {
        state = flashFast;
      }
      break;
      
    case flashFast:
      if (buttonState == true) {
        state = debounceToSlow;
        lastMillis = currentMillis;               // Start debounce timer
      } else {
        if (currentMillis - lastMillis >= fastFlash) {
          lastMillis = currentMillis;
          digitalWrite (LED_BUILTIN, !(digitalRead(LED_BUILTIN)));
        }
      }
      break;

    case debounceToSlow:
      if (buttonState == true) {
        lastMillis = currentMillis;
      } else if (currentMillis - lastMillis >= debounceTime) {
        state = flashSlow;
      }
      break;
  }
}

I think I get the concept.

That said, the delays I have in the motor power code are to keep it from ramping too fast. If I leave those in the new state code will that be a problem? I think it will be so what other option would I have to slow down the power increases and decreases?

That said, the delays I have in the motor power code are to keep it from ramping too fast.

I've given you an example of how to do timing without delays, you need to adapt it to what you are doing. There are plenty of other examples if you look.

PerryBebbington:
I've given you an example of how to do timing without delays, you need to adapt it to what you are doing. There are plenty of other examples if you look.

I'll look again. I guess I don't understand right off how to make that adaptation for this particular application. I'll give it a deeper look and take a shot.

In my opinion learning to do this is one of the most important steps from beginner to experienced programmer, even if, like me, you just do it as a hobby.

PerryBebbington:
In my opinion learning to do this is one of the most important steps from beginner to experienced programmer, even if, like me, you just do it as a hobby.

I don't know if this is the most elegant solution but I believe I've sorted millis() for this application.

Thanks!

If interested:

#include <SyRenSimplified.h>
#include <LiquidCrystal.h>
#include <Wire.h>
// Connections:
// rs (LCD pin 4) to Arduino pin 12
// rw (LCD pin 5) to Arduino pin 11
// enable (LCD pin 6) to Arduino pin 10
// LCD pin 15 to Arduino pin 13
// LCD pins d4, d5, d6, d7 to Arduino pins 5, 4, 33, 32
LiquidCrystal lcd(12, 11, 10, 5, 4, 33, 32);
int backLight = 13;

SyRenSimplified SR;

const int BUTTON1 = 52; //ccw direction button
const int BUTTON2 = 53; //cw direction button
const int LED1 = 22;    //input confirmation light for B1
const int LED2 = 23;    //input confirmation light for B2
int BUTTONstate1 = 0;
int BUTTONstate2 = 0;
int power = 0;
int accel = 40; //Set ramp up 'delay'
int decel = 40; //Set ramp down 'delay'
volatile long curPos = 0;
int tgtPos = 225;  //Placeholder variable until I figure out ASCOM details

volatile long temp, counter, aziTemp = 0; //This variable will increase or decrease depending on the rotation of encoder
int azi = 0; //Limit encoder position data size, don't really know if this is necessary

void setup()
{
  pinMode(BUTTON1, INPUT);
  pinMode(LED1, OUTPUT);
  pinMode (LED2, OUTPUT);
  SyRenTXPinSerial.begin(9600);
  Wire.begin();

  pinMode(backLight, OUTPUT);
  digitalWrite(backLight, HIGH); // turn backlight on. Replace 'HIGH' with 'LOW' to turn it off.
  lcd.begin(16, 2);             // columns, rows.  use 16,2 for a 16x2 LCD, etc.
  lcd.clear();                  // start with a blank screen
  lcd.setCursor(0, 0);          // set cursor to column 0, row 0 (the first row)
  lcd.print("Tgt Pos:");        // fixed text
  lcd.setCursor(13, 0);         // position for next fixed text
  lcd.print("DEG");             // fixed text
  lcd.setCursor(0, 1);          // next line
  lcd.print("Cur Pos:");        // fixed text
  lcd.setCursor(13, 1);         // position for next fixed text
  lcd.print("DEG");             // fixed text

  pinMode(2, INPUT_PULLUP);     // internal pullup input pin 2

  pinMode(3, INPUT_PULLUP);     // internal pullup input pin 3
  //Setting up interrupt
  //A rising pulse from encodenren activated ai0(). AttachInterrupt 0 is DigitalPin nr 18 on moust Arduino.
  attachInterrupt(0, ai0, RISING);

  //B rising pulse from encodenren activated ai1(). AttachInterrupt 1 is DigitalPin nr 19 on moust Arduino.
  attachInterrupt(1, ai1, RISING);
}

void loop()

{
  //Math to scale encoder pulses to a 360deg base
  if ( counter != temp ) {
    temp = counter * 1.8;
    aziTemp = temp % 360;
  }
  if (aziTemp < 0) {
    azi = aziTemp + 360;
  }
  else {
    azi = aziTemp;
  }

  //Take the output of the above math to display current position on LCD
  lcd.setCursor (9, 1);
  lcd.print("   ");
  lcd.setCursor (9, 1);
  lcd.print(round(azi));

  //Default lights to off
  digitalWrite(LED1, LOW);
  digitalWrite(LED2, LOW);

  //Declare variables for millis() math
  static unsigned long lastAccelTime = 0;
  static unsigned long lastDecelTime = 0;
  unsigned long currentTime = millis();

  //CCW Button ramp motor up if high ramp to max if nigh, ramp down to 0 if low, ramp to zero if both pressed
  if (digitalRead(BUTTON1) == HIGH && digitalRead(BUTTON2) == LOW)
  {
    digitalWrite(LED1, HIGH);
    if (currentTime - lastAccelTime > accel) {
      lastAccelTime = currentTime;
      if (power <= 100) {
        power ++;
        SR.motor(power);
      }
    }
  }

  if (power > 0 && digitalRead(BUTTON1) == LOW || power > 0 && digitalRead(BUTTON1) == HIGH && digitalRead(BUTTON2) == HIGH)
  {
    if (currentTime - lastDecelTime > decel) {
      lastDecelTime = currentTime;
      power = power / 1.01;
      SR.motor(power);
    }
  }

  // CW Button ramp motor down to max negative if high, ramp up to 0 if low, ramp to 0 if both pressed
  if (digitalRead(BUTTON2) == HIGH && digitalRead(BUTTON1) == LOW) //ramp up in positive direction with B1 pressed
  { //but return speed to zero if conflicting inputs
    digitalWrite(LED2, HIGH);

    if (currentTime - lastAccelTime > accel) {
      lastAccelTime = currentTime;
      if (power >= -100) {
        power --;
        SR.motor(power);
      }
    }
  }

  if (power < 0 && digitalRead(BUTTON2) == LOW || power < 0 && digitalRead(BUTTON1) == HIGH && digitalRead(BUTTON2) == HIGH)
  {
    if (currentTime - lastDecelTime > decel) {
      lastDecelTime = currentTime;
      power = power / 1.01;
      SR.motor(power);
    }

    //display target position, placeholder until I figure out how to extract telescope azimuth from ASCOM
    lcd.setCursor (9, 0);
    lcd.print(tgtPos);

  }
}


/////////////////Encoder math//////////////////
void ai0() {
  // ai0 is activated if DigitalPin nr 2 is going from LOW to HIGH
  // Check pin 3 to determine the direction
  if (digitalRead(3) == LOW) {
    counter++;
  } else {
    counter--;
  }
}

void ai1() {
  // ai0 is activated if DigitalPin nr 3 is going from LOW to HIGH
  // Check with pin 2 to determine the direction
  if (digitalRead(2) == LOW) {
    counter--;
  } else {
    counter++;
  }
}

Nice to see no delays!

If it does what you want then it's good.

My next suggestion for you is learn to write everything in functions that do one particular thing. Loop() should contain only calls to functions, not actual code. Functions should do a clearly defined thing and have a name that indicates what that thing is.

PerryBebbington:
Nice to see no delays!

If it does what you want then it's good.

My next suggestion for you is learn to write everything in functions that do one particular thing. Loop() should contain only calls to functions, not actual code. Functions should do a clearly defined thing and have a name that indicates what that thing is.

That's exactly what I've started doing! SOOOO much easier to read this way!

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.