ivanseidel / ArduinoThread

⏳ A simple way to run Threads on Arduino
MIT License
955 stars 196 forks source link

Use unsigned long for representing times. #4

Closed edgar-bonet closed 9 years ago

edgar-bonet commented 9 years ago

All the time calculations made on signed longs were wrong after 2^31 ms ~ 24.9 days. This patch fixes this by using unsigned longs. As unsigned numbers follow the rules of modular arithmetic, the computations are correct event after the millis() rollover in ~ 49.7 days.

Fixes issue #3: Improper use of signed integers.

edgar-bonet commented 9 years ago

This patch is untested. Here is what it does:

void runned() { runned(millis()); }
bool shouldRun() { return shouldRun(millis()); }

this generates smaller and faster code than the current version, because there is no need to test for the parameter being −1.

bool time_to_run = (signed long)(time - _cached_next_run) >= 0;

and it would work as expected if compiled with gcc. But since this is implementation-defined behavior, it should be avoided in portable code. Instead of casting to a signed number, the same result can be achieved by testing what would be the sign bit:

// If the "sign" bit is set the signed difference would be negative
bool time_remaining = (time - _cached_next_run) & 0x80000000;

which is as fast as a comparison. Then, shouldRun() returns !time_remaining && enabled.

I am not sure I will be able to test all this today. If you want to test yourself, on an AVR-based Arduino you can time-travel to just before the millis() rollover as follows:

extern unsigned long timer0_millis;

void setup() {
    timer0_millis = -1000;  // rollover in one second
    ...
}

On the Dues this does not work, because the equivalent variable is declared as static. You may make it work if you modify the Arduino core by removing the static qualifier from _dwTickCount in hardware/arduino/sam/system/libsam/source/timetick.c.

edgar-bonet commented 9 years ago

Update: I tested the patched library to see how it deals with the millis() rollover. It runs as expected, keeping a regular pace across the rollover. Here is my test program:

#include <Thread.h>
#include <ThreadController.h>

const int ledPin = 13;
extern unsigned long timer0_millis;

// First thread: toggle the LED every 200 ms.
void blink_led() {
    static bool ledState;
    ledState = !ledState;
    digitalWrite(ledPin, ledState);
}
Thread blink_thread(blink_led, 200);

// Second thread: print the current time every second.
void print_time() {
    Serial.println(millis());
}
Thread print_thread(print_time, 1000);

// Controller for both threads.
ThreadController scheduler;

void setup() {
    pinMode(ledPin, OUTPUT);
    Serial.begin(9600);
    Serial.println("\rStarting");
    scheduler.add(&blink_thread);
    scheduler.add(&print_thread);

    // Test across the millis() rollover.
    timer0_millis = -4300;  // time-travel to rollover minus 4.3 s
    blink_thread.run();     // update _cached_next_run
    print_thread.run();     // ditto
}

void loop() {
    scheduler.run();
}
ivanseidel commented 9 years ago

Awesome! That is just perfect. And with 100% compatibility...

Will merge.

Thanks for the effort

ivanseidel commented 9 years ago

fixes #3