7

I've used enough products with embedded microcontrollers and unresponsive UIs to know that it can make or break a product. Even a slight delay or lag between the button presses and the display updating, or button presses being ignored or double counted, can be extremely frustrating.

What design patterns can be used to deal with user input (from buttons) and updating displays without causing these issues?

I currently use the following kind of pattern (this is simplified down to the bare minimum):

#include <Bounce.h>

#define GREEN_LED       6 // Pin for green LED

#define BUTTON_PIN      15 // Pin for button - uses external pull down so active high

#define DISPLAY_REFRESH_INT 100 // How many MS between display updates

Bounce button = Bounce(BUTTON_PIN, 5);

// To signal between the button read and the display update
bool ledState = false;

// Used to keep track of last display update
long displayUpdate = 0;

void setup()
{
    pinMode(GREEN_LED, OUTPUT);
    pinMode(BUTTON_PIN, INPUT);
}

void loop()
{
    // Read the button status
    if (button.update())
    {
        if (button.risingEdge())
        {
            ledState = !ledState;
        }
    }

    // Update the display periodically
    if (millis() - displayUpdate > DISPLAY_REFRESH_INT)
    {
        displayUpdate = millis();

        digitalWrite(GREEN_LED, ledState);
    }

}

What other options are there? Is it ever worth using pin change interrupts to read buttons (if we are not considering battery life!).

4 Answers 4

6

The answer depends on exactly how the sketch is meant to respond to the user interaction.

External interrupts
If the interaction depends on responding very accurately to the rising or falling edge of an input event (as in your example), then an external interrupt may be the way to go, if possible.

This is particularly important if the input event might be shorter that the duration of a call to loop(). If that happens, consecutive attempts to poll the input could completely miss the state change. It's quite rare for a user input event to be that short though, unless loop() is quite slow to execute.

External hardware to get help that though, e.g. by having an input which latches until it is read and reset by the microcontroller.

Improving polling frequency
External interrupts often aren't possible (e.g. because the chip only supports a limited number), or they are simply overkill/unnecessary for a given application.

In those cases, it may still be possible to improve the timing precision when using polling. One option is to split up all the different parts of the main loop into separate functions. On each successive iteration of loop(), it will call the next function, and then wrap round to the first. In between each of those functions, it can poll the button. Here's a simple example:

unsigned int g_section = 0;

void setup() { }

void pollButton()
{
    //...
}

void runSection0()
{
    //...
}

void runSection1()
{
    //...
}

void runSection2()
{
    //...
}

void loop()
{
    // Poll the button on every pass:
    pollButton();

    // Execute the next section of the main code:
    switch (g_section)
    {
    case 0: runSection0(); break;
    case 1: runSection1(); break;
    case 2: runSection2(); break;
    }

    // On the next iteration, run the next section:
    if (++g_section > 2) g_section = 0;
}

One important issue with this approach is that it's very difficult to make each of those runSectionX() functions take the same execution time. That can result in the button being polled inconsistently from one iteration to the next. If it's fast enough though, then it shouldn't be too big of a problem.

Display update
Updating a display is unfortunately often quite slow, compared to many other operations. That means you need to be quite careful where you do it. Inside an Interrupt Service Routine often wouldn't be appropriate, as that could introduce other timing issues in your sketch.

Realistically, you'd have to do it in the main loop. If you absolutely have to run it quickly though, then it can be included in the faster polling example I've given above -- i.e. update the display on every iteration, the same as polling. You have to be careful to avoid partial updates though.

1
  • This is a great answer by the way. Exactly the kind of answer the site needs. Mar 3, 2014 at 7:46
2

For program control, I made something of a menu handling framework for one of my projects and shared the source on Github. Note that this is for chipKIT but the menu handling code is generic. After building my 3rd or 4th huge if-else-if and switch structure, I decided there had to be a better way. Credit for the source of the static menu structures.

I just use the main loop to check the button.

I'm wanting to say the first step is to get a TFT. If you try it, you'll never go back... I particularly love the Adafruit 1.8" TFT with joystick. Arduinos are slow enough that I cannot recall caring to debounce user input.

4
  • I like the way you've done that - it's understandable, clean, and very small in size. I had been looking at using a library (MenuBackend) but it is hard to understand for new programmers (using callbacks) and can't sit in PROGMEM. Mar 6, 2014 at 7:27
  • I find debounce often isn't required, but some switches seem to be much noisier than others. The upright, edge mounted PCB tactile switches seem the worst by far, maybe because instead of pressing, users often lever these forwards. Debouncing is so easy that I nearly always do it now. Mar 6, 2014 at 7:30
  • Sorry for all the questions - why a TFT? I'm currently working with a 128x64 LCD which has a lot of benefits (mainly being physically large). Also cheap. Mar 6, 2014 at 7:37
  • 1
    @Cybergibbons ah, didn't see any mention of a display. The one I like is a ready made shield, cheap enough, and you can do things with colors. Here's a build report of my most recent project: forums.adafruit.com/…
    – Krista K
    Mar 6, 2014 at 9:27
1

You could interrupt instead of polling if you can de-bounce the buttons in hardware. Otherwise you'll be take many more interrupts than there were button presses and still have to de-bounce them in software.

But to the primary question, a collection of state machines can be very useful, at least for the ui, and give you most of the benefits of multi-tasking without needed a m/t kernel. can be in RAM limited devices, unfortunately, because

The technique could be especially useful on these small devices but for the severe limits on RAM, making it something of a challenge to divide your state-machine data between flash and RAM, to fit.

0

As Chris K i've also implemented a static menu library with a wide variety of output options like Serial,LCD,TFT, etc.. I've started it to help in a friends project and I'm also sharing it open source for people that with similar problems, hope you can find it useful.

https://github.com/neu-rah/ArduinoMenu

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.