Code

Software / State Machine

The software libraries we will use include a servo library called Servo.h and an MPU library called MPU6050.h. The sensors include a sound sensor module, PIR sensor, force sensor, ultrasonic sensor, tilt switch, and an MPU.

State Machine:

Pseudo Code:

  • Randomly make 6 out of 16 LEDs turn on

  • Send positional data from standby user input to master operator

  • Master Operator moves to target

  • Master Operator receives signal locked on target.

  • Master Operator fires at target

  • Respective action is triggered on master operator side

  • Information sent to Standby Operator

  • Standby Operator target counter is increased

  • Process is repeated until all targets are hit

  • Show Custom Game Over animation

Standby Operator

Random Generator:

  1. Set repeat variable

    • the purpose of this variable is to allow the random generator to keep generating values until they are all different randomly generated values.

  2. For the for loop, I generate six random values from 0 to 15.

    • The six comes from the number of targets that are defined.

  3. The while loop simply compares the values then checks that they are different, if they are not different then another value is generated until they are all different.

Continuation...

4. Next, there is a sorting sorting function which organizes those randomly generated values.

5. Finally, the values are printed onto the serial monitor for further debugging.

Reposition Servo

This code takes the input from the digital pins 38, 39, 40, and 41, then allows for a 10 millisecond delay for denouncing.

In the for loop, there is two cycles that calculate the x and y integer position from 0 to 3 in the 4x4 matrix. In other words, one cycle recalculates the x position and the other recalculates the y position on the matrix. This for loop also allows for the value to overflow, but if it does overflow it will be reset to 0, and if it goes below 0 it will cycle back to 3.

Below, I stay in this if statement only while I'm pressing the button and allow for a 10 millisecond delay for every check.

This selectPosition function simply allows the Standby Operator (this program) to increase, decrease, or send the position depending on the button that is being pressed at the moment. It only supports having one button pressed at a time. In other words, if we press a button then keep holding that button down and press another button, then the program will not read the other button that is being pressed. This function could have been more efficient if we convert each digital value with a number containing its binary representation but this function works fine. Two buttons that may be pressed and registered are the position buttons and the send button. If the position button and the send button are pressed together, then the position button is released while the send button is still being pressed, then the program should send over the information to the Master Operator within this function. Whether it happens on this loop or in another loop would not make a significant difference to our program.

One of the lines of code is simply too long for this image. Here is a copy of the full line:

for(int i = 0; i < 2; i++) pos[i] += (digitalRead(39 + 2*i) == 0)*(pos[i] < 3) - (digitalRead(38 + 2*i) == 0)*(pos[i] > 0) + 3*((pos[i] == 0) && (digitalRead(38 + 2*i) == 0)) - 3*((pos[i] == 3) && (digitalRead(39 + 2*i) == 0));

Check if a target has been hit:

In the checkHIT function, the program checks each position that we have randomly generated then sets the bool array of 'targetsHIT' to high depending on if a target has been hit. If it has not been processed previously, this function may also keep track of the total number of targets hit. That number is stored on the allTargetsHit integer.

7 Segment Display:

Set the 7 Segment Display main: (Click Here for the link):

This set7SEGDISP function was obtained from the Arduino website. It is a very efficient use of the seven segment display and is therefore used in this project. A link to this function along with the other corresponding functions is provided towards the global variable initializations section.

Update the 7 Segment Display:

In myfnUpdateDisplay the user obtains the eight bits that are required for the seven segment display as an input. It does not return any value, but it does set the respective value onto the seven segment display. Notice how the function required turning off the latchPin then setting the new value and turning on the latchPin again after the value has been set. This will update the value to the new output.

Convert the desired integer value to the respective 8 bit value:

In the myfnNumToBits function, depending on the integer number from 0 to 15 in hex, the function will return the respective number in bits to be set to the 7 Segment Display.

Setup:

In the following image I show the configurations for UART0, UART1, the two servos, the targets, the buttons, the LEDs, the LCD, the laser, and the 7 Segment Display.

Initialize buttons as inputs:

The following program will initialize the five buttons starting on pin 38 with the internal pull-up resistor of the microcontroller.

Initialize 7 Segment Display:

The following program will initialize the data pin, latch pin, and clock pin with respect to the preconfigured values as outputs.

Print Targets:

In the printarray program, I will print the whole array on the serial monitor.

Initialize LED's:

This initLEDs program initializes the LEDs that are attatched from pin 22 to pin 37 of the Arduino MEGA. It will then assuire that the output is which is redundant and likely can be removed altogether. Finally, it sets the respective LEDs for the random targets that the program finds using the random function generator.

Initialize LCD:

In the initLCD function, the strings 'R:', 'C:', and 'TRGS:' are set so that they only need to be set once in the program.

Print onto the LCD:

In this lcdPRINT program, the x position and y position are provided in the function then the function displays the corresponding value on the LCD display. Afterwards, the program checks which targets are hit then turns on and off the respective targets. Here is where the function checks if all the targets have been hit. If they all are, the program goes into the Game Over function which will be shown on another portion of the program. For the complicated looking calculation within the setCursor function of the lcd object, the program is simply processing how to space out each target so that they can all be shown neatly on the LCD display.

Gameover Animation:

For the GameOver function, the laser is turned off, the LCD is cleared, and the program is locked into a while loop. Inside of this loop, are the calculations for a tiny animation showing us that the game has ended.

Move the servos to the expected positions:

This moveSERVOS function used to position the servos with respect to the positions that we want to move. The positions are calculated on the findAngles function. In this updated version, all of the hardcoded values are read into and used to reposition the servos.

(Outdated) Calculate the position of the servos:

This findAngles function finds the angles in degrees that the servo needs to go to so that the laser can point at the photoresistors.

Check if the targets are hit:

In the checkHIT function, the program checks each position that we have randomly generated then sets the bool array of 'targetsHIT' to high depending on if a target has been hit. If it has not been processed previously, this function may also keep track of the total number of targets hit. That number is stored on the allTargetsHit integer.

The main Loop:

This is the main function. This function loops rapidly through all of the programs so that out input will be seemingly instant and the input of the master operator will mostly depend on the UART communication between the ESP32 and the Arduino MEGA. The first function selectPosition simply allows this program to listen to any inputs from the buttons to check if we would like the Master Operator to change the position of the laser. The lcdPRINT function prints those values onto the LCD. The if statement checks if the Master Operator has returned anything, and if the Master Operator has, then the returnedPOS or returned positions are calculated depending on the number that was recieved. That output is then displayed onto the Serial Monitor for debugging purposes. In the moveSERVOS function, we use the positions that were gathered from the master operator. Then we check if any target was hit, then update the 7 Segment Display to display the number of targets that are down.

Master Operator

More Pseudo Code:

  • Initialize all of the sensors and outputs.

  • Listen for a character and letter from the ESP32.

  • Trigger the respective sensor depending on the number received after moving into position.

  • Send the position data to the Standby Operator.

  • Have a desired action be performed.

  • Wait for input from the Standby Operator.

Pre Configurations:

For this project I include the three libraries: LiquidCrystal.h, I2Cdev.h, and MPU6050.h.

MPU:

In this block of code we include the Wire.h library, initialize accelgyro as an MPU object, and initialize six sixteen bit integer variables which will contain the three axis of acceleration and three axis of gyroscopic motion.

Received Indicator:

This variable becomes true when we receive receive a letter from A to P from the ESP32.

Dedicated Connections:

In this image I show define which inputs will be used for.

Liquid Crystal Display and Communication Variable:

pins 7 to 12 are used by the LCD display to control the content that is being displayed through the LiquidCrystal library. The ESPData string stores the data that is returned from the UART1 lines.

Global Variables:

For the RGB and barLED byte type variable, I will store the connection number for the RBG LEDs and the Bar LEDs respectively.

Store the current potentiometer position:

The goPOS[2] stores the column and row that will be sent to the standby operator. The targetNUM stores the target number received and the pot[2] stores the current position of the Potentiometers.

Setup:

continuation...

For the setup I initialize the UART0 for debugging and UART1 for the communication between the Arduino MEGA and the ESP32. The button, motor, BAR LEDs, RGB LEDs, the LCD, PIR sensor, Tilt sensor, MPU, and the Ultrasonic Sensor.

UART1 Communication:

In essence, this code:

  1. Reads UART1 data, if the first letter received is from an A to a P, then convert that letter into a two dimensional code.

  2. Update the LCD display with the Row and Column values.

  3. if the target number is not set to the default value then we have received a value from the ESP32.

  4. Set received variable 'r' to high.

Sensors Code:

Force Sensor - Target Number 1:

If we have received a message, the received number is 0, and the Force sensor is pressed, then send over the values of the potentiometer to the ESP32. Next run the barLEDfunc function to show that the position data has been sent.

Ultrasonic Sensor - Target Number 2:

The ultrasonic sensor reads in the position data depending on the trigger; if the target number is 1, a message has been received from the ESP32, and the distance is within a range that is easy to trigger, then send the position data. The rbgLEDs function is then initiated and the received bool value is set to low indicating that we have sent the position and are waiting on more instructions from the Standby Operator.

PIR Sensor - Target Number 3:

This sensor is triggered with respect to the third target being received. It is triggered by detecting motion. By moving more often in front of the motor, the motor speed should increase. By stopping the motion, the motor speed should decrease. Since the motion detector is all digital it was important to decrease the delay of the sensor and the trigger distance for no accidental triggers. This sensor seemed to be sensitive to the ambient light at one point, but I was evidently able to make the sensor function. For testing purposes the sensor is then printing how it controls the motor speed onto the serial monitor.

Audio Sensor Module - Target Number 4:

For this audio sensor, we used the amplitude module to detect when a threshold has been surpassed. That intensity threshold is controlled by a physical dial of the Audio Sensor Module. This sensor is only triggered when receiving the third input from the ESP32. After triggering, the batLEDfun function runs and the system is prepared to listen for a new target.

Tilt Sensor - Target Number 5:

The most simple sensor had to be the tilt sensor. This sensor is input with the internal pull up resistor and treated as a button. When the tilt sensor is tilted, the input becomes true. Since the input is negative logic, the value is inverted for the if statement. This sensor triggers the barLEDfunc function.

MPU6050 - Target Number 6:

Hands down, this sensor had to be the most difficult sensor to implement. The code and many examples are easily attainable but the values that were being read in seemed to be mostly noise. The MPU was too sensitive which made it very difficult to use for controlling the motor speed.

Continuation...

LCD Update:

This code updates the LCD Display depending on the value of the potentiometer. The fourRange function converts the potentiometer value to a digital value.

Target Actions and Extra Functions:

Bar LED Function:

This function turns on the LED bar for 10 seconds after the respective trigger has been triggered.

RGB LEDs function:

This function enables the RGB LEDs one at a time then prints out the time it took to run the function.

Analog to Digital Converter:

This function converts the analog output to a value from 0 to 3 depending on the value sensed. It is not absolutely necessary since Arduino's map function could have been used.

Motor Speed PRI control function:

This function increases or decreases the motor speed depending on the value of the PIR sensor. Every 10th of a second, the PIR will be sensed. The PIR has a digital output depending on the motion that is detected by the PIR. Every time the PIR output is sensed, the value is changed by three where 0 is the minimum and 255 represents the motor speed.

Master Operator

Standby Operator