Repeating an action over a random amount of time.

I’ll try ot make this quick.
I made a little cat toy out of 2 servos and a laser. An X and Y axis servo with a laser at their end.
I have a random number given for degrees to point the X and Y servos and a random number for the time between movements.

To better keep the cats attention, I want to add a little stutter to the servo jittering the laser back and forth a degree or two on each axis.

I know I could say "Jitter the servo a random amount of times before moving to the next general movement, but that feels kinda clunky and I like to attempt to learn a thing or two from these small projects. So how would you guys implement this idea of jittering the servos the entire time between the larger movements?

A second question I had was, are you able to read each random input from separate analog pins? Right now it seems that roughly 50% of the time only one servo moves instead of both, but this also could be a power issue since I’m running both micro servos and the laser off the Unos 5V output.

So what are your ideas on how to code this? And if there are any flaws in mine, feel free to point them out. It’s been a while since I’ve played with random numbers in Arduino.

#include <Servo.h>

Servo servoX;
Servo servoY;
int laser = 2;

long rand1; //ServoX
long rand2; //ServoY
long rand3; //Time between new X and Y angles given

void setup() {

  pinMode(laser, OUTPUT); //Laser on pin 2
  servoX.attach(7); //ServoX on pin 7
  servoY.attach(8); //ServoY on pin 8
}

void loop() {
  digitalWrite(2, HIGH); //Turns the laser on
  
  rand1 = random(20, 160); //Keeps the laser from hitting the mount
  rand2 = random(120, 180); //Keeps the laser from hitting walls or the ceiling
  rand3 = random(2000, 5000); //Min time 2 seconds, max time 5 seconds
  
  servoX.write(rand1); //Moves ServoX
  servoY.write(rand2); //Moves ServoY
  
  delay(rand3); //Delays for the random time generated
}

Before you enhance your sketch can I suggest that you tidy up the current one ?

For instance, why are rand1 and rand2 declared as longs when their value will never exceed 255 ? Why is rand3 an unsigned long when its value will never exceed 5000 ?

As to your possible enhancements, you will need to move to miilis() for timing as using delay() blocks the code from doing anything else. You will need 3 timing periods, an overall period during which the servos jitter and a timing period for each servo to implement the jitter periods

In psuedo code something like this :

start of loop()
  use random to define the wait time
  use random to define the x jitter period
  use random to define the y jitter period
  use random to define the base x position
  use random to define the base y position
  save the millis() value as the start time
  save start time as x jitter start time
  save start time as y jitter start time
  move servos to base positions
  
  while current time - start time >= wait time
    if current time - x jitter start time > x jitter period
      move x a small random amount
      save current time as x jitter start time
    end if
    if current time - y jitter start time > y jitter period
      move y a small random amount
      save current time as y jitter start time
    end if
  end while
end of loop()

Take a look at Using millis() for timing. A beginners guide, Several things at the same time and the BlinkWithoutDelay example in the IDE

I just used longs because that was in the Arduino tutorial page for Random Numbers and worked well enough. I do appreciate the pointer on just using an Int in this case for future knowledge. But as to why I did so, well it worked and I didn't want to fix what wasn't broken. Not trying to micro manage my code into the tiniest memory possible typically. I would rather spend time making things, than make things perfect.

As far as the While Loops go, I think that is what I was looking for. Just feels more tidy.
I'll play around with some While Loops tomorrow. Thank you :smiley:

Whilst there is nothing wrong with using a variable type capable of holding a value an order of magnitude (or two) larger than needed, it is not a habit that you should get into. For your current sketch you have plenty of memory available but that may not always be the case and there are other reasons for using as small a variable type as possible

As to the while loop in my pseudo code, like your variable types, it is not actually a good idea in general because it can cause problems by blocking code execution. As an example, what if you wanted two independent pairs of servos, each with their own timing ? The timing for one of the pairs would interfere with the timing of the other pair so a different technique would be preferable

But it is a case of horses for courses and for your needs a while loop should work OK

In this instance, the X and Y coordinates are intended to be spaced out by the same time, and update at the same time. I never thought about each axis running on their own random times. The cats are having a hard enough time tracking the laser with how fast the servos are, but as a learning exorcise that would be a fun obstacle to overcome.

Once you have got the overall timing loop working using millis() you can do whatever you like during the period as long as your code does not block the free running of the code so moving one or two servos independently makes little difference

@UKHeliBob "Whilst there is nothing wrong with using a variable type capable of holding a value an order of magnitude (or two) larger than needed, it is not a habit that you should get into."

LOL. TBC this is binary order of magnitude, and if I have to go out later I'll need to put on a clean shirt.

a7

I don't see you seeding the random number generator. The random() function is a pseudo-random number generator. it uses a mathematical function to create a sequence of numbers that appears random to our human evaluations. Without seeding, the function will always have the same starting point, meaning that its output sequence will be identical every time it is run. If you want it to run differently every time, you must seed it with something different every time the program is run, meaning you need to take information from an external source of noise (random() - Arduino Reference).

Ever watch a video game speedrun and hear them talk about "RNG manipulation"? That means the PRNG's formula is known and they've discovered ways of taking advantage of its predictability.

Without seeding, the function will always have the same starting point, meaning that its output sequence will be identical every time it is run

Just how clever do you think these cats are !

UKHeliBob:
Just how clever do you think these cats are !

Very.

The other members of my family own a lot of pets. They aren't stupid.

Well the main intention from this project is to learn. So just as maybe not "needing" to change to Int from Long, I am glad to learn that, and I do want to better understand the Random function. I was semi aware of this, as I never called to read an analog pin but as the project went on I kinda ran into the, it's not broken so lets not fix it mentality.

So how would I go about linking the "randomness" to the analog pin, or better yet. How, if possible do I tie each of the 3 random numbers I wish to generate to their own analog read pin? I know it might likely not be needed but doing so would make my tiny brain happy! :stuck_out_tongue:

I've seen the:

randomSeed(analogRead(0));

But I don't understand how it works. How my code knows to connect my Random function to this line of code.

I don't understand how it works.

The random() function will always output the same sequence of numbers depending on the starting point in the sequence, ie it is not truly random at all

An analogue pin not connected to anything will be floating at an unknown voltage somewhere between 0 and 5V. The parameter passed to randomSeed() determines where exactly the random() function starts to produce its "random" number, so effectively you are using a semi random number as the input to randomSeed() thus making it less likely that the random() function will produce the same sequence of numbers each time that the program is run

Note, however, that in practice the range of values returned by a floating analogue pin may not be as wide as 0 to 5V so the range of "random" values it returns may not be as wide as expected. For most applications, however, it is good enough

As there is only one random() function, seeded by randomSeed() if you use it, then it is not easily possible to create more than one random sequence of numbers

Ah I see. So I was using a pseudo random number but not from a pseudo random starting point. So it was the same random sequence over and over?

I think the last question I have here is. I have 3 inputs, each wanting a random number(X, Y and Time). If there is only one randomSeed() then are my 3 variables(X, Y and Time) going to be pretty similar to each other if they are checked for one after another? Or does the static on the pin change so fast that even being checked by the time the next line of code is ran that I will have a random enough number still?

So I was using a pseudo random number but not from a pseudo random starting point. So it was the same random sequence over and over?

Yes

If there is only one randomSeed() then are my 3 variables(X, Y and Time) going to be pretty similar to each other if they are checked for one after another?

Whilst the random() function produces the same sequence if used with the same seed, or no seed, that is not to say that the values returned will be similar to one another in any way

It would be bad practice to call randomSeed() before each call to random(). Think what would happen if the seed value had not changed. One technique that I have used is to have the sketch wait in a loop in setup() until a button is pressed, then to use the value of millis() in randomSeed(). This makes the seed dependant on the time taken to press the button which is not very predictable

For your current purposes using randomSeed() in setup() with an analogRead() value will almost certainly be good enough I would have thought. It is easy enough for you to do some tests by outputting the "random" numbers as comma separated values and importing them into Excel for analysis if you really want to

UKHeliBob:
[..] using randomSeed() in setup() with an analogRead() value will almost certainly be good enough I would have thought.

I ran a little test to see what values to expect from analogRead():

  for (int i = 1; i <= 10; i++) {
    for (int pin = A0; pin <= A5; pin++) {
      Serial.print(analogRead(pin));
      Serial.print("\t");
    }
    Serial.print("\n");
  }

and I did not like the results very much:

380	337	307	283	290	359	
368	352	333	311	283	325	
344	343	337	323	295	308	
319	324	325	323	318	310	
306	309	311	312	303	301	
298	299	301	302	299	304	
297	294	293	291	307	298	
296	292	290	287	283	291	
295	291	288	285	281	295	
289	287	285	284	274	289

Same loop, 100 times instead of 10, restarted Uno 3 times, shown on the serial plotter:

Thank you all very much. Gave me a lot to think about.

Erik I will have to look at my numbers but in practice I do get one of the servos not moving much if at all every iteration. I need to look at my actual numbers, not the servos since I could just be experiencing an issue somewhere else, but I’m pretty sus about my servos not getting a large difference in coordinate adjustments.

Thanks guys. I know its a silly cat toy, but this was a great catalyst to explore the Random function and how to implement it. <3

Seeding the prng is a bit of a classic problem. On more sophisticated systems you typically have various entropy sources which are in some way combined to generate a "truly random" number which you can then use to seed the prng.

As an example, if a system has a keyboard and mouse (or other user inputs), the timing between interrupts can be a source of entropy.

In practice, unless you're doing cryptographic things, worrying about this level of detail is overkill. People often seed the prng using approaches along the lines of "take the current time, and just use the microseconds part of the timestamp". On a desktop or server type machine that's probably good enough for most typical applications.

Obviously on an Arduino that's a bit more tricky. Boot times are probably very deterministic, so relying on a timestamp is unlikely to produce much randomness.

Practically speaking, a floating input pin is probably as good as it gets!

The outputs of your experiment are probably not as bad as you imagine. The prng output from being seeded with 1014 will be very different if it is seeded with e.g. 1015 or 1013 -- just try it and see.

Furthermore, if you're only seeding the prng once per boot, even if you do hit the same seed value every now and then, it's probably not the end of the world for the purposes of confusing your cats :slight_smile:

tomparkin:
Seeding the prng is a bit of a classic problem. On more sophisticated systems you typically have various entropy sources which are in some way combined to generate a "truly random" number which you can then use to seed the prng.

One needs a prng to seed the prng, so to speak... :wink:

[..] on an Arduino [..] Boot times are probably very deterministic, so relying on a timestamp is unlikely to produce much randomness.

When Serial.println(micros()) is the first in setup(), the result is 8 (µs) on my Uno. Every time. Later on (after ~ 10ms of for() loops with Serial.print()s) there is some difference in the timing!

Practically speaking, a floating input pin is probably as good as it gets!

True. But only if the pin is floating, so not connected to any device with pullup resistors, like mine were when I did the first test... :wink:

it's probably not the end of the world for the purposes of confusing your cats :slight_smile:

I only want the best for my cats. They only want the best. :-p

Erik_Baas:
I only want the best for my cats. They only want the best. :-p

This totallllly isn't my side project while I wait for more parts to show up for my motion sensing spray bottle project to keep my cats off the counter :stuck_out_tongue:

tomparkin:
it's probably not the end of the world for the purposes of confusing your cats :slight_smile:

Over engineering is half the fun!
Besides, they love to play with my jumper wires. They might soon figure out how to use pull down resistors on my Analog pins!

Erik_Baas:
One needs a prng to seed the prng, so to speak... :wink:

It's turtles all the way down! :slight_smile: