How does a microcontroller can get button input while doing things? - Page 1
Forum section: electronics / general

author: BGenc
author_rank: Newbie
timestamp: September 28, 2015, 09:11:35 pm
content: Greetings EEVblog Forums!Since I started playing around with arduino and raspberry pi, I got stuck with something that looks simple but really hard with my limited knowledge.For example, say I want to show changing temperature values on a standard 16x2 LCD screen. Without any buttons or stuff, the code logic should be like this:while True {getTemperatures()}It is all good for now! But what about adding a button to switch between clock and thermometer? I think something like this would work:clock = false;while True {if (clock==false){getTemperatures()else {getTime()}if (buttonPin == high){if (clock == false){clock = true;else{clock = false;}}And this is a horrible method. It makes the controller run without any delay indefinitely, and if you hold the button just a tiny bit more than the internal clock, it would switch between true and false continuously.Oh, not to mention that if you put a delay that is a bit long, you have to hold it until it hits the cycle.What is the correct way to do something similar? As far as I know microcontrollers like Atmel168P can't really multitask.

author: suicidaleggroll
author_rank: Super Contributor
timestamp: September 28, 2015, 09:28:51 pm
content: Interrupts and button debouncing, lots of examples.One example is to run a timer that throws an interrupt every 1 ms. In the ISR you grab the value of the GPIO connected to the button and increment or decrement a counter. If that counter is >= 50, set a flag, that means the button is currently being pressed (also cap it at 50 so it doesn't overrun the integer). If that counter is <= 0, clear the flag, that means the button is no longer being pressed (also cap it at 0 so it doesn't overrun the integer). Then just keep track of the previous value of the flag and the current value of the flag. If it moves from 0 to 1, then the button was pressed, if it moves from 1 to 0 then the button was released. You can then do with that information what you wish. You can even use it to set your "clock" variable directly in the ISR, so the next iteration of your main loop knows what to do.The "50" is more or less arbitrary, that will give you 50 ms of debouncing, which may be just right or not enough depending on the behavior of your button.

author: BGenc
author_rank: Newbie
timestamp: September 28, 2015, 09:46:25 pm
content: ISR, that is the thing that associated with this, then. Especially knowing what you want but not knowing the term draw me mad.Anyway, so what I understood after making a quick research around arduino website, ISR is something that is mostly associated to several pins on the controller. Is it some sort of hidden loop that runs under the visible code (like embedded in assembly or some sort)?I'll take a deeper look to this, thanks!

author: helius
author_rank: Super Contributor
timestamp: September 28, 2015, 09:50:31 pm
content: You may want to investigate "event-driven programming". Interrupts and timers are not required for these problems, but they are sometimes convenient (which is why all microcontrollers have them). Most microcontrollers also have pin-change interrupts, when you can't use a timer for various reasons.

author: ajb
author_rank: Super Contributor
timestamp: September 28, 2015, 10:01:23 pm
content: If you have a number of different tasks that need to performed at different intervals, you probably want to implement some sort of system timer. I believe that Arduino has this built-in, but it's as simple as setting up a timer that increments at whatever resolution you need (typically ~milliseconds for user-oriented tasks like updating displays, blinking LEDs, or debouncing buttons). Setup a variable for each of your tasks to hold the last time that task was executed, and in your main loop check how long it's been since each task was run by comparing the current time to each task's last timestamp. If it's been longer than that task's assigned interval, execute it and record the current time. There are a couple of traps to watch out for involving integer overflow, but that's the gist of it.If the tasks have strict timing requirements then interrupts are a better bet, but using interrupts to schedule more than one task gets tricky unless you can guarantee that each task will finish before the next one is due.For more complex systems, an RTOS starts to make more sense, but that's a whole other league from just reading button presses and updating an LCD.Quote from: BGenc on September 28, 2015, 09:11:35 pmThe way around this is to execute the action when the button changes from not pressed to pressed, rather than executing the action whenever it's pressed. Of course, you need to look for that transition after you've debounced it. You could use something like suicidaleggroll suggests and increment a counter as long as the button is pressed, and when it hits your debounce threshold, you set a variable that says 'button one has just been pressed'. The next time you check the button, if it's still pressed, you change that value to say 'button one is still pressed'. Then if you check the button and it's been released you set that variable to 'button one just released'. That allows you to capture the transitions as events separate from the button's steady states. You can also extend it with long-press functionality, so if the button is pressed for a longer period of time you can trigger yet another action.

author: pelule
author_rank: Frequent Contributor
timestamp: September 28, 2015, 10:24:55 pm
content: Interrupt programming (event driven) is not a simple task.An easier way to do such kind of programm is a loop.If you need timed controlled action (fe every 50ms) you created a timer setting a flag every 50ms.If you need an additional 1/2 second action, you execute that every 10th run throuh the loop using a counter to 10.If you runs the button read function in the timed lood you get automatical a button debouncing.Example:do {timed = readtimer(); // returns true, if timer is >= 50 msif (timed) {timed = 0; // clear flag for next timed actionprocess_any_timed_action();if (button = pressed) {readbutton();processbutton();}}getTemperatures();}while (continue)

author: suicidaleggroll
author_rank: Super Contributor
timestamp: September 28, 2015, 10:55:47 pm
content: Quote from: pelule on September 28, 2015, 10:24:55 pmNo, you don't. You will get both false positives and false negatives by doing that. There is zero "debouncing" in "grab the value every 50 ms and react on it".

author: obiwanjacobi
author_rank: Super Contributor
timestamp: September 29, 2015, 07:17:40 am
content: If you use (pin-change) interrupt debounce in hardware (RC). This will ensure the ISR does not take the bulk of your processing time triggered by bounces etc.On small size MCUs an RTOS (threading) is too much overhead and a bit ridiculous IMHO.A method that I use is a cooperative multi-tasking. This is where you process multiple tasks in small pieces at a time but there is no enforcement of changing Tasks.See this link for some code:http://atl.codeplex.com/SourceControl/latest#Source/Code/ArduinoTemplateLibrary/Task.hAnd an example:http://atl.codeplex.com/SourceControl/latest#Source/Code/ArduinoTemplateLibrary/TimeoutTask.hButton example:http://atl.codeplex.com/SourceControl/latest#Source/Code/ArduinoTemplateLibrary/PushButton.hIn the PushButton example, I implement debounce- and hold-timeouts without blocking the execution.I think this is one of the cleanest ways to structure this kind of code (but I might be biased ;-) ).Hope it helps.

author: Rerouter
author_rank: Super Contributor
timestamp: September 29, 2015, 08:21:09 am
content: I opted for a resetable input latch that abused the pullup on the input, 1npn, 1 pnp and 2 resistorsSo when the button pulls to ground, it latches to ground, When my routine comes around, it reads the latch pulling down, turns off the pin pull up to reset it, And reads again to see that it reset, If it has, it accepts it as a press, otherwise it starts a timer, and continues doing what its doing until it comes around and the latch reset works, and registers it as a long press if it exceeds a certain time,