Can we do multitasking?

@jurs

Thanks for your answer.

The question was posed just in what concerns the apparent "multitasking".

I am an unusual programmer (yes, I know that almost all of the programmers think they are unusual); altough I've not dedicated all my professional career to it (programming), I developped an alarm 8085 based system in 1979 and I've made some other programs that were quite interactive with both operator and evironement. Even in case the software that do not interface with real world in real time I found FSM method quite useful to programm interactive software (by the way: I've never used object oriented programming).

The point is that, coming back to arduino programming, I discovered the Robin's "Doing several things . . . " (I find it both pedagogical and usefull). On the other hand i wrote a small piece of C code to move a robot: I did'n manage it working until I used FSM. But, and this is the question, I cannot see the difference (in terms of acieving tasks to look simultaneous) between the two approaches, altough I feel I wouldn't be able to program some complicated software without using FSM tecnique.

Am I wrong?

I think that any comparison of different "multitasking" systems has to take into account the overhead associated with the housekeeping. In the Several Things at a Time model that overhead is the test if (millis() - prevMillis >= interval) { that is generally used in each function and most of the time (perhaps 90% or more) returns false.

A time slicing system would have the overhead of saving and restoring the stack and registers and the other stuff that is necessary to figure out which task to call next at every Timer interrupt.

Another issue that needs to be considered is how to deal with delinquent functions that, for example, get into an infinite loop() or a too-long wait for something to complete. In the Several Things model it is clearly a matter for the programmer to deal with that. But, for example, the Linux programmers have no idea what sort of stupid thing I might try to do and their system needs to be able to deal with me. This also adds to the housekeeping overhead.

Because an Arduino is small and slow the housekeeping overhead should be as small as possible.

I would be very interested if someone could write the functionality of the Several Things example using a different multi-tasking technique so that we could compare the methods.

...R

For my Fencing Scoring machine, which tracks time, score, RF remote commands, and touches made, I just did it as series of tests.
RF command received via VirtualWire? Process it: start/stop/set/reset the time, change the score, etc. Set the "Display Update needed flag".

Touch made? Stop the timer, turn on the appropriate touch light (left/right score, left/right ground, left/right off target). Send light info to remote light box. Ring buzzer.
One second elapsed & timer running? Change the appropriate variable for MAC7219 to display, set the "Display Update needed flag", stop time running if down to 00:00. Ring buzzer.

Update needed flag set? clear it, send info to the MAX7219 for display.

In most cases, loop() is just checking for the 3 or 4 things very quickly, and every once in a while some action is needed, generally turning on/off a light, or updating the timer, and responding to an RF command from Virtual wire, so the system is very responsive to anything happening.

If you would allow me, you might find the following link of interest:

It has the interesting property of NOT requiring "millis" or "delay" yet can maintain timing via Timer1 support,
and uses a state machine which does not require a tutorial.

There is a youtube ( 6 min. ) clip which just demonstrates the Timer1 support:

TechnoBubba:
If you would allow me, you might find the following link of interest:

Can you post the code here, please?

Is your program something that is specifically designed for blinking an LED or is it a more general system for managing timing?

...R

you can use the ufficial scheduler also on Arduino Avr chip.
inserting a yield within a timer interrupt , such as the watch dog , you can get the preemption.

Sounds like a couple of recent spark fun exercises:

you can view preemption example in this thread
And sound it's more like because are full Arduino compatible, please write +1 on pull request

Responding to #25 ( Robin2)


#include <TimerOne.h>  // can also use TimerThree instead (Mega ONLY!)
 
// Many Thanks go to --> https://github.com/PaulStoffregen/TimerOne
// Note: we're only using it for a cyclic interrupt source

//------------------------------------------------------------------------
//  Arduino State Machine MultiTasking 

//  Using a Timer1 interrupt to process multiple
//  global "software" counters.

// This example works only on the Arduino Mega,
// but can be modified for the other boards.

// The code:
// *  State Machine based execution
// *  Allows 'cooperative' multitasking while maintaining 'timing.
// *  Eliminates the '53' day overflow w. Millis

// created 13 Jan. 2016
// by Vic Bojarski
// This example code is in the public domain.
//------------------------------------------------------------------------

//--------------------------------------------------------------------
// These are "started" by setting them to a 'time' value
// max value is 65535 times 0.1 sec --> (6553 secs)
// These can be made 'unsigned long' if needed (at higher interrupt rates).
// (Don't confuse that with the hardware 16-bit resolution)
//--------------------------------------------------------------------
 
unsigned int volatile TimR1 = 0 ;
unsigned int volatile TimR2 = 0 ;
unsigned int volatile TimR3 = 0 ;

unsigned int state_var = 1 ; // my 'state variable' actually an "index" for array

// 'state' array of pointers to functions
void (*st_array[4])(void) = {state0, state1, state2, state3} ;


void state0(void) {
  state_var++ ;
  return ;
}

void state1(void) {
  if (!TimR1) { // Is TimR1 == 0 ?
    digitalWrite( 22,  digitalRead( 22 ) ^ 1 ); // XOR and output
    TimR1 = 17 ;  // TimR1 started !
  }
  state_var++ ; //   increment state_var
  return ;
}

void state2(void) {
   if (!TimR2) { // Is TimR2 == 0 ?
    digitalWrite( 24,  digitalRead( 24 ) ^ 1 );
    TimR2 = 12 ;  // TimR2 started !
  }
  state_var++ ; //   increment state_var

  return ;
}

void state3(void) {
   if (!TimR3) { // Is TimR3 == 0 ?
    digitalWrite( 26,  digitalRead( 26 ) ^ 1 );
    TimR3 = 24 ;   // TimR3 started !
  }
  state_var = 1 ; // back to begin

  return ;
}

void setup()
{
 // Initialize the digital pins as output.
  pinMode(22, OUTPUT);
  pinMode(24, OUTPUT);
  pinMode(26, OUTPUT);

    
  // Initialize the Timer1
  Timer1.initialize(100000); // set a timer of length 100000 microseconds
  //(or 0.1 sec - or 10Hz  )
  Timer1.attachInterrupt( timerIsr ); // attach the service routine here

}

void loop()
{
  // Main code loop
  // NOTE: instead of the "digitalWrite" a 'State Machine' is being called.

// The line below is a "dispatch" (call) to one of the pointers in the array
  st_array[state_var]( ) ;
 
}

/// --------------------------
/// ISR Timer Routine
/// Please note: BY DECREMENTING WE AVOID THE 'OVERFLOW' PROBLEM.
///              EACH TIMER IS 'SELF-RESETTING'.
/// These can me made 'unsigned long' if more time (resolution) is needed or the
/// interrupt rate is higher.
/// --------------------------
void timerIsr()
{
  if (TimR1)
    TimR1-- ; // Only decrement if non-zero !!!!!!!!!!!!
  if (TimR2)
    TimR2-- ; // Analogy is that of an "egg-timer" which self resets!!!!!
  if (TimR3)  // Can add more "egg-timers" if needed.
    TimR3-- ;
}

The code is an example of a simple state-machine using the Timer1 interrupt and 'global' software
counters to create fast and efficient timing w/o millis or delays. The state machine execution is based on an array of pointers to those states. Dispatch to the state is via a pointer to function indexed by
an integer ( state_var / state variable). Basically a 'faster' version of a 'switch' statement. The fact that the states themselves execute a 'trivial' led blink should not detract from the program's value or novelty.

Reply to #27 ( keithRB)

Not quite, since my code doesn't have a "Rollover" problem because I'm decrementing instead. This
guarantees a 'reset' on the counter. But yes, this gets done at interrupt.
Thanks

TechnoBubba:
Responding to #25 ( Robin2)
...

The code is an example of a simple state-machine using the Timer1 interrupt and 'global' software
counters to create fast and efficient timing w/o millis or delays. The state machine execution is based on an array of pointers to those states. Dispatch to the state is via a pointer to function indexed by
an integer ( state_var / state variable). Basically a 'faster' version of a 'switch' statement. The fact that the states themselves execute a 'trivial' led blink should not detract from the program's value or novelty.

I like this concept a lot, but I don't care a great deal for doing it with TimerOne. This sort of thing would be perfect for using with code tacked onto the end of the millis ISR. I really like this old thread, and wish the code would be added to the Arduino core:

http://forum.arduino.cc/index.php?topic=46928.0

Response to dmjlambert (#31)

Anything that uses millis which increments is "doomed" to rollover and requires extra code to work around that problem. Please see #30.
Thanks. :slight_smile:

Can we do multitasking?

Can we cook bacon and eggs at the same time?

Hint: yes.

In attempting to do something complex, you run out of pins before you run out of anything else.

A better solution can sometimes be to get two arduinos. They're twenty bucks. If the components have to talk to each other, then you can do the co-operative multitasking on one arduino, or you can have multiple microprocessors talking o each other.

We always talk about use millis and you end up with the usual Christmas tree,Let's take a case a little more real and serious.
Implement S.N.A.P. protocol without reading the packet blocks the execution of the main program .

This is my simple implementation with multitask

struct snap
{
    uint8_t hdb2;
    uint8_t hdb1;
    uint8_t dst[3];
    uint8_t src[3];
    uint8_t data[64];
    uint8_t chk;
};

struct snap incoming[8];
uint8_t pw = 0;
uint8_t pr = 0;

uint8_t sex_osread(void)
{
    while ( 0 == Serial.available() ) delay(10);
    return Serial.read();
}

void snap_read(void)
{
    uint8_t d,i,c,crc;
    struct snap* s = &incoming[pw & 0x7];

    //start byte
    while( 0x54 != sex_osread() );

    //header2
    s->hdb2 = sex_osread();
    crc = _crc8(s->hdb2, 0);

    //header1
    s->hdb1 = sex_osread();
    crc = _crc8(s->hdb1, crc);

    //dest address
    c = (s->hdb2 >> 6);
    for( i = 0; i < c; ++i )
    {
        s->dst[i] = sex_osread();
        crc = _crc8(s->dst[i], crc);
    }

    //src address
    c = ( (s->hdb2 >> 4) & 3);
    for( i = 0; i < c; ++i )
    {
        s->src[i] = sex_osread();
        crc = _crc8(s->src[i], crc);
    }

    //proto not used
    c = ( (s->hdb2 >> 2) & 3);
    for( i = 0; i < c; ++i )
    {
        d = sex_osread();
        crc = _crc8(d, crc);
    }

    //get data
    int cdata = s->hdb1 & 0x0F;
    if ( cdata > 8 && cdata < 0x0F )
        cdata = 0x10 << ( c - 9 );
    else if ( cdata == 0x0F )
        cdata = 64;

    //add cmd to data
    cdata += s->hdb1 >> 7;

    //read
    c = 64;
    for( i = 0; i < cdata && c > 0; ++i, --c )
    {
        s->data[i] = sex_osread();
        crc = _crc8(s->data[i], crc);
    }
    for(; i < cdata; ++i)
    {
        d = sex_osread();
        crc = _crc8(d, crc);
    }


    //error
    switch ( (s->hdb1 >> 4) & 7 )
    {
        case 0: //none
            s->chk = 0;
        break;

        case 1: //3time not used;
        return;

        case 2: //checksum not used
        return;

        case 7:
        case 3: //crc 8
            s->chk = sex_osread();
            if ( crc != s->chk ) return;
        break;

        case 4: //crc 16 not used
            d = sex_osread();
            d = sex_osread();
        return;

        case 5: //crc 32 not used
            d = sex_osread();
            d = sex_osread();
            d = sex_osread();
            d = sex_osread();
        return;

        default: //impossible but return :)
        return;
    }


    //check available space
    while( ((pw + 1) & 7) == (pr & 7) ) delay(10); 
    ++pw;
    
}

You know produce a code as elegant with the use of millis and/or state machine ?

Referring to @TechnoBubba's Reply #29

Thanks for the example.

I know I am just a crusty old luddite, but I suspect your concept will be even more difficult to grasp by those who struggle with Blink Without Delay.

There is another current Thread about multitaking systems. It would be nice if there was a program that could test each system to see which facilitates the greatest useful throughput - i.e. most CPU cycles available for the tasks rather than the control.

...R

I do not think that the scheduler has been invented to run faster your machine, to go fast you do not have to use it.
it is also true that directly write assembly code is more compact and faster , because it uses C? , or even more complex C++ certainly not born for the MCU.

I think we must be able to choose the method most suitable to the specific problematic.

Robin2:
I know I am just a crusty old luddite, but I suspect your concept will be even more difficult to grasp by those who struggle with Blink Without Delay.

I am with Robin on this. Way too complicated and very off putting compared with BWOD/millis() concepts.

The scheduler should not be a substitute to the millis ().
They are two different things.

Millis() it seems to be very complicated, if you do a search on the forum you will find thousands of threads of millis and the scheduler is not the solution to that problem.
To solve the problem of millis () could be simplified with a delayAsync()

/*********************************************/
/* Delay Async v. beta                       */
/* vbextreme <vbextreme@vbextreme.netai.net> */ 
/*********************************************/

uint8_t _delayAsync(uint8_t id, uint32_t ms)
{
    struct ellapsed
    {
        uint8_t id;
        uint32_t Mst;
        uint32_t mst;
        struct ellapsed* next;
    };
    static struct ellapsed* at = NULL;
    struct ellapsed* t;
    
    uint32_t mst = micros();
    uint32_t Mst = millis();
    
    if ( at && at->id == id )
    {
        if ( millis() - at->Mst >= ms - 1)
        {
            mst = at->mst;
            t = at;
            at = at->next;
            free(t);

            while ( micros() - mst < ms * 1000 );
            return 1;
        }
        return 0;
    }

    for ( t = at; t->next; t = t->next )
    {
        if ( t->next->id == id )
        {
            if ( millis() - t->next->Mst >= ms - 1)
            {
                mst = t->next->mst;
                struct ellapsed* r = t->next;
                t->next = t->next->next;
                free(r);
                
                while ( micros() - mst < ms * 1000 );
                return 1;
            }
            return 0;
        }
    }

    t = (struct ellapsed*) malloc( sizeof(struct ellapsed) );
    t->id = id;
    t->mst = mst;
    t->Mst = Mst;
    
    t->next = at;
    at = t;
    
    return 0;
}

#define delayAsync(MS) _delayAsync(__COUNTER__, MS)

/********************************************************************/

a big and complicated to be able to write code like this:

loop()
{
    if ( delayAsync(500) )
    {
        state1 = !state1;
        digitalWrite (13, state1);
    }

   if ( delayAsync(1000) )
    {
        state2 = !state2;
        digitalWrite (13, state2);
    }
}

vbextreme:
Millis() it seems to be very complicated, if you do a search on the forum you will find thousands of threads of millis and the scheduler is not the solution to that problem.

So what problem is it intended to solve?

I don't see the millis() concept as any more complicated than the kitchen clock.

...R