Stepper motor control panel with keypad
In this video I continue the development of the universal stepper motor control panel. The most important feature is that I added a 16-key keypad to the control panel, so now it is much more easier to enter the input parameters. Because of the new part, I redesigned the front panel and I also designed a 3D-printable enclosure. If you have any suggestions for further features, please let me know in the comment section.
Schematics
The schematics is similar to the previous version of the control panel but this version has a 16-key keypad added to it. The 16-bit keypad is connected to the microcontroller via a PCF8574 I/O expander. The expander uses i2C (SDA: PB7 and SCL: PB6). The expander’s pins P0 to P7 are connected to the keypad’s pins in the same order (1-8). The last free pin (INT) on the I/O expander is not connected. The UP and DOWN button are removed and now B is the UP button and C is the DOWN button. The * and # characters have no function in this project. The Nokia 5110 LCD is connected to the MCU via SPI and it is very important that the display is only compatible with 3.3 V. There are two limit switches which are expected to be connected to the MCU, otherwise the code will get stuck (on purpose). The rotary encoder is just a simple encoder with a button. The encoder is used to navigate in the menu, move the motor or change the value of the different parameters.
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 //this is optional! //Nokia pins: //RST: PA0 //CS: PA4 //DC: PA1 //BL: PA2 (optional) //DIN: PA7 //CLK: PA5 #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 //i2c / Wire #include <Wire.h> //Defining pins const int RotaryCLK = PB8; //CLK pin on the rotary encoder const int RotaryDT = PB9; //DT pin on the rotary encoder const int RotarySW = PB0; //SW pin on the rotary encoder (Button function) const int LimitSwitch_1 = PB11; //Input for the limit switch const int LimitSwitch_2 = PB10; //Input for the limit switch const int enablePin = PB1; //enable pin for the stepper drivers - not yet implemented! //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 = 0; //Rotary encoder timing float RotaryTime2 = 0; unsigned long previousInterrupt; //Button timing float LCDRefreshTimer = 0; //Menu-related variables //Values int RotaryButtonValue; //Pressed or not pressed rotary switch volatile int menuCounter = 0; //this is for counting the main menu (menu item number) volatile int stepperMaxSpeed = 400; //for setMaxSpeed volatile int stepperSpeed = 400; //for setSpeed volatile int stepperAcceleration = 800; //for setAcceleration volatile int stepperAbsoluteSteps; //moveTo() volatile int stepperRelativeSteps; //move() volatile int stepperRotarySteps; // This keeps track of the steps done with the rotary encoder volatile int stepperButtonSteps; // This keeps track of the steps done with the buttons volatile int stepperGoToLocation; //Go to steps volatile int stepperPingPongSteps; //amount of steps for Ping-pong demo volatile 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 int pingpongInitialPosition = 0; //initial position for the step-based ping-pong int pingpongFinalPosition = 0; //final position for the step-based ping-pong int limitSwitchTempPosition = 0; //temporary position for remembering the position when the limit switch was triggered //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 stepperGoTo_Selected = false; //Go To function bool pingpongStep_Selected = false; //ping-pong demo function - based on steps bool pingpongStep_Enabled = false; //step-based ping pong demo enable/disable swtich bool pingpongLimit_Selected = false; //ping pong demo function - based on limit switch bool pingpongLimit_Enabled = false; //limitswitch based pingpongdemo bool stepperhoming_Selected = false; //Homing with limit switch 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 bool pingpong_CW = false; //keeps track of the direction inside the limit switch-based pingpong bool pingpong_CCW = false; //keeps track of the direction inside the limit switch-based pingpong char buf[25]; //buffer for printing on the LCD //Keypad variables float buttonTimer = 0; //Timer for avoiding multiple keypresses at a time bool buttonPressed = false; //status which keeps track if a key was pressed char fullString[16] = ""; //concatenated string char pressedCharacter[8] = ""; //recently pressed character on the keypad bool stringAdded = false; //keeps track of the concatenation int convertedNumber = 0; //final number. fullString converted into integer void setup() { //Serial.begin(9600); //i2C - for the I/O module Wire.begin(); Wire.setClock(400000L); //Definition of the pins, remember where you need pinMode(PB9, INPUT); //CLK pinMode(PB8, INPUT); //DT pinMode(PB0, INPUT_PULLUP); //SW pinMode(PB11, INPUT); //LimitSwitch_1 (default is 1 for me) pinMode(PB10, INPUT); //LimitSwitch_2 (default is 1 for me) pinMode(enablePin, OUTPUT); digitalWrite(enablePin, LOW); //Store states CLKPrevious = digitalRead(RotaryCLK); DTPrevious = digitalRead(RotaryDT); //Rotary encoder interrupts attachInterrupt(digitalPinToInterrupt(RotaryDT), 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.setContrast(58); //Adjusting the contrast of the LCD lcd.clrScr(); //clear the whole display printLCD(); //print the welcome message delay(3000); // show the welcome message for 3 seconds updateMenuPosition(); updateValue(); } void loop() { if (menuChanged == true) { updateMenuPosition(); } if (valueChanged == true ) { updateValue(); } if (updateValueSelection == true) { updateSelection(); } if (pingpongStep_Enabled == true) { PingPongStep(); } if (pingpongLimit_Enabled == true) { PingPongLimit(); } stepper.run(); //If a step is due, this function will do a step in each loop() iteration //Looping buttons CheckRotaryButton(); readKeyPad(); //read the keypad checkInput(); //do something if a key was pressed - UP and DOWN functions are also handled within this function LimitSwitchPressed(); //If you do not have the limit switches connected to the MCU, it will cause it hang!!! /* //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 } */ //I removed this on 2021-05-09 because the currentPosition() is very slow! I just use a timer if (millis() - LCDRefreshTimer > 500) { stepperPosition = stepper.currentPosition(); updatePosition(); //Pos. on the bottom of the LCD //updateValue(); //Pos or value in the middle of the screen LCDRefreshTimer = millis(); } } 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; } //------------------------------------------------------------------------------- //GOTO else if (stepperGoTo_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) { if (stepperGoToLocation < 4) //5 menu items (0, 1, 2....4) { stepperGoToLocation++; } else { stepperGoToLocation = 0; //0 comes after 4, so we move in a "ring" } } else { // Encoder is rotating CW so decrease if (stepperGoToLocation > 0) { stepperGoToLocation--; } else { stepperGoToLocation = 4; //4 comes after 0 when we decrease the numbers } } valueChanged = true; } CLKPrevious = CLKNow; // Store last CLK state } //------------------------------------------------------------------------------- //Ping-pong based on user input else if (pingpongStep_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) { stepperPingPongSteps = stepperPingPongSteps + stepperStepsize; //1 step increment of the step size } else { stepperPingPongSteps = stepperPingPongSteps - stepperStepsize; } valueChanged = true; } CLKPrevious = CLKNow; // Store last CLK state } //------------------------------------------------------------------------------- else if (pingpongLimit_Selected == true) { //do nothing } //------------------------------------------------------------------------------- //Homing else if (stepperhoming_Selected == true) { //do nothing } //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 < 11) //12 menu items (0, 1, 2....11) { menuCounter++; } else { menuCounter = 0; //0 comes after 11, so we move in a "ring" } menuChanged = true; } else { // Encoder is rotating CW so decrease if (menuCounter > 0) { menuCounter--; } else { menuCounter = 11; //9 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, "Please"); lcd.printStr(ALIGN_CENTER, 4, "Wait"); lcd.printStr(ALIGN_CENTER, 5, "3 seconds..."); } 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: stepperhoming_Selected = !stepperhoming_Selected; updateValueSelection = true; break; // case 8: stepperGoTo_Selected = !stepperGoTo_Selected; updateValueSelection = true; break; // case 9: pingpongStep_Selected = !pingpongStep_Selected; updateValueSelection = true; break; // case 10: pingpongLimit_Selected = !pingpongLimit_Selected; updateValueSelection = true; break; // case 11: 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;//------------------------------- //HOMING case 7: lcd.setFont(c64enh); lcd.printStr(ALIGN_CENTER, 0, " "); lcd.printStr(ALIGN_CENTER, 0, "Homing"); lcd.setFont(Term9x14); snprintf(buf, 8, "%d", stepper.currentPosition()); lcd.printStr(10, 2, buf); break; //------------------------------- //GOTO case 8: lcd.setFont(c64enh); lcd.printStr(ALIGN_CENTER, 0, " "); lcd.printStr(ALIGN_CENTER, 0, "Go To..."); lcd.setFont(Term9x14); snprintf(buf, 8, "%d", stepperGoToLocation); lcd.printStr(10, 2, buf); break; //------------------------------- //PINGPONG - STEP case 9: lcd.setFont(c64enh); lcd.printStr(ALIGN_CENTER, 0, " "); lcd.printStr(ALIGN_CENTER, 0, "Ping-Pong-s"); lcd.setFont(Term9x14); snprintf(buf, 8, "%d", stepperPingPongSteps); lcd.printStr(10, 2, buf); break; //------------------------------- //PINGPONG - LIMIT case 10: lcd.setFont(c64enh); lcd.printStr(ALIGN_CENTER, 0, " "); lcd.printStr(ALIGN_CENTER, 0, "Ping-Pong-l"); //lcd.setFont(Term9x14); //snprintf(buf, 8, "%d", stepperGoToLocation); //lcd.printStr(10, 2, buf); break; //------------------------------- //RESET case 11: 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; //------------------------------- //GoTo menu - increment-decrement location ID case 8: lcd.setFont(Term9x14); lcd.printStr(10, 2, " "); snprintf(buf, 8, "%d", stepperGoToLocation); lcd.printStr(10, 2, buf); break; //------------------------------- //Ping-Pong - Step-based case 9: lcd.setFont(Term9x14); lcd.printStr(10, 2, " "); snprintf(buf, 8, "%d", stepperPingPongSteps); lcd.printStr(10, 2, buf); break; //------------------------------- //Ping-Pong - LimitSwitch-based case 10: //empty 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 (stepperhoming_Selected == true) { snprintf(buf, 8, "%s", "Homing"); lcd.printStr(0, 2, buf); } else if (stepperGoTo_Selected == true) { snprintf(buf, 8, "%s", ">"); lcd.printStr(0, 2, buf); } else if (pingpongStep_Selected == true) { snprintf(buf, 8, "%s", ">"); lcd.printStr(0, 2, buf); } else if (pingpongLimit_Selected == true) { snprintf(buf, 8, "%s", "Start!"); 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 LimitSwitchPressed() { if (pingpongLimit_Enabled == false) { //--- Limit switch 1 if (digitalRead(LimitSwitch_1) == 0) //0 or 1, depends on the wiring! { if (millis() - previousInterrupt > 300) { limitSwitchTempPosition = stepper.currentPosition(); //we save the position where the limit switch was hit stepper.stop(); //"soft" stop - decelerates to 0. stepper.runToPosition(); 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 while (digitalRead(LimitSwitch_1) == 0) //0 or 1, depends on the wiring! { stepper.setSpeed(-200); stepper.runSpeed(); } stepper.setCurrentPosition(limitSwitchTempPosition); //we set the position where the limit switch was hit } } //--- Limit switch 2 if (digitalRead(LimitSwitch_2) == 0) //0 or 1, depends on the wiring! { if (millis() - previousInterrupt > 300) { limitSwitchTempPosition = stepper.currentPosition(); //we save the position where the limit switch was hit stepper.stop(); //"soft" stop - decelerates to 0. stepper.runToPosition(); 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 while (digitalRead(LimitSwitch_2) == 0) //0 or 1, depends on the wiring! { stepper.setSpeed(200); stepper.runSpeed(); } stepper.setCurrentPosition(limitSwitchTempPosition); //we set the position where the limit switch was hit } } } else { //do nothing } } void readKeyPad() { if (buttonPressed == true) { if (millis() - buttonTimer > 300) { //The button pressed is only set back to false after 300 ms, so we cannot press a button twice quickly buttonTimer = millis(); buttonPressed = false; } else { //do nothing } } else { //B11101111 Wire.beginTransmission(0x20); //00100000 Wire.write(B11101111); //[P7]B11101111[P0] -> [P7]1110[P4] - activates first row, [P3]1111[P0] - Sets all pins high on the MUX Wire.endTransmission(); Wire.requestFrom(0x20, 1); switch (Wire.read()) { //11101110 - P0 pin went low after pressing the button -> 1 was pressed case 238: //Button 1 //Serial.print("1"); pressedCharacter[0] = '1'; buttonPressed = true; stringAdded = true; break; //11101101 - P1 pin went low after pressing the button -> 2 was pressed case 237: //Button 2 //Serial.print("2"); pressedCharacter[0] = '2'; buttonPressed = true; stringAdded = true; break; //11101011 - P2 pin went low after pressing the button -> 3 was pressed case 235: //Button 3 //Serial.print("3"); pressedCharacter[0] = '3'; buttonPressed = true; stringAdded = true; break; //11100111 - P3 pin went low after pressing the button -> A was pressed case 231: //Button A //Serial.println("A"); pressedCharacter[0] = 'A'; buttonPressed = true; break; } //------------------------------------------- //B11011111 Wire.beginTransmission(0x20); //00100000 Wire.write(B11011111); //[P7]B11011111[P0] -> [P7]1101[P4] - activates second row, [P3]1111[P0] - Sets all pins high on the MUX Wire.endTransmission(); Wire.requestFrom(0x20, 1); switch (Wire.read()) { //11011110 - P0 pin went low after pressing the button -> 2 was pressed case 222: //Button 4 //Serial.print("4"); pressedCharacter[0] = '4'; buttonPressed = true; stringAdded = true; break; case 221: //Button 5 //Serial.print("5"); pressedCharacter[0] = '5'; buttonPressed = true; stringAdded = true; break; case 219: //Button 6 //Serial.print("6"); pressedCharacter[0] = '6'; buttonPressed = true; stringAdded = true; break; case 215: //Button B //Serial.println("B"); pressedCharacter[0] = 'B'; buttonPressed = true; break; } //------------------------------------------- //B10111111 Wire.beginTransmission(0x20); //00100000 Wire.write(B10111111); Wire.endTransmission(); Wire.requestFrom(0x20, 1); switch (Wire.read()) { case 190: //Button 7 //Serial.print("7"); pressedCharacter[0] = '7'; buttonPressed = true; stringAdded = true; break; case 189: //Button 8 //Serial.print("8"); pressedCharacter[0] = '8'; buttonPressed = true; stringAdded = true; break; case 187: //Button 9 //Serial.print("9"); pressedCharacter[0] = '9'; buttonPressed = true; stringAdded = true; break; case 183: //Button C //Serial.println("C"); pressedCharacter[0] = 'C'; buttonPressed = true; break; } //------------------------------------------- //B01111111 Wire.beginTransmission(0x20); //00100000 Wire.write(B01111111); Wire.endTransmission(); Wire.requestFrom(0x20, 1); switch (Wire.read()) { case 126: //Button * //Serial.print("."); pressedCharacter[0] = '.'; buttonPressed = true; break; case 125: //Button 0 //Serial.print("0"); pressedCharacter[0] = '0'; buttonPressed = true; stringAdded = true; break; case 123: //Button # - this is used as the minus sign symbol for negative steps..etc //Serial.print("#"); pressedCharacter[0] = '-'; buttonPressed = true; stringAdded = true; break; case 119: //Button D //Serial.println("D"); pressedCharacter[0] = 'D'; buttonPressed = true; break; } buttonTimer = millis(); } } void checkInput() { //Note: Since we use char arrays, the code is sensitive to the "" and '' differences. We have to use single quote marks if (buttonPressed == true) //if a button was pressed... { if (stringAdded == true) //if a numerical button was pressed... { strcat(fullString, pressedCharacter); //concatenate //strcat(target array, array to be added to the target) //Print the string on the LCD lcd.setFont(Term9x14); lcd.printStr(10, 2, " "); //erase entire the previous content snprintf(buf, 8, "%s", fullString); lcd.printStr(10, 2, buf); //print the new content stringAdded = false; //don't let the code enter this part again } if (pressedCharacter[0] == 'A') //if we pressed the A on the keypad... { //When A is pressed, we want to pass the entered number to the actual menu's value (if it is a valid menu item) convertedNumber = atoi(fullString); //string to integer. Everything is an integer here (no half steps...etc) //Do something based on the menu position switch (menuCounter) { case 0: //maximum speed stepperMaxSpeed = convertedNumber; break; case 1: //acceleration stepperAcceleration = convertedNumber; break; case 2: //absolute steps stepperAbsoluteSteps = convertedNumber; break; case 3: //relative steps stepperRelativeSteps = convertedNumber; break; case 4: //rotary-based steps, we do not operate the buttons break; case 5: //do nothing break; case 6: //Step size stepperStepsize = convertedNumber; break; case 7: //Homing //do nothing break; case 8: //Go To //do nothing break; case 9: //Ping-pong steps stepperPingPongSteps = convertedNumber; break; case 10: //Ping-pong limit switch //Do nothing break; case 11: //Set new zero point //do nothing break; } //Print the string on the LCD lcd.setFont(Term9x14); lcd.printStr(10, 2, " "); //erase the entire previous content snprintf(buf, 8, "%s", fullString); lcd.printStr(10, 2, buf); //print the new content memset(fullString, 0, sizeof fullString); //zero the array convertedNumber = 0; //reset the number to 0 pressedCharacter[0] = ' '; //reset pressed character (this part will be skipped in further iterations until A is pressed again) } else if (pressedCharacter[0] == 'D') //D = delete { if (strlen(fullString)) //if the size is not zero { fullString[strlen(fullString) - 1] = '\0'; //print an "empty character" over the n-1 part of the non-zero item } lcd.setFont(Term9x14); lcd.printStr(10, 2, " "); //erase the previous content snprintf(buf, 8, "%s", fullString); lcd.printStr(10, 2, buf); //print the new, shortened content pressedCharacter[0] = ' '; } //-------------------------------------------------------------------------------------------------------- else if (pressedCharacter[0] == 'B') //up/start button { 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 } else if (stepperhoming_Selected == true) { //homing part - negative direction while (digitalRead(LimitSwitch_1) == 1) //0 or 1, depends on the wiring! { stepper.setSpeed(-400); stepper.runSpeed(); } //parking part - positive direction while (digitalRead(LimitSwitch_1) == 0) //0 or 1, depends on the wiring! { stepper.setSpeed(200); stepper.runSpeed(); } stepper.setCurrentPosition(0); //reset the position to 0 //Message to the user about the completion of the homing process lcd.setFont(Term9x14); snprintf(buf, 8, "%s", "Parked!"); lcd.printStr(0, 2, buf); //Note: Homing is only implemented for one limit switch because it does not make sense to have 2 home positions } else if (stepperGoTo_Selected == true) { //List of programatically predefined positions //The positions are always measured from the limit switch-defined home position switch (stepperGoToLocation) { case 0: stepper.moveTo(4300); break; case 1: stepper.moveTo(7500); break; case 2: stepper.moveTo(15400); break; case 3: stepper.moveTo(22300); break; case 4: stepper.moveTo(25400); break; default: // break; } //Note: with the ball-screw+linear rail, 1 turn results in 4 mm linear displacement //1 turn = 400 steps (with my MS setting), so 1 mm = 100 steps. } else if (pingpongStep_Selected == true) { pingpongStep_Enabled = true; //Enable stepping based ping-pong demo pingpongInitialPosition = stepper.currentPosition(); pingpongFinalPosition = pingpongInitialPosition + stepperPingPongSteps; } else if (pingpongLimit_Selected == true) { stepper.move(999999999); //some really large value, for demonstration pingpongLimit_Enabled = true; } 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); } } //-------------------------------------------------------------------------------------------------------- else if (pressedCharacter[0] == 'C') //down/stop button { 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(); } else if (stepperhoming_Selected == true) //Homing (interrupted) { stepper.stop(); stepperPosition = stepper.currentPosition(); } else if (pingpongStep_Selected == true) { pingpongStep_Enabled = false; stepper.stop(); stepperPosition = stepper.currentPosition(); } else if (pingpongLimit_Selected == true) { pingpongLimit_Enabled = false; stepper.stop(); stepperPosition = stepper.currentPosition(); } else if (stepperGoTo_Selected == true) { stepper.stop(); stepperPosition = stepper.currentPosition(); } 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); } } } } void PingPongStep() { if (pingpong_CW == false) //CW rotation is not yet done { stepper.moveTo(pingpongFinalPosition); //set a target position, it should be an absolute. relative (move()) leads to "infinite loop" if (stepper.distanceToGo() == 0) //When the above number of steps are completed, we manipulate the variables { pingpong_CW = true; //CW rotation is now done pingpong_CCW = false; //CCW rotation is not yet done - this allows the code to enter the next ifs } } if (pingpong_CW == true && pingpong_CCW == false) //CW is completed and CCW is not yet done { stepper.moveTo(pingpongInitialPosition); //Absolute position if (stepper.distanceToGo() == 0) //When the number of steps are completed { pingpong_CCW = true; //CCW is now done pingpong_CW = false; //CW is not yet done. This allows the code to enter the first if again! } } } void PingPongLimit() { if (digitalRead(LimitSwitch_1) == 0) { stepper.move(-99999999999); } if (digitalRead(LimitSwitch_2) == 0) { stepper.move(99999999999); } }
Enclosure and front panel 3D files
Enclosure. The hole in the bottom is for the 2x3 pin and 4 pin screw terminals. The groove on the top of the left side if the enclosure is for the USB cable. The diameter of the 4 holes is 5 mm.
Front panel. It accommodates the rotary encoder, the Nokia 5110 LCD and the 16-key keypad. The large screw holes at the corners are 5 mm, the holes for the Nokia LCD are 3 mm and the holes for the keypad are 2.5 mm.
Disclaimer: Dimensional inaccuracies might occur which might cause slight misalignments or imprecise fit. I do not take any responsibilities for these issues.