2

I come from web world, so i usually code in a more procedurally manner, so the loop is a bit difficult to handle.

i mean if i do something like if(condition) doSomething() it will exec doSomething() a lot, or a click of a button....

bool condition = false;
void loop(){
  if(condition){
    doSomething();
  }
  
}


void doSomething(){
  condition = false;
  // ........
}

iam doing things like this, but i feel its wrong somehow

so iam struggling why could be the "best" architecture to execute doSomething() only once, in a way it not become a hell of state management, some kind of event driven, event sourcing or something more battle tested for arduino

iam totally newbie in this hardware/arduino world

2
  • add a flag that signals that the function already ran ... it is unclear what the actual problem is ... if you have a real world example then add it to your post
    – jsotola
    Commented Jul 22, 2023 at 21:35
  • I guess that for a web browser application, when you register an event in a script, say a button press, that in a loop (hidden somewhere) that button object will be tested regularly and, if pressed, the event handler will be executed. With the Arduino there is no run time system to support such things directly so if you want to test a button regularly, you have to do so explicitly in a loop.
    – 6v6gt
    Commented Jul 23, 2023 at 11:52

3 Answers 3

9

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.

2
  • 1
    Great answer, but I'd just make a modification in your periodic example. I think usually what you prefer when doing a periodic check is to have something periodic, not something guaranteed to be above the time. I mean, if the period is 1 second and the first cycle is 1.1s then I'd prefer the second to be 0.9s to "catch up" rather than 1s (so delays don't add up). So usually I prefer to write last_event_time += event_period;.
    – frarugi87
    Commented Jul 28, 2023 at 8:26
  • 1
    @frarugi87: Edited the answer, thanks. Commented Aug 14, 2023 at 12:09
3

The usual way to do things only once (at startup) is to put them in setup().

You put only statements in loop() that need endless repetitions.

You can think of the underlying main function as:

int main() {
    setup();
    for (;;) {
        loop();
    }
}

Therefore, your example can be:

void setup() {
    doSomething();
}

void loop() {
}

void doSomething() {
    // ........
}

Addition after clarification

You commented:

as far as i know i cant detect a button press outside of the loop right? when i said to execute once i mean, when press a button, it should do somenthing only once, this control of what should/was executed is a bit tricking, can go out of control and became a mess

With that requirement, you are right, you need to put the check and reaction inside loop(). However, you can "hide" the "mechanic" inside the called function:

void setup() {
}

void loop() {
    doSomething();
}

void doSomething() {
    static bool isDone = false;
    if (!isDone && condition) {
        isDone = true;
        // ........
    }
}

A dedicated variable is simple and clear. You might choose some other type and value, depending on the actual things to do.

2
  • as far as i know i cant detect a button press outside of the loop right? when i said to execute once i mean, when press a button, it should do somenthing only once, this control of what should/was executed is a bit tricking, can go out of control and became a mess Commented Jul 25, 2023 at 14:42
  • 1
    @NeuberOliveira See my edit. It is always better to tell us your complete intent in the question. ;-) Commented Jul 25, 2023 at 16:20
0

You don't have to use their "setup" and "loop" paradigm, you know. This will work:

#include <Arduino.h>

int main ()
  {
  init ();  // initialize timers
  Serial.begin (115200);
  Serial.println ("Hello, world");
  Serial.flush (); // let serial printing finish
  }  // end of main

So, inside your code, if you want to do something once, do it once. If you want to make a loop, make one. Just like in C++. Which is what the Arduino code is.


so iam struggling why could be the "best" architecture to execute doSomething() only once

Put it in setup if you want to run with their "setup" and "loop" paradigm. It was just designed for beginners, to clarify that some things you do once, and some things you do in a loop.

Your Answer

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

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