Control panel for stepper motors using the Accelstepper library
In this video I show you how to make a comprehensive control panel for stepper motors. I used a Nokia 5110 LCD to implement a menu system and show the values of the different variables. The buttons are used for multiple purposes as well as the rotary encoder. The whole setup allows the user to use a stepper motors for careful positioning (e.g. for a milling table or camera slider) or pre-programmed movements. The circuit also contains a limit switch which stops the movement of the motor.
Useful links:
Schematics
STM32/Arduino source code
//The LCD-related code was based on the following library: //https://github.com/cbm80amiga/N5110_GUI_encoder_demo_STM32 //Defining the pins for the Nokia 5110 LCD #define N5110_RST PA0 #define N5110_CS PA4 #define N5110_DC PA1 #define N5110_BACKLIGHT PA2 #include "N5110_SPI.h" #if USESPI==1 #include <SPI.h> #endif N5110_SPI lcd(N5110_RST, N5110_CS, N5110_DC); // RST,CS,DC #include "c64enh_font.h" //smaller font #include "term9x14_font.h" //larger font //make sure that the font files are in the same folder as the project file! //AccelStepper #include <AccelStepper.h> AccelStepper stepper(1, PA9, PA8);// pulses/steps 9; Direction 8 //Defining pins const int RotaryCLK = PB9; //CLK pin on the rotary encoder const int RotaryDT = PB8; //DT pin on the rotary encoder const int RotarySW = PB7; //SW pin on the rotary encoder (Button function) const int UpButton = PB12; //Up button which is also a start button const int DownButton = PB13; //Down button which is also a stop button const int LimitSwitch_1 = PB11; //Input for the limit switch //Statuses of the DT and CLK pins on the encoder int CLKNow; int CLKPrevious; int DTNow; int DTPrevious; //Timing related variables, act mainly as debouncing float RotaryTime1; //Rotary encoder timing float RotaryTime2; unsigned long previousInterrupt; //Button timing //Menu-related variables //Values int RotaryButtonValue; //Pressed or not pressed rotary switch int menuCounter = 0; //this is for counting the main menu (menu item number) int stepperMaxSpeed = 400; //for setMaxSpeed int stepperSpeed = 400; //for setSpeed int stepperAcceleration = 800; //for setAcceleration int stepperAbsoluteSteps; //moveTo() int stepperRelativeSteps; //move() int stepperRotarySteps; // This keeps track of the steps done with the rotary encoder int stepperButtonSteps; // This keeps track of the steps done with the buttons int stepperStepsize = 1; //Step size for changing the variable. One click on the encoder will in/decrease with this amount int stepperPosition = 0; //position of the stepper motor, measured in steps int previousStepperPosition = 0; //This helps us to update the display //booleans bool stepperMaxSpeed_Selected = false; //for setMaxSpeed bool stepperSpeed_Selected = false; //for setSpeed bool stepperSpeedRun = false; //for "indefinite" running bool stepperAcceleration_Selected = false; //for setAcceleration bool stepperAbsoluteSteps_Selected = false; //moveTo() bool stepperRelativeSteps_Selected = false; //move() bool rotaryStepping_Selected = false; //precisely step the motor with the rotary encoder bool buttonStepping_Selected = false; //precisely step the motor with the buttons (UpButton/DownButton) bool stepperStepsize_Selected = false; //Step size menu bool stepperSetZero_Selected = false; //Reset to 0 menu bool menuChanged = false; //Switched between menus bool valueChanged = false; //changed the value of a menu item bool updateValueSelection = false; //if we want to change the value of a variable, it becomes true char buf[25]; //buffer for printing on the LCD void setup() { //Definition of the pins, remember where you need pinMode(PB9, INPUT); //CLK pinMode(PB8, INPUT); //DT pinMode(PB7, INPUT_PULLUP); //SW pinMode(PB12, INPUT); //UP pinMode(PB13, INPUT); //DOWN pinMode(PB11, INPUT); //LimitSwitch_1 //Store states CLKPrevious = digitalRead(RotaryCLK); DTPrevious = digitalRead(RotaryDT); //Rotary encoder interrupts attachInterrupt(digitalPinToInterrupt(RotaryCLK), RotaryEncoder, CHANGE); //Stepper setup stepper.setSpeed(stepperSpeed); //SPEED = Steps / second stepper.setMaxSpeed(stepperMaxSpeed); //SPEED = Steps / second stepper.setAcceleration(stepperAcceleration); //ACCELERATION = Steps /(second)^2 lcd.init(); //initialize LCD lcd.clrScr(); //clear the whole display printLCD(); //print the welcome message } void loop() { if(menuChanged == true) { updateMenuPosition(); } if(valueChanged == true ) { updateValue(); } if(updateValueSelection == true) { updateSelection(); } //Looping buttons CheckRotaryButton(); UpButtonPressed(); DownButtonPressed(); LimitSwitchPressed(); stepper.run(); //If a step is due, this function will do a step in each loop() iteration //The below part takes care of the position stepperPosition = stepper.currentPosition(); if (stepperPosition != previousStepperPosition) { updatePosition(); //Pos. on the bottom of the LCD updateValue(); //Pos or value in the middle of the screen previousStepperPosition = stepper.currentPosition(); //^if the motor does not move in the next loop() iteration, the code skips this part of the code } } void RotaryEncoder() { if(stepperMaxSpeed_Selected == true) { CLKNow = digitalRead(RotaryCLK); //Read the state of the CLK pin // If last and current state of CLK are different, then a pulse occurred if (CLKNow != CLKPrevious && CLKNow == 1) { // If the DT state is different than the CLK state then // the encoder is rotating in A direction, so we increase if (digitalRead(RotaryDT) != CLKNow) { stepperMaxSpeed = stepperMaxSpeed + stepperStepsize; //1 step size increment } else { if(stepperMaxSpeed<2) { //Don't decrease further, it should be at least 1 } else { stepperMaxSpeed = stepperMaxSpeed - stepperStepsize; //1 step size decrement } } valueChanged = true; } CLKPrevious = CLKNow; // Store last CLK state } //Stepper acceleration else if(stepperAcceleration_Selected == true) { CLKNow = digitalRead(RotaryCLK); //Read the state of the CLK pin // If last and current state of CLK are different, then a pulse occurred if (CLKNow != CLKPrevious && CLKNow == 1) { // If the DT state is different than the CLK state then // the encoder is rotating in A direction, so we increase if (digitalRead(RotaryDT) != CLKNow) { stepperAcceleration = stepperAcceleration + stepperStepsize; //1 step size increment } else { stepperAcceleration = stepperAcceleration - stepperStepsize; //1 step size decrement } valueChanged = true; } CLKPrevious = CLKNow; // Store last CLK state } //Absolute rotation else if(stepperAbsoluteSteps_Selected == true) { CLKNow = digitalRead(RotaryCLK); //Read the state of the CLK pin // If last and current state of CLK are different, then a pulse occurred if (CLKNow != CLKPrevious && CLKNow == 1) { // If the DT state is different than the CLK state then // the encoder is rotating in A direction, so we increase if (digitalRead(RotaryDT) != CLKNow) { stepperAbsoluteSteps = stepperAbsoluteSteps + stepperStepsize; //1 step size increment } else { stepperAbsoluteSteps = stepperAbsoluteSteps - stepperStepsize; //1 step size decrement } valueChanged = true; } CLKPrevious = CLKNow; // Store last CLK state } //Relative rotation else if(stepperRelativeSteps_Selected == true) { CLKNow = digitalRead(RotaryCLK); //Read the state of the CLK pin // If last and current state of CLK are different, then a pulse occurred if (CLKNow != CLKPrevious && CLKNow == 1) { // If the DT state is different than the CLK state then // the encoder is rotating in A direction, so we increase if (digitalRead(RotaryDT) != CLKNow) { stepperRelativeSteps = stepperRelativeSteps + stepperStepsize; //1 step size increment } else { stepperRelativeSteps = stepperRelativeSteps - stepperStepsize; //1 step size decrement } valueChanged = true; } CLKPrevious = CLKNow; // Store last CLK state } //Rotary movement else if(rotaryStepping_Selected == true) { CLKNow = digitalRead(RotaryCLK); //Read the state of the CLK pin // If last and current state of CLK are different, then a pulse occurred if (CLKNow != CLKPrevious && CLKNow == 1) { // If the DT state is different than the CLK state then // the encoder is rotating in A direction, so we increase if (digitalRead(RotaryDT) != CLKNow) { stepperRotarySteps = stepper.currentPosition() + stepperStepsize; //1 step size increment relative to the current position } else { stepperRotarySteps = stepper.currentPosition() - stepperStepsize; //1 step size decrement relative to the current position } valueChanged = true; } CLKPrevious = CLKNow; // Store last CLK state stepper.moveTo(stepperRotarySteps); } else if(buttonStepping_Selected == true) { //do nothing } //Stepper step size else if(stepperStepsize_Selected == true) { CLKNow = digitalRead(RotaryCLK); //Read the state of the CLK pin // If last and current state of CLK are different, then a pulse occurred if (CLKNow != CLKPrevious && CLKNow == 1) { // If the DT state is different than the CLK state then // the encoder is rotating in A direction, so we increase if (digitalRead(RotaryDT) != CLKNow) { stepperStepsize++; //1 step increment of the step size } else { if(stepperStepsize < 2) { //If it goes below 2 (=1), it won't be decreased anymore further } else { stepperStepsize--; //1 step decrement of the step size } } } CLKPrevious = CLKNow; // Store last CLK state valueChanged = true; } //Set new zeropoint else if( stepperSetZero_Selected == true) { //do nothing } else //MENU COUNTER---------------------------------------------------------------------------- { CLKNow = digitalRead(RotaryCLK); //Read the state of the CLK pin // If last and current state of CLK are different, then a pulse occurred if (CLKNow != CLKPrevious && CLKNow == 1) { // If the DT state is different than the CLK state then // the encoder is rotating CCW so increase if (digitalRead(RotaryDT) != CLKNow) { if(menuCounter < 7) //8 menu items (0, 1, 2....7) { menuCounter++; } else { menuCounter = 0; //0 comes after 7, so we move in a "ring" } menuChanged = true; } else { // Encoder is rotating CW so decrease if(menuCounter > 0) { menuCounter--; } else { menuCounter = 7; //7 comes after 0 when we decrease the numbers } menuChanged = true; } } CLKPrevious = CLKNow; // Store last CLK state } } void printLCD() //Prints a "welcome screen" { lcd.setFont(c64enh); lcd.printStr(ALIGN_CENTER, 0, "Stepper"); lcd.printStr(ALIGN_CENTER, 1, "Controller"); lcd.printStr(ALIGN_CENTER, 3, "Rotate the"); lcd.printStr(ALIGN_CENTER, 4, "encoder"); lcd.printStr(ALIGN_CENTER, 5, "to continue..."); } void CheckRotaryButton() { RotaryButtonValue = digitalRead(RotarySW); //read the button state if(RotaryButtonValue == 0) //0 and 1 can differ based on the wiring { RotaryTime1 = millis(); if(RotaryTime1 - RotaryTime2 > 1000) { switch(menuCounter) { case 0: stepperMaxSpeed_Selected = !stepperMaxSpeed_Selected; //we change the status of the variable to the opposite if (stepperMaxSpeed_Selected == false)//exiting { stepper.stop(); //stop upon exiting the menu } updateValueSelection = true; break; // case 1: stepperAcceleration_Selected = !stepperAcceleration_Selected; updateValueSelection = true; break; // case 2: stepperAbsoluteSteps_Selected = !stepperAbsoluteSteps_Selected; if (stepperAbsoluteSteps_Selected == false)//exiting { stepper.stop(); //stop upon exiting the menu } updateValueSelection = true; break; // case 3: stepperRelativeSteps_Selected = !stepperRelativeSteps_Selected; if (stepperRelativeSteps_Selected == false)//exiting { stepper.stop(); //stop upon exiting the menu } updateValueSelection = true; break; // case 4: rotaryStepping_Selected = !rotaryStepping_Selected; updateValueSelection = true; valueChanged = true; break; // case 5: buttonStepping_Selected = !buttonStepping_Selected; updateValueSelection = true; valueChanged = true; break; // case 6: stepperStepsize_Selected = !stepperStepsize_Selected; updateValueSelection = true; break; // case 7: stepperSetZero_Selected = true; //this always has to be set to true and we set it to false somewhere else updateValueSelection = true; break; } RotaryTime2 = millis(); menuChanged = true; //Refresh LCD after changing the value of the menu } } } void updateMenuPosition() { lcd.clrScr();//delete the entire screen switch(menuCounter) //this checks the value of the counter (0, 1, 2 or 3) { //MAX SPEED case 0: lcd.setFont(c64enh); //set the font (small one) lcd.printStr(ALIGN_CENTER, 0, " "); //erase previous text lcd.printStr(ALIGN_CENTER, 0, "Max speed"); //print new text lcd.setFont(Term9x14); //Set another (larger) font snprintf(buf, 8, "%d", stepperMaxSpeed); //the decimal value of the variable is in the buf lcd.printStr(10, 2, buf); //set the cursor to 2nd line 10th pixel and print the buffer break; //------------------------------- //ACCELERATION case 1: lcd.setFont(c64enh); lcd.printStr(ALIGN_CENTER, 0, " "); lcd.printStr(ALIGN_CENTER, 0, "Acceleration"); lcd.setFont(Term9x14); snprintf(buf, 8, "%d", stepperAcceleration); lcd.printStr(10, 2, buf); break; //------------------------------- //ABSOLUTE STEPPING case 2: lcd.setFont(c64enh); lcd.printStr(ALIGN_CENTER, 0, " "); lcd.printStr(ALIGN_CENTER, 0, "Absolute"); lcd.setFont(Term9x14); snprintf(buf, 8, "%d", stepperAbsoluteSteps); lcd.printStr(10, 2, buf); break; //------------------------------- //RELATIVE STEPS case 3: lcd.setFont(c64enh); lcd.printStr(ALIGN_CENTER, 0, " "); lcd.printStr(ALIGN_CENTER, 0, "Relative"); lcd.setFont(Term9x14); snprintf(buf, 8, "%d", stepperRelativeSteps); lcd.printStr(10, 2, buf); break; //------------------------------- //MOVEMENT WITH ROTARY ENCODER case 4: lcd.setFont(c64enh); lcd.printStr(ALIGN_CENTER, 0, " "); lcd.printStr(ALIGN_CENTER, 0, "Rotary-move"); lcd.setFont(Term9x14); snprintf(buf, 8, "%d", stepperRotarySteps); lcd.printStr(10, 2, buf); break; //------------------------------- //MOVEMENT WITH BUTTONS case 5: lcd.setFont(c64enh); lcd.printStr(ALIGN_CENTER, 0, " "); lcd.printStr(ALIGN_CENTER, 0, "Button-move"); lcd.setFont(Term9x14); snprintf(buf, 8, "%d", stepperRelativeSteps); lcd.printStr(10, 2, buf); break; //------------------------------- //STEPSIZE case 6: lcd.setFont(c64enh); lcd.printStr(ALIGN_CENTER, 0, " "); lcd.printStr(ALIGN_CENTER, 0, "Step size"); lcd.setFont(Term9x14); snprintf(buf, 8, "%d", stepperStepsize); lcd.printStr(10, 2, buf); break; //------------------------------- //RESET case 7: lcd.setFont(c64enh); lcd.printStr(ALIGN_CENTER, 0, " "); lcd.printStr(ALIGN_CENTER, 0, "Set zero"); break; } //"Global" value lcd.setFont(c64enh); snprintf(buf, 8, "%s", "Pos: "); lcd.printStr(0, 5, buf); snprintf(buf, 8, "%d", stepperPosition); lcd.printStr(30, 5, buf); // menuChanged = false; //next loop() iteration will not enter again } void updateValue() { switch(menuCounter) //this checks the value of the counter (0, 1, 2 or 3) { //Max speed menu case 0: stepper.setMaxSpeed(stepperMaxSpeed); lcd.setFont(Term9x14); lcd.printStr(10, 2, " "); snprintf(buf, 8, "%d", stepperMaxSpeed); lcd.printStr(10, 2, buf); break; //------------------------------- //Acceleration menu case 1: stepper.setAcceleration(stepperAcceleration); lcd.setFont(Term9x14); lcd.printStr(10, 2, " "); snprintf(buf, 8, "%d", stepperAcceleration); lcd.printStr(10, 2, buf); break; //------------------------------- //Absolute movement - moveTo() case 2: lcd.setFont(Term9x14); lcd.printStr(10, 2, " "); snprintf(buf, 8, "%d", stepperAbsoluteSteps); lcd.printStr(10, 2, buf); break; //------------------------------- //Relative movement - move() case 3: lcd.setFont(Term9x14); //Command values lcd.printStr(10, 2, " "); //block 8 in line 2 snprintf(buf, 8, "%d", stepperRelativeSteps); lcd.printStr(10, 2, buf); break; //------------------------------- //Rotary encode movement - step by step case 4: lcd.setFont(Term9x14); lcd.printStr(10, 2, " "); snprintf(buf, 8, "%d", stepperPosition); //location lcd.printStr(10, 2, buf); break; //------------------------------- //Button movement - step by step case 5: lcd.setFont(Term9x14); lcd.printStr(10, 2, " "); snprintf(buf, 8, "%d", stepperPosition); //location lcd.printStr(10, 2, buf); break; //------------------------------- //Step size menu - increment-decrement case 6: lcd.setFont(Term9x14); lcd.printStr(10, 2, " "); snprintf(buf, 8, "%d", stepperStepsize); lcd.printStr(10, 2, buf); break; } //"Global" values //this is always shown on the bottom of the display lcd.setFont(c64enh); lcd.printStr(30, 5, " "); snprintf(buf, 8, "%d", stepperPosition); lcd.printStr(30, 5, buf); // valueChanged = false; //next loop() iteration will not enter again } void updatePosition() { lcd.setFont(c64enh); lcd.printStr(30, 5, " "); snprintf(buf, 8, "%d", stepperPosition); lcd.printStr(30, 5, buf); } void updateSelection() { lcd.setFont(Term9x14); //Large font size for the cursor (same as of the values) if(stepperMaxSpeed_Selected == true) { snprintf(buf, 8, "%s", ">"); //print the ">" as the cursor, the ">" indicates the selection/activation lcd.printStr(0, 2, buf); //put it on the display: 2nd line 0 pixel } else if(stepperAcceleration_Selected == true) { snprintf(buf, 8, "%s", ">"); lcd.printStr(0, 2, buf); } else if(stepperAbsoluteSteps_Selected == true) { snprintf(buf, 8, "%s", ">"); lcd.printStr(0, 2, buf); } else if(stepperRelativeSteps_Selected == true) { snprintf(buf, 8, "%s", ">"); lcd.printStr(0, 2, buf); } else if(rotaryStepping_Selected == true) { snprintf(buf, 8, "%s", ">"); lcd.printStr(0, 2, buf); } else if(buttonStepping_Selected == true) { snprintf(buf, 8, "%s", ">"); lcd.printStr(0, 2, buf); } else if(stepperStepsize_Selected == true) { snprintf(buf, 8, "%s", ">"); lcd.printStr(0, 2, buf); } else if(stepperSetZero_Selected == true) { snprintf(buf, 8, "%s", "Reset!"); //we indicate that the position was reset to 0 by printing a message lcd.printStr(0, 2, buf); stepper.setCurrentPosition(0); //set the current position to zero stepperSetZero_Selected = false; //we reset this here } else { snprintf(buf, 8, "%s", " "); lcd.printStr(0, 2, buf); } updateValueSelection = false; //next loop() iteration will not enter } void UpButtonPressed() { if(digitalRead(UpButton) == 1) //1 or 0, depends on your wiring also! { if(millis() - previousInterrupt > 300) //debounce, sort of. If another click comes within 300 ms, we don't care about it { if(stepperMaxSpeed_Selected == true) //runSpeed() { //Runs the motor "indefinitely", until it stopped by the user stepper.move(999999999); //some really large value, for demonstration } else if(stepperRelativeSteps_Selected == true) //move() { stepper.move(stepperRelativeSteps); //start relative stepping } else if(stepperAbsoluteSteps_Selected == true) //moveTo() { stepper.moveTo(stepperAbsoluteSteps); //start absolute stepping } previousInterrupt = millis(); //update the value so the counting from 300 ms is started over from 0 } else if(buttonStepping_Selected == true) { //buttonstepping does not care about the 300 ms time limit //a little more explanation in the DownButtonPressed() function stepperButtonSteps = stepper.currentPosition() + stepperStepsize; //increase the target position by 1 stepsize stepper.moveTo(stepperButtonSteps); } valueChanged = true; //display has to be updated } } void DownButtonPressed() { if(digitalRead(DownButton) == 1) //1 or 0, depends on your wiring also! { if(millis() - previousInterrupt > 300) { if(stepperMaxSpeed_Selected == true) //runSpeed() { stepper.stop(); //stop and note down the position (next line) stepperPosition = stepper.currentPosition(); } else if(stepperRelativeSteps_Selected == true) //move() { stepper.stop(); stepperPosition = stepper.currentPosition(); } else if(stepperAbsoluteSteps_Selected == true) //moveTo() { stepper.stop(); stepperPosition = stepper.currentPosition(); } previousInterrupt = millis(); //update the value so the counting from 300 ms is started over from 0 } else if(buttonStepping_Selected == true) { //Optionally, this part should also be in a timing part, but then the time between two steps is limited. //On the other hand, that can be good if you just want one step per buttonclick //Otherwise, pressing the button continuously will still move the motor slowly, but even 1 click will cause several steps //We always want to move 1 stepsize away from the current position stepperButtonSteps = stepper.currentPosition() - stepperStepsize; //decrease the target position by 1 stepsize stepper.moveTo(stepperButtonSteps); } valueChanged = true; //display has to be updated } } void LimitSwitchPressed() { if (digitalRead(LimitSwitch_1) == 0) //0 or 1, depends on the wiring! { if (millis() - previousInterrupt > 300) { stepper.stop(); //"soft" stop - decelerates to 0. previousInterrupt = millis(); //Alternatively we can make it to go to a specific place: //stepper.moveTo(0); //This goes back to absolute 0, which is technically the homing } } }