Pages: [1] 2   Go Down
Author Topic: runEvery (the next Blink Without Delay)  (Read 5099 times)
0 Members and 1 Guest are viewing this topic.
Offline Offline
Edison Member
*
Karma: 17
Posts: 1041
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Code:
#define runEvery(t) for (static uint_16 _lasttime;\
                         (uint16_t)((uint16_t)millis() - _lasttime) >= (t);\
                         _lasttime += (t))

Almost every day, new users on the Programming Questions forum are directed to the Blink Without Delay example and struggle to understand it. With a little help, I have come up with this macro to make running things asyncronously a little easier.

Using this macro is easy. You call it like a control statement (such as if, while, or for) and give it the argument of after how many milliseconds it should run, and call it within loop(), like this:
Code:
void loop() {
  runEvery(500) Serial.println("500 milliseconds have passed since last printed");

  runEvery(100) {
    Serial.print("Sensor Value: ");
    Serial.println(analogRead(A1));
  }
}
This will call the first print every 500 milliseconds, and the second set every 100 milliseconds forever.
You just have to make sure that there are no blocking calls (especially delay()) within loop(). However, this is easy if you just use this macro for everything.

Thus, I present the next BlinkWithoutDelay:
Code:
// Define the macro. Should be put in Arduino.h eventually
#define runEvery(t) for (static typeof(t) _lasttime;\
                         (typeof(t))((typeof(t))millis() - _lasttime) > (t);\
                         _lasttime += (t))

// constants won't change. Used here to
// set pin numbers:
const int ledPin =  13;      // the number of the LED pin

// Variables will change:
int ledState = LOW;             // ledState used to set the LED

// the follow variables is a long because the time, measured in miliseconds,
// will quickly become a bigger number than can be stored in an int.
long interval = 1000;           // interval at which to blink (milliseconds)

void setup() {
  // set the digital pin as output:
  pinMode(ledPin, OUTPUT);      
}

void loop()
{
  // here is where you'd put code that needs to be running all the time.

  // Run the following block of code every 1 second
  runEvery(interval) {
    // if the LED is off turn it on and vice-versa:
    if (ledState == LOW)
      ledState = HIGH;
    else
      ledState = LOW;

    // set the LED with the ledState of the variable:
    digitalWrite(ledPin, ledState);
  }
}

---

Another neat thing about this macro is that it only uses as much memory as it has to. It needs to store the last time the block was run, but this variable will only take up as much space as the argument does. Thus, the macro can deal with delays of up to 50 days, while still only using 1 byte for delays of less than 250 milliseconds. This does contain the caveat that calling the macro with an argument that is close to the maximum value of the type is likely to break things.



Hopefully this will help everyone, and I would appreciate comments. I will submit this as a feature to Arduino once it gets tested a little more.


Edits:
 * Changed > to >= in macro
 * Made _lasttime always 16 bit
 * Fixed parentheses
« Last Edit: October 03, 2012, 07:23:38 pm by WizenedEE » Logged

Seattle, WA USA
Online Online
Brattain Member
*****
Karma: 551
Posts: 46208
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
Hopefully this will help everyone, and I would appreciate comments.
Probably not mine...

I think that it is important that people UNDERSTAND the blink without delay example. Providing some macros that hide the details is not going to help.

The concept is pretty simple, but the ease of use of delay() suckers many people in. Making blink without delay's concepts easier to use is not necessarily a good thing, if that detracts from the ability to understand the concepts.

And, it is my humble opinion that macros do just that.
Logged

Offline Offline
Edison Member
*
Karma: 17
Posts: 1041
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
Hopefully this will help everyone, and I would appreciate comments.
Probably not mine...

I think that it is important that people UNDERSTAND the blink without delay example. Providing some macros that hide the details is not going to help.

The concept is pretty simple, but the ease of use of delay() suckers many people in. Making blink without delay's concepts easier to use is not necessarily a good thing, if that detracts from the ability to understand the concepts.

And, it is my humble opinion that macros do just that.

I should have addressed this point in the first post.

Using delay() means that it becomes impossible to use any other scheduling method, like the one presented here or whatever else. Using this macro means that you can use a different scheduling method for something else. So if people get "suckered in" to using this macro, then instead of telling a new user "Sorry, it's impossible for you to add a second LED to your code without rewriting the entire thing," the user can just learn how to do it the proper way then and go ahead.

I agree that this macro may impede learning, but not necessarily in a way that will affect users. Once users "outgrow" the macro, they will learn how to do it the manual way just the same as they would if they initially were using delay.

I feel that a macro such as this one is consistent with these, ie consistent with the arduino philosophy.
Code:
#define lowByte(w) ((uint8_t) ((w) & 0xff))
#define highByte(w) ((uint8_t) ((w) >> 8))
Logged

Global Moderator
Dallas
Offline Offline
Shannon Member
*****
Karma: 176
Posts: 12286
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
typeof(t)

If t is a simple integer, isn't the typeof signed?
Logged

Offline Offline
Edison Member
*
Karma: 17
Posts: 1041
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
typeof(t)

If t is a simple integer, isn't the typeof signed?


Yes. However, it turns out it doesn't matter. I've tested with signed and unsigned 8-bit and 16-bit integers well beyond their rollovers and it works throughout. This sketch ran successfully on my uno for over an hour (I piped the serial output to a file and it was over a megabyte by the end)
Code:
#define runEvery(t) for (static typeof(t) _lasttime;\
                         (typeof(t))((typeof(t))millis() - _lasttime) > (t);\
                         _lasttime += (t))

void setup() {
    Serial.begin(115200);
}

void loop() {

    // char version
    runEvery((int16_t) 50) {
        Serial.print("50 ms Current time: ");
        Serial.println(millis());
    }

    // int version
    runEvery(5000) {
        Serial.print("five seconds Current time: ");
        Serial.println(millis());
    }

    // long version
    runEvery(10ul * 60ul * 1000ul) { // ten minutes
        Serial.print("Ten minutes are up! Current time: ");
        Serial.println(millis());
    }
}

edit: I guess I made some changes, so undoing those
« Last Edit: September 29, 2012, 10:10:23 pm by WizenedEE » Logged

Global Moderator
Dallas
Offline Offline
Shannon Member
*****
Karma: 176
Posts: 12286
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Code:
void loop( void )
{
  runEvery( 32767 )
  {
    Serial.println( millis() );
  }
}

...fails.
Logged

Offline Offline
Edison Member
*
Karma: 17
Posts: 1041
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Code:
void loop( void )
{
  runEvery( 32767 )
  {
    Serial.println( millis() );
  }
}

...fails.
Yes, as mentioned.
Do you have a good solution for that besides just always making _lasttime 32 bit? I think that people should be expected to make their variables long if they're going to be near the maximum, especially since the problem only appears if you're going one or two milliseconds below the max (admittedly more if your loop actually does something that takes some time.

I changed the original post from using > to using >=. After that, I could use runEvery with 32766 but not 32767. And unless you're doing many milliseconds worth of processing in the loop, it'll work up to at least 32760, which is fine for me, especially since it's saving so many bytes (use it 3 times with ints = 6 bytes saved)
« Last Edit: September 30, 2012, 12:47:40 am by WizenedEE » Logged

North Queensland, Australia
Offline Offline
Edison Member
*
Karma: 53
Posts: 1798
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I have a solution. I created a type to calculate the smallest unsigned integer for a given constant integer value, applied to your macro, it seems to work well. Also the gcc specific typeof is not needed now, but 't' has to be a constant.

Code:
template <bool _Flag, typename _True, typename _False> struct If{ typedef _True Result; };
template <typename _True, typename _False> struct If<false, _True, _False>{ typedef _False Result; };

template< uint64_t _Number >
  class BestFitUInt{
    protected:
      enum{
        T64 = _Number >> 0x20,
        T32 = _Number >> 0x10,
        T16 = _Number >> 0x08,
      };
      typedef typename If< T16, uint16_t, uint8_t >::Result TypeB;
      typedef typename If< T32, uint32_t, TypeB >::Result   TypeA;
    private:
    public:
      typedef typename If< T64, uint64_t, TypeA >::Result   Result;
};
#define BFUI(n) BestFitUInt< n >::Result

#define runEvery(t) for (static  BFUI(t) _lasttime;\
                         (BFUI(t))((BFUI(t))millis() - _lasttime) >= (t);\
                         _lasttime += (t))



void setup(){
  Serial.begin(9600);
};

void loop() {
  runEvery(500) Serial.println("500 milliseconds have passed since last printed");

  runEvery(100) {
    Serial.print("Sensor Value: ");
    Serial.println(analogRead(A1));
  }
}
Logged


Global Moderator
Dallas
Offline Offline
Shannon Member
*****
Karma: 176
Posts: 12286
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset


I was trying to get you to explore a bit more before declaring finished.  That obviously hasn't worked.

millis returns an unsigned long.  Assume t is a signed char type (int8_t for example).  After casting, the value from millis falls into the range...

(char)(unsigned long) --> -128 to +127

t is assumed to be positive so it falls in the range of +1 to +127.

Assume t is +100, millis is zero, and _lasttime is zero.  When the millis value reaches +99, there is a 28 millisecond window where your macro works correctly.  If the rest of the application delays 29 or more milliseconds, millis wraps to -128 and the macro fails rather badly.

Is the risk of that kind of failure worth supporting signed 8-bit values?  Bear in mind the target Arduino audience.
« Last Edit: September 30, 2012, 03:44:24 am by Coding Badly » Logged

Offline Offline
Edison Member
*
Karma: 17
Posts: 1041
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Based off of pYro_65's post, I have come up with this:
Code:
#ifdef __cplusplus
template <bool _Flag, typename _True, typename _False> struct If{ typedef _True Result; };
template <typename _True, typename _False> struct If<false, _True, _False>{ typedef _False Result; };

template< uint64_t _Number >
class BestFitUInt{
protected:
    static const uint32_t max_32 =  ((~(uint32_t)0) - 100);
    static const uint16_t max_16 =  ((~(uint16_t)0) - 100);
    static const uint8_t  max_8  =  ((~(uint8_t )0) - 100);

    // uint64_t should be an error.. Perhaps use this to link in a file with
    //  #error explaining better?
    typedef typename If< (_Number <= max_32), uint32_t, uint64_t >::Result tA;
    typedef typename If< (_Number <= max_16), uint16_t, tA >::Result tB;
public:
    typedef typename If< (_Number <= max_8 ), uint8_t , tB >::Result Result;
};

#define RETYPE(n) BestFitUInt< n >::Result

#else // cplusplus
#define RETYPE(n) uint32_t
#endif //cplusplus

#define runEvery(t) for (static  RETYPE(t) _lasttime;\
                         (RETYPE(t))((RETYPE(t))millis() - _lasttime) >= (t);\
                         _lasttime += (t))

tested in this sketch:
Code:
#ifdef __cplusplus
template <bool _Flag, typename _True, typename _False> struct If{ typedef _True Result; };
template <typename _True, typename _False> struct If<false, _True, _False>{ typedef _False Result; };

template< uint64_t _Number >
class BestFitUInt{
protected:
public: // Remove for final version
    static const uint32_t max_32 =  ((~(uint32_t)0) - 100);
    static const uint16_t max_16 =  ((~(uint16_t)0) - 100);
    static const uint8_t  max_8  =  ((~(uint8_t )0) - 100);

    // uint64_t should be an error.. Perhaps use this to link in a file with
    //  #error explaining better?
    typedef typename If< (_Number <= max_32), uint32_t, uint64_t >::Result tA;
    typedef typename If< (_Number <= max_16), uint16_t, tA >::Result tB;
public:
    typedef typename If< (_Number <= max_8 ), uint8_t , tB >::Result Result;
};

#define RETYPE(n) BestFitUInt< n >::Result

#else // cplusplus
#define RETYPE(n) uint32_t
#endif //cplusplus

#define runEvery(t) for (static  RETYPE(t) _lasttime;\
                         (RETYPE(t))((RETYPE(t))millis() - _lasttime) >= (t);\
                         _lasttime += (t))

#define DEBUG(x) do {Serial.print(#x ": "); Serial.print(x); Serial.println(/*'\t'*/); } while (0)

#define LOOKATTYPE(n) do {typename BestFitUInt< n >::Result var; Serial.print("Number: "); Serial.print(n); Serial.print("\tType: "); Serial.println(mytypeof(var)); } while(0)

template <class T> const char* mytypeof(T&) { return "unknown";}
template <> const char* mytypeof(uint8_t &) { return "uint8_t ";}
template <> const char* mytypeof(uint16_t&) { return "uint16_t";}
template <> const char* mytypeof(uint32_t&) { return "uint32_t";}
template <> const char* mytypeof(uint64_t&) { return "uint64_t";}

void setup() {
    Serial.begin(115200);

    DEBUG(BestFitUInt<5>::max_32);
    DEBUG(BestFitUInt<5>::max_16);
    DEBUG(BestFitUInt<5>::max_8);
    
    Serial.println();

    LOOKATTYPE(126);
    LOOKATTYPE(127);
    LOOKATTYPE(128);

    Serial.println();

    LOOKATTYPE(154);
    LOOKATTYPE(155);
    LOOKATTYPE(156);

    Serial.println();

    LOOKATTYPE(65434);
    LOOKATTYPE(65435);
    LOOKATTYPE(65436);

    Serial.println();

    LOOKATTYPE(65534);
    LOOKATTYPE(65535);
    LOOKATTYPE(65536);

    Serial.println();

    LOOKATTYPE(4294967194);
    LOOKATTYPE(4294967195);
    LOOKATTYPE(4294967196);

    Serial.println();

    LOOKATTYPE(4294967294);
    LOOKATTYPE(4294967295);
    //LOOKATTYPE(4294967296); // Serial.print can't handle 64 bit

}

void loop() {

}

Which prints out this:
Code:
BestFitUInt<5>::max_32: 4294967195
BestFitUInt<5>::max_16: 65435
BestFitUInt<5>::max_8: 155

Number: 126 Type: uint8_t
Number: 127 Type: uint8_t
Number: 128 Type: uint8_t

Number: 154 Type: uint8_t
Number: 155 Type: uint8_t
Number: 156 Type: uint16_t

Number: 65434 Type: uint16_t
Number: 65435 Type: uint16_t
Number: 65436 Type: uint32_t

Number: 65534 Type: uint32_t
Number: 65535 Type: uint32_t
Number: 65536 Type: uint32_t

Number: 4294967194 Type: uint32_t
Number: 4294967195 Type: uint32_t
Number: 4294967196 Type: uint64_t

Number: 4294967294 Type: uint64_t
Number: 4294967295 Type: uint64_t

It checks the number and if it's more than (100 less than the maximum value of the type) then it promotes it to the next type. Thus, even if the user made used the value 65435 (100 less than maximum), they would still have to have 100 milliseconds of calculation in their loop for it to fail. That value could be made greater at little cost (as most values would probably be about 500, which is much much less than the maximum value for an int).

If it's not using C++, it just makes it 32 bit.

My next goal is to make the macro work even if t is not a constant, using the gcc builtin _bulitin_constant_p. Unfortunately, that function/macro/thing doesn't appear to work properly, as this fails to compile:
Code:
template<int t> struct test { static const int num = t; };

void setup(){
    Serial.begin(115200);

    volatile int a = 5;

    int b = test<_builtin_constant_p(a)>::num;

    Serial.print("b: ");
    Serial.print(b);
}

void loop() {
}
Code:
build-cli/tmp.cpp: In function 'void setup()':
build-cli/tmp.cpp:9:38: error: 'a' cannot appear in a constant-expression
build-cli/tmp.cpp:9:39: error: a function call cannot appear in a constant-expression
build-cli/tmp.cpp:9:40: error: template argument 1 is invalid

... which is silly because even though a is not constant, _builtin_constant_p(a) should be constant. So, workarounds are necessary.


My other goal, as seen in the comments, is to get a good-looking error if someone tries to run the macro with something like 4294967294 (less than 100 below maximum 32 bit integer). I thought that maybe it could use a class that then linked in another cpp file that had nothing but that class and a #error "value for runEvery is too high"


Aparently contrary to popular belief, I welcome comments smiley
« Last Edit: September 30, 2012, 01:25:32 pm by WizenedEE » Logged

Seattle, WA USA
Online Online
Brattain Member
*****
Karma: 551
Posts: 46208
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
Aparently contrary to popular belief, I welcome comments  smiley
Wouldn't it be simpler to just understand the blink without delay concept?
Logged

Offline Offline
Edison Member
*
Karma: 17
Posts: 1041
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
Aparently contrary to popular belief, I welcome comments  smiley
Wouldn't it be simpler to just understand the blink without delay concept?
Yes.

If you have further doubts about how useful this is, perhaps you could respond to some of my points in post 3.
Logged

Global Moderator
Dallas
Offline Offline
Shannon Member
*****
Karma: 176
Posts: 12286
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Do you have a good solution for that besides just always making _lasttime 32 bit?

Yes.  Make _lasttime a uint16_t.  60 seconds will cover the vast majority of uses, the documentation is fairly simple, and the chances of encountering a nasty bug are reduced.
Logged

Canby, OR
Offline Offline
Full Member
***
Karma: 1
Posts: 158
Arduino rocks
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

On a technical level, I think this is pretty cool.
Logged

U.K
Offline Offline
Jr. Member
**
Karma: 1
Posts: 70
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

surely a better value than the hardcoded 100 (ms) ( the expected time for your code to execute in ) would be the parameter calls natural size ?

ie.  if i'm wishing to run something every 150ms that being suitable for going into an unsigned 8 bit var.  then me ( the programmer expects this bit of code will execute within that 150ms, so that it can called again 150 ms later.)

so double the 150,  and then see what minimum size is needed for it to allow overflows.  in this case it will be a 16 bit var.
(  this being 2 * 150 = 300, which is greater than 256 so we move to next range, ie 65536 )
this way,  you wont get any "run after x" routines not running by overflows of the vars subtraction in the macro.

likewise if i want to run something every 2000 ms.   then 4000 is within the 65536 scope of watching the 32 bit long millis var counting. so the 16 bit var is fine.
if i'm wanting something to run every 60000ms ( 1 minute ) then prehaps my routine might take say 59 seconds of that minute. and so, we need to watch using the 32 bit sozed var.

or am i missing something here ?
Logged

--
 Darryl

Pages: [1] 2   Go Up
Jump to: