1

I have this simple sketch I use on Arduino Micro:

int fan = 3;
int led = 13;
int ledState = HIGH;         //all'inizio gira
//unsigned long time;
unsigned long prevMicros = 0;        // will store last time LED was updated  +
unsigned long interval =  7000000;           // lunghezza della pausa in secondi
unsigned long intervalON =  1000000;           // lunghezza del ON in secondi

void setup() {    
 pinMode(fan, OUTPUT);   
 pinMode(led, OUTPUT);  
 prevMicros = micros();
}

void loop() {
  unsigned long currMicros = micros();

  // manage overflow? BOH
   if (ledState == HIGH && (((unsigned long)(currMicros  - prevMicros)) >= intervalON)){
     prevMicros = currMicros;
      ledState = LOW;
    }
    else if (ledState == LOW && (((unsigned long)(currMicros - prevMicros)) >= interval)){
      ledState = HIGH;//riparte
      prevMicros = currMicros;     
    }

    digitalWrite(led, ledState);
    digitalWrite(fan, ledState);
}

I could use millis() instead of micro(), but I decided to keep this to shorten the overflow rate (should be 72 minutes). I shall go back to millis() once my issue is fixed.

The sketch works as expected, turning on the LED for a second every 7, but the sketch hangs after 40 minutes. Can you tell me why, and how to manage overflow properly? Why is it hanging after 40 and not 72 minutes? Thanks

10
  • Shouln't it be (((unsigned long)(currMicros - prevMicros)) >= intervalON) instead of (((long)(currMicros - prevMicros)) >= intervalON), since the timing value from micros() is unsigned?
    – MarkU
    Jun 4, 2015 at 21:06
  • 1
    To confirm: if the problem really is (long) vs (unsigned long) time interval comparison, then the observed behavior should be LED stops blinking after at t=40 minutes, but then starts blinking again at t=70 minutes when then micros() overflows back to 0. Definitely a good idea to test/verify with micros() first.
    – MarkU
    Jun 4, 2015 at 21:22
  • I think your observations deserve a voted answer :)
    – Shine
    Jun 4, 2015 at 21:26
  • 1
    40 minutes is approximately 2^31 microseconds, so it seems an overflow of some kind. Also it is good practice to use type definition like uint32_t instead of unsigned long for readability and cross platform portability.
    – jippie
    Jun 4, 2015 at 22:06
  • 1
    Here is how you can speed up your tests: globally declare extern unsigned long timer0_overflow_count; then, in setup(), set this global variable to 0x001f0000. This way the high bit of micros() will toggle in ≈ 67 seconds. Set it to 0x003f0000 and micros() will roll back to zero, again in ≈ 67 seconds. Jun 5, 2015 at 10:06

1 Answer 1

1

When comparing time intervals, use unsigned long instead of long.

if ( ... (((unsigned long)(currMicros - prevMicros)) >= intervalON) )

The time counter overflows at some point -- micros() overflows after 70 minutes, milis() overflows after nearly a month -- but these are unsigned values.

When an unsigned value is interpreted as a signed value -- (long) instead of (unsigned long) -- then half of the values are interpreted as negative numbers. So after 40 minutes, the micros() value looks like a negative number, and the (curr-prev) >= interval comparison returns false. After 70 minutes, the micros() overflows back to 0, and the signed comparisons work as expected.

The solution is to use unsigned long for all delta-time comparisons.

This is easier to see if you consider a smaller, 3-bit number: there are 8 values for a 3-bit number. If the 3-bit number is unsigned, then the values are 0, 1, 2, 3, 4, 5, 6, 7. But if the 3-bit number is signed, then those same binary code numbers are interpreted as 0, 1, 2, 3, -4, -3, -2, -1. Same principle for int and long values, just a larger range of values.

EDIT:

Based on your comment below, I did some testing on my own hardware (Sparkfun "red board" Arduino UNO R3 clone), and added some code to help make it more testable. Some highlights:

  1. Added a diagnostic LED on Arduino pin 9, to indicate when the timer overflow has happened (i.e. new time is less than previous time).
  2. Added a mask to force the timer overflow to happen sooner, so I don't have to wait an hour to verify whether the code had any effect.
  3. Calculated timeSinceLastChange and applied mask, to handle overflow.
  4. Added TIMER_DATATYPE based on jippie's comment

The complete code:

//--- based on comment from jippie about using C++ standard width integer types
// #define TIMER_DATATYPE unsigned long
#define TIMER_DATATYPE uint32_t

//--- MarkU: diagnostic LED to indicate that timer overflow happened.
// Comment out this line for normal operation.
#define DIAGNOSTIC_LED_TIME_OVERFLOW 9
//---

//--- MarkU: diagnostic: force timer overflow to happen sooner.
// This must be significantly longer than the intervals being measured.
// Must be a binary all-1's bitmask.
// 0x3FFFFFF overflow after approximately 1 minute
// 0xFFFFFFF overflow after approximately 5 minutes
// 0xFFFFFFFF overflow after approximately 70 minutes
#define DIAGNOSTIC_TIMER_OVERFLOW_MASK 0x3FFFFFF
//---

int fan = 3;
int led = 13;
int ledState = HIGH;         //all'inizio gira
//unsigned long time;
TIMER_DATATYPE prevMicros = 0;        // will store last time LED was updated  +
TIMER_DATATYPE interval =  7000000;           // lunghezza della pausa in secondi
TIMER_DATATYPE intervalON =  1000000;           // lunghezza del ON in secondi

void setup() {
  pinMode(fan, OUTPUT);
  pinMode(led, OUTPUT);

  //--- MarkU: diagnostic LED indicate that timer overflow happened
#if DIAGNOSTIC_LED_TIME_OVERFLOW
  pinMode(DIAGNOSTIC_LED_TIME_OVERFLOW, OUTPUT);
  digitalWrite(DIAGNOSTIC_LED_TIME_OVERFLOW, LOW);
#endif // DIAGNOSTIC_LED_TIME_OVERFLOW
  //---

  prevMicros = micros();
}

void loop() {
  TIMER_DATATYPE currMicros = micros();

  //--- MarkU: diagnostic: force timer overflow to happen sooner
#if DIAGNOSTIC_TIMER_OVERFLOW_MASK
  currMicros = (TIMER_DATATYPE)(currMicros & ((TIMER_DATATYPE)DIAGNOSTIC_TIMER_OVERFLOW_MASK));
#endif // DIAGNOSTIC_TIMER_OVERFLOW_MASK

  //--- MarkU: consolidate time delta calculation
  TIMER_DATATYPE timeSinceLastChange;
  if (currMicros < prevMicros) {
    //--- MarkU: handle timer overflow
#if DIAGNOSTIC_LED_TIME_OVERFLOW
    digitalWrite(DIAGNOSTIC_LED_TIME_OVERFLOW, HIGH);
#endif // DIAGNOSTIC_LED_TIME_OVERFLOW
#if DIAGNOSTIC_TIMER_OVERFLOW_MASK
    timeSinceLastChange = (currMicros - prevMicros) & DIAGNOSTIC_TIMER_OVERFLOW_MASK;
#else // DIAGNOSTIC_TIMER_OVERFLOW_MASK
    timeSinceLastChange = (currMicros - prevMicros) & 0xFFFFFFFF;
#endif // DIAGNOSTIC_TIMER_OVERFLOW_MASK
  } else {
    timeSinceLastChange = currMicros - prevMicros;
  }
  //---

  // manage overflow? BOH
  if (ledState == HIGH && (timeSinceLastChange >= intervalON)) {
    prevMicros = currMicros;
    ledState = LOW;
  }
  else if (ledState == LOW && (timeSinceLastChange >= interval)) {
    ledState = HIGH;//riparte
    prevMicros = currMicros;
  }

  digitalWrite(led, ledState);
  digitalWrite(fan, ledState);
}
6
  • I'm sorry it's still not working, I updated the code above
    – Shine
    Jun 4, 2015 at 21:51
  • I'm testing your edited code. Anyway, I'm beginning to suspect the problem isn't overflow, since it's not re-starting ever. Your code is working as expected though, switching the DIAGNOSTIC_LED_TIME_OVERFLOW to ON after specified time. Let's wait to see if it hangs again... Thank you for your time!
    – Shine
    Jun 5, 2015 at 21:07
  • nothing to do, it blocked after 2 hours, this time with ledState set to HIGH permanently. I don't think it's a circuit problem , but should I try removing the load on fan?
    – Shine
    Jun 5, 2015 at 23:02
  • Yes, I tested the above code driving an LED, not a fan. Perhaps the real problem is some kind of power disruption when turning the fan on or off -- maybe it's acting like an inductive load? Relays and motors are a bit tricky to drive. What circuit are you using to drive the fan, or is the fan just directly connected to an Arduino digital pin? And what kind of fan is it exactly (make/model number)?
    – MarkU
    Jun 5, 2015 at 23:42
  • the fan is a small 3V motor absorbing up to 0,5A. I'm driving it using a 2N3904 connected to Vin and a potentiometer 0-9k ohm from digital pin to transistor's base. Input voltage to Arduino Micro is 5V regulated
    – Shine
    Jun 5, 2015 at 23:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.