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

Shine
  • 111
  • 4
  • 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 04 '15 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 04 '15 at 21:22
  • I think your observations deserve a voted answer :) – Shine Jun 04 '15 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 04 '15 at 22:06
  • I think the problem is in the prevMicros being pushed up all the way and never being reset when the currMicros rolls over to 0. Better to use a delta for currMicros in this case rather than an absolute number. You'll have to detect and correct for micros() roll over though. – jippie Jun 04 '15 at 22:13
  • What about using unsigned long long? – CharlieHanson Jun 05 '15 at 01:33
  • Your code looks rollover-safe to me, as does the current implementation of micros(). This should work just fine across the micros() rollover. What version of Arduino are you using? – Edgar Bonet Jun 05 '15 at 09:43
  • BTW, you have too many casts and parentheses: if (ledState == HIGH && currMicros - prevMicros >= intervalON) is the same, but easier to read. – Edgar Bonet Jun 05 '15 at 09:44
  • 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. – Edgar Bonet Jun 05 '15 at 10:06
  • I just tested the program on an Uno, using the technique mentioned above to trigger a prompt rollover, and with a Serail.println(micros()); for debugging. It works exactly as expected: it does not hang. The question asked is “Why does the program hang?”. Since it does not really make sense, shouldn't the question be closed? – Edgar Bonet Jun 06 '15 at 10:05

1 Answers1

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);
}
MarkU
  • 450
  • 4
  • 11
  • I'm sorry it's still not working, I updated the code above – Shine Jun 04 '15 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 05 '15 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 05 '15 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 05 '15 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 05 '15 at 23:49
  • the motor is similar to pkz3916, with a small propeller on it – Shine Jun 06 '15 at 00:00