Random timer

regpye

New Member
I have been trying to make a simple random timer, but with no luck.
What I am trying to do is have a mosfet turn on using a 08M2 chip on pin C.0, that is not the problem. I want the mosfet to be on for about 3 minutes and then go off, also no problem and the problem, a random timer set between 60 minutes and 120 minutes until the C.0 turns on again for another 3 minutes, wait 22 hours and then start the whole process again.
I tried to do it myself without much success and then I tried using chatGPT and that was even worse.
Maybe what I am trying to do can't be done?
 

Aries

New Member
All untested, but here are some basics ...
Code:
symbol Hours    = b18            ' current "time"
symbol Mins        = b19
symbol HoursX = b20            ' time to turn LED on again
symbol MinsX    = b21
symbol HoursY = b22            ' time to restart process
symbol MinsY    = b23
symbol LastTimer = w12
symbol Randomiser = w13

Initialise:                                            ' set clock and save current value of timer
    Hours = 0
    Mins = 0
    HoursX = 0
    MinsX = 1                                            ' to avoid tests passing at the start
    HoursY = 200
    MinsY = 0
    LastTimer = timer
    Randomiser = timer                        ' seed Randomiser
    
CheckClock:
    w0 = timer                                        ' current time
    w1 = w0 - LastTimer                        ' elapsed since last update
    if w1 > 60 then                                ' at least a minute has passed
        w2 = w1 / 60                                ' whole minutes passed
        LastTimer = w2 * 60 + w0        ' update LastTimer with whole minutes (multiple of 60 secs)
        Mins = Mins + w2
        Hours = Mins / 60 + Hours        ' if Mins > 60, increment Hours
        Mins = Mins // 60                        ' minutes (0-59)
        random Randomiser                        ' randomise each time this is executed
    endif
    
    if Hours > HoursX then SecondSwitchOnTime
    if Hours = HoursX and Mins >= MinsX then SecondSwitchOnTime
    goto NotSecondSwitchOnTime
SecondSwitchOnTime:
        HoursX = 0                                    ' reset to avoid another switch-on
        MinsX = 0
        gosub TurnLedOn
        HoursY = Hours + 22                    ' elapsed 22 hours
        MinsY = Mins
NotSecondSwitchOnTime:

    if Hours > HoursY then Initialise
    if Hours = HoursY and Mins >= MinsY then Initialise
    
    end
    
RandomDelay:
    random Randomiser                            ' get a random value
    w0 = Randomiser ** 61                    ' value between 0 and 60 inclusive
                                                                ' A**B is equivalnet to A * B / 65536
    w0 = w0 + 60                                    ' value between 60 and 120
    MinsX = Mins + w0                            ' set time for next switching on
    HoursX = MinsX / 60 + Hours        ' current time + elapsed
    MinsX = MinsX // 60                        ' 0-59
    return
Initialise simply sets the clock to zero and puts values into the other times to make sure the events are not triggered prematurely.
random starts the process of creating a random number

CheckClock uses timer (updates once per second) to count minutes and hours in Mins and Hours
It also updates the random number so that it is not consistent every time the program is run

HoursX and MinsX are set by the RandomDelay routine, which must be called after the first LED switch-on, to set the time for the second switch-on

After the second switch-on, HoursY and MinsY are set to 22 hours after current time, and when that time is reached, the program starts again (with a different value for timer, so a different random delay)
 
Last edited:

regpye

New Member
Thanks for all your effort Aries, a lot of things to think about there.

I managed to get this far with the code, but I am not able to get the TurnLedOn section to come on.

main:
symbol Hours = b18 ' current "time"
symbol Mins = b19
symbol HoursX = b20 ' time to turn LED on again
symbol MinsX = b21
symbol HoursY = b22 ' time to restart process
symbol MinsY = b23
symbol LastTimer = w12
symbol Randomiser = w13
symbol timer = 0


Initialise: ' set clock and save current value of timer
Hours = 0
Mins = 0
HoursX = 0
MinsX = 1 ' to avoid tests passing at the start
HoursY = 200
MinsY = 0
LastTimer = timer
Randomiser = timer ' seed Randomiser

CheckClock:
w0 = timer ' current time
w1 = w0 - LastTimer ' elapsed since last update
if w1 > 60 then ' at least a minute has passed
w2 = w1 / 60 ' whole minutes passed
LastTimer = w2 * 60 + w0 ' update LastTimer with whole minutes (multiple of 60 secs)
Mins = Mins + w2
Hours = Mins / 60 + Hours ' if Mins > 60, increment Hours
Mins = Mins // 60 ' minutes (0-59)
random Randomiser ' randomise each time this is executed
endif
if Hours > HoursX then SecondSwitchOnTime
if Hours = HoursX and Mins >= MinsX then SecondSwitchOnTime
goto NotSecondSwitchOnTime
SecondSwitchOnTime:
random Randomiser ' get a random value
w0 = Randomiser ** 61 ' value between 0 and 60 inclusive
w0 = w0 + 60 ' value between 60 and 120
MinsX = Mins + w0 ' set time for next switching on
HoursX = MinsX / 60 + Hours ' current time + elapsed
MinsX = MinsX // 60 ' 0-59
gosub TurnLedOn
HoursY = Hours + 22 ' elapsed 22 hours
MinsY = Mins
return
NotSecondSwitchOnTime:
if Hours > HoursY then Initialise
if Hours = HoursY and Mins >= MinsY then Initialise


goto CheckClock ' add a loop to repeat the program

TurnLedOn:
high c.0
for b10 = 1 to 3 ; define on in minutes 3
pause 60000 ; wait 60 seconds
next b10
low c.0
return
 

Aries

New Member
Without being able to spend much time on it at the moment, there is a "return" in the middle of CheckClock, which will send your chip into the dark blue yonder. Put some "sertxd" staements in the code to see what the values are and where it is going
 

Aries

New Member
Code:
symbol Hours = b18 ' current "time"
symbol Mins = b19
symbol HoursX = b20 ' time to turn LED on again
symbol MinsX = b21
symbol HoursY = b22 ' time to restart process
symbol MinsY = b23
symbol LastTimer = w12
symbol Randomiser = w13


Initialise: ' set clock and save current value of timer
    Hours = 0
    Mins = 0
    HoursX = 0
    MinsX = 1 ' to avoid tests passing at the start
    HoursY = 200
    MinsY = 0
    LastTimer = timer
    Randomiser = timer ' seed Randomiser

    gosub TurnLedOn                    ' start by switching on once

' set next turn-on time (60-120 minutes)
    random Randomiser ' get a random value
    w0 = Randomiser ** 61 ' value between 0 and 60 inclusive
    w0 = w0 + 60 ' value between 60 and 120
    MinsX = Mins + w0 ' set time for next switching on
    HoursX = MinsX / 60 + Hours ' current time + elapsed
    MinsX = MinsX // 60 ' 0-59

CheckClock:
    w0 = timer ' current time
    w1 = w0 - LastTimer ' elapsed since last update
    if w1 > 60 then ' at least a minute has passed
        w2 = w1 / 60 ' whole minutes passed
        LastTimer = w2 * 60 + w0 ' update LastTimer with whole minutes (multiple of 60 secs)
        Mins = Mins + w2
        Hours = Mins / 60 + Hours ' if Mins > 60, increment Hours
        Mins = Mins // 60 ' minutes (0-59)
        random Randomiser ' randomise each time this is executed
    endif

    if Hours > HoursX then SwitchOnTime
    if Hours = HoursX and Mins >= MinsX then SwitchOnTime
    goto NotSwitchOnTime

SwitchOnTime:
    gosub TurnLedOn
    MinsX = 100                ' disable turn-on
    HoursY = Hours + 22 ' elapsed 22 hours until restart
    MinsY = Mins

NotSwitchOnTime:
    if Hours > HoursY then Initialise
    if Hours = HoursY and Mins >= MinsY then Initialise
    
    goto CheckClock ' add a loop to repeat the program

TurnLedOn:
high c.0
for b10 = 1 to 3 ; define on in minutes 3
pause 60000 ; wait 60 seconds
next b10
low c.0
return
This may be better (still untested)

The first call to TurnLedOn is right at the start, followed by setting of the random time for the next turn-on
The second turn-on then disables the test (by setting MinsX to 100)

The major correction is changing timer to time (timer is the X2 timer, which is what I am using at the moment - apologies). Setting timer to zero using symbol - as you did - means the value never changes anyway.
 

regpye

New Member
Thanks Aries,
I changed to time and it appears to be working.
Is there a way I can let it run and have some form of readout to test the times on and off over a full day period? I have no idea of how to do that if it is possible.
I really appreciate the work you have done so far, I could never have done that myself with my limited knowledge at present.
 

Aries

New Member
Use sertxd which outputs to the serial terminal display.

If you are using the 08M2 at default frequency, then this should do more or less what you need (based on my original code):

Code:
#terminal 4800

symbol Hours = b18 ' current "time"
symbol Mins = b19
symbol HoursX = b20 ' time to turn LED on again
symbol MinsX = b21
symbol HoursY = b22 ' time to restart process
symbol MinsY = b23
symbol LastTimer = w12
symbol Randomiser = w13

pause 5000                        ' give terminal time to start up

Initialise: ' set clock and save current value of time
    Hours = 0
    Mins = 0
    HoursX = 0
    MinsX = 1 ' to avoid tests passing at the start
    HoursY = 200
    MinsY = 0
    LastTimer = time
    Randomiser = time ' seed Randomiser

    gosub TurnLedOn                    ' start by switching on once

' set next turn-on time (60-120 minutes)
    random Randomiser ' get a random value
    w0 = Randomiser ** 61 ' value between 0 and 60 inclusive
    w0 = w0 + 60 ' value between 60 and 120
    MinsX = Mins + w0 ' set time for next switching on
    HoursX = MinsX / 60 + Hours ' current time + elapsed
    MinsX = MinsX // 60 ' 0-59

CheckClock:
    w0 = time ' current time
    w1 = w0 - LastTimer ' elapsed since last update
    if w1 > 60 then ' at least a minute has passed
        w2 = w1 / 60 ' whole minutes passed
        LastTimer = w2 * 60 + w0 ' update LastTimer with whole minutes (multiple of 60 secs)
        Mins = Mins + w2
        Hours = Mins / 60 + Hours ' if Mins > 60, increment Hours
        Mins = Mins // 60 ' minutes (0-59)
        random Randomiser ' randomise each time this is executed
        sertxd(13,10,"Time ",#Hours,":",#Mins)
    endif

    if Hours > HoursX then SwitchOnTime
    if Hours = HoursX and Mins >= MinsX then SwitchOnTime
    goto NotSwitchOnTime

SwitchOnTime:
    gosub TurnLedOn
    MinsX = 100                ' disable turn-on
    HoursY = Hours + 22 ' elapsed 22 hours until restart
    MinsY = Mins

NotSwitchOnTime:
    if Hours > HoursY then Initialise
    if Hours = HoursY and Mins >= MinsY then Initialise
    
    goto CheckClock ' add a loop to repeat the program

TurnLedOn:
sertxd(13,10,"Turn on LED at ",#Hours,":",#Mins)
high c.0
for b10 = 1 to 3 ; define on in minutes 3
pause 60000 ; wait 60 seconds
next b10
low c.0
return
#terminal 4800 sets the terminal speed to match the speed of your 08M2 (if you change the frequency of the M2, you will need to change the terminal setting as well).

sertxd is a "print" statement, which prints whatever you put as its arguments (see the Picaxe manual for details). 13 and 10 are respectively "carriage return" and "line feed" which start a new line of output, rather than carrying on after any previous printing. You can use CR and LF instead of the values. The two statements shown print
(a) the time whenever it updates and
(b) the time when the LED is turned on.
Note, that during the TurnLedOn routine, the clock variables are not updated, although the time variable still is. This means that the initial time reported is always zero.
 

hippy

Technical Support
Staff member
First thing I would do is list the sequence desired ...

On for about 3 minutes
Off for 60 to 120 minutes
On for about 3 minutes
Off for 22 hours
Repeat from the beginning

I would suggest everything there should be in seconds to make things simple. Unfortunately 22 hours is 79200 seconds which is too large to hold in a 16-bit word but two sets of 11 hours is the same as 22 hours, and each of those is just 39600 which is acceptable. 10+12 and other combinations would also work.

And, even though my code would be using seconds there's no reason not to use the units the specification uses itself. My starting point would be ...
Code:
Do
  Between(  3 MINUTES,   5 MINUTES, Light_On  ) ; On for about 3 minutes
  Between( 60 MINUTES, 120 MINUTES, Light_Off ) ; Off for 60 to 120 minutes
  Between(  3 MINUTES,   5 MINUTES, Light_On  ) ; On for about 3 minutes
  Between( 11 HOURS,    11 HOURS,   Light_Off ) ; Off for 11 hours - 22 in total
  Between( 11 HOURS,    11 HOURS,   Light_Off ) ; Off for 11 hours
Loop                                            ; Repeat from the beginning
So that's it, job done.

Well, apart from creating the wrapping code which allows that to do what we want, and this would be my first pass attempt at that ...
Code:
Symbol LIGHT         = C.0	; Pin the light is connected to

Symbol seconds_count = w1	; Number of seconds light is on or off

#Macro Between( min_secs, max_secs, action )
  seconds_count = min_secs
  Gosub action
#EndMacro

#define SECONDS		* 1
#define MINUTES		* 60
#define HOURS   	* 60 * 60

Do
  Between(  3 MINUTES,   5 MINUTES, Light_On  ) ; On for about 3 minutes
  Between( 60 MINUTES, 120 MINUTES, Light_Off ) ; Off for 60 to 120 minutes
  Between(  3 MINUTES,   5 MINUTES, Light_On  ) ; On for about 3 minutes
  Between( 11 HOURS,    11 HOURS,   Light_Off ) ; Off for 11 hours - 22 in total
  Between( 11 HOURS,    11 HOURS,   Light_Off ) ; Off for 11 hours
Loop                                            ; Repeat from the beginning

Light_On:
  High LIGHT
  Goto Wait_Number_Of_Seconds

Light_Off:
  Low  LIGHT
  Goto Wait_Number_Of_Seconds

Wait_Number_Of_Seconds:
  Do While seconds_count > 0
    Pause 1000
    seconds_count = seconds_count - 1
  Loop
  Return
That's not complete. The first line of the '#Macro Between()', line 6, sets the 'seconds_count' to the 'min_secs' amount, not a random amount of time between 'min_secs' and 'max_secs'. That can come later.

As it is it's good enough to test it works though one will probably want to reduce the hours of waiting to something less while testing.
 

hippy

Technical Support
Staff member
And a quick note about output, using SERTXD to monitor progress.

On an 08M2 that SERTXD goes to C.0 which is the same pin you have your MOSFET connected to. Trying to use C.0 for both will create some problems, with 'random data' being displayed in the Terminal and worse the light flickering on and off and potentially being left in a different state to what your code has told it to be in.

The simplest solution is to move the MOSFET to a different pin, C.1, C.2 or C.4, and adjust the pin number in the program.
 

regpye

New Member
Thanks Hippy, that all looks very interesting too, and a different approach. And thank you Aries as well.
The only reason I chose C.0 was because the existing board I have uses C.0 for a mosfet that is already built in, but I also have C.1 and C.2 available as LED outputs that could be used. Having C.0 was a bit of a pain because after programming I had to change a jumper to activate the mosfet each time.
I don't have enough knowledge about using the random, I still am getting my head around many things, but it is very interesting and I enjoy learning more each time all you guys show something that is new to me.
 

regpye

New Member
That's not complete. The first line of the '#Macro Between()', line 6, sets the 'seconds_count' to the 'min_secs' amount, not a random amount of time between 'min_secs' and 'max_secs'. That can come later.

As it is it's good enough to test it works though one will probably want to reduce the hours of waiting to something less while testing.
I have been away for a few days and after getting back I decided to speed up the code that Hippy produced for testing.
It all seems to work OK in the current form, just needs the randomising introduced of which I have no idea how that works in concept.
Maybe someone can help with that function?
 

Aries

New Member
AllyCat has given a description of the theory and function of RANDOM here ...
 

hippy

Technical Support
Staff member
The PICAXE has a RANDOM command which turns the value in a word variable into another value in a somewhat random way. It's not a truly random way but we can ignore that for now.

The first thing you need is a word variable to hold that random number -
Code:
Symbol random_number    = w2	; Continually varying random number
Then we will need to alter it to make it hold a new value. We can do that within a "Randomize" macro so we can use it simply by specifying "Randomize" rather than having to type out the entire RANDOM command each time -
Code:
#Macro Randomize()
  Random random_number
#EndMacro
Now we have to use that "Randomize" within the code so the number does regularly change. You can have just one occurrence of that just before you actually use the random value but, because it's not a truly random number, it is best to do that regularly or a number of times.

A good candidate for that is within our "Wait_Number_Of_Seconds" routine. We can give it a kick when we enter and kick it every second we wait. Our "Wait_Number_Of_Seconds" routine then becomes -
Code:
Wait_Number_Of_Seconds:
  Randomize
  Do While seconds_count > 0
    Pause 1000
    Randomize
    seconds_count = seconds_count - 1
  Loop
  Return
Now, while your program is running, the 'random_number' will be regularly changing. When we come to use it we will have some kind of random value to use. Using it will be the next instalment.
 

hippy

Technical Support
Staff member
We currently have ...
Code:
#Macro Between( min_secs, max_secs, action )
  seconds_count = min_secs
That's not random at all, always uses the minimum number of seconds to delay. What we can do is create a macro which allows a variable to be set to a random value between a minimum and maximum value -
Code:
#Macro Between( min_secs, max_secs, action )
  SetRandom( seconds_count, min_secs, max_secs )
Of course we now need to define that "SetRandom" macro to be able to use it.

One way to do that is to have two variables, "random_min" and "random_max" which are set to the range we want our random value to be within, call a function which creates a random value in another variable 'random_use' which can be placed in the variable we ultimately want to update. For example -
Code:
#Macro SetRandom( var, min, max )
  random_min = min
  random_max = max
  Gosub Calculate_Random
  var = random_use
#EndMacro
We are not there just yet. There are two more things we need to do.

First we need to define the variables we are now using -
Code:
Symbol random_min       = w3	; Minimum random value wanted
Symbol random_max       = w4	; Maximum random value wanted
Symbol random_use       = w5	; Random value to use
And secondly we need to define the "Calculate_Random" routine which takes "random_min" and "random_max" and creates "random_use". We can start with a simple assignment which doesn't do that, just uses the minimum value -
Code:
Calculate_Random:
  random_use = random_min
  Return
We have added a fair bit of code and our first program has now become - You may need to change the pin the light is connected to -
Code:
Symbol LIGHT            = C.0	; Pin the light is connected to

Symbol seconds_count    = w1	; Number of seconds light is on or off
Symbol random_number    = w2	; Continually varying random number
Symbol random_min       = w3	; Minimum random value wanted
Symbol random_max       = w4	; Maximum random value wanted
Symbol random_use       = w5	; Random value to use

#Macro Randomize()
  Random random_number
#EndMacro

#Macro SetRandom( var, min, max )
  random_min = min
  random_max = max
  Gosub Calculate_Random
  var = random_use
#EndMacro
   
#Macro Between( min_secs, max_secs, action )
  SetRandom( seconds_count, min_secs, max_secs )
  Gosub action
#EndMacro

#define SECONDS		* 1
#define MINUTES		* 60
#define HOURS   	* 60 * 60

Do
  Between(  3 MINUTES,   5 MINUTES, Light_On  ) ; On for about 3 minutes
  Between( 60 MINUTES, 120 MINUTES, Light_Off ) ; Off for 60 to 120 minutes
  Between(  3 MINUTES,   5 MINUTES, Light_On  ) ; On for about 3 minutes
  Between( 11 HOURS,    11 HOURS,   Light_Off ) ; Off for 11 hours - 22 in total
  Between( 11 HOURS,    11 HOURS,   Light_Off ) ; Off for 11 hours
Loop                                            ; Repeat from the beginning

Light_On:
  High LIGHT
  Goto Wait_Number_Of_Seconds

Light_Off:
  Low  LIGHT
  Goto Wait_Number_Of_Seconds

Wait_Number_Of_Seconds:
  Randomize
  Do While seconds_count > 0
    Pause 1000
    Randomize
    seconds_count = seconds_count - 1
  Loop
  Return

Calculate_Random:
  random_use = random_min
  Return
That's no better than what we previously had; it always uses the minimum value rather than a random value. But at least we have a program which compiles and can be tested.

We only need to improve the "Calculate_Random" routine to make it actually random. And that's in the next instalment.
 

hippy

Technical Support
Staff member
Now we come to making "Calculate_Random" actually calculate a random number. If 'random_max' is the same as 'random_min' it's not actually random, the value to use will always be 'random_min', or 'random_max' for that matter, so that case is easy -
Code:
Calculate_Random:
  If random_max <= random_min Then
    random_use = random_min
  Else
     ... ? ...
  End If
  Return
It's figuring out what should be in the else clause which gets a little complicated, involves some maths.

Calculating a value between 'min to max' is also the same as calculating 'min + ( 0 to (max - min) )', and if we first subtract min from max the number we want is 'min + ( 0 to max )'.

What we now need to do is convert our 'random_number' to be a value between 0 and max.

One way of doing that is to use the modulo operator '//' which gives us the remainder after a division. 'n = random_number // max' will give us a value of '0 to max-1', but we can increment max before that and get the result we want.

We now have a multi-step process to get our random value between 'random_min' and 'random_max' as follows -

Code:
    random_max = random_max - random_min
    random_max = random_max + 1
    n = random_number // random_max
    random_use = random_min + n
We can combine the first two lines into one, and we can replace 'n' with 'random_max', because we don't need its value after the modulo, which gives us a final routine of -
Code:
Calculate_Random:
  If random_max <= random_min Then
    random_use = random_min
  Else
    random_max = random_max - random_min + 1
    random_max = random_number // random_max
    random_use = random_min + random_max
  End If
  Return
And that should be it.
 

regpye

New Member
And that should be it.
Wow! I am going to have to read this several times to get my head around it all. What first appeared to be a so simple project has turned out to be rather complicated. At this time I don't fully understand it all, but I have only read it through once and have got some idea of what is going on.


AllyCat has given a description of the theory and function of RANDOM here ...
Thank you for that detailed information, it will take several reads for it to register in my mind, but my manuals don't have any information like this, so that is why I am in the dark about several things yet. I have purchased a number of books on Picaxe programming and have downloaded all I can find from the picaxe websites. Still a very lot to learn and a project like what I am working on has opened my eyes on how little I know at this stage.
 
Last edited:

regpye

New Member
And that should be it.
Thanks Hippy,
I have studied the structure of your code and it is very easy to navigate and make changes where/if needed.
I am starting to understand how the random works now and I certainly learnt a lot from the way you tackled this project, very organised way of doing things.
All appears to be working fine on a simulated test, I will put the hardware together soon and give it a live test, I am sure it will work okay.
Thanks for all your effort and teaching, very much appreciated.
 
Top