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.

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.

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.

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.


Previous
Previous

SZBK07 DC-DC converter with multiturn potentiometers

Next
Next

Why is the Peltier cooler-based air conditioning a BAD idea?