[follow up] Control speed and timing for dc motor using keypad input

Hi,

I am a newcomer trying to make / set a motor controller for a project that should move a tank in cycles. The hardware elements for the basic testing are:

  • Arduino UNO
  • IBT-2 controller
  • LCD 2IC
  • keypad
  • external 5V supply for the motor

The configurations is the standard I have found in this forum and elsewhere (e.g., dronebotworkshop. For the test, I am using an small motor but later a large one is to be used.

I was grateful to find a nice code written by @sterretjeToo (do not know how to link). I modified the code to include, as suggested a stop and restart states. The code compiles and runs but has a bug. To start the motor I should press "A". This does not happens at the beginning. The motor immediately starts after the time is inserted. In the second reintroduction of parameters, it works as it should.

Original source of the controller I found in this post Original post:

I have no idea what causes this error. I need to say that I am also not an experienced C programmer. I write other kinds of algorithms mostly in Fortran and other languages.

Perhaps, someone could provide me a hint(s) to solve this issue. The potentiometer appearing in the picture is not connected. The hookup of the components is given in the code below (I deleted the compiler directives of the original code to be able to compile in my system (Arduino IDE 2.2.1 in macOs Sonoma 14.1.2 ).

I would like to say that I am really impressed about the sources and valuable information I found in this Forum. Thanks for sharing! I started with my new Arduino a week ago and with the help found in discussions of this Forum, I have been able to progress with my project. I have learned a lot. It is really amazing what you do! Kudos to all.

// source:  https://forum.arduino.cc/t/control-speed-and-timing-for-dc-motor-using-keypad-input-and-shown-on-lcd-disply/385810/11
//          sterretjeToo, Karma: 2000+

const byte ST_DISPLAY_MAINMENU=0;
const byte ST_WAIT=1;
const byte ST_SETSPEED=2;
const byte ST_SETTIMING=3;
const byte ST_STARTMOTOR=4; 
const byte ST_IDLE=5; 

// options to control the motor
int MOTOR_CONTINUE=0;
int MOTOR_START=1;
int MOTOR_FORCESTOP=2;

//int motorPin = 3;

// Motor (both PWM)
const int RPWM = 10;
const int LPWM = 11;

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Keypad.h>

// LCD
const byte ROWS = 4;  //  Constants for row and column sizes
const byte COLS = 4;
const int I2C_addr = 0x27;  //  Define I2C Address - found with scanner


// Keypad
char hexaKeys[ROWS][COLS] = {  // Array to represent keys on keypad
  { '1', '2', '3', 'A' },
  { '4', '5', '6', 'B' },
  { '7', '8', '9', 'C' },
  { '*', '0', '#', 'D' }
};


// byte rowPins[ROWS] = {10, 9, 8, 7}; //connect to the row pinouts of the keypad
// byte colPins[COLS] = {13, 12, 11}; //connect to the column pinouts of the keypad


// initialize the library with the numbers of the interface pins
// LCD
const int en = 2, rw = 1, rs = 0, d4 = 4, d5 = 5, d6 = 6, d7 = 7, bl = 3;

// Keypad
byte rowPins[ROWS] = { 9, 8, 7, 6 };
byte colPins[COLS] = { 5, 4, 3, 2 };


Keypad keypad = Keypad( makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS );
//char NO_KEY=0;


//Keypad kdp = Keypad(makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);
LiquidCrystal_I2C lcd(I2C_addr, en, rw, rs, d4, d5, d6, d7, bl, POSITIVE);

// the state of our application; start with main menu display
byte currentState = ST_DISPLAY_MAINMENU;

// speed and timing variables
// both are global so we can display them in the main menu
int theSpeed = 0;
long theTiming = 0;

// posts # 21 & 22

void setup() {
  Serial.begin(9600);

  //pinMode(motorPin, OUTPUT);
  pinMode(RPWM, OUTPUT);    // set motor connections as outputs
  pinMode(LPWM, OUTPUT);

  // set up the LCD's number of columns and rows:
  lcd.begin(20, 4);
  Serial.println("20x4 display");

}

void loop()
{
  // single key from keypad
  char key;
  // text from keypad
  char *text;

  // let the motor do what it was doing
  runMotor(MOTOR_CONTINUE);

  //Serial.print("Speed: "); Serial.println(theSpeed);

  
  switch (currentState)
  {
    case ST_DISPLAY_MAINMENU:
      // display main menu
      displayMenu();
      // switch to wait state
      currentState = ST_WAIT;
      break;
    case ST_WAIT:
      // get key
      key = getKeyWithEcho();
      // if speed setting selected
      if (key == '1')
      {
        // display speed 'menu'
        displaySpeedMenu();
        // change to state where user can enter speed
        currentState = ST_SETSPEED;
      }
      // if timing setting selected
      if (key == '2')
      {
        // display 'timing' menu
        displayTimingMenu();
        // change to state where user can enter timing
        currentState = ST_SETTIMING;
      }
      // if START key is selected
      if (key == 'A')
      {
         // display main menu
        displayMenu();
        // change to state where user can start the motor
        currentState = ST_STARTMOTOR;
      }
      // if END key is selected
      if (key == 'D')
      {
         // display main menu
        displayMenu();
        // change to state where user can reset
        currentState = ST_IDLE;
      }

      // Note: state does not change if entry is not '1', '2', 'A'
      break;
    case ST_SETSPEED:
      // get the text entered on the keypad
      text = getKeypadText();
      // if text complete
      if (text != NULL)
      {
        // if user did enter a speed
        if (text[0] != '\0')
        {
          theSpeed = atoi(text);
        }
        currentState = ST_DISPLAY_MAINMENU;
      }
      break;
    case ST_SETTIMING:
      // get the text entered on the keypad
      text = getKeypadText();
      // if text complete
      if (text != NULL)
      {
        // if user did enter a speed
        if (text[0] != '\0')
        {
          theTiming = atoi(text);
          // NEW (LS) originaly here was the start of the motor (moved to ST_STARTMOTOR)
        }
        currentState = ST_DISPLAY_MAINMENU;
      }
      break;
      // new (LS)
    case ST_STARTMOTOR:
      // start motor 
      runMotor(MOTOR_START);
      currentState = ST_DISPLAY_MAINMENU;
      break;
    case ST_IDLE:
      // stop motor 
      runMotor(MOTOR_FORCESTOP);
      currentState = ST_DISPLAY_MAINMENU;
      break;
  } // end_of_switch

}


/*
  display a title on the first line of the display; it always clears the LCD
*/
void displayTitle()
{
  // clear the lcd
  lcd.clear();

  // print the project title on the first line
  lcd.setCursor(0, 0);
  lcd.print("Speed controller");
}

/*
  display main menu
*/
void displayMenu()
{
  // current line where to write on LCD; every time that we write, currentLine will be incremented
  byte currentLine = 0;
  // text buffer for 20 characters and string terminator
  char textbuffer[21];

  // display the title on the first line and update currentLine
  displayTitle();
  currentLine = 1;

  // print the current settings on the second line
  lcd.setCursor(0, currentLine++);
  sprintf(textbuffer, "S = %d, T = %d", theSpeed, theTiming);
  lcd.print(textbuffer);

  // print the first menu option on the third line
  lcd.setCursor(0, currentLine++);
  lcd.print("1 Set speed");

  // print the second menu option on the fourth line
  lcd.setCursor(0, currentLine++);
  lcd.print("2 Set time");
}

/*
  display a 'menu' where the user can enter a speed
*/
void displaySpeedMenu()
{
  // display the title on the 1st line
  displayTitle();
  // display additional info on 3rd line
  lcd.setCursor(0, 2);
  lcd.print("# Finish, * Cancel");
  lcd.setCursor(0, 3);
  lcd.print("A Start, D STOPl");  
  // prompt user to set speed (2nd line)
  lcd.setCursor(0, 1);
  lcd.print("Set speed: ");
}

/*
  display a 'menu' where the user can enter a timing
*/
void displayTimingMenu()
{
  // display the title on the 1st line
  displayTitle();
  // display additional info on 3rd line
  lcd.setCursor(0, 2);
  lcd.print("#Finish, *Cancel");
  // prompt user to set speed (2nd line)
  lcd.setCursor(0, 1);
  lcd.print("Set time: ");
}

/*
  get a keystroke from the keypad
  echo the key that was pressed to the LCD
  the echo will be on the current position of the LCD
  returns
    the key that was pressed or NO_KEY
*/
char getKeyWithEcho()
{
  // read a key
  char key = keypad.getKey();

  // if no key pressed
  if (key != NO_KEY)
  {
    // for debugging, output to serial monitor
    Serial.print("Key: "); Serial.println(key);
    // display on current position of LCD
    lcd.print(key);
  }
  return key;
}

/*
  get a text from the keypad (max 20 characters)
  '#' finishes the entry of data
  '*' cancels the entry of data
  returns
    NULL if not complete
    empty text if canceled
    entered tet (might be empty)
*/
char *getKeypadText()
{
  // a buffer for 20 keypresses and one
  static char keypadbuffer[21];
  // index in above buffer
  static char index = 0;

  // read a key
  char key = getKeyWithEcho();
  // if nothing pressed
  if (key == NO_KEY)
  {
    // indicate no complete data
    return NULL;
  }
  // if 'cancel' key
  if (key == '*')
  {
    // reset index
    index = 0;
    // create empty text
    keypadbuffer[index] = '\0';
    // return text
    return keypadbuffer;
  }
  // if 'enter' key
  if (key == '#')
  {
    // add a string terminator
    keypadbuffer[index] = '\0';
    // reset index for next time
    index = 0;
    // return the text
    Serial.println(keypadbuffer);
    return keypadbuffer;
  }
  // check for buffer overflow
  if (index >= sizeof(keypadbuffer))
  {
    // add a string terminator
    keypadbuffer[sizeof(keypadbuffer) - 1] = '\0';
    // reset index for next time
    index = 0;
    // return the text
    return keypadbuffer;
  }

  // add the character to the buffer
  keypadbuffer[index++] = key;

  // indicate that text is not complete
  return NULL;
}


/*
  starts or stops the motor or runs the motor for a given time
  parameter
    option: option to control motor
    MOTOR_FORCESTOP: force an (emergency) stop; oberrides any other options
    MOTOR_START: if not started, start it; if already started, it's ignored
    for any other option value, motor keeps on doing what it was doing
*/
void runMotor(byte options)
{
  // the 'time' that the motor started running
  static unsigned long motorRunStarttime = 0;

  // if FORCESTOP received
  if ((options & MOTOR_FORCESTOP) == MOTOR_FORCESTOP)
  {
    // for debugging
    Serial.println("FORCESTOP received");
    // stop the motor
    //analogWrite(motorPin, 0);
    analogWrite(RPWM, 0);  // Stop = 0
    analogWrite(LPWM, 0);
    // reset start 'time'
    motorRunStarttime = 0;
    // nothing more to do
    return;
  }

  // if START received and motor not started yet
  if ((options & MOTOR_START) == MOTOR_START && motorRunStarttime == 0)
  {
    // for debugging
    Serial.println("START received while motor was not running");
    Serial.print("Speed: "); Serial.println(theSpeed);
    Serial.print("Timing: "); Serial.println(theTiming);

    // set the start 'time'
    motorRunStarttime = millis();
    // display the start time
    Serial.print("Time: "); Serial.println(motorRunStarttime);
  }

  // if the motor has run for the specified duration
  if (millis() - motorRunStarttime >= (theTiming * 1000UL))
  {
    // inform the user that motor has stopped
    if (motorRunStarttime != 0)
    {
      Serial.println("Motor stopped");
      Serial.print("Time: "); Serial.println(millis());
    }
    // stop the motor;
    //analogWrite(motorPin, 0);
     analogWrite(RPWM, 0);  // Stop = 0
     analogWrite(LPWM, 0);
    // reset the start 'time'
    motorRunStarttime = 0;
  }
  else
  {
    // set the motor speed
    if (theSpeed >= 0 && theSpeed <= 255)
    {
      //analogWrite(motorPin, theSpeed);
       analogWrite(LPWM, theSpeed);
    }
  }
}

type or paste code here

First off, confirm what voltage these are attached to ?

Welcome and congratulations you apparently took the time to read the forum guidelines and posted your code appropriately! I would recommend you learn how to do schematics, lots of users use frizing and as the project progresses it will no longer work and most of the information needed for troubleshooting is missing. Also if you do not have a module that the freezer shows we are out of luck answering questions.

Thanks for the comments.

The voltage of the external source is 5V. It is independent from the Arduino.
I tried to install Fritzing with available packages in Homebrew and failed. So my 1st circuit graph is not the best that you may expect but is similar to the circuit I have built.

Below the prototype of my circuit.

This module is simply a power source. Represented as a battery in my circuit. Sorry for any inconvenience. I have no software to represent circuits. Fritzing is open source but quite challenge to install by source code. It is also not available in homebrew or conda. I found Tinkering (Autodesk) but does not have the IBT-2 module. So my circuit became a collage.

1 Like

Actually one of the best CAD (Computer Aided Design) software packages is free, it is called KiCad and runs on most platforms. I use it from schematic capture through PCB (Printed Circuit Board) design including the Gerber files needed to manufacture the board. It is easy to install, you do not have to compile it or anything like that. This is a full blown CAD program, not a toy and it will take some time to get it to work for you. I suspect in a few hours you will start to get some schematics out of it. By the way users of this forum like the KiCad output, it is clean and easy to follow. If you substitute like you did with the battery make a note of it on the picture label it 2A 12V or something like that.

1 Like

If the 5V comes comes from a voltage regulator on that PCB, it is unlikely that supply will be able to supply enough current for the motor driver/motor_fan.

In fact, the circuit works. the motor accelerates and decelerates. The code works but at some levels of the time parameter, over 1000 s, the motor starts automatically. It should not happen unless I press the Key A. This is the issue that I have.

looks like running the motor at theSpeed is the default action. shouldn't this be done under the MOTOR_START case?

1 Like

Exactly! This piece of code was the relique of the previous post when the ST_MOTOR_START was not implemented (see previous post). I overlook at this last piece. Thanks a lot for taking your time.

for the sake of completeness. This is the code that stops this error.

if ((options & MOTOR_START) == MOTOR_START && motorRunStartTime == 0)
  {
    // for debugging
    Serial.println("START received while motor was not running");
    Serial.print("Speed: "); Serial.println(theSpeed);
    Serial.print("Timing: "); Serial.println(theTiming);

    // set the start 'time'
    motorRunStartTime = millis();
    // display the start time
    Serial.print("Time: "); Serial.println(motorRunStartTime);
    
    // start the motor at current speed
    if (theSpeed >= 0 && theSpeed <= 255)
    {
      //analogWrite(motorPin, theSpeed);
       analogWrite(LPWM, theSpeed);
    }
  }

and comment out this part:

//else
  //{
    // set the motor speed
    // if (theSpeed >= 0 && theSpeed <= 255)
    // {
    //   //analogWrite(motorPin, theSpeed);
    //    analogWrite(LPWM, theSpeed);
    // }
  //}

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