Events in Arduino

I am sure there is a better way to word the title to this question, and if I did know it, I think I'd have better luck with a search :slight_smile:

The problem I am having is not unique to Arduino but really applies to all "loops." I know it all leads back to me thinking in too much of an ActionScript model. I think of nearly every project I build as needing event-based structure, but I know that it's just that I don't know the best way to execute my ideas in this format. My latest efforts leave me at a familiar stumbling block -- the bloated code that results from me trying to make things happen inline or solely in loops. This is not working code below, but it may illustrate where I get stuck, and the problems I continue to have, in other words I know there must be a better way and I just don't know what it is. Let's say in this case I wanted to trigger a synth via midi to play a scale, fast to slow, or slow to fast, in a tweening/easing fashion, behaving differently based on some analog input ranges. Let's also say that I want to allow input from multiple pins at one time. I'd send some values over a serial/midi port but I'd need some way to send them gradually, and in an overlapping fashion so that more than one sequence of midi signals could be sent to different synths, etc.

void setup(){
//various instantiated things etc.
int cSynth = 1;
}

void loop(){

if (analogRead(input1) != currentVal1){ // if the value has changed
currentVal1 = analogRead(input1);

if (analogRead(input1) >= 50 && analogRead(input1) <= 100){

startTween1(cSynth1);
}else if analogRead(1) >= 100{
startTween2(cSynth1);
}else{
startTween3(cSynth1);
}
}

if (analogRead(input2) != currentVal2){// if the value has changed do something
currentVal1 = analogRead(input1);

if (analogRead(input2) >= 50 && analogRead(input2) <= 100){

startTween4(cSynth2);
}else if analogRead(input2) >= 100{
startTween5(cSynth2);
}else{
startTween6(cSynth2);
}

}
}

void startTween1(int var){
// initiate a tween a value from 255 -> 0 over time with easing and send to midi port
// As it is now I'd have to set some sort of timer within the main loop, each time doing some maths that will update this value.
// Feel there must be a better way, a set and forget or "callback" structure.
// For me this is always the same problem for events that overlap/span time.
}

void startTween2(int var){
// initiate a tween value from 0 -> 255 and send to midi port
}

void startTween3(int var){
// initiate a tween value from 10 -> 0 and send to midi port
}

In the Actionscript world I'd have a Tween class or such, that I create a new instance of, add an object to it, and every time an event fired (or sensor was in range) I'd TweenObject1.start() and that class would manage the tweening of that object, maybe with tens of other objects and inputs continuing to listen/respond. So in summary I want to be able to keep reading inputs and tweening values, rather than have to execute the entire "tween" before I move on. I guess the key is how to "initiate a process" without continually updating it and testing it in the main loop (or is there a way at least to abstract it some, even if it's technically still using the loop) The code that results (when I want to do more than manage a few simple actions) quickly becomes a mess and unusable. Sorry if this is a convoluted way to ask this question.

Thanks!

I guess the key is how to "initiate a process" without continually updating it and testing it in the main loop (or is there a way at least to abstract it some, even if it's technically still using the loop) The code that results (when I want to do more than manage a few simple actions) quickly becomes a mess and unusable.

You can have functions that you call on every pass through loop(). In each function, you can determine if it is time to do something. If so, do it and reset the last time that event occurred. If not, just return.

Actionscript is a PC world thing, where multiple threads are possible. Writing software for the Arduino requires a completely different mindset.

Think about how YOU would do things, given a watch and a notepad, NOT how you would tell a computer to do things.

PaulS:

I guess the key is how to "initiate a process" without continually updating it and testing it in the main loop (or is there a way at least to abstract it some, even if it's technically still using the loop) The code that results (when I want to do more than manage a few simple actions) quickly becomes a mess and unusable.

You can have functions that you call on every pass through loop(). In each function, you can determine if it is time to do something. If so, do it and reset the last time that event occurred. If not, just return.

Actionscript is a PC world thing, where multiple threads are possible. Writing software for the Arduino requires a completely different mindset.

Think about how YOU would do things, given a watch and a notepad, NOT how you would tell a computer to do things.

I have to say it still feels like having a million little calls to functions with their respective timers feels inefficient. And I realize they are different worlds, and I think I clearly stated awareness of that different mindset above. What I'm saying is that calling functions on every iteration of the loop feels like a waste of cycles, and for me adds up to really long, messy and bug-prone code. Breaking things out into functions if it's all executing in order anyway...does absolutely nothing other than group things... unless I have a proper way of utilizing structure like a callback, it's really not much help. As I've said, once there are more than a couple of inputs and outputs this gets ugly fast. Thanks for the advice, I'm already aware of the mindset, and as I've said it still feels like there must be a better way. Even if it's a higher level framework or library.

If anyone has any tips on what I could look at next to get past this hump, I'd appreciate it. It's been a year or more at this plateau.

How many sensors and how many synths do you envision for your system - and what kind of time precision do you need to deliver an "acceptable" performance?

At the moment I have six analog inputs, and time precision will have to be "as good as I can get." I have 3 synths/channels I can use, but it could also be just one physical instrument receiving a bunch of different parameters as cc messages.

Overall though this example is used to ask a more general question, as I'm trying to get better at this kind of code and I feel like I always end up in the same backwater.

What I'm saying is that calling functions on every iteration of the loop feels like a waste of cycles, and for me adds up to really long, messy and bug-prone code.

Function calls are pretty damned fast. Modular, debugged, code is generally smaller than a really big loop().

Breaking things out into functions if it's all executing in order anyway...does absolutely nothing other than group things

I have to disagree. Putting related code in a function with a meaningful name does more than "group things".

unless I have a proper way of utilizing structure like a callback, it's really not much help.

There are timer libraries, with callbacks. They obviously have to do the same work you would have to do to determine if it is time to make the callback.

Even if it's a higher level framework or library.

All that a library or "higher level framework" does is shift the burden of writing code to someone else. It doesn't reduce the amount of code needed.

You are free to develop such a library if you think you can improve on existing ones.

A while ago I wrote some code to allow spawning a large number of independent, asynchronous tasks on the arduino using a simplistic cooperative multitasking approach. I think it would allow you to keep track of all eight analog inputs and (depending on control complexity) drive the same number of synths. I based the entire lash-up on being able to schedule function calls for dispatch either at a specific time or after a specific interval.

Because it's cooperative multi-tasking, the timing accuracy of those scheduled events is dependent on the currently-running function not hogging the CPU. In my current project I have three sensor inputs (temperature, pressure, and geiger counter) and seven output devices (3 solenoid valves, 2 steppers, data logging, and a color LCD display) and seem to be able to keep timing accuracy (generally) to -0/+50 usec on a Mega 2560.

I posted an early version of the code as an attachment to a post here on February 26 at blink 1.05 - Programming Questions - Arduino Forum

I don't know if it's the kind of solution you're seeking, but the code might at least provide a few ideas... :grin:

There's more than one way to skin a cat. This is a sketch for testing hardware, with the concept of a base polling class, then inheriting from that class (which knew how to do the timing) for the other functions I needed. So the main loop becomes a list of calls to the poll method of the objects, and each one does its own timing (the call to retime).

It is not as low level as the example from Morris, but may be suitable if you do not need a varying number of tasks/times, and like to think of objects.

#include <EEPROM.h>

/* Blink, controlled via serial line.  A to increase blink rate, B to
 * decrease, R to reset 
 *
 * Written with a base polling class, and three classes using that 
*/

#define TIME_ADD 11
#define PIN_ADD 10

  int test_in_pin =2;
  int test_out_pin = 10;
  boolean V_report = true;

// class to poll for next interval
class Poll_Wait {
private:
  unsigned long milliwait;
  unsigned long millinext;
public:

  void change_time(unsigned long new_wait) 
  {
    milliwait = new_wait;
  }

  bool retime() {
    unsigned long m = millis();
    if (m >= millinext) {
      millinext += milliwait; 
      return true;
    }
    else
    {
      return false;
    }
  };

  Poll_Wait(unsigned long wait_interval) : 
  milliwait(wait_interval) {
    retime(); 
  };
}; // end class


// does the checking of the test line for the current value
class Poll_test_line : Poll_Wait {
 private:
   unsigned long interval;
  
public:
 Poll_test_line() : Poll_Wait(800) {};
 void poll();
};

void Poll_test_line::poll()
{
  int v;
  if (retime()) {
      v = digitalRead(test_in_pin);
      // suppress reporting (but left the read in to keep the timing the same
    if (!V_report) return;
    if (v == 1) {
      Serial.print("thi ");
    }
    else
    {
      Serial.print("tlo ");
    }
  }
}


// sets up the interval for when the blink occurs
class Poll_Blink : 
Poll_Wait {
private:
  int ledPin;                // LED connected to digital pin 13
  int value;                // previous value of the LED

public:
  Poll_Blink() : 
  Poll_Wait(1000), ledPin(test_out_pin), value(LOW) {
    pinMode(ledPin, OUTPUT);
  };

  void change_time(unsigned long new_wait) 
  {
    Poll_Wait::change_time(new_wait);
  }
  
  void new_pin(int pin) 
  {
    pinMode(ledPin, INPUT);
    ledPin = pin;
    pinMode(ledPin, OUTPUT);
  }


  void poll() {
    if (retime()) {
      // if the LED is off turn it on and vice-versa.
      value = (value == LOW) ? HIGH : LOW;
      digitalWrite(ledPin, value);      
    }
  }
}; // end class

Poll_Blink  b1;

// does the checking of the serial line for a new interval value
class Poll_Serial : 
Poll_Wait {
private:
  unsigned long interval;
  int in_pin;
  int out_pin;

public:
  Poll_Serial() : 
  Poll_Wait(15), interval(EEPROM.read(TIME_ADD)), in_pin(test_in_pin), out_pin(test_out_pin) { 
  };

  unsigned long get_time(void) const
  {
    return interval;
  }

  void poll(void)
  {
    if (retime() && Serial.available()) {
      int c;
      c = Serial.read();
      switch (c) {
      case 'H':
        Serial.print("F/S/R Faster/Slower/Reset on blinking out pin. ");
        Serial.print("E/D Enable/Disable output pin 9.\n");
        Serial.print("Q/W Test in pin up/down. ");
        Serial.print("P/O Test out pin (blinking) up/down\n");
        Serial.print("V/v Report input pin state on/off\n");
        Serial.print("K/k Store/Retrieve test out pin\n");
        break;
      case 'F': // faster
        interval /= 2;
        EEPROM.write(TIME_ADD, interval);
        break;
      case 'S':  // slower
        interval *= 2;
        EEPROM.write(TIME_ADD, interval);
        break;
      case 'R':  //reset timing
        interval = 1000;
        break;
        case 'E': // enable outputs
        digitalWrite(9, HIGH);
        break; 
        case 'D':  // disable outputs
        digitalWrite(9, LOW);
        break;
        case 'Q':  // in pin up
            test_in_pin += 1;
            if (test_in_pin > 8) { test_in_pin = 8; };
            Serial.print("in pin now ");
            Serial.print(test_in_pin);
            Serial.println();        
        break;
        case 'W': // in pin down  
            test_in_pin -= 1;
            if (test_in_pin < 2) { test_in_pin = 2; };
            Serial.print("in pin now ");
            Serial.print(test_in_pin);
            Serial.println();
        break;
        case 'P':  // out pin up
            test_out_pin += 1;
            if (test_out_pin > 13) { test_out_pin = 13; };
            Serial.print("out pin now ");
            Serial.print(test_out_pin);
            Serial.println();
            b1.new_pin(test_out_pin);
        break;
        case 'O':  // out pin down
            test_out_pin -= 1;
            if (test_out_pin < 10) { test_out_pin = 10; };
            Serial.print("out pin now ");
            Serial.print(test_out_pin);
            Serial.println();
            b1.new_pin(test_out_pin);
        break;
        case 'V':  //reporting enabled
          V_report = true;
          Serial.print("input pin reporting on ");
        break;
        case 'v':  //reporting disabled
          V_report = false;
          Serial.print("input pin reporting off ");
          break;
          case 'k': // write the current out pin value to EEPROM
            EEPROM.write(PIN_ADD, test_out_pin);
          break;
          case 'K': // read the out pin value from EEPROM and use it
            test_out_pin = EEPROM.read(PIN_ADD);
            b1.new_pin(test_out_pin);
        break;
      default:
        Serial.print("char not recognised ");
        Serial.println(c);
      }
    }
  }
}; // end class (and don't forget the semicolon)




class Poll_Report : 
Poll_Wait {
private:
  unsigned long old_time;
  Poll_Serial *pp1;
  
public:
  Poll_Report(Poll_Serial *_pp1) : 
  Poll_Wait(100) {
    pp1 = _pp1;
  };


  void poll() {
    if (retime()) {
      if (old_time != pp1->get_time()) {
         Serial.println(pp1->get_time()); 
         old_time = pp1->get_time();
      }
            
    }
  }
}; // end class

Poll_Serial p1;
Poll_Report r1(&p1);
Poll_test_line ptl1;



void setup()
{
  Serial.begin(9600);
  pinMode(test_in_pin, INPUT);
  pinMode(9, OUTPUT);
  digitalWrite(9, LOW); // disable outputs
}

void loop()
{
  p1.poll();
  b1.poll();
  r1.poll();
  b1.change_time(p1.get_time());
  ptl1.poll();  
}

If anyone has any tips on what I could look at next to get past this hump, I'd appreciate it. It's been a year or more at this plateau.

My tip: learn about different programing paradigms. If you insist on event driven programs you ultimately limit yourself.

There are event driven, procedural, object oriented, stack oriented, function oriented and probably much more approaches. Whoever thinks that one is superior over the other has probably just not enough knowledge and/or experience.

misterdna:
I have to say it still feels like having a million little calls to functions with their respective timers feels inefficient.

misterdna:

[quote author=Morris Dovey link=topic=98853.msg741082#msg741082 date=1332965564]
How many sensors and how many synths do you envision for your system ...

At the moment I have six analog inputs ...
[/quote]

Well six is hardly millions, is it?

The processor is pretty fast. Checking a dozen or so events (eg. inputs, whether time is up for playing a note) isn't particularly inefficient. Especially when the time-frame for music tends to be 10ths of a second, not milliseconds.

I would follow PaulS's advice. Keep it simple. Think in terms of how you might do it (perhaps at a slower rate) armed with a clock, pencil and paper.

If you have six or so inputs, that part could be an array. So rather than having 6 functions you have one function called six times. It could all be quite neat.

... with their respective timers ...

If you are working out when dinner is cooked, and when your favourite TV program starts, you don't need two clocks do you? One clock will serve both purposes.

Thanks so much, I'll check it out!

If you are working out when dinner is cooked, and when your favourite TV program starts, you don't need two clocks do you? One clock will serve both purposes.

I understand the watch and notepad analogy and I understand that frameworks are other people writing code, believe me. But honestly I've been around this block a few times. I don't have time to hand roll everything I do, I admit it. I use what I can get the job done with. Either my brain is not going to absorb this kind of thing or I'm learning the wrong way. I never end up feeling like it works well this way, and is hard to debug, so I think I'm doing it wrong. When those six sensors all do different things and are interdependent in terms of state and input range, E.g. "this one has to be in a certain range before this other one can start its timer loop" -- it gets messy and I never end up with code I feel very confident about. This usually gets to be a couple hundred lines quick. Your suggestion for using an array is the kind of thing that makes life easier and code cleaner to manage. As far as the clock goes I'm not sure I understand though. Can you help me understand that part?

let's say I'm trying to just create "faders" and each sensor applies to a track/channel. If I'm easing through values for each parameter that I'm trying to manipulate, and these are coming from different inputs, then each has to keep resetting the interval of time it waits before it sends a serial or midi value... in the case of a rather primitive easing, E.g. wait 125 ms, now wait 250, now 500, now 1000, etc. As well as detect when it has reached any min or max threshold. As I see it they each individually need to compare a unique "last reset time" to current time. Is there a better way?

When I refer to the clock I was alluding to your mention of "timers". I wasn't sure if you meant hardware timers, of which you only have a few.

At the end of the day, a complex problem can't be simplified past its original complexity, if you know what I mean. You can have individual functions, arrays, classes, event libraries, lots of "if" tests, whatever. With one solution the basic code can be a mess, with another the code is simplified but then you need to understand (and maybe write yourself) the libraries which simplify the basic code. So you have just transferred the problem, not eliminated it.

If you wanted to go down the route of events, and "do something in 10 mS" then you could write a simple class to do that (there probably is one already). You might pass down to it how much time you want to elapse, and a function to be called when that time is up.

I'm a bit opposed to searching for the solution before the problem is well-defined. We've seen this a bit before where people want to know how to "break out of interrupts" or "go back to the start of the main loop" without really clarifying in what way that solves their problem.

The initial code you posted seems to me to lend itself to being turned into a function. That is, instead of:

if (analogRead(input1) >= 50 && analogRead(input1) <= 100){
...
}

if (analogRead(input2) >= 50 && analogRead(input2) <= 100){
...
}

So that could be turned into a function that reads a specified analog input, compares them to be in some sort of range, and then executes some function.

Perhaps the netduino would suit you more. It will allow you to continue to use the event-driven paradigm.

http://netduino.com/

Excellent points, thanks again for the help, this does help me think about what I really need in terms of classes. Part of my frustration is just not being able to write a program that writes programs :wink:

dxw00d:
Perhaps the netduino would suit you more. It will allow you to continue to use the event-driven paradigm.

http://netduino.com/

Ah! Hadn't considered that. Thanks

Part of my frustration is just not being able to write a program that writes programs :wink:

I've done that. It's a lot more work than you'd guess - and neither C nor C++ is a particularly good tool for the job. :grin: