Speed Maze
Team Name: Speed Maze
Team Members: Nate Offutt, Nathan Zavanelli
Purpose: The purpose of this project was to design a rotating maze game with a Raspberry Pi GUI powered by servos that a user controls by an accelerometer. The user also interacts with a GUI display on a PI to start the game and receive information upon conclusion. The game ends when a lidar detects that the ball has reached the end state, at which point a sound is also played.
GUI
Relevant Parts
- Raspberry Pi 3
- RGB LED
- 3 x 220ohm Resistors
- 5" LCD touchscreen
The purpose of the GUI was to allow for an enjoyable game experience for the user. The GUI was shown on the 5" touchscreen display (https://www.amazon.com/Resistive-interface-compatible-Raspberry-Pi/dp/B00U21UA16) which was connected to the Raspberry PI via HDMI and pin header. The GUI controlled an LED to display when the game was being played (RED = not playing, GREEN = playing). A highscore list was saved on the SD card of the Raspberry Pi and displayed on the GUI. Lastly, the GUI interacted with the mbed to send a signal to start the game and receive a signal when the game was finished. The GUI was coded in python using the Tkinter python library (https://wiki.python.org/moin/TkInter). The GPIO library (https://sourceforge.net/p/raspberry-gpio-python/wiki/BasicUsage/) was imported to control the LED as well as the control signals with the mbed. Lastily, the pickle library (https://stackoverflow.com/questions/35409779/pickle-high-scores-and-then-print) was used to implement the highscore list.
The code for the GUI is shown below
maze_gui.py
from Tkinter import * import tkFont import time from operator import itemgetter import pickle import RPi.GPIO as GPIO GPIO.setmode(GPIO.BOARD) GPIO.setup(40, GPIO.OUT, initial=GPIO.LOW) #communicate with mbed GPIO.setup(36, GPIO.OUT, initial=GPIO.HIGH) #RED LED GPIO.setup(32, GPIO.OUT, initial=GPIO.LOW) #green LED GPIO.setup(38, GPIO.IN) # communicate with mbed win = Tk() win.title("SPEED MAZE GAME") # win.attributes('-fullscreen', True) win.geometry('800x480') buttonFont = tkFont.Font(family = 'Helvetica', size = 26, weight = 'bold') title_font = tkFont.Font(family = 'Helvetica', size = 40, weight = 'bold') score_font = tkFont.Font(family = 'Helvetica', size = 15) x = 1 counter = 0.0 score = 0.0 # pickle with open('/home/pi/Desktop/highscores.txt', 'r') as f: high_scores = pickle.load(f) def startGame(): #print("Start button pressed") global x global counter GPIO.output(40, GPIO.HIGH) GPIO.output(36, GPIO.LOW) GPIO.output(32, GPIO.HIGH) if (x==1): startButton["text"] = "RESTART" name_title["text"] = "HURRY!!" x=0 count() else : finish() def count(): global counter global job1, job2 counter += .1 timer["text"]=str(counter) job1 = timer.after(100, count) if (GPIO.input(38)): #mbed input? stop game finish() if (counter >= 30): tooSlow() if (counter >= 2): GPIO.output(40, GPIO.LOW) def exitProgram(): print("Exit Button pressed") win.destroy() GPIO.cleanup() def tooSlow(): global job1, x, counter name_title["text"] = "Too Slow. Try Again" timer.after_cancel(job1) startButton["text"] = "START" x = 1 counter = 0 def finish(): global job1, x, counter, score name_title["text"] = "Nice Job!" timer.after_cancel(job1) startButton["text"] = "START" GPIO.output(32, GPIO.LOW) GPIO.output(36, GPIO.HIGH) score = counter counter = 0 x=1 updateScores() def updateScores(): global score, high_scores name = nameEntry.get() high_scores.append((name[:3], round(score,1))) high_scores = sorted(high_scores, key=itemgetter(1))[:5] with open('/home/pi/Desktop/highscores.txt', 'w') as f: pickle.dump(high_scores, f) high_scores = [] with open('/home/pi/Desktop/highscores.txt', 'r') as f: high_scores = pickle.load(f) # print(high_scores) score1["text"] = high_scores[0] score2["text"] = high_scores[1] score3["text"] = high_scores[2] score4["text"] = high_scores[3] score5["text"] = high_scores[4] #Labels and Buttons name_title = Label(win, text="Welcome to Speed Maze", font = title_font) name_title.pack(side=TOP) nameEntry = Entry(win, text = "Name:", width = 3, font = buttonFont, bd = 2) nameEntry.pack(side=TOP) startButton = Button(win, text = "Start", font = buttonFont, command = startGame) startButton.pack(side = TOP) timer = Label(win, text="0.0", fg="green", font = buttonFont) timer.pack(side=TOP) highscore_title = Label(win, text="Highscores", font = score_font) highscore_title.pack(side=TOP) score1 = Label(win, text=high_scores[0], font = score_font) score1.pack(side=TOP) score2 = Label(win, text=high_scores[1], font = score_font) score2.pack(side=TOP) score3 = Label(win, text=high_scores[2], font = score_font) score3.pack(side=TOP) score4 = Label(win, text=high_scores[3], font = score_font) score4.pack(side=TOP) score5 = Label(win, text=high_scores[4], font = score_font) score5.pack(side=TOP) exitButton = Button(win, text = "Exit", font = buttonFont, command = exitProgram, bg='red') exitButton.pack(side = BOTTOM) win.mainloop()
Complications: a hardware defect seems to have messed up part of the 5 inch screen .
MBED and Peripherals
Relevant Parts
- MBED LPC1768
- XNucleo53L0A1 LIDAR
- LSM9DS1 Accelerometer
- Speaker
- Power MOSFET
- 5V External Supply
The Mbed is used to interface with all the peripherals involved in controlling the maze. It has three peripheral inputs and outputs. On the input side, it receives a start signal from the PI, interfaces with the accelerometer via SPI on pins P9 and P10 and the LIDAR on pins P28 and P27. For the output, it communicates the end state to the PI, controls the Servos via PWM based on the accelerometer readings, and provides a PWM signal to the speaker once the LIDAR end condition is reached. In this way, the user can seamlessly move the controller and click on the touch screen GUI to control the maze and receive feedback from the output devices.
The code for the MBed peripheral interface is shown below
mbed_peripherals.cc
#include "LSM9DS1.h" #include "Servo.h" #include "XNucleo53L0A1.h" #include <stdio.h> #include "SongPlayer.h" //Pin definitions DigitalOut myled(LED1); Serial pc(USBTX, USBRX); Servo out1 (p21); Servo out2 (p22); DigitalOut shdn(p26); SongPlayer mySpeaker(p24); DigitalOut end(p18); AnalogIn start(p19); #define VL53L0_I2C_SDA p28 #define VL53L0_I2C_SCL p27 //additional definitions static XNucleo53L0A1 *board=NULL; float note[18]= {1568.0,1396.9,1244.5,1244.5,1396.9,1568.0,1568.0,1568.0,1396.9, 1244.5,1396.9,1568.0,1396.9,1244.5,1174.7,1244.5,1244.5, 0.0 }; float duration[18]= {0.48,0.24,0.72,0.48,0.24,0.48,0.24,0.24,0.24, 0.24,0.24,0.24,0.24,0.48,0.24,0.48,0.48, 0.0 }; int main() { //defines 53LOA1 status int status; uint32_t distance; DevI2C *device_i2c = new DevI2C(VL53L0_I2C_SDA, VL53L0_I2C_SCL); /* creates the 53L0A1 expansion board singleton obj */ board = XNucleo53L0A1::instance(device_i2c, A2, D8, D2); shdn = 0; //must reset sensor for an mbed reset to work wait(0.1); shdn = 1; wait(0.1); /* init the 53L0A1 board with default values */ status = board->init_board(); while (status) { pc.printf("Failed to init board! \r\n"); status = board->init_board(); } //initialize accelerometer LSM9DS1 imu(p9, p10, 0xD6, 0x3C); //establish the initial servo position float posx = 0.5; float posy = 0.5; out1.write(posx); out2.write(posy); //read the position float curr1 = out1.read(); float curr2 = out2.read(); //start the accelerometer imu.begin(); if (!imu.begin()) { pc.printf("Failed to communicate with LSM9DS1.\n"); } imu.calibrate(); //boolean for end condition bool play = 0; bool done = 1; end = 0; wait (5); //main loop while(1) { play = 0; //look for start signal if (start.read() >= 1.0) play = 1; //if signal given, then play while(play && done) { //read and scale accelerometer values imu.readAccel(); float x = (float) imu.ax; float y = (float) imu.ay; x = x/17000; y = y/17000; //increment servo pos based on accelerometer if (x > 0.3) {posx+= 0.02; out1.write(posx);} if(x < -0.3) {posx-= 0.02; out1.write(posx);} if( y > 0.3) {posy-=0.02; out2.write(posy);} if(y < -0.3) {posy+=0.02; out2.write(posy);} //write position to servos out1.write(posx); out2.write(posy); //read servo state curr1 = out1.read(); curr2 = out2.read(); //read lidar distance status = board->sensor_centre->get_distance(&distance); if (status == VL53L0X_ERROR_NONE) { pc.printf("D=%ld mm\r\n", distance); } //print values to terminal for debugging pc.printf("accel: %f %f %d\n\r", x, y, imu.az); pc.printf("pos: %f %f\n\r", curr1, curr2); //end condition if (distance > 10 && distance < 40) { end = 1.0; mySpeaker.PlaySong(note,duration); done = 0; play = 0; } } } }
Speed Maze Game Features Video
Speed Maze Gameplay Video
Please log in to post comments.