long random timing (max 33 minutes) problem

Hello forum,

I am a newB at this forum and in C/C++ programming as well. (I did some turbo Pascal programming before), so I hope my question is not the one that has already been answered many times before…
Lately I bought my Arduino for a project but in my first serious trials things work another way then I expected.

I try to make a (relatively simple) timer with which I can switch a light at random. Random times should vary between about 3 minutes and 33 minutes. The light should stay on for about 12 seconds.
Another light should switch on 2 seconds after the first light switches on, but switch off at the same moment.
Not necessary but practical is the third channel I included for a control led that shows activity, shortly blinking each second.

The program seems to work but random times vary only between 3 minutes and say 11 minutes.
No matter my changes in the numbers. So my first question here is: What did I do wrong there?

Second: When I want to use more randomized values do I need to put in extra pinreadings to read another pin or can I just keep on referring to a read of pin0?

Another thing is that in order to learn the programming I tried to avoid hard delays (so I based my sketch on the “blinking without delays” example), but in the case of the light that switches on 2 seconds after the first, I didn’t manage to get that working.
This is not a serious problem for this specific project, but in order to improve my skills and a better understanding of the syntax I would like to get that fixed as well.

underneath my code so far (so here with the delay I try to get rid off).

int Pin13 = 13; //control led
int Pin12 = 12; //maintimer/switch
int Pin11 = 11; //second switch following mainswitch with startdelay.
int value = LOW;

long previousMillis13a = 0;
long previousMillis13u = 0;
long aan13 = (20);
long uit13 = (980);

long previousMillis12a = 0;
long previousMillis12u = 0;
long aan12;
long uit12;

void setup()
{
Serial.begin(9600);
pinMode(Pin13, OUTPUT);
pinMode(Pin12, OUTPUT);
pinMode(Pin11, OUTPUT);
}

void loop()
{
if (millis() - previousMillis13a > aan13) //“blinking without delay” for the control led
{
previousMillis13a = millis();
if (value == HIGH)
value = LOW;
digitalWrite(Pin13, value);

if (millis() - previousMillis13u > uit13)
{
previousMillis13u = millis();
if (value == LOW)
value = HIGH;
digitalWrite(Pin13, value);
}
}

aan12 = 10000;
if (millis() - previousMillis12a > aan12)
{
previousMillis12a = millis();
digitalWrite(Pin12, LOW);
digitalWrite(Pin11, LOW);

randomSeed(analogRead(0));
uit12 = (random(200,2200)*1000); //200000=3minutes 2200000=33minutes
if (millis() - previousMillis12u > uit12)
{
previousMillis12u = millis();
digitalWrite(Pin12, HIGH);

delay(2000); //the delay I would like to get rid off
digitalWrite(Pin11, HIGH);
}
}
}

thank you all in advance for your help!
Matthijs

Hi!

Generally speaking, you don’t have to call randomSeed() more than once; do it in setup() and forget about it. analogRead(0) is a pretty good way of seeding although that limits you to at most 1024 random sequences, and probably much fewer in actual practice. Another good technique I’ve used, if your design allows, is to wait at the beginning of a sketch for a human user to press some sort of “start” button and then use millis() at that point as a seed.

I’ll think about the second part of your problem…

Mikal

Hi!

analogRead(0) is a pretty good way of seeding although that limits you to at most 1024 random sequences, and probably much fewer in actual practice.

It looks like randomSeed() can take a long as an argument, couldn’t you do something like:

randomSeed( ((long) analogRead(0) * 1024L) ); // is ‘L’ applicable here? I’ve seen ‘UL’ used, but not ‘L’ for signed long before

to increase that set?

Being limited to 1024 random values would certainly make it unlikely to have a good range along the set.

For controlling timing without delays, you need to measure millis() passing… something like this: (I use this for an intervalometer timing where there are other components that delay() to get ‘best case timing’):

void loop 
{

  static unsigned long cur_tm = 0;
  static unsigned long pre_tm = 0;
  static unsigned long diff_tm = 0;

  if( cur_tm > pre_tm )
   {
     diff += cur_tm - pre_tm;
    }
    else if( cur_tm < pre_tm )
     {
        // millis() rolled over on us
        // just make sure it can't roll over more than once on you =)
        diff_tm += ( cur_tm + ( 34359737 - pre_tm ));
      }
 
  pre_tm = cur_tm;
  cur_tm  = millis();

     // diff_tm, being an unsigned long, can be up to ~ 49 days

  if( diff_tm > MAX_DIFFERENCE_TM ) 
   {
      // do your thing
    }

}

Hi drone,

couldn’t you do something like:

randomSeed( ((long) analogRead(0) * 1024L) ); // is ‘L’ applicable here? I’ve seen ‘UL’ used, but not ‘L’ for signed long before

Yup, and the L syntax is valid too, but unfortunately this doesn’t improve things. There are still at most 1024 possible distinct values being passed to randomSeed, and this limits the “randomness” of the program. I’ll write a dissertation on how random() works some other time, but suffice it to say that the analogRead() approach, while perfectly adequate for many apps, is somewhat limiting for those require a broad spectrum of random behavior. (Just FYI… having only 1024 possible seeds does NOT mean that you will only ever get 1024 different random numbers, only 1024 different possible SEQUENCES of numbers.)

afgeschermd–

I thought about your problem some today. What about the approach below? It turns on and off the lamps in the desired sequence while flashing the LED every second, but doesn’t use delay anywhere. (I haven’t tested to make sure this handles the millis() overflow yet, so don’t consider this a finished product! :))

Mikal

int Pin13 = 13;                //control led 
int Pin12 = 12;                //maintimer/switch 
int Pin11 = 11;                //second switch following mainswitch with startdelay. 

long start;
long turnon1stlight;
long turnon2ndlight;
long turnoffbothlights;

void calculateNewEventTimes()
{
  start = millis();
  turnon1stlight = random(200,2200)*1000;       //200000=3minutes 2200000=33minutes 
  turnon2ndlight = turnon1stlight + 2000;          // 2 seconds later
  turnoffbothlights = turnon1stlight + 12000;      // 12 seconds later
  Serial.print("start is ");
  Serial.println(start);
  Serial.print("turnon1stlight at ");
  Serial.println(turnon1stlight);
  Serial.print("turnon2ndlight at ");
  Serial.println(turnon2ndlight);
  Serial.print("turnoffbothlights at ");
  Serial.println(turnoffbothlights);
}

void setup()    
{
  Serial.begin(9600);                
  pinMode(Pin13, OUTPUT);          
  pinMode(Pin12, OUTPUT);          
  pinMode(Pin11, OUTPUT);
  
  int seed = analogRead(0);
  Serial.print("Seed is ");
  Serial.println(seed);
  randomSeed(seed);
  
  calculateNewEventTimes();
} 
 
 
void loop()                       
{
  long int elapsed = millis() - start;
  if (elapsed < turnon1stlight)
  {
    digitalWrite(Pin11, LOW);
    digitalWrite(Pin12, LOW);
  }
  
  else if (elapsed < turnon2ndlight)
  {
    digitalWrite(Pin11, LOW);
    digitalWrite(Pin12, HIGH);
  }

  else if (elapsed < turnoffbothlights)
  {
    digitalWrite(Pin11, HIGH);
    digitalWrite(Pin12, HIGH);
  }
  
  else
  {
    digitalWrite(Pin11, LOW);
    digitalWrite(Pin12, LOW);
    calculateNewEventTimes();
  }
  
  // turn on led every other second
  digitalWrite(Pin13, (millis()/1000 % 2) ? HIGH : LOW);
}

Hi Drone,
Thanks for your input!
I have to have a closer look to understand your suggestion, I’ll dive into it later.
Considering the posts of mikalhart I first try his suggestion.

Hi mikalhart,
Thank you very much.
I loaded your lines and it seems to work better. I have to run it longer to see how much variation I get with this. I am not sure if I really understand the random problem and its 1024 boundary.
As in Turbo Pascal, random is not really random which is no problem in my project. The 1024 boundary is that the theoratical max amount of variations (and in practice a lot less)?
I am quite busy at this moment but coming days I will try to understand exactly what you did in your approach and I will give an update about my findings in the variations.
greetings,

afgeschermd

random() doesn't return truly random values; it is a pseudorandom number generator. It feeds the previous value generated to a particular equation and gives you the next value in the sequence, which appears to be random, and behaves in many ways as if it were random. randomSeed() sets that "previous" value before the first run in order to give a different sequence (or start you at a different place in the sequence).

-j

Hi Drone, Thanks for your input! I have to have a closer look to understand your suggestion, I'll dive into it later. Considering the posts of mikalhart I first try his suggestion.

No problem! BTW, his suggestion and mine are effectively the same, he just has more complete code for you. =) You could just take my roll-over code and drop it in to effectively calculate for millis() overflow grin

!c

BTW, his suggestion and mine are effectively the same...

Quite right! It inspired me to respond in the first place! :)

Mikal

Hei Mickalhart, Drone, I checked your code extensively and cannot find an error occurring... ;) So I am very glad you wrote it for me! (and Drone helped to make the start!).

Only one thing: it seems that led 13 keeps on blinking while after a longer period (the boundary seems to be around 35 000 000 milliseconds? = 9,7 hours) the switching channels 11 and 12 stop switching.... Is it possible to extend the max period the program is working correctly? I tried some by trying to set START back to zero but probably I was messing up syntax again....

Thanks again and greetings,

afgeschermd

the millis() function rolls over every 9.3 hours. You may want to read this thread: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1210889307#3

Thanks MEM for the swift response.

I will have to have a look into it because I am a newB.

At least this makes it clear now that the problem I was having was in the software (thirst I thought I ruined my board and the problem was a hardware and it took some time to finally conclude it was not the hardware).

afgeschermd

afteschermd–

I think you can work around the rollover problem in your specific case by adding these few lines

  long int elapsed = millis() - start;
  if (elapsed < 0) // **** begin new
  {
    start = millis();
    elapsed = 0;
  } // **** end new

(This isn’t perfect, but it is simple and should keep your application running forever…)

EDIT: I haven’t tried this, but this should be better:

  if (elapsed < 0)
  {
    elapsed += 0x20C49BA; // this is the millis() rollover value
  }

Mikal

Mikalhart, I have been testing the solutions you posted: the second I tried first, but didn't work: just silence after the 9+ hours. It took some time to test, try it in another place, wait and try to see what is wrong etc but no, that doesn't work. Your first solution however works fine! (I will have to let it run/test it further/longer to see if it really rules out the boundary completely but at this moment it runs for about 11 hours and still seems to work fine.) I am very glad you made it for me. So thank you very much!

afgeschermd

Afgeschermd–

I’m glad you have a solution! When you did your first test (with the second solution), did you calculate “elapsed” as “millis() - start”? I have several examples here working fine with that “second” solution, so I’m surprised if it failed for you.

void loop()
{
  long int elapsed = millis() - start;
  if (elapsed < 0)
  {
    elapsed += 0x20C49BA; // this is the millis() rollover value
  }
...

The “first” solution is fine, except that at the moment it detects the 9-hour rollover, it resets your timing sequence. This may be perfectly fine for your application, but in others it might be a problem.

By the way, if you want to test rollover behavior, you can reset the internal Arduino clock to a value very close to the rollover by temporarily adding these two lines to setup():

extern volatile unsigned long timer0_overflow_count;
timer0_overflow_count = 0xFFFF0000; // set clock to about 60 seconds from rollover

(You don’t need to wait the whole nine hours this way.)

Cheers,

Mikal

Hi Mikal,
Yes, I did it with the calculation (as you can see in the code):

void loop()                       
{
  long int elapsed = millis() - start;
 
 if (elapsed < 0)
 {
    elapsed += 0x20C49BA; // this is the millis() rollover value
  } 
 
 else if (elapsed < turnon1stlight)
  {
    digitalWrite(Pin11, HIGH);
    digitalWrite(Pin12, HIGH);
  }
  
  else if (elapsed < turnon2ndlight)
  {
    digitalWrite(Pin11, HIGH);
    digitalWrite(Pin12, LOW);
  }

  else if (elapsed < turnoffbothlights)
  {
    digitalWrite(Pin11, LOW);
    digitalWrite(Pin12, LOW);
  }

  else
  {
    digitalWrite(Pin11, HIGH);
    digitalWrite(Pin12, HIGH);
    calculateNewEventTimes();
  }

(due to a change in the hardware I changed hight to low and vice versa, but that cannot be the problem.)

I will try it again and thanks for the temp clock addition, that really makes it a lot easier to see what happens. I’ll keep you posted.

afgeschermd

Ah, I see the problem. Remove the “else”

  if (elapsed < 0)
  {
    elapsed += 0x20C49BA; // this is the millis() rollover value
  }

  [s]else[/s] if (elapsed < turnon1stlight)
  {

Mikal

YES!!! now it works. Just in time to implement it into my project. Thanks again and again.

Hopefully in a couple of months I have a better understanding of the syntax and will ask for more "advanced" support ;-)

afgeschermd