Structure of code

I have been trying to build a more complex program and feel a little overwhelmed trying to define the best way to structure my program.

My project involves using a closed loop stepper motor/driver to move an object to and fro between 2 positions.
I would like to do the following in the implementation:

  • using a jog button be able to move the stepper to each end of travel position and then store the positions by button and to be used in the automatic cycling.[/li]
  • set the total cycles to be run (full CW and full CCW between above limits.
  • enable/disable the stepper motor so that the object could be moved manually without killing power.
  • pause/resume the test cycle.
  • have control over the acceleration/deceleration of the motion.
  • display the total cycles completed, total cycles to be completed, speed/acc parameters of the stepper.
  • display error status of the stepper driver.

Do you have any advice on how I should structure the code - I have been piecing it together as I go as you can see....

// #include <SD.h> // To implement ability to store cycle progress and limits to SD card
#include <SpeedyStepper.h>
// #include <Adafruit_GFX.h>    // Core graphics library // to implement display for # cycles completed and travel setpoints vs current position
// #include <Adafruit_ST7735.h> // Hardware-specific library



//VAR DEFINITIONS
int CWLimit; //to be used to store the CW extent of travel
int CCWLimit; // to be used to store the CCW extent of travel
int CurrentPos; // current stepper position - will be written to when cycle is paused?
int Steps_per_Rev = 800;
int StepperSpeed = 20;
int OutputLED = 13;

// SIGNAL DEFINITIONS
# define PULPLUS 1 //PUL+ Motor Step Control > BLUE
# define PULMINUS 2 //PUL- Motor Step Control > BLACK COMMON GND
# define DIRPLUS 3 //DIR+ Motor Direction Control > PURPLE
# define DIRMINUS 4 //DIR- Motor Direction Control > BLACK COMMON GND
# define ENAPLUS 5 // Motor Enable Signal (active LOW) > ORANGE
# define ENAMINUS 6 // Motor Enable Signal (active LOW) > BLACK COMMON GND
# define PENDPLUS 7 // travel complete + > YELLOW // Can this be used to report that a full travel is complete instead of using a timer?
# define PENDMINUS 8 // travel complete - > GREY
# define ALMPLUS 9 // FAULT signal > WHITE
# define ALMMINUS 10 //FAULT signal > BROWN
# define SCLK 52 // for Display/SD Card - CLOCK SPEED
# define MOSI 51 // for Display/SD Card
# define TFTCS 11 // for Display/SD Card CHIP Select
# define DC 12 // for Display/SD Card Data/Command Pin
# define RST 14 // for Display/SD Card - can also connect to board reset pin


// CONTROL DEFINITIONS
# define SETCW 15 //Switch to store the CW position?
# define SETCCW 16 //Switch to set the CCW position?
# define enableButton 17 //Button
# define jogCWButton 18 // 3 position momentary switch to jog
# define jogCCWButton 19 // 3 position momentary switch to jog
# define startButton 20 // to start/resume the cycling
# define stopButton 21 // to stop/pause the cycling

int enableState = HIGH;
int enableButtonReading;
int previousEnableState = LOW;
long enableButtonTime = 0;
long enableButtonDebounce = 200;

#if defined(__SAM3X8E__)
#undef __FlashStringHelper::F(string_literal)
#define F(string_literal) string_literal
#endif

//OUTPUTS
# define OutputLED 13 //LED
// Adafruit_ST7735 tft = Adafruit_ST7735(TFTCS, DC, RST);
float p = 3.1415926;

//STEPPER PARAMETER DEFINITION
SpeedyStepper stepper;


void setup()
{
  // DEFINE THE I/O
  pinMode (ENAPLUS, OUTPUT); // ENA+
  pinMode (ENAMINUS, OUTPUT); // ENA-
  pinMode (DIRPLUS, OUTPUT); // DIR+
  pinMode (DIRMINUS, OUTPUT); // DIR-
  pinMode (PULPLUS, OUTPUT); // PUL+
  pinMode (PULMINUS, OUTPUT); // PUL-
  pinMode (PENDPLUS, INPUT); // PEND+ Travel Complete
  pinMode (PENDMINUS, INPUT); // PENS- Travel Complete
  pinMode (ALMPLUS, INPUT); // ALM+
  pinMode (ALMMINUS, INPUT); // ALM-
  pinMode (SETCW, INPUT_PULLUP); // Sets the CW TRAVEL Limits
  pinMode (SETCCW, INPUT_PULLUP); //Sets the CCW Travel Limits
  pinMode (enableButton, INPUT_PULLUP); // Enable button state
  pinMode (OutputLED, OUTPUT);

  //INIT THE I/O
  digitalWrite (ENAMINUS, LOW); // ENA- 0V
  digitalWrite (ENAPLUS, HIGH); // ENA+ 5V (ENABLED LOW)
  digitalWrite (DIRPLUS, LOW); //
  digitalWrite (DIRMINUS, LOW); //
  digitalWrite (PULPLUS, LOW); //
  digitalWrite (PULMINUS, LOW); //
  // digitalWrite (SETCW, LOW); //
  // digitalWrite (SETCCW, LOW); //

  Serial.begin(115200);

  // CONNECT STEPPER PINS
  stepper.connectToPins(PULPLUS, DIRPLUS);
  stepper.setSpeedInStepsPerSecond(100);
}

//DISPLAYS PARAMETERS
void Display_Parameters()
{
  Serial.println("Stepper Info:");
  Serial.print("CW Limit - ");
  Serial.println(CWLimit);
  Serial.print("CCW Limit - ");
  Serial.println(CCWLimit);
  Serial.print("Current Position - ");
  Serial.println(CurrentPos);
  Serial.print("Steps/Revolution - ");
  Serial.println(Steps_per_Rev);
}


void ButtonPress()
{

}

//SETS CW Limit
int RecordCWLimit ()
{
  Serial.print(stepper.getCurrentPositionInSteps());
  Serial.println("This code will be triggered to read the current position and set as the new value of CW limit");
  delay(1000);
}

//SETS CCW Limit
int RecordCCWLimit ()
{
  Serial.println("This code will be triggered to read the current position and set as the new value of CCW limit");
}

void loop()
{
  // Enable Button Code

  enableButtonReading = digitalRead(enableButton);

  if (enableButtonReading == LOW && previousEnableState == HIGH && millis() - enableButtonTime > enableButtonDebounce)
  {
    if ( enableState == LOW)
      enableState = HIGH;
    else
      enableState = LOW;

    enableButtonTime = millis();
  }
  digitalWrite(OutputLED, enableState);
  previousEnableState = enableButtonReading;
  Serial.println(enableState);

  //


  // Trying to read the positon of encoder and give feedback

  int CW_SET = digitalRead (SETCW);
  int CCW_SET = digitalRead (SETCCW);


  if (CW_SET == LOW)
  {
    RecordCWLimit ();
    Serial.print("CW Button - ");
    Serial.println(CW_SET);
  }
  else
  {
    if (CCW_SET == LOW)
    {
      Serial.print("CCW Button - ");
      Serial.println(CCW_SET);
      RecordCCWLimit ();

    }
    else
    {
      Serial.println("No Buttons Pressed");
      Serial.println();
      delay(1000);
      Display_Parameters();
    }
  }
  //Move forwards 200 steps
  stepper.setSpeedInStepsPerSecond(100);
  stepper.setAccelerationInStepsPerSecondPerSecond(100);
  stepper.moveToPositionInSteps(200);
  delay(1000);
  // Moves forward another 200 steps
  stepper.moveToPositionInSteps(400);
  delay(1000);
  //Returns to start position
  stepper.setSpeedInStepsPerSecond(250);
  stepper.setAccelerationInStepsPerSecondPerSecond(800);
  stepper.moveToPositionInSteps(0);
  delay(2000);


}

I have not studied your program - I am not good at analysing code when there is no specific fault with symptoms.

In general your description sounds as if you should use a "state machine" (look it up) approach to the project. "State machine" is a fancy name for a simple concept of using one or more variables to keep track of progress through a project - for example JOG, DISABLED etc. Note that it may make sense to have two (or more) groups of states - for example one for the high-level state selected by the user and another for the motion such as MOVE LEFT, LEFT LIMIT TRIGGERED etc.

Associated with that it is also a good idea to divide your program into separate single purpose functions that can each be tested separately. See Planning and Implementing a Program.

That way you can have (say) a function to read buttons, another function to figure out what the inputs mean and (perhaps) to update a state variable. The function for motion will only look at the state variable and will have no direct connection with the code for the buttons. The state variable might also be updated by a limit switch in another function.

...R
Stepper Motor Basics
Simple Stepper Code

also look up the AccelStepper library

Reduce necessary structure:

Learn the how to do many things at once lesson.

Make the button and stepper into separate handler tasks and the decision making to a task of its own.

No task runs inside of any other. A task may use a variable to run, another task may change the value of the variable.
So the button task sets a button variable after debouncing and the decision task sees that and sets a variable that the servo task runs on. The variables become the meta-structure.

I would start by grounding all of the MINUS inputs to your stepper driver because there is no sense in driving them LOW with an output pin when they never need to change.

Then you can move the PULPLUS (Step?) signal off of Pin 1 which is the TX line of the hardware serial port, just in case you want to use Serial.print() for debugging.

I like to use 'const byte' instead of '#define' for pin numbers.

I think the AccelStepper library would be well suited for your needs.
Manual: AccelStepper: AccelStepper library for Arduino
Download: http://www.airspayce.com/mikem/arduino/AccelStepper/AccelStepper-1.59.zip

I read the materials suggested - still a lot to digest - and decided to try to map out the program flow on paper 1st. Attached is where I'm at....

The code cycles the stepper back and forth between 0 and 12.5 revolutions indefinitely.

I have also included the code as it stands now - I was trying to compile it after adding the bounce2.h library to begin taking inputs from buttons and now I get an error every time....

/*List of Functions
   Enable/Disable Stepper with by toggling a button > enableStepper
   Jog Steppers with 3 position switch > jogStepper
   Set CW Position limit > setCWLimit
   Set CCW Position limit > setCCWLimit
   Start Cycling > cycleDoor
   Pause Cycling > pauseDoor
   Set cycle target count > setDoorCycle
   Count cycles > cycleInc
   display limits/position of stepper on tft screen > diaplyInfo
   set rotation speed/acceleration
   Reset counter > resetCounter
*/
//-------------------------------------------------------------------



// Define all I/O pin names

byte stepPin = 2; // -- BLUE
byte directionPin = 3; // -- PURPLE
byte enaPin = 4; // -- ORANGE
byte moveCWPin = 5; // YELLOW
byte moveCCWPin = 6; // GREY
byte moveCompletePin = 7; // YELLOW
byte startPin = 8;
byte setCWLimitPin = 9;
byte setCCWLimitPin = 10;
byte resetPin = 11;


boolean jogCWPressed = false;
boolean jogCCWPressed = false;
boolean EnablePressed = false;
boolean StartPressed = false;
boolean setCWLimit = false;
boolean setCCWLimit = false;


# include <Bounce2.h>
# define LEDPin 13
# define numButtons 7
const uint8_t BUTTON_PINS[numButtons] = {enaPin, moveCWPin, moveCCWPin, startPin, setCWLimitPin, setCCWLimitPin, resetPin};
int ledState = LOW;
Bounce * buttons = new Bounce[numButtons];


// Add libraries to include
# include <AccelStepper.h>
AccelStepper stepper(AccelStepper::DRIVER, stepPin, directionPin); //


//-------------------------------------------------------------------

void checkButtons()
{
  bool needToToggleLed = false;
  for (int i = 0; i < numButtons; i++)  {
    // Update the Bounce instance :
    buttons[i].update();
    // If it fell, flag the need to toggle the LED
    if ( buttons[i].fell() ) {
      needToToggleLed = true;
    }
  }
  // if a LED toggle has been flagged :
  if ( needToToggleLed ) {
    // Toggle LED state :
    ledState = !ledState;
    digitalWrite(LEDPin, ledState);
  }
}
/*
  //-------------------------------------------------------------------
  void enableStepper()
  {

  }
  //-------------------------------------------------------------------
  void jogStepper ()
  {

  }
  //-------------------------------------------------------------------
  void setCWLimit()
  {

  }
  //-------------------------------------------------------------------
  void setCCWLimit()
  {

  }
*/
//-------------------------------------------------------------------
void cycleDoor()
{



}
//-------------------------------------------------------------------
void pauseDoor()
{

}
//-------------------------------------------------------------------
void setDoorCycle()
{

}
/
//-------------------------------------------------------------------
void displayInfo()
{

}
//-------------------------------------------------------------------
void resetCounter()
{

}
//-------------------------------------------------------------------

void setup() {
  // put your setup code here, to run once:

  // Definition if I/O Types
  pinMode (enaPin, OUTPUT);
  pinMode (moveCompletePin, INPUT);
  pinMode (moveCWPin, INPUT_PULLUP);
  pinMode (moveCCWPin, INPUT_PULLUP);

  digitalWrite (enaPin, LOW);

  Serial.begin(9600);

  stepper.setMaxSpeed(100000);
  stepper.setAcceleration(5000);
  stepper.moveTo(10800);

  for (int i = 0; i < numButtons; i++) {
    buttons[i].attach( BUTTON_PINS[i] , INPUT_PULLUP  );       //setup the bounce instance for the current button
    buttons[i].interval(25);              // interval in ms
  }
  // Setup the LED :
  pinMode(LEDPin, OUTPUT);
  digitalWrite(LEDPin, ledState);


}

void loop() {
  // put your main code here, to run repeatedly:
  checkButtons();

  if (stepper.distanceToGo() == 0)
  {
    stepper.moveTo(-stepper.currentPosition());
  }

  stepper.run();

}

Arduino: 1.8.10 (Windows 10), Board: "Arduino Mega ADK"

In file included from C:\Users\awilson\Documents\Arduino\DoorTester\DoorTester.ino:42:0:

C:\Users\awilson\Documents\Arduino\libraries\Bounce2-master\src/Bounce2.h:205:10: warning: extra qualification 'Bounce::' on member 'changed' [-fpermissive]

bool Bounce::changed( ) { return getStateFlag(CHANGED_STATE); }

^~~~~~

C:\Users\awilson\Documents\Arduino\libraries\Bounce2-master\src/Bounce2.h: In member function 'bool Bounce::changed()':

C:\Users\awilson\Documents\Arduino\libraries\Bounce2-master\src/Bounce2.h:205:51: error: 'CHANGED_STATE' was not declared in this scope

bool Bounce::changed( ) { return getStateFlag(CHANGED_STATE); }

^~~~~~~~~~~~~

Multiple libraries were found for "Bounce2.h"
Used: C:\Users\awilson\Documents\Arduino\libraries\Bounce2-master
Multiple libraries were found for "AccelStepper.h"
Used: C:\Users\awilson\Documents\Arduino\libraries\AccelStepper
exit status 1
Error compiling for board Arduino Mega ADK.

This report would have more information with
"Show verbose output during compilation"
option enabled in File -> Preferences.

I am struggling to understand how different functions are prioritized - when in a looped function (IF) does the code execute the loop until it completes before moving the next function in sequence or does it execute in some other pattern?

I would also appreciate some explanation on how I can 'record' a value from one function (like the setpoint for stepper travel) and pass it to another separate function.

I was trying to compile it after adding the bounce2.h library to begin taking inputs from buttons and now I get an error every time....

Your code compiles for me with both UNO or MegaADK selected as the target board.

I suggest that you remove whatever Bounce2 libraries you have installed, and reinstall using the Library Manager.

when in a looped function (IF) does the code execute the loop until it completes before moving the next function in sequence or does it execute in some other

if() is not a loop. It is a block of code which is entered if the condition is fulfilled, and it is executed line by line as it is written. It will not repeat until loop() gets back to it. Perhaps you can explain more about what you are thinking and what you find confusing.

I would also appreciate some explanation on how I can 'record' a value from one function (like the setpoint for stepper travel) and pass it to another separate function.

Global variables are good everywhere.
https://www.arduino.cc/reference/en/language/variables/variable-scope--qualifiers/scope/

I'm trying to pick this back up after a couple of weeks of over-eating turkey.

I'm trying to start small since I have clearly overestimated my capacity to:

  • devote time to this
  • understand the programming language...

I am trying to use a button [enableButton] to enable/disable the stepper (this works though I believe it may be because I disable the stepper instead of the program flow?)
I would also like to be able use the [startButton] to begin the process of cycling the stepper motor between the extents of travel until the button is toggled again.

when calling the stepper.run() function I believe it continuously runs this piece of code without returning to the loop - is that right?

I would like to be able to count the number of cycles the stepper runs through and be able to set a zero position for the stepper (it's a closed loop stepper if that changes things) and then drive to a specific point and return to the 0 point - the code I have is bouncing between +/- steps and I can't seem to figure a better way to do that...

#include <AccelStepper.h>
#include <SPI.h>
#include <Bounce2.h>

#define enableButton 16
#define enaPlus 4
#define startButton 15
#define resetButton 18
int enaState = HIGH;
int startState = LOW;

Bounce enaBouncer = Bounce(); // Instantiate a Bounce object
Bounce startBouncer = Bounce ();

AccelStepper stepper(AccelStepper::DRIVER, 2, 3); // Defaults to AccelStepper::FULL4WIRE (4 pins) on 2, 3, 4, 5
int stepsPerRev = 800;
long totalTravelDistance = 72;
long pi = 3.142;
long pulleyDiameter = 2;
long distancePerRev = pulleyDiameter * pi;

void cycleDoor()
{
  // If at the end of travel go to the other end
  if (stepper.distanceToGo() == 0)
    stepper.moveTo(-stepper.currentPosition());

  

  /*    Serial.print("Current -");
      Serial.print(stepper.currentPosition()/stepsPerRev*distancePerRev);
      Serial.print("    Target -");
      Serial.println(stepper.targetPosition()/stepsPerRev*distancePerRev);
  */
}

void buttonToggle() {

  enaBouncer.update(); // Update the Bounce instance
  startBouncer.update();

  if ( enaBouncer.fell() ) {  // Call code if button transitions from HIGH to LOW
    enaState = !enaState; // Toggle enable state
    digitalWrite(enaPlus, enaState); // Apply new enable state
    Serial.print("Enable State -");
    Serial.println(enaState);
  }
  if ( startBouncer.fell()) {
    startState = !startState;
    Serial.print("Run State -");
    Serial.println(startState);
  }
}

void setup()
{
  // Change these to suit your stepper if you want

  stepper.setMaxSpeed(100000);
  stepper.setAcceleration(5000);

  //  stepper.moveTo(0);
  Serial.begin(115200);
  stepper.setCurrentPosition(stepsPerRev * (totalTravelDistance / distancePerRev) / 2);
  
  enaBouncer.attach(enableButton, INPUT_PULLUP); // Attach the debouncer to a pin with INPUT_PULLUP mode
  enaBouncer.interval(25); // Use a debounce interval of 25 milliseconds
  startBouncer.attach(startButton, INPUT_PULLUP); // Attach the debouncer to a pin with INPUT_PULLUP mode
  startBouncer.interval(25); // Use a debounce interval of 25 milliseconds

  pinMode(enaPlus, OUTPUT); // Setup the Enable BIT
  digitalWrite(enaPlus, enaState);

}

void loop() {
    buttonToggle();
  do{
  do {cycleDoor();
  stepper.run();
  buttonToggle();}
  while (startState == HIGH);
    }
while (enaState == LOW);
}

So much of your code is wrapped into the Bounce library (of which I have no experience) that I have no idea where the problem might lie.

Generally IMHO it is not a good idea to use WHILE loops because they block the Arduino until they complete. Just use IF and allow loop() to do the repetition.

If you want the movement to be turned on or off when a button is pressed then something like this should work (not complete and not tested)

void loop() {
    prevButtonState = buttonState;
    buttonState = digitalRead(buttonPin);
    if (buttonState != prevButtonState and buttonState == LOW) { // assumes LOW when pressed
        motorRunState =   ! motorRunState;
    }
    if (motorRunState == true) {
        stepper.run();
    }
}

...R

Thank you Robin2 that definitely helped! I thought I had been trying to use this previously without success but your suggestion evidently caused me to think a slightly different path.

I am still struggling to figure out how to set a zero position on the stepper (I think since it's a closed loop controller). If anyone has any ideas I'm here....

I am still struggling to figure out how to set a zero position on the stepper (I think since it's a closed loop controller).

From the links you provided https://www.omc-stepperonline.com/1-axis-closed-loop-stepper-kit-11-5-nm-1628-86-oz-in-nema-34-closed-loop-stepper-motor-driver.html

the closed loop capabilities will help with missing steps and torque control.

You will still require a homing routine with some sort of end switch to find a zero position. You certainly addressed this with the code structure a few posts ago

byte setCWLimitPin = 9;
byte setCCWLimitPin = 10;

Set CW Position limit > setCWLimit
Set CCW Position limit > setCCWLimit