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()

/media/uploads/nzavanelli/gui.png

/media/uploads/nzavanelli/gui_pic.png

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.