Introduction: Force Feedback With LCD Screen

Picture of Force Feedback With LCD Screen

In today's Instructable, we'll be going over how to monitor the current of an actuator, and adjust the values using an LCD screen. This Instructable is a continuation to the Monitoring the Load Feedback of an Actuator Instructable. We'll be adding in the LCD screen so you can see how much current the actuator is pulling, as well as set the current limits.

First, we'll go over the LCD buttons and how they integrate with the Arduino, and then go on to see how we can write and set values to the LCD screen so we can see what the program is doing.

For this project, you will need:

- 1x MegaMoto

- 1x Arduino Uno

- 1x Linear Actuator

- 1x LCD Screen

- 1x 12V Power Supply

Let's get started!

Step 1: LCD Screen Buttons

Picture of LCD Screen Buttons

The LCD screen has six buttons on it, five that are available for use. We'll be using four of them for our project. The buttons are Up, Down, Left, Right, Select, and Reset. The Reset button resets the entire Arduino. We'll use the four directional buttons. Up and Down will set the current limit, Left and Right will move the actuator.

The LCD screen puts all five buttons through a Digital to Analog Converter so that they only take up one pin. The flip side to this is that you need to calibrate your buttons.

The easiest way to do so is to use the Arduino AnalogReadSerial example, and see what value you get for each button press. Most LCD screens use pin A0 for the buttons. Be sure to change the delay value in AnalogReadSerial so that you can actually see what values appear in the serial monitor. There are six readings that you need to get, the five readings of the buttons, plus the reading that you get when no buttons are pressed.

Once you have the readings of the buttons, you can use the code here to ensure that they are all working. One more value that is used is the threshold. The threshold is to ensure that when you press the button, it will actually register. Since the button presses go through an DAC, they may be slightly different each press. You use the threshold value to give a +/- addition so that the button presses can fall within a range, rather then one exact value. Usually, a small threshold like 2 or 3 is enough for a single board, but if you want to make a program that will work with many different LCD boards (for example, you're making 10 copies of your project) then you would use a larger threshold to account for the slight differences in the boards.

Extra comments have been added to the code so you can see what everything is for. Once you've tested the code and gotten all your button values, we can move on to the next program.

//Sample using LiquidCrystal library

#include <LiquidCrystal.h>

/*******************************************************

This program will test the LCD panel and the buttons
Mark Bramwell, July 2010

********************************************************

// select the pins used on the LCD panel
LiquidCrystal lcd(8, 9, 4, 5, 6, 7); //rs, enable, d4,d5,d6,d7

// define some values used by the panel and buttons
int lcd_key     = 0;//which button was pressed
int adc_key_in  = 0;//what was the input of the DAC

#define btnRIGHT  0
#define btnUP     1
#define btnDOWN   2
#define btnLEFT   3
#define btnSELECT 4
#define btnNONE   5 //set button return values

const int btnup = 130;
const int btndown = 306;
const int btnleft = 479;
const int btnright = 0;
const int select = 720;
const int none = 127; //Put your button values here
const int threshold = 2; //Put threshold value here

int read_LCD_buttons()
{
 adc_key_in = analogRead(A0);      // read the value from the sensor 
if (adc_key_in > none - threshold && adc_key_in < none + threshold)   return btnNONE; // We make this the 1st option for speed reasons since it will be the most likely result
if (adc_key_in > btnup - threshold && adc_key_in < btnup + threshold)   return btnUP;
if (adc_key_in > btndown - threshold && adc_key_in < btndown + threshold)   return btnDOWN;
if (adc_key_in > btnleft - threshold && adc_key_in < btnleft + threshold)   return btnLEFT;
if (adc_key_in > btnright - threshold && adc_key_in < btnright + threshold)   return btnRIGHT;
if (adc_key_in > select - threshold && adc_key_in < select + threshold)   return btnSELECT;

//if else statements could be used, but since each statement uses return, you don't lose speed for using all if's

 return btnNONE;  // when all others fail, return this...
}//end read_LCD_Buttons

void setup()
{
 lcd.begin(16, 2);              // start the library
 lcd.setCursor(0,0); // Set cursor to upper left corner
 lcd.print("Push the buttons"); // print a simple message
}//end setup
 
void loop()
{
 lcd.setCursor(9,1);            // move cursor to second line "1" and 9 spaces over
 lcd.print(millis()/1000);      // display seconds elapsed since power-up

 lcd.setCursor(0,1);            // move to the begining of the second line
 lcd_key = read_LCD_buttons();  // read the buttons

 switch (lcd_key)               // depending on which button was pushed, we perform an action
 {
   case btnRIGHT:
     {
     lcd.print("RIGHT ");
     break;
     }
   case btnLEFT:
     {
     lcd.print("LEFT   ");
     break;
     }
   case btnUP:
     {
     lcd.print("UP    ");
     break;
     }
   case btnDOWN:
     {
     lcd.print("DOWN  ");
     break;
     }
   case btnSELECT:
     {
     lcd.print("SELECT");
     break;
     }
     case btnNONE:
     {
     lcd.print("NONE  ");
     break;
     }
 }//end switch

}//end loop

Step 2: Connect to MegaMoto

Picture of Connect to MegaMoto

First, we need to wire the MegaMoto. Do the following:

- Connect your 12V supply to the BAT+/- terminals

- Connect the actuator to the MOTA and MOTB terminals

- OPTIONAL Connect BAT+ to Vin if you want to power the Arduino away from the computer, or leave the USB plugged in.

- Set the MegaMoto pins as follows:

- Enable = 13

- PWMA = 11

- PWMB = 3

- Sensor = A5

The left button should make the actuator extend, the right button should make the actuator retract. If your directions are reversed, then reverse the MOTA and MOTB connections.

Now that the buttons are configured we can add the LCD program to the rest of the program. We've added four new functions. Read_LCD_buttons(), updateTrip(), updateLCD() and printFeedback().

Read_LCD_buttons() is the same as the test program, it reads the status of the buttons from the LCD screen.

UpdateTrip() is used to check the results of the Up and Down buttons, and change the amp limit to trip the safety shutdown at.

UpdateLCD() is the general LCD writing function, that writes all the new information to the LCD screen.

PrintFeedback() writes the current amperage draw to the LCD screen. This is separate from the main updateLCD() function, so that we can see the current draw in real time, rather then waiting for the program to loop.

Upload the program to test is, in the next step we'll be going over the different sections of the code in detail.

NOTE: The trip limits and current feedback aren't actually the number of amps that are being drawn, they are just the results of the current sensor. You can add an equation in to convert the value to amps before you write it to the LCD screen.

/*  Code to display the current amp draw of the actuator, and to cut power if it
  rises above a certain amount.

  Written by Progressive Automations
  July 16th, 2015

  Hardware:
  - RobotPower MegaMoto control boards
  - LCD shield
  - Arduino Uno
 */
#include <LiquidCrystal.h>


LiquidCrystal lcd(8, 9, 4, 5, 6, 7);// select the pins used on the LCD panel

// define some values used by the panel and buttons
#define btnRIGHT  1
#define btnUP     2
#define btnDOWN   3
#define btnLEFT   4
#define btnSELECT 5
#define btnNONE   0

int lcd_key     = 0;
int adc_key_in  = 0;//value to read LCD button input

const int btnup = 130;
const int btndown = 306;
const int btnleft = 479;
const int btnright = 0;
const int select = 720;
const int none = 1023;
const int threshold = 2;//Analog values for LCD buttons

const int EnablePin = 13;
const int PWMPinA = 11;
const int PWMPinB = 3; // pins for Megamoto

const int CPin1 = A5;  // motor feedback

int button = 0;//value to read LCD buttons

int leftlatch = LOW;
int rightlatch = LOW;//motor latches

int hitLimits = 0;
int hitLimitsmaxfront = 10;
int hitLimitsmaxback = 5;//values to know if travel limits were reached

long lastfeedbacktime = 0;
int firstfeedbacktimedelay = 750; //first delay to ignore current spike
int feedbacktimedelay = 50; //delay between feedback cycles
long currentTimefeedback = 0;

int debounceTime = 500; //amount to debounce
long lastButtonpress = 0; // timer for debouncing
long currentTimedebounce = 0;

int CRaw = 0;      // raw A/D value

int maxAmps = 0; // trip limit

long currentTimetrip = 0;
long lastUpdate = 0;
int updateTime = 100;//how fast can trip values update

bool dontExtend = false;
bool firstRun = true;//program logic

char* title[] = {"  PROGRESSIVE", "  AUTOMATIONS"};        //Power on title

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

  pinMode(EnablePin, OUTPUT);
  pinMode(PWMPinA, OUTPUT);
  pinMode(PWMPinB, OUTPUT);//Set motor outputs

  pinMode(CPin1, INPUT);//set feedback input

  lcd.begin(16, 2);// start the library
  lcd.setCursor(0, 0), lcd.print(title[0]), lcd.setCursor(0, 1), lcd.print(title[1]); //Print title
  delay(2000);//show title for 2 seconds

  currentTimetrip = millis();
  currentTimedebounce = millis();
  currentTimefeedback = 0;//Set initial times

  lcd.setCursor(0, 0);
  lcd.print("Amps Feedback"); // print the menu message

  updateLCD(); //write new values to the screen

}//end setup

void loop()
{
  //Serial.println("Main loop");

  updateTrip();//check buttons, update amperage

  latchButtons();//check buttons, see if we need to move

  moveMotor();//check latches, move motor in or out

}//end main loop

void updateLCD()
{
  lcd.setCursor(0, 1);
  lcd.print("Amps:  0");

  lcd.setCursor(8, 1);
  lcd.print("Trip:  0");

  lcd.setCursor(13, 1);
  lcd.print("   ");//clear old value

  if (maxAmps > 99) lcd.setCursor(13, 1);
  else if (maxAmps > 9) lcd.setCursor(14, 1);
  else lcd.setCursor(15, 1);

  lcd.print(maxAmps);//write new value

}//end updateLCD

void latchButtons()
{
  button = read_LCD_buttons();

  if (button == btnLEFT && rightlatch != HIGH)//left is forwards
  {
    currentTimedebounce = millis() - lastButtonpress;
    if (currentTimedebounce > debounceTime && dontExtend == false)//once you've tripped dontExtend, ignore all forwards presses
    {
      leftlatch = !leftlatch;
      button = btnNONE;
      firstRun = true;
      lastButtonpress = millis();
      return;
    }//end if
  }//end btnLEFT

  if (button == btnRIGHT && leftlatch != HIGH)//right is backwards
  {
    currentTimedebounce = millis() - lastButtonpress;
    if (currentTimedebounce > debounceTime)
    {
      rightlatch = !rightlatch;
      button = btnNONE;
      firstRun = true;
      lastButtonpress = millis();
      return;
    }//end if
  }//end btnRIGHT
}//end latchButtons

int read_LCD_buttons()
{
  adc_key_in = analogRead(A0);      // read the value from the sensor
  delay(20);

  if (adc_key_in > none - threshold && adc_key_in < none + threshold)   return btnNONE;
  if (adc_key_in > btnup - threshold && adc_key_in < btnup + threshold)   return btnUP;
  if (adc_key_in > btndown - threshold && adc_key_in < btndown + threshold)   return btnDOWN;
  if (adc_key_in > btnleft - threshold && adc_key_in < btnleft + threshold)   return btnLEFT;
  if (adc_key_in > btnright - threshold && adc_key_in < btnright + threshold)   return btnRIGHT;
  if (adc_key_in > select - threshold && adc_key_in < select + threshold)   return btnSELECT;

  return btnNONE;  // when all others fail, return none

}//end readLCD

void updateTrip()
{
  if (button == btnUP)
  {
    currentTimetrip = millis() - lastUpdate;
    if (currentTimetrip > updateTime)
    {
      maxAmps = maxAmps + 1;

      if (maxAmps > 999) maxAmps = 0;//check for rollover

      lcd.setCursor(13, 1);
      lcd.print("   ");//clear old value

      if (maxAmps > 99) lcd.setCursor(13, 1);
      else if (maxAmps > 9) lcd.setCursor(14, 1);
      else lcd.setCursor(15, 1);//set cursor accordingly

      lcd.print(maxAmps);//write new value

      lastUpdate = millis();
    }//end if
  }//end if btnUP

  else if (button == btnDOWN)
  {
    currentTimetrip = millis() - lastUpdate;
    if (currentTimetrip > updateTime)
    {
      maxAmps = maxAmps - 1;

      if (maxAmps < 0) maxAmps = 999;//check for rollover

      lcd.setCursor(13, 1);
      lcd.print("   ");//clear old value

      if (maxAmps > 99) lcd.setCursor(13, 1);
      else if (maxAmps > 9) lcd.setCursor(14, 1);
      else lcd.setCursor(15, 1);//set cursor accordingly

      lcd.print(maxAmps);//write new value

      lastUpdate = millis();
    }//end if
  }//end else if btnDOWN

}//end updateTrip

void moveMotor()
{
  if (leftlatch == HIGH) motorForward(255); //speed = 0-255
  if (leftlatch == LOW) motorStop();
  if (rightlatch == HIGH) motorBack(255); //speed = 0-255
  if (rightlatch == LOW) motorStop();

}//end moveMotor

void motorForward(int speeed)
{
  while (dontExtend == false && leftlatch == HIGH)
  {
    //Serial.println("Moving forwards");
    digitalWrite(EnablePin, HIGH);
    analogWrite(PWMPinA, speeed);
    analogWrite(PWMPinB, 0);//move motor
    if (firstRun == true) delay(firstfeedbacktimedelay);
    else delay(feedbacktimedelay); //small delay to get to speed

    getFeedback();

    firstRun = false;

    latchButtons();
  }//end while

}//end motorForward

void motorBack (int speeed)
{
  while (rightlatch == HIGH)
  {
    //Serial.println("Moving Back");
    digitalWrite(EnablePin, HIGH);
    analogWrite(PWMPinA, 0);
    analogWrite(PWMPinB, speeed);//move motor
    delay(3); //small delay to get to speed

    currentTimefeedback = millis() - lastfeedbacktime;

    if (firstRun == true && currentTimefeedback > firstfeedbacktimedelay) getFeedback(); //if it hasnt been long enough, do nothing
    if (firstRun == false && currentTimefeedback > feedbacktimedelay) getFeedback(); //if it hasnt been long enough, do nothing

    firstRun = false;

    latchButtons();

  }//end while

  dontExtend = false;

}//end motorBack

void motorStop()
{
  analogWrite(PWMPinA, 0);
  analogWrite(PWMPinB, 0);

  digitalWrite(EnablePin, LOW);
  firstRun = true;//once the motor has stopped, reenable firstRun to account for startup current spikes

}//end stopMotor

void getFeedback()
{
 CRaw = analogRead(CPin1);

  if (CRaw == 0 && hitLimits < hitLimitsmaxback) hitLimits = hitLimits + 1;
  else if (CRaw == 0 && hitLimits < hitLimitsmaxfront) hitLimits = hitLimits + 1;
  else hitLimits = 0;

  if (hitLimits == hitLimitsmaxback)
  {
    rightlatch = LOW;
  }//end if

  if (hitLimits == hitLimitsmaxfront)
  {
    leftlatch = LOW;
    hitLimits = 0;
  }//end if

  if (CRaw > maxAmps)
  {
    dontExtend = true;
    leftlatch = LOW;
  }//end if

  printFeedback();

  lastfeedbacktime = millis();//store previous time for receiving feedback
}//end getFeedback

void printFeedback()
{
  lcd.setCursor(5, 1);
  lcd.print("   ");

  if (CRaw > 99) lcd.setCursor(5, 1);
  else if (CRaw > 9) lcd.setCursor(6, 1);
  else lcd.setCursor(7, 1);
  lcd.print(CRaw);

}//end printFeedback

Step 3: Detailed Program Overview

Here we will go over the four new functions that were added, and explain each one. If the other parts of the code confuse you, check the Monitoring the Load Feedback Instructable for a detailed explanation of the rest of the code.

First, the read_LCD_buttons() function. This function does an analogRead() on the button pin of the LCD screen, and compares the value that it reads to the preset button values. If the value falls within the programmed value (plus or minus the threshold), then it returns which button was pressed. If it fails to match one of the programmed values, it returns as no button being pressed.

adc_key_in = analogRead(A0);      // read the value from the sensor
delay(20);

  if (adc_key_in > none - threshold && adc_key_in < none + threshold)   return btnNONE;
  if (adc_key_in > btnup - threshold && adc_key_in < btnup + threshold)   return btnUP;
  if (adc_key_in > btndown - threshold && adc_key_in < btndown + threshold)   return btnDOWN;
  if (adc_key_in > btnleft - threshold && adc_key_in < btnleft + threshold)   return btnLEFT;
  if (adc_key_in > btnright - threshold && adc_key_in < btnright + threshold)   return btnRIGHT;
  if (adc_key_in > select - threshold && adc_key_in < select + threshold)   return btnSELECT;

  return btnNONE;  // when all others fail, return none

Second, the updateTrip() function. This checks the Up and Down buttons, and increases or decreases the trip limit accordingly. It has debouncing as well, so the trip value can't update too fast. You can adjust updateTime to change how fast the trip values update. You can hold down the button to change trip values quickly. By changing updateTime, you can change how fast (or slow) the trip value changes when you hold the Up or Down button down. If the value hits 0, and you continue to go down, it will roll over to 999. If you hit 999 and continue up, it will roll over to 0.

if (button == btnUP)
  {
    currentTimetrip = millis() - lastUpdate;
    if (currentTimetrip > updateTime)
    {
      maxAmps = maxAmps + 1;

      if (maxAmps > 999) maxAmps = 0;//check for rollover
     lastUpdate = millis();
    }//end if
  }//end if btnUP

  else if (button == btnDOWN)
  {
    currentTimetrip = millis() - lastUpdate;
    if (currentTimetrip > updateTime)
    {
      maxAmps = maxAmps - 1;

      if (maxAmps < 0) maxAmps = 999;//check for rollover

      lastUpdate = millis();
    }//end if
  }//end else if btnDOWN

Third is the updateLCD() function. This writes new values to the LCD screen, so you can see what is happening in your program. It uses setCursor() to move the cursor around the screen, and put the messages in the correct place. the first value in setCursor() is the row position that the cursor is in, the second value is whether the cursor is in the upper or lower row. Since the trip limit can be between 0-999, the cursor moves to different spots depending if it is a 1, 2 or 3 digit value. There are spaces printed (" ") that are used to clear the spot where the number is printed. If the spot was not cleared, when you change from a 2 digit value down to a 1 digit value, the second digit won't get cleared when the first digit is updated, meaning that your LCD screen would display weird numbers.

  lcd.setCursor(0, 1);
  lcd.print("Amps:  0");
  lcd.setCursor(8, 1);
  lcd.print("Trip:  0");
  lcd.setCursor(13, 1);
  lcd.print("   ");//clear old value

  if (maxAmps > 99) lcd.setCursor(13, 1);
  else if (maxAmps > 9) lcd.setCursor(14, 1);
  else lcd.setCursor(15, 1);

  lcd.print(maxAmps);//write new value

Last is the printFeedback() function. It is very similar to the updateLCD() function, it is just separate so that the value of the current amp draw is updated in real time, rather then once every time the program loops. This function is called to update the amp draw whenever the getFeedback() function is called to read what the amp draw is.

  lcd.setCursor(5, 1);
  lcd.print("   ");

  if (CRaw > 99) lcd.setCursor(5, 1);
  else if (CRaw > 9) lcd.setCursor(6, 1);
  else lcd.setCursor(7, 1);
  lcd.print(CRaw);

Step 4: Conclusion

Picture of Conclusion

In this Instructable, we learned how an LCD screen can be used to adjust parameters of your program in real time. We used it as an extension to the Monitoring the Load Feedback Instructable to adjust the current limits for the actuator to stop at. An addition to this program would be to add the EEPROM library, so that each time you power the system on and off the values you set are saved.

If you'd like to take a look at our selection of linear actuators, motions control systems and microcontrollers then please visit us at www.progressiveautomations.com for all your actuator needs! We can even build a custom actuator or control system for you based on your own custom specifications with the help of our highly trained staff of engineers. You can learn more about the custom order process right here!

Follow, Like and Subscribe!

Twitter - twitter.com/ProgAutoInc

Facebook - www.facebook.com/ProgressiveAutomations

Youtube - www.youtube.com/user/MrActuators

Comments

max_barton (author)2015-10-15

About a year ago I entered a contest to make a can crusher that you can mount on the side of a 50 gallon trash bag. so I used a liner actuator, some pvc piping as the hopper and bracket, sacrificed my Xbox 360 as a power supply (which works great because it outputs 5V and 12V), Arduino, seed motor drive board, and the same lcd board use here along with some other metal work. I learned how to program Arduinos for this and put tons of time and effort to get it done. When the crusher was hooked up and attached you would drop the can down the hopper and a sensor would trigger the actuator to activate, then the number of cans crushed is logged and outputs the number of cans crushed and the amount of space saved in each trash bag. Before I started I looked everywhere for an Instructable that would suggest how to do this and of course now that I don't need it, I find it. Life has a great sense of humor. Good instructable aside from that.

About This Instructable

9,004views

134favorites

License:

Bio: Progressive Automations is your primary source for electric linear actuators, motion control systems and automation accessories. For over a decade, we have supplied various industries ... More »
More by Progressive Automations:Potentiometer Feedback Control Part 2: RF Control of Extended LimitPotentiometer Feedback Control: Implementing a Soft Extend LimitDIY Portable Single Axis Solar Tracker
Add instructable to: