Coming from the Web world, you are most likely used to work in a
single-threaded environment (save for Web workers). You have been
trained to program in a non-blocking fashion, as blocking the browser's
UI thread would freeze the user interface. This is actually a valuable
asset when moving to Arduino programming. Most Arduino programs run on
a single-threaded, bare-metal platform. Non-blocking code is a must for
all but the most trivial programs, and is not easy to master for
newcomers.
The most striking difference between an Arduino and your typical
JavaScript environment (be it a browser or Node.js) is that in
JavaScript the event loop is implicit. You do not see it, but it runs
under the hood of your interpreter. It looks conceptually like this:
void JavaScript_event_loop() {
if (there_is_a_pending_event()) {
dispatch_the_event_to_the_registered_callback();
}
}
On the Arduino, however, you have to write the event loop yourself. Most
often you do not need full-blown event queue management, as you know
beforehand the set of events your program will have to handle. Your
typical Arduino loop should normally look like this:
void loop() {
if (event_foo_happened())
handle_event_foo();
if (event_bar_happened())
handle_event_bar();
if (event_baz_happened())
handle_event_baz();
// etc...
}
There are a few recurring idioms for testing common events. Here are
some examples:
// Handle a periodic event (like setInterval()).
uint32_t now = millis();
static uint32_t last_event_time;
if (now - last_event_time >= event_period) {
// Use `last_event_time = now;` instead of this if you want to
// guarantee a minimal interval between events:
last_event_time += event_period; // this avoids systematic drift
handle_periodic_event();
}
// Handle a rising edge on an input_pin.
uint8_t pin_state = digitalRead(pin);
static uint8_t previous_pin_state;
if (previous_pin_state == LOW && pin_state == HIGH)
handle_input_rising_edge();
previous_pin_state = pin_state;
// Handle Serial data.
if (Serial.available())
handle_input_byte(Serial.read());
// Handle a push button with a debouncing library.
button.update();
if (button.fell())
handle_button_press();
A few recommended readings:
- The Blink Without Delay Arduino tutorial shows how to handle
periodic events
- Reading Serial on the Arduino is a tutorial showing how to
buffer serial input in order to handle one full message at a time
- Bounce2 is an example of a button-debouncing library
- The Finite State Machine is a nice tutorial on writing finite
state machines, which sooner or later you will need for handling some
situations in a non-blocking way
Edit: Regarding the example code you posted, I would like to rewrite
your loop()
like this:
void loop() {
if (condition) {
doSomething();
condition = false; // <- move this out of doSomething()
}
}
It doesn't look like a big change, but there is an idea here that can
help make your code more readable and maintainable. The idea is to think
of your program in terms of “events” you have to respond to. This should
feel natural to someone raised on JavaScript, and it is also a good
approach for programming microcontrollers. You then want to handle the
event detection/dispatching and the event response as separate problems.
The function doSomething()
should not have to care about the
condition
variable, as it is only used to handle the event detection
and dispatching, which is the responsibility of the event loop.
When i said to execute once I mean, when press a button, it should do
something only once, this control of what should/was executed is a bit
tricking, can go out of control and became a mess.
To detect a button press you do have to manage some state, there is no
way around it. Your program should not respond to the state of the
button (pressed or released) but instead to specific transitions of
this state (press or release events).
In its most basic form, the problem is about detecting a falling edge
(or rising edge, depending on how the button is wired) on the input
signal. This is handled by remembering the state you had on the previous
loop iteration, and comparing this previous state with the state you are
reading right now, as show in the “Handle a rising edge on an input pin”
example above.
Real buttons have an additional issue, namely mechanic bounce: when the
button is actuated, the signal can go through many fast transitions
before settling into a stable state. In order to deal with this, you
have to look at the timing of the transitions and sort out the legit
ones from the spurious ones. This is called debouncing. There are a
few libraries out there than can handle this for you. Most will also do
edge detection, which makes handling button presses as simple as testing
for button.fell()
(again, see example above). Some will even report
distinct event types for short press, long press and double press.