An Arduino-based random number generator with results between 1 and 100 that don’t duplicate.
BUILD TIME: 1 hour
DIFFICULTY RATING: Intermediate
Have you ever had the need to pull random numbers within a specific range out of your head? You could be playing a game, helping a family member pick lottery numbers, or even creating a game where your elements need a random pathing, etc. The reality is, true randomness is not something that is achievable in the digital realm. However, there are a few tricks to provide results good enough to appear random to us mere humans.
The Broad overview
For this project, we will create a “simple” non-repeating and non-duplicating random number generator. That is to say, a project that can produce random numbers between 1 and 100 (inclusive), for example, without listing the same number more than once.
We put “simple” in quotations because, in reality, there is a bit of a trick to ensuring that you do not get the same number re-occurring when using the usual pseudorandom Arduino / C++ functions. Thus, it actually took us several iterations to create a program that worked the way we intended.
Our project will use an Arduino Nano and tactile switches on a breadboard, with results displayed on an LCD screen.
Generating Random numbers
In digital electronics, it is not truly possible to generate random numbers. Let’s say, for example, we use the Arduino Random function to produce a number between 1 and 10 using the following code:
int randomNumber = 0;
void setup() {
Serial.begin(9600);
}
void loop() {
for (int j = 0; j < 30; j++) {
randomNumber = (random(1, 11));
Serial.print(randomNumber);
Serial.print(", ");
}
delay(2000);
for(int i = 0; i < 2; i ++){
Serial.println(" ");
}
}
If we use the Serial Monitor in the Arduino IDE to view the output, we get the following results.
Looking at the top line, we see that several numbers have been called twice and sometimes directly after they were called, such as 3 and 10. A true random number generator would have the ability to produce the same number multiple times, however, that may not be useful for many applications. As such, we want to prevent this from occurring in our project.
If we look at the numbers they truly do look like the program has randomly picked the numbers. However, if we reset the microcontroller on the Arduino development board, we will see the exact same number sequence repeated. This is because digital electronics uses a mathematical algorithm to generate these numbers. Thus, they will always produce the same results from the same mathematical algorithm.
We can force the math to change by using the random seed function in the Arduino language to change the results using the following code.
int randomNumber = 0;
void setup() {
Serial.begin(9600);
randomSeed(2);
}
void loop() {
for (int j = 0; j < 30; j++) {
randomNumber = (random(1, 11));
Serial.print(randomNumber);
Serial.print(", ");
}
delay(2000);
for(int i = 0; i < 2; i ++){
Serial.println(" ");
}
}
This gives us another completely different set of numbers to the previously “un-seeded” results.
However, like with the un-seeded results, if we were to reboot the microcontroller, we would once again get the exact same results.
This is because, once again, the math used in the algorithm isn’t changing. We need a way to have the seed be assigned a truly random value anytime the microcontroller is booted. In a device connected to the internet, such as your PC, the standard way of doing this is to calculate the number of milliseconds that have elapsed since a specific date. You simply compare the current time against a predefined time and use the difference as the seed.
This works fine on your connected device which can just access a web server and get the current time. Presumably such a method is impossible when dealing with standalone microcontrollers. There are, of course, viable alternatives. For the most part, we generally resort to reading the analog voltage of a pin with nothing connected.
Normally, when we think of microcontrollers, we think of the general-purpose input and output pins (GPIO) as being in a known or controlled state and having either 5V or 0V potential. In reality, however, any pin which has not been told in programming to be at a set potential and with no external electronics connected to it can have a fluctuating voltage on it. To demonstrate this, we attached our oscilloscope probe to Analog pin 0 with the volts per division set to 5mV and the time per division set to 8ms.
This produced a voltage signal which fluctuated from -7mV to +17mV with a period of about 20ms. Frequency is the inverse of time, and thus, the frequency is 50Hz.
f = 1 / T = 1 / 0.02 = 50Hz
This voltage is inductively coupled into the microcontroller’s analog pins from your home’s electrical wiring and is also coupled via the oscilloscope and power supply etc. Whilst the wave itself is entirely predictable, the voltage at any given moment changes so rapidly that we humans would be unable to reliably power the device so that the noise on the pin was consistently the same level.
Note: The amplitude of this signal will also differ based on the proximity to devices that emit electromagnetic interference as well as the position of objects and people in the area, etc. The closer the microcontroller is to a device emitting EMI, the higher the amplitude of the sine wave.
To demonstrate this we can write a simple program to capture and record the voltages seen by the analog pins and display them using the Serial Monitor.
int minValue = 1024;
int maxValue = 0;
void setup() {
Serial.begin(9600);
}
void loop() {
int inputValue = analogRead(A0);
if (inputValue > maxValue){
maxValue = inputValue;
}
if (inputValue < minValue){
minValue = inputValue;
}
Serial.print(" current Value = ");
Serial.print(inputValue);
Serial.print(" Min Value = ");
Serial.print(minValue);
Serial.print(" Max Value = ");
Serial.println(maxValue);
}
This shows us that reading that analog pin in our situation produces a signal ranging between 400 and 540 with 18 readings being taken over a period of about 1 second.
If we were to use this pseudorandom value as our seed, we would be able to create a seed value that differs every time you power the device. Despite the noise itself not being truly random, as it follows a repeatable pattern which could thus be calculated. The exact moment we power the device will produce a pseudorandom value based on the voltage at the precise moment it was measured. We can easily modify the previous random demonstration to now set the seed based on this principle using the following code.
int randomNumber = 0;
void setup() {
Serial.begin(9600);
randomSeed(analogRead(0));
}
void loop() {
for (int j = 0; j < 30; j++) {
randomNumber = (random(1, 11));
Serial.print(randomNumber);
Serial.print(", ");
}
delay(2000);
for(int i = 0; i < 2; i ++){
Serial.println(" ");
}
}
This will produce a different string of numbers every time the microcontroller is rebooted, and thus, produces a random enough series of results, at least for us humans. This isn’t the only way that you can implement such a random seed for your project though. You could use the Millis function to provide the seed when a button is pushed for example, but for our use, the analogRead function will suffice.
With this knowledge on board, we set about creating a program that would generate random numbers between a minimum value and a maximum value. We started with the following code:
int randomNumber = 0;
int minNumber = 10;
int maxNumber = 50;
int numGenerated = 10;
void setup() {
Serial.begin(9600);
randomSeed(analogRead(0));
}
void loop() {
for (int j = 0; j < numGenerated; j++) {
randomNumber =
(random(minNumber, maxNumber));
Serial.print(randomNumber);
Serial.print(", ");
}
delay(2000);
for(int i = 0; i < 2; i ++){
Serial.println(" ");
}
}
However, we soon found this program had the potential to produce the same number multiple times.
Naturally, we did not want our number generator to have the ability to randomly select the same number more than once, so this simplistic approach is not ideal.
While there are many times where having the same number called more than once is fine, there are equally as many where it simply wouldn’t work. For example, if you wanted to make a program that randomly calls lotto or bingo numbers, you do not want any duplicates.
Our first thought was to create a For loop that checks the most recently generated number against the previously generated numbers and would re-generate if the number had already been called. However, this became quite a significant task. If you wanted to generate ten numbers, for example, you would need to check ten elements of an array ten times, which while not impossible to write, would take quite a while. Moreover, the more numbers you want to select, the longer the program takes.
We figured a more efficient way to produce a random number that could not be repeated was to store all useable numbers in an array and simply shuffle that array.
int count = 0;
int myArray[100] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
51, 52, 53, 54, 55, 56, 57, 58, 59, 60,
61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
81, 82, 83, 84, 85, 86, 87, 88, 89, 90,
91, 92, 93, 94, 95, 96, 97, 98, 99, 100
};
void setup() {
randomSeed(analogRead(0));
Serial.begin(9600);
}
void loop() {
randomize(myArray);
for (int i = 0; i < 100; i++) {
count ++;
Serial.print(myArray[i]);
Serial.print(" ");
if (count >= 10) {
Serial.println(" ");
count = 0;
}
}
for(int i = 0; i < 4; i++){
Serial.println("");
}
delay(2000);
}
void swap (int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
void randomize(int myArray[]) {
for (int i = 0; i < 100; i++) {
long j = random(0, 100);
swap(&myArray[i], &myArray[j]);
}
}
We have an array, 100 elements long with each element storing a unique number starting from 1 and ending at 100. The program then swaps each element starting from element 0 and ending at element 99, with the element contained in a randomly generated position.
Using this technique, we can generate an entirely random array of all 100 digits as you see here.
We can further build on this by filtering out values that are not within a desired field. Let’s say, for example, the user only wants random numbers between 1 and 80. First we add two new variables:
int minDigit = 1;
int maxDigit = 80;</div>
We can then adjust the loop to filter the results from this array so that only the values between the desired range are shown using a simple If statement inside the original "for" iterator.
void loop() {
randomize(myArray);
for (int i = 0; i < 100; i++) {
if ((myArray[i] > minDigit) &&
(myArray[i] < maxDigit)) {
Serial.print(myArray[i]);
Serial.print(", ");
if(count >= 10){
Serial.println(" ");
count = 0;
}
count++;
}
}
This will still shuffle the entire array but only display the values within the range set by the minDigit and maxDigit variables.
The only thing left to do is make the project self-contained so that we don’t need a computer and keyboard to view the results. For this, we opted to use the 1602 LCD that we are all familiar with and some tactile switches.
The switches will be used to allow the user to set the min and maximum values along with setting how many digits are to be displayed on the screen.
The Build:
Parts Required: | Jaycar | ||
---|---|---|---|
1 x Arduino Nano or Compatible Board | XC4414 | ||
1 x 1602 LCD | QP5521 | ||
1 x I2C Backpack | XC3706 | ||
4 x Tactile Switches | SP0601 |
You’ll also need a breadboard and prototyping hardware.
Electronics
This is a simple project to build thanks to the low number of components required and the use of the I2C backpack.
We have built our prototype using an Arduino Nano, however, we will include a wiring diagram if you only have access to an Arduino Uno.
You first need to solder the backpack to the LCD screen. If you’re not sure how to go about this, you can follow the instructions from our Keyless Entry project from Issue 36.
Note: we have our LCD and backpack soldered in a way to clearly display the hardware in images during prototyping, and it does not need to be soldered this way for home use.
All four of the tactile switches are connected to GND on the Arduino Nano.
Note: We are not using pullup or pulldown resistors for this project to reduce the components but are using the pullups in the microcontroller.
The switches from left to right are as follows.
DECREMENT:This switch decreases the current value displayed on the LCD.
INCREMENT: This switch increases the value displayed on the LCD.
SELECT: This button writes the value displayed on the LCD to the appropriate memory location.
RESET: This button resets the selected values and allows you to start the program again, providing new values.
Wiring guide
Once you’re all wired up, you can connect the Arduino Nano to your computer and upload the program which we will provide on the website.
Testing
Once the program is uploaded and powered on, you will first see a splash screen.
This is just a splash screen letting you know what the project is. This screen will disappear after a 2-second delay. You will be greeted by the following screen.
This is asking you how many digits you want to be displayed at a time. You can use the increment button (second from the left) and decrement button (first button from the left) to set this value.
We can only fit ten digits on the LCD at any given time so the program will not let you increment past this value. Likewise, the program won’t allow you to enter a value less than one.
For our example, we will set the value to ten, to display ten digits per screen.
Once you have entered your desired value you can press the select button (third from the left).
This screen is asking you to enter the lowest number you want in the selection pool. This will let you randomly pick a number between 10 and 50, for example. Using the increment and decrement buttons you can set this value to your desired minimum number.
The program will not let you enter a value less than 1 or higher than (100 - the number of digits you selected earlier). This way, you’re unable to select a value that is outside of the maximum range of the program. For example, if you chose to display 10 digits, the highest minimum number you can select will be 90 as picking a value higher will mean you cannot get 10 unique digits. When you’re done, press the select button to write the value to memory.
This screen allows you to use the increment and decrement buttons to set the maximum allowable value. This will automatically increment to the minimum possible value given your previous settings.
Thus, if you have asked for 10 digits with the lowest value being 1, the program will automatically start the highest value from 11 and will not allow you to set a value lower. For us, we set the value to 100 and pressed the select button to randomise the array and display the first set of digits on the LCD.
From here, you have two options. Firstly, you can press the select button to show you the next set of results from the array within your desired range. Secondly, you can press the reset button (furthest right button) to start the program again, which allows you to change the input values and re-shuffle the array once more.
Nice work! You now have your very own random number generator, capable of generating a set of random numbers anywhere within a range between 1 and 100.
Where to from here?
There are a few things one can do to improve on this project. Firstly, you could design an enclosure to keep the electronics or go one step further by putting the circuitry onto a perfboard or custom PCB.
Our prototype uses tactile switches as the input to increment and decrement the desired value, however, you will find that incrementing or decrementing up to 99 digits can be quite a chore. i.e. up to 99 button presses.
A system that detects if the button has been held down and increments quicker if so would be a massive improvement to this project. Or even better, you could do away with the increment and decrement buttons altogether and swap them for an indented rotary encoder with velocity control. This would give a much better user experience in regard to changing the values, and would make the enclosure for such a project much simpler.
If you want to try the same circuit with an Arduino Uno, check out the resources section. The same code applies.