learner's question (could be wrong) - Event-driven programming best practices?

This is a learner's question, I could be wrong or offend people, if so I apologize.

Are there common practices to add event-driven programming to an Arduino sketch?

For example, suppose that I wanted to add an event that occurred every 5 seconds (or handled late if another program didn't return in time). Do I have to use millis and manually do this, or is there a best practice to add event-driven programming?

For example (this is just an illustrative example) suppose you wanted these three things done:

  • Every 5 seconds, call function 1
  • Every 3 minutes, call function 2
  • Whenever button1 state changes, call function 3
  • Whenever button2 state changes, call function 4

What is the best practice around doing this? Is there an event-driven way to do this?

I realize that there are a variety of approaches.

Thank you for telling me how you would do this. I am trying to learn about best practices for event-driven programming.

(I realize that multitasking will be cooperative, not preemptive, and if for any reason any function didn't return quickly, the others would wait during that time.)

[EDIT: I will be summarizing the extensive information I learned in an article. This subject goes very, very deep.]

You also have timers which can be set to periodically call an interrupt service routine in which you put you own code. You also have pin interrupts which again trigger an interrupt service routine.
For button press detection, it is usual to use the loop() with millis() for debouncing.
For initiating activities at regular intervals, it is usual to use a timer.

There are some time-alarm libraries that will do the first two things. Most of the time they don't do exactly what you want so you end up making your own solution with millis().

The second two things are basic Arduino. The main loop runs thousands of times per second and it's easy to check the buttons fast enough that it appears 'instant' to a human.

But the Arduino can do so much more than that. It can play music, which requires precisely timed pulses at exact frequencies. It can synchronise with rotating machinery and other sensors. It can read and write serial data with precise timing. It can operate RC servos where the pulse width in microseconds is the important variable.

It can also do mathematics and remember things which happened before, to change its output based on what it has stored. There's a bazillion things it can do. Flashing an LED once every 5 seconds is the easiest.

curious48:
I realize that there are a variety of approaches.

No, there are not. Event driven programming is always based on just three things: event queue, event producers, and event consumers. That is the definition of event driven programming.

  • Every 5 seconds, call function 1
  • Every 3 minutes, call function 2
  • Whenever button1 state changes, call function 3
  • Whenever button2 state changes, call function 4

That is essentially twisted and backwards. In that world you would have somethings (e.g. "timers") that produce timing events (put an event in a queue every five seconds; put an event in a queue every three minutes) and separate somethings (usually object instances of a common class) that consume those events. The producers (timers) would have no awareness of consumers (function 1; function 2) and vice versa.

There are implementations that forego an actual queue. Those implementations are too restrictive to be of general use. Even just a queue with a depth of four is vastly more useful. With microcontrollers a queue is essentially required to limit maximum stack depth and allow run-to-completion.

A complete implementation provides a very loose coupling where consumers can subscribe to arbitrary events of interest. That is rarely possible with microcontrollers because of limited resources (managing the subscription lists is a burden).

A practical compromise is to pass each event to every consumer giving them all a chance to consume the event.

This is an excellent place to learn the panacea of event driven programming for microcontrollers (and, indeed, larger systems)...

There isn't an event-driven framework for arduino programming that I see in common use on these boards. I have been thinking of putting one together, of course. The problem is that a general system would tend to use message-passing, which means message objects being dynamically allocated, which can be a bit problematic when memory is tight.

Personally, I use an approach outlined here. That is, I use a set of programming patterns that could be interpreted as event driven.

Having said that:

You know, you could implement message queues with this pattern without having to dynamically allocate things. The key is to use linked lists rather than arrays of listeners. If object A wants to register a listener for an event on object B, A would statically instantiate a listener object and ask B to append it to a list. The listener object abstract superclass would hold a pointer to the 'next' listener. When an event fires, the listenerlist object would walk the list and call the listeners, either using a visitor pattern or with a virtual method.

There would be C++ class templating involved, yes indeed.

The question is: would this framework be simpler and easier to use than what we have?

PaulMurrayCbr:
The problem is that a general system would tend to use message-passing, which means message objects being dynamically allocated, which can be a bit problematic when memory is tight.

Dynamic allocation (I assume by that you mean heap) is not the only available choice. And is certainly not the best choice for a memory constrained environment.

The implementations I have worked with all use an array of structures (a pool of "message objects") with corresponding code to manage the pool. An exhausted pool indicates a design failure.

Delta_G:
I think in the case of these particular requirements:

  • Every 5 seconds, call function 1
  • Every 3 minutes, call function 2
  • Whenever button1 state changes, call function 3
  • Whenever button2 state changes, call function 4

no, such a system would not be any simpler. It would indeed be far more complicated. Although I can see a number of other cases where it might be a great option.

For those requirements, the pattern I use for stuff would work fine. A base class with loop and setup. Two subclasses: a time based one and a pin-based one. You can construct the instance with a 'thing to do' function pointer, or you can subclass further and put the specific code for each item there.

class Runnable {
  public:
    Runnable() {}
    virtual void setup() = 0;
    virtual void loop() = 0;
};

class Button: public Runnable {
  public:
    const byte pin;
    int state;
    Button(byte pin) : pin(pin) { }

    void setup() {
      pinMode(pin, INPUT_PULLUP);
      state = digitalRead(pin);
    }

    virtual void onRise() {}
    virtual void onFall() {}

    void loop() {
      int prevState = state;
      state = digitalRead(pin);
      if (prevState == LOW && state == HIGH) onRise();
      if (prevState == HIGH && state == LOW) onFall();
    }

};

class Timer : public Runnable {
  public:
    const unsigned long intervalMs;
    unsigned long startMs;
    Timer(unsigned long intervalMs) : intervalMs(intervalMs) {}

    void setup() {
      startMs = millis();
    }

    virtual void onTick() {}

    void loop() {
      if (millis() - startMs > intervalMs) {
        onTick();
        startMs = millis();
      }
    }
};

class Func1 : public Timer {
  public:
    Func1() : Timer(5 * 1000L) {}
    void onTick() {
      Serial.println("Function 1!");
    }
} func1;

class Func2 : public Timer {
  public:
    Func2() : Timer(3 * 60 * 1000L) {}
    void onTick() {
      Serial.println("Function 2!");
    }
} func2;

class Button1 : public Button {
  public:
    Button1() : Button(3) {}

    void onFall() {
      Serial.println("button 1 pressed!");
    }
    void onRise() {
      Serial.println("button 1 released!");
    }

} button1;

void setup() {
  func1.setup();
  func2.setup();
  button1.setup();
}

void loop() {
  func1.loop();
  func2.loop();
  button1.loop();
}

curious48:
Are there common practices to add event-driven programming to an Arduino sketch?

For example, suppose that I wanted to add an event that occurred every 5 seconds (or handled late if another program didn't return in time). Do I have to use millis and manually do this, or is there a best practice to add event-driven programming?

It would help if you explain what you actually want to achieve. Personally I have found that the use of millis() as in Several Things at a Time meets all of my needs and it is very flexible, simple to understand and, especially, is easy to debug.

AFAIK any other approach is just a complicated wrapper around the basic BWoD technique and probably wastes memory and CPU cycles - neither of which is abundant.

...R

I created a library to do

  • Every 5 seconds, call function 1
  • Every 3 minutes, call function 2

not sure whether it matches your requirement. Check one of my post, this library is based on Timer library.

It's not event driven, it's a simple library based on millis().

Hi Everyone,

I apologize for the delay getting back to you. This is a very deep subject and I discovered a huge number of existing scheduling, timing, interrupt, and "operating system" libraries and approaches, in addition to what everyone has linked.

First I'd like to state that I'm very humble in this subject and don't mean to offend anyone with my questions: I don't know anything. My questions are meant to poke into how other, more experienced, people would do it.

@sarouje I wanted to answer you first as you wrote a complete library. I haven't been able to review this yet.

The tasks that I gave were meant to serve as a simple example.

This set of frameworks is very extensive, and I haven't read into it - http://www.state-machine.com/

Let me introduce another example for everyone. Suppose that we have a very long-running function that uses machine learning and some kind of recurrent neural network. Okay, this is just funny. I Googled this and found this example: Jordan net: a 3-layer recurrent neural network (neural net) for Arduino DUE - Robotics - Arduino Forum

If you open the file, the very first line in the .ino is:

// to do:

*/

(I don't know what that wave file is supposed to be, it doesn't load for me. - okay, I found it on the Internet archive - at the above link.)

Unfortunately, I'm not an expert in machine learning, so I can't follow the code easily.

But if we assume that it does some difficult, long-running computations then what can we do to process messages under a typical "consumer and producer" framework?

Is that how yield() works? That you just have to sprinkle it into your code whenever you're okay with some other functions getting handled?

I'm afraid that at the moment this subject still is quite wild and difficult.

How can "preemptive" multitasking work without an operating system? (even a small one)? I mean other than hardware interrupt pins.

Let's try a well-defined question: what is preemptive multitasking and does it exist on Arduinos? Can functions be interrupted by events, even without yield'ing control at explicit points?

This might narrow down the number of possible approaches.

I think possibly, for the upcoming article I might work up from simple, manual, hand-made approaches (such as Sony also made) through full frameworks like QP. But it's really hard because there seems to be little information about it.

I might not be the best person to write about this but, since my audience is other people who are also learning, I think I can try to get into it and summarize my findings.

I apologize in advance if I offend people with my lack of understanding. hopefully you can see that I don't purport to know much/anything on this subject and am just learning for the first time.

Preemptive multitasking can be simulated in software. You can have some interrupt source set your supervisor code going, which will store all the registers of the currently-running process and start a new one. But certain operations should not be interrupted. For example, serial data cannot be interrupted in the middle of the byte: you must send a complete byte in one go without pauses and gaps.

So that indicates that Serial must be a function provided by the operating system and all the user threads have to ask the operating system to send and receive on their behalf. I would not be surprised if doing this 'properly' took up 80% of the available memory on an Uno. So, how complex of a program can you write in the remaining 20%? It would not be the sort of complex program that could benefit from having an operating system.

A proper multitasking system like the one in the PC I'm typing on relies on having processor hardware that supports this usage. There's special machine instructions to save and restore all registers very quickly. There's subsystems dedicated to serial (USB) and whatever, which enable the programs to call upon the operating system to perform I/O tasks on behalf of the programs.

The Arduino processors do have a relatively complete interrupt system. For tasks which absolutely must occur at a specific time, such as advancing a stepper motor one step, you can use an interrupt. For serial or PWM tasks, there are specific hardware modules that can work relatively independently. So it's good at doing certain things like controlling motors.

While you could write an "operating system", it is only going to be a toy and it is only going to be capable of running toy programs.

curious48:
I don't know anything.

....

I think possibly, for the upcoming article

Please hold off on the article until you do know something - and preferrably about the subject of your article.

I (foolishly ?) assumed that your Original Post in this Thread was a request for assistance with an Arduino project that you were bulding for yourself.

...R

curious48:
For example (this is just an illustrative example) suppose you wanted these three things done:

  • Every 5 seconds, call function 1
  • Every 3 minutes, call function 2
  • Whenever button1 state changes, call function 3
  • Whenever button2 state changes, call function 4

What is the best practice around doing this? Is there an event-driven way to do this?

I'm not sure what you mean by the question. You write code that detects these conditions, and acts upon them.

eg. (pseudo-code)

if (5 seconds are up)
  function1 ();
if (3 minutes are up)
  function2 ();
if (button1-state-changed)
  function3 ();
if (button2-state-changed)
  function4 ();

That's an "event driven way". What else did you have in mind?

That's how most programs work. They detect conditions and do something if the condition is met.

I think you are over-thinking this.

suppose you wanted these three things done

Four things. :slight_smile:

Hi Robin2. For anyone else reading, Robin wrote about my active arduino project that i'm building, which is a mechanical remote manipulator so you can work on very fine things by hand (mechanically). A bit more than just having a magnifying lens (which still needs very fine hand-eye coordination, if you're trembling and holding things in place it's hard) but not by any means a pick and place robot. In between the two. My summary for him I'm placing in a quote block.

Well I need to do it all - if I don't have any readers, then what good is completing my prototype - I wouldn't have anyone to read about the fact that it's now on Indiegogo (like Kickstarter). So, yes, I am still working on the active Arduino project, which is the thing you helped me with.

I haven't been posting about it much, since 1) this forum doesn't have a mechanics sub-forum and you and others complained about my asking about mechanical aspects here. People specifically said "there are plenty of mechanics forums for that". If you're interested in updates, one thing you could do is vote with your support to open a "General kinematics, dynamics, and statics for Arduino projects: the gears and levers between Arduino and the real world" sub-forum, since I am still working on that and it would be amazing to be able to ask questions about it or post what I find. Instead, I'm posting on other forums, for example you can see me ask a question here - I asked that just yesterday, and got good responses.

I'd love to also ask these questions here but we just don't have the sub-forum for it.

So, absolutely my project is active, you just can't see my current questions because there's no good place for them. I'm doing it, learning about and using a CAD, and have arranged for help with printing them. The project is moving forward.

Separately I am building an audience so that once I have this thing that will be useful to hobbyists and others who cannot afford a $5,000 or $10,000 pick and place machine and are just putting a prototype together by hand, might benefit and support my indiegogo campaign.

The next steps after proving my ability to deliver value to hobbyists via this small project with a relatively simple Arduino subsystem (button activates solenoid - easy), with the goal of being cheap is to explore a more complicated project and deliver that as well. As you know, for that I will be exploring the idea of a shoe-box sized contraption that will catalogue and hold small electronics components. Not a full large dispensing system, something small. that project is on hold until I've shipped the mechanical remote manipulator so I have nothing to say about it, since this forum isn't a "sounding board" for every idea i have. I'm not sure if the shoebox-sized easy catalogue is feasible: I believe that it is. (Though you joked that one part is worth a nobel prize, but I think it may be feasible.) To emphasize, that is completely on hold and I haven't posted anything about it whatsoever. I will only post about it when I've actually shipped my Arduino project.

Finally, the reason that it is very important for me to build credibility and a proven track record of shipping things, is because I would like to sign up up to about 1,000 small niche manufacturers who want to purchase around a thousand each, of a small $5 + $1.50/$2.50 combined Raspberry Pi-like and Arduino subsystem board. Which will be a very long and arduous project and I will also need a lot of credibility. (Several possible partners already got in touch with me, including electronics engineers who have worked on stuff like that). By the way, a ton of my ideas in this space closely match the recently announced $5 omega2 board. It's amazing, very good, and matches a lot of my thoughts on design. But there is an issue. I wrote to them to ask if they would fulfill in batches of 1,000 or 10,000 so I could include them in the article I wrote earlier comparing the availability of Raspberry Pi Zero, Orange Pi One, and C.H.I.P. computers. However, they have not yet responded. Their developer kit components are very expensive in the listed kickstarter - you really have to buy a ton of stuff to actually develop on these boards. So, I don't know if the $5 board is real and can be used in products at scale. Wifi is an expensive component with the ESP8266 being one of the cheapest options and itself costing $2 or so. By including a complete Linux ARM CPU, Wifi (which I actually don't plan to include on my board, as not every application needs it, and also for security reasons), including 64 MB of memory (bumpable to 128) and a 580 Mhz CPU, it seems to me a loss leader at $5. It is my estimation that they would not ship a manufacturer 5,000 of them for $5 each. Their starter pledge kit is $75. So, to me, the $5 seems like marketing which is why I did not add them to the article as an update.

You see I have been writing about and following these developments, which is part of building my credibility and audience. Another thing I have to do is constantly post articles, even while I work on my own project, to keep people interested, to keep them knowing about my board and what I'm doing, etc. On my Forum I'm extremely transparent about wanting to move into development.

My trials and what I'm learning, I'm summarizing for them. These are very popular articles with great analytics (you can see the articles here). When I write about things that are well beyond my current understanding, it also shows that as a curious, interested person, I am able to explore new ideas.

Finally none of the projects I have in mind are really commercial, I want to do it all as a service to the community, and my benefit as a 'commercial' publisher would be getting access to a 1000x boards that I can use in production. The forum is not for profit and I don't have any other benefit from it.

On the forum, people are allowed only to contribute positive things that move things forward, so while you're welcome to contribute, your negative posts would be edited to be more positive or, if they contain no information or anything of value, they would be moved off. This policy is clearly stated and we're transparent about our editing and give people a chance to respond or delete their posts. Our goal is to be a place for substantive, high-quality content about learning.

I actually absolutely loved a remark I saw by you on another thread, saying that the harshest thing anybody says in a forum like arduino.cc is nothing compared to how harsh and unforgiving a compiler is in its error messages. That's fantastic! However, one thing to note is that it really does indicate that compilers could be more friendly! There's no reason only to write 'token type error' whereas it could have written 'A statement was not expected here - did you forget a semicolon on a previous line?' Likewise, although I agree, that you're right, compilers are harsher than the harshest person here -- that still means by being friendlier the harsher people can improve.

I greatly appreciate your help in these forums so I would put up with the most trollish phrasing in the world :smiley: On my forum (which you're welcome to join!) those would be edited however.

Returning to the subject of this thread -- you say I should hold off on the article until I do know something. I've actually had to work with multitasking issues quite a bit, found tons through hours of Googling, and have a lot to say on the subject. Part of this research is being done in this very thread - so, if you have something to say by all means, but otherwise I hope you will let people who are adding to this thread add to it. The article I'm planning goes from hand-doing things or using an exceedingly simple framework, to summarizing the other research I've done including in this thread. I'll post it when it's up! :slight_smile:

Thanks for that. However, I don't believe so. For example, suppose one of the things I do involves a long-running calculation for some reason. Then none of those events would fire until I were done with the calculation. so, in that sense these are not events that have been registered with the system. Moreover, as others stated, there would be no queue. If someone managed to press a button 5 times before my function were done, then even if my function weren't interrupted, the program would not see 5 events afterward. I believe this is the reason people talk about a message queue.

However, I could be very wrong with the above, please understand that I'm still learning about this. When I've come to an understanding, I can go back and strike out earlier mistaken impressions. Don't take this as me indicating that I know something or am overthinking things. Thanks for your understanding. I will be sharing my final thoughts and conclusions after feedback from everyone. :slight_smile:

Thanks for your help.

I think you might be expecting more than what these micro controllers can handle.

If you have some long running application and wanted to call after it completes, then you can always use function callbacks.

Program can see 5 button press, if you are not using delays in loop and listen to these button press and increment some counters.

In any programming language nothing will happen, if you didnt code to handle a given situation. Do you think C# or Java will add some event to message queue automatically without any code instructions from the developer?

For example, suppose one of the things I do involves a long-running calculation for some reason.

That's the reason you don't write long-running functions, calculations or not. However to detect switch presses you can use an interrupt, note the fact that the switch was pressed, and go back to what you were doing.

Your other examples (time being up) is something that hopefully you can check in the main loop. If a function is taking more than 5 seconds to complete you could consider rewriting it.

This is all a bit hypothetical. You are making up "example" cases. More practically, if you have a particular thing you want to do, you are better off discussing that. Interrupts might be useful for some situations, and not for others.

can someone give me an example of what is described in the above line? (i.e. example code that does the above.)

Here's an example that can wait up to 4 seconds to complete blinking before responding to the button. If you test it, you will immediately see how silly it is to wait this long.

/*
  Blink with a button interrupt

  Tested on Arduino UNO, with a pushbutton betwen pin 2 and GND.

  This is not a good way to handle a button, but it is a simple way of testing the interrupt
 */
 
// Pin 13 has an LED connected 
const int led = 13;
// Pin 2 is a special interrupt pin on the UNO
const int ButtonPin = 2;

volatile boolean Pressed = false;
volatile unsigned long LastPressed = 0;

unsigned long BlinkDelay = 2000; //milliseconds between turning on and off

//The interrupt service routine (function)
void InterruptHandler() {
  //Must minimize processing inside an interrupt, just record the fact that it occurred and the time
  Pressed = true;
  LastPressed = millis();
}

void setup() {                
  pinMode(led, OUTPUT);     
  Serial.begin(9600);
  Serial.println("Starting Button/blink test.");
  //attach the interrupt
  pinMode(ButtonPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(ButtonPin), InterruptHandler, FALLING);
}
void loop() {
  digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(BlinkDelay);               // wait for a second
  digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
  delay(BlinkDelay);               // wait for a second
  if(Pressed) {
    Serial.print("Hey, I was busy earlier but someone pressed the button ");
    Serial.print(millis() - LastPressed);
    Serial.println(" milliseconds ago.");
    Pressed = false;
  }
}