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.
