LCD Screen invites users to push button - but problem with interrupts and delays

Hello,

I'm an Arduino neophyte, and my project consists of using an UNO with an i2c lcd and a pushbutton. Basically, the lcd should display an attention-getting script inviting people to push a button. I've played around with scrolling, blinking, and flashing, and they're all great. Exactly what I want.

However, I also want the system to respond immediately when the button is pushed. (What happens after the button is pushed has already been solved.)

Everything I've read so far recommends that I use an interrupt routine to make sure the button's state change is picked up right away. But - and here is my problem - all the lcd.print sketches I've seen use delay(x) - and I can't use "delay" with an interrupt.

So, is there something I can do to make sure I can run my snazzy script, while at the same time responding instantly to user input?

Thanks very much for any help.

Yes... don't use delay() in your sketch!

Sorry if that sounds condescending, but you haven't explained why you are using delay(), nor posted your sketch for us to review. (If you do post your sketch, remember to use code tags.)

Can we assume the delays are part of your attention-grabbing technique? If so, the answer to "how to get Arduino to appear to do 2 things at once" is: see the "blink without delay" example.

Paul

Sorry Paul, you’re quite right - here is the code:

This is the part that deals with the button press and what happens after, what I call the main part:

#define DIR_PIN 8
#define STEP_PIN 9

void rotate(int steps, float speed){ 
   int dir = (steps > 0)? HIGH:LOW;
  steps = abs(steps);

  digitalWrite(DIR_PIN,dir); 

  float usDelay = (1/speed) * 70;

  for(int i=0; i < steps; i++){ 
    digitalWrite(STEP_PIN, HIGH); 
    delayMicroseconds(usDelay); 

    digitalWrite(STEP_PIN, LOW); 
    delayMicroseconds(usDelay); 
  } 
} 

void rotateDeg(float deg, float speed){ 
   int dir = (deg > 0)? HIGH:LOW;
  digitalWrite(DIR_PIN,dir); 

  int steps = abs(deg)*(1/0.225);
  float usDelay = (1/speed) * 70;

  for(int i=0; i < steps; i++){ 
    digitalWrite(STEP_PIN, HIGH); 
    delayMicroseconds(usDelay); 

    digitalWrite(STEP_PIN, LOW); 
    delayMicroseconds(usDelay); 
  } 
}

int button = 2;
int buttonState = 0;
int lastButtonState = 0;
int debouncer = 0;

int SLEEP = 12;      // PIN 12 = SLP
int val;
int ignore = 0;

void setup() { 
  pinMode(DIR_PIN, OUTPUT); 
  pinMode(STEP_PIN, OUTPUT); 
  pinMode(SLEEP, OUTPUT); // set pin 12 to output
  Serial.begin(9600);
  pinMode(button, INPUT);
  (val == LOW);
  
} 

void loop(){ 

buttonState = digitalRead(button);
  
if ((buttonState != lastButtonState)&&(ignore == 1)) {
  delay(90);
  debouncer = 1;
}

if (debouncer == 1)
{ 
  buttonState = 0;
lastButtonState = 0;

 digitalWrite(SLEEP, HIGH);
 rotate(1600, .5); 
  delay (10);
 rotate (1600, .0375);
 
 delay (4000);
 rotate (-3200, .5);
 digitalWrite(SLEEP, LOW);
   delay(10000); 
   
   debouncer = 0;
  ignore = 0;
   

}
else {
debouncer = 0;
ignore = 1;
}

lastButtonState = buttonState;


 }

And this is the part that writes to the lcd:

#include <Wire.h>  // Comes with Arduino IDE
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); 

void setup() 
{
lcd.begin(20, 4);
}

void loop()
{
lcd.setCursor(0, 1);
lcd.print("Hello There!");
lcd.blink();
delay(5000);

lcd.clear();
lcd.print("PRESS MY BUTTON!!");
lcd.blink();
delay(5000);

lcd.clear();
lcd.print("COME ON, YOU KNOW!!");
lcd.blink();
delay(5000);

lcd.clear();
lcd.print("YOU WANT TO!!");
lcd.blink();
delay(5000);

lcd.clear();
}

I will be adding more to it later, and fancying it up later.

The difficulties I alluded to earlier include the delays in the lcd routine and in the debounce for the button.

(Whoops, sorry for format error)

Let us get things straight.

You do not (ever) want to use interrupts for sensing pushbuttons - which necessarily includes touch screens. There is one exception which is where you need an interrupt to wake from sleep, but even in this case the button is otherwise polled.

"Right away" is complete nonsense. We are dealing with a device which executes many millions of instructions each second. The very problem is that it can execute thousands of instructions while the button bounces, making contact and losing contact repeatedly over a few milliseconds, which is why you need to consider de-bouncing.

Please just forget about interrupts completely until at some stage well into the future, you need to use interrupts. What you need to do is to learn to code to a "state machine" where you alternately monitor the state of the button, execute a debounce (though you may not actually need to if you know the button has not been pressed beforehand and the action you take when it is pressed will take some time and when completed, the state of the button will no longer matter), and wait for timeouts not by using the inappropriate "wait()" function, but by checking the millis() clock and comparing it to a value you have determined to be a suitable time later than when the last event occurred - as is the nub of the "Blink without delay" example.

The common "newbie" misunderstanding, is that an interrupt is for "interrupting" a current sequence of events. This is completely wrong. An interrupt is for performing an action that needs to be performed and completed essentially invisibly from that current sequence of events. It may be that an interrupt will set a flag which will subsequently be noticed by the main program path, but this is rarely to advantage as the main program loop can just as well poll the event itself - which is precisely how you should deal with pushbuttons.

Thanks again, Paul.

Just to reply to a few things:

"You do not (ever) want to use interrupts for sensing pushbuttons - which necessarily includes touch screens. "

I'm basing this idea on something I read below (linked), which appears to have been recommended by various users.

http://www.engblaze.com/we-interrupt-this-program-to-bring-you-a-tutorial-on-arduino-interrupts/

I'm honestly confused here - the author makes it sound as if it's a great idea to use interrupts to sense button presses. He states "Let’s implement this using a simple example: detecting when a pushbutton has been pressed, and performing an action based on that press." To me, this sounded ideal: the lcd screen would continue to post messages until someone pressed a button, and then the program would move to the rotation, etc.

I'm not arguing here, I just don't get it. Why is he wrong?

""Right away" is complete nonsense."

I wasn't clear. This device will be placed in a lobby, where I don't know exactly when the next user arrives. So I want the lcd "advertising" to run until someone presses the button. They shouldn't have to push it more than once, or wait to push it.

I wouldn't be quite so prescriptive as Paul__B. There could be cases where interrupts could be the best way of dealing with button presses, but they would be rare, and in this case he is right, there is no need for interrupts in your application.

For example I was recently working on a circuit that will be battery powered, so I needed to minimise current consumption. I settled on a design that "deep sleeps" the Arduino (Pro Micro in this case) and wakes on a timer 20 times per second to poll for button presses and perform other actions. I also tried handling button presses and other external events with interrupts, hoping to improve the current savings still further, but it made hardly any extra improvement, mainly because of the current consumption of other components in the circuit. The response to button presses with the timer method works fine and appears to happen instantaneously to the user.

Anyway, back to your sketch(s). Seems to me most of the delays in the first sketch will be fine, they are mostly short. I'm guessing its those delay(5000) in the second sketch that need to be removed.

I would suggest a couple of possible approaches:

  1. The "finite state machine"/"blink without delay" approach. Sounds complex, but in practice all it really involves is a couple of extra global variables, one to keep tabs on which message in the sequence you are currently displaying, and another to record the time (in milliseconds) that the next change of message should occur.

  2. Make your own version of the delay() function, e.g. "myDelay()", and call that in your second sketch instead of the standard function. Make this new function perform a loop where it polls the button and then calls the standard delay() function for perhaps around 50ms.

Paul

MarkC: I'm basing this idea on something I read, which appears to have been recommended by various users.

Indeed. There is the typical "internet" problem - lots of "helpful" articles, written by enthusiastic hobbyists who have cleverly discovered how to do a particular thing. And many of them are extremely helpful, but some are not, and that is why I tend to be strident about such things, to get people to sit up and think. I hope.

MarkC: I'm honestly confused here - the author makes it sound as if it's a great idea to use interrupts to sense button presses.

Well, to be fair, he uses it as an example. He refers to "a project that relies on very precise timing or needs to react quickly to an input", and therein is the confusion. A pushbutton is - as i mentioned - simply not something that requires a quick response in microcontroller terms of micro*seconds. Some things however *are, for example a hard disk spinning 120 times a second with hundreds of sectors of information on each track, or a serial interface receiving 153,600 bits per second.

MarkC: He states "Let’s implement this using a simple example: detecting when a pushbutton has been pressed, and performing an action based on that press."

Actually, a very poor example however, and herein is the problem. Strictly, he is correct - "performing an action based on that press" - given that that action is complete within the interrupt (as he also specifies). For example (and again, a very bad example because of the more pragmatic consideration of debouncing), switching on a LED on a single port pin.

MarkC: To me, this sounded ideal: the LCD screen would continue to post messages until someone pressed a button, and then the program would move to the rotation, etc.

But you have mis-read him, and that was my point. As he says: "Once triggered, an interrupt pauses the current activity and causes the program to execute a different function. This function is called an interrupt handler or an interrupt service routine (ISR). Once the function is completed, the program returns to what it was doing before the interrupt was triggered."

So that is clearly not what you want at all. (But I can see the confusion - you may think that after you have performed your pushbutton work, you do want to go back to the LCD animation, but this is not something you do inside the interrupt where no other functions including timing, are possible!)

MarkC: I just don't get it. Why is he wrong?

He isn't entirely wrong, except in suggesting that interrupts are appropriate for pushbuttons. :D

It's just that in the excitement of describing how to implement interrupts, he fails to properly describe where and why they are actually needed.

MarkC: I wasn't clear. This device will be placed in a lobby, where I don't know exactly when the next user arrives. So I want the LCD "advertising" to run until someone presses the button. They shouldn't have to push it more than once, or wait to push it.

There is no problem. You write the code to interleave between checking the pushbutton and performing successive steps in the LCD "animation". This is what the " loop" is for, not for repeating the overall sequence of the animation. Here is some (untested) code I posted to an earlier question:

const int buttonPin = 7;    
const int ledPin = 11;      
const int ledPin2 = 10;      
const int ledPin3 = 9;     


int ledState = HIGH;        
int buttonState;             
int lastButtonState = HIGH;   

long lastDebounceTime = 0;  
long debounceDelay = 10;

void setup() {
  pinMode(buttonPin, INPUT);
  digitalWrite(buttonPin, HIGH); // sets pull-up
  pinMode(ledPin, OUTPUT);
  pinMode(ledPin2, OUTPUT);
  pinMode(ledPin3, OUTPUT);

}

void loop() {
  int reading = digitalRead(buttonPin);

  if (reading != lastButtonState) {
    lastDebounceTime = millis();
  }

  if ((millis() - lastDebounceTime) > debounceDelay) {

    if (reading != buttonState) {
      buttonState = reading;

      if (buttonState == LOW) {
        ledState = !ledState;
      }
    }
  }

  if (ledState) {  
    analogWrite(ledPin, random(120)+135);
    analogWrite(ledPin2, random(120)+135);
    analogWrite(ledPin3, random(120)+135);
    delay(random(100));
  } 
  else {   
    analogWrite(ledPin, 0);
    analogWrite(ledPin2, 0);
    analogWrite(ledPin3, 0);
  }

  lastButtonState = reading;
}

It flickers some LEDs when - and while the button is pressed.

And another example:

// seed counter

/*  This program should count seeds as they drop
 and track how many have been planted */

const byte seedPin_1 = 2;            // sensor pin - active LOW
const byte ledPin = 13;              // LED indicator
unsigned long seedCount;             // actual seed count
byte seedState_1;                    // sensor status
byte lastseedState_1;                // previous sensor status
byte bounce_1;                       // debounce counter
long lasttime;                       // clock

void setup()
{                                    
  pinMode(seedPin_1, INPUT);         // seedState as an input
  digitalWrite(seedPin_1, HIGH);     // pullup enabled (reliable way!)
  pinMode(ledPin, OUTPUT);           // LED as an output
  digitalWrite(ledPin, LOW);
  Serial.begin(9600);                // serial communication
  seedCount = 0;
  seedState_1 = HIGH;
  lastseedState_1 = HIGH;
  bounce_1=10;                       // Bounce time >= 10 ms
  Serial.println("on");
}


void loop() {
  if (millis() > lasttime) {         // next clock tick
    lasttime = millis();
    if (digitalRead(seedPin_1) == seedState_1)
    {                                // if the state is same
      bounce_1 = 10;                 // nothing to bounce
    } 
    else if (--bounce_1 == 0) {      // if we hit zero, we act:
      seedState_1 = digitalRead(seedPin_1); // update the saved
      bounce_1=10;                   // Bounce time reset

      if (seedState_1 == LOW) {      // If input now active
        seedCount++;                 // increment the counter
        Serial.print("seeds planted: ");
        Serial.println(seedCount);
      }
    }
    if (seedState_1 % 100 == 0)
    {
      digitalWrite(ledPin, HIGH);
    }
    else
    {
      digitalWrite(ledPin, LOW);
    }
  }
}

based on my generic description of debouncing.