CSC270 Team 6 project 2019
Contents
Overview
Project Description
Our project is a game of snake on a 8x8 LED matrix with an LCD screen to display the score. The snake is controlled by 4 direction buttons mounted on a handheld board, and there is also a start/reset button that is used to start the game, or to reset and start a new game if one is currently in progress. It also tracks and displays high scores.
The project proposal can be found here.
Our development process is recorded in our progress log.
Maddy Fox and Grace Moore, Spring 2019
Materials and Methods
We coded in Python for the Raspberry Pi and in C for the Arduino.
Hardware
We used:
- An Arduino MEGA2560
- A Raspberry Pi 3
- An 8X8 LED matrix MAX7219
- An LCD display LCM1602A
- A 10k ohm potentiometer
- 5 small buttons
Libraries
- gpiozero for input and output to and from the Pi
- LiquidCrystal for the LCD display accessible through Arduino IDE
- pySerial for serial communication from the Pi
- LedControl for the LED matrix accessible through Arduino IDE
Installation Procedures
PySerial:
sudo apt-get update sudo apt-get install python-pip sudo pip install pySerial
LedControl, LiquidCrystal:
Followed this tutorial to download into Arduino IDE
Final Results
Our final project is a working game of Snake using 5 buttons (4 directional and 1 to restart the game), an 8x8 LED matrix, a LCD board, the Arduino MEGA 2560, and Raspberry Pi 3. The buttons are wired to the Raspberry Pi and the Pi is connected to the Arduino through the serial USB port. The Pi sends a byte containing a character to the Arduino which is read by the Arduino to determine the movement of the snake. When the snake successfully runs into 'food', the LED matrix tells the Arduino and the score is updated. The score along with some other messages about the game are displayed on the LCD board. The LCD board also shows the highest score reached since that program was last run.
We had to replace the joystick for buttons because we were missing a MCP3008 chip (analog-to-digital converter) so we could not connect the joystick to the Pi. The buttons are also now connected to the Pi because we decided that one way communication was easier than sending information from the Arduino to the Pi back to the Arduino. In the future, if we wanted to, we could easily make the game have different modes of difficultly in which the easy mode would allow the snake to wrap around the board and the harder mode would prevent the snake from hitting the borders of the board. Additionally, we could try to set up the high score to save even if the Arduino program is re-run.
Resources
A few project guides we used:
- https://circuitdigest.com/microcontroller-projects/arduino-snake-game-using-8x8-led-matrix
- A tutorial for snake using just the Arduino and buttons instead of a joystick. This tutorial includes an LCD score board.
- https://www.hackster.io/arduino-bro/snake-led-matrix-game-1c7222
- A tutorial for snake using Arduinos and a joystick but no LCD score board.
- https://www.sunfounder.com/blog/rpi-ard/
- A tutorial for connecting the Arduino to the Raspberry Pi
- https://classes.engineering.wustl.edu/ese205/core/index.php?title=Serial_Communication_between_Raspberry_Pi_%26_Arduino
- Another tutorial on connecting Arduino to Pi
- https://www.instructables.com/id/RUNNING-Arduino-IDE-on-RPi-800-X-480-Resolution/
- A tutorial on changing window size of Pi display
- https://www.arduino.cc/en/Tutorial/HelloWorld
- A tutorial on the LCD screen
- https://www.raspberrypi.org/documentation/linux/usage/rc-local.md
- A tutorial on getting the Pi to run a specific program upon boot
Progress Log
4/18/19
Today we worked on getting the snake to wrap around the the LED board instead of the game ending when it hit the edge. We did this by learning more about the code from Hackster and commenting it so we know what is going on.
We also re-wired the LED matrix and the joystick so that it was cleaner and attached the LED matrix and joystick to a breadboard with rubber bands. We then brought in the Pi and thought about how to attach the joystick to it. After some Googling, we realized that we do not have one of the parts to use the joystick with the Pi so we made the decision to switch to using buttons to control the movement of the snake. We wired up 5 buttons to the Pi, 4 to control the snake and 1 to start the game, and tested them.
We then moved on to trying to set up a connection between the Arduino and the Pi. We found a tutorial and began to work through the steps. We installed the pip package and then the pySerial module to the Pi. We successfully set up a connection that sent information from the Arduino to the Pi. Next, class we will work in the opposite direction.
We used the commands below to install the pip package and the pySerial module on the Pi:
sudo apt-get update sudo apt-get install python-pip sudo pip install pySerial
4/23/19
Today we worked on getting the Arduino and the Pi to send information back and forth to each other. While we could get each to send and read data, we had some issues with figuring out what type of data we should be sending. For instance, we were trying to get Pi to read a message from the Arduino, recognize it matched a phrase in the code, have it ask the user for a number, send the number to the Arduino, and have the Arduino read it and flash an LED the number of times specified (as per the Sunfounder page). First, we had issues with the Pi matching the message to the string in the code, and then once we got that working, we had issues with it recognizing the user input as a number and writing it to the Arduino. Eventually we got the Pi matching the string, asking for user input, and writing the input to the Arduino. However the Arduino still wasn't blinking the right amount of times, and we couldn't print to the serial monitor to test because the Arduino can't be connected to one of our laptops while it runs because it is connected to the pi instead.
We also started writing code to take in data from the buttons connected to the pi and transmit it to the Arduino. However, before we can really test this, we have to figure out some way for the Arduino to have a serial monitor and a connection to the pi at the same time. Tomorrow we would like to try downloading the Arduino IDE on the pi and running it from there, or alternately chopping open a usb cable and manually wiring another serial connection using jumper cables.
4/24/29
Today we installed the Arduino IDE on the Pi. We then went into the config.txt file using nano to change the window display so we could access the tool bar. We tried to add a library that we need to run our game to the Pi but we kept getting the same error.
4/25/19
Today we FINALLY got the Pi and the Arduino to communicate with a serial port. We had to upload the Arduino code from a separate laptop instead of the Pi because we still have not figured out how to run the LEDControl library on the Pi. We edited the Arduino code so that it takes in a character from the Pi and the sets the vector of movement for the LED snake on the board. We also set up a reset button so after the game ends it can be started again.
We also managed to set up the LCD screen following this tutorial to display the score and some messages about the game. Our code can be seen in the Arduino Code section.
4/29/19
Today we added a high score feature. The LCD screen now displays the high score in addition to the player's score during gameplay. It also displays a celebratory message if you beat it. The high score variable updates at the end of each game as needed.
5/5/19
Today we worked on getting the Python program to run when the Pi reboots. We used this tutorial to do so. It works, but it takes a while. The Arduino restarts 3 times for every Pi restart, and the buttons don't work until the final restart. The whole process takes about 35 seconds.
We also went through and made some minor organizational changes to the Arduino code, such as creating an updateLCD function instead of having multiple lines to clear, set the cursor, and rewrite each time.
Finally, we mounted the Arduino, Pi, LED matrix, and lcd screen on a sheet of plexiglass and soldered the buttons onto a little board with long wires. This way, the plexiglass can go inside of a display case, but the player can hold the little board with the buttons.
Final Code
Arduino Code
/**
* Snake
* Implementation of the classic Snake game
* where the snake moves constantly and the player
* has to avoid intersections with itself.
* When the snake eats food it grows and speeds up a little bit.
*
* Updated by Maddy Fox and Grace Moore
* Original written by Sergey Royz and Hunter Buzzell
* Upload to Arduino via serial port to run
*/
#include <LedControl.h>
#include <LiquidCrystal.h>
/* Display PINs */
#define CLK 8
#define CS 9
#define DIN 10
#define SIZE 8
#define ADVANCE_DELAY 20
/* Set up snake and food variables */
int snake[SIZE*SIZE][2];
int length;
int food[2], v[2];
int score = 0;
int high_score = 0;
bool is_game_over = false;
long current_time; //Part of speeding up snake
long prev_advance; //Part of speeding up snake
int blink_count; //Blinks 3 times when game over
const short messageSpeed = 5;
/* LCD Board PINs */
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
/* Set up LED matrix */
LedControl lc = LedControl(DIN, CLK, CS, 1);
/* Initialize game with one food and small snake */
void init_game() {
prev_advance = current_time = 0;
blink_count = 3;
int half = SIZE / 2;
length = SIZE / 3;
for (int i = 0; i < length; i++) {
snake[i][0] = half - 1;
snake[i][1] = half + i;
}
food[0] = half + 1;
food[1] = half - 1;
v[0] = 0;
v[1] = -1;
// display player's score and high score
updateLCD( "Score: ", 0 );
lcd.print(score);
updateLCD( "High Score: ", 1 );
lcd.print(high_score);
}
/* Update LED matrix */
void render() {
for (int i = 0; i < length; i++) {
lc.setLed(0, snake[i][0], snake[i][1], 1);
}
lc.setLed(0, food[0], food[1], 1);
}
/* Clear LED matrix */
void clearScreen() {
for (int x = 0; x < SIZE; x++) {
for (int y = 0; y < SIZE; y++) {
lc.setLed(0, x, y, 0);
}
}
}
/* Update LCD board */
void updateLCD( char *message, int pos) {
if (pos == 0) {
lcd.setCursor(0,0);
lcd.print(message);
}
else if (pos == 1) {
lcd.setCursor(0,1);
lcd.print(message);
}
}
/**
* moves the snake forward
* returns true if the game is over
*/
bool advance() {
int head[2] = {snake[0][0] + v[0], snake[0][1] + v[1]};
// if you go off the top of the board, loop back around to the bottom
if (head[0] < 0) {
snake[0][0] = SIZE;
}
// if you go off the bottom of the board, loop back around to the top
if (head[0] >= SIZE) {
snake[0][0] = -1;
}
// if you go off the left of the board, loop back around to the right
if (head[1] < 0) {
snake[0][1] = SIZE;
}
// if you go off the right of the board, loop back around to the left
if (head[1] >= SIZE) {
snake[0][1] = -1;
}
// if snake eats itself, it dies
for (int i = 0; i < length; i++) {
if (snake[i][0] == head[0] && snake[i][1] == head[1]) {
delay(1000);
showGameOverMessage();
return true;
}
}
// if the snake eats food then it gets bigger
bool grow = (head[0] == food[0] && head[1] == food[1]);
if (grow) {
length++;
randomSeed(current_time);
food[0] = random(SIZE);
food[1] = random(SIZE);
// Update score
score = score + 1;
}
// update score on LCD
updateLCD("Your Score: ", 0);
lcd.print(score);
// wrap snake around the LED matrix
for (int i = length - 1; i >= 0; i--) {
snake[i + 1][0] = (snake[i][0]+SIZE) % SIZE; // modulus is to deal with wrapping, +SIZE is to elimate -1 modulus issue
snake[i + 1][1] = (snake[i][1]+SIZE) % SIZE;
}
// Move snake
snake[0][0] += v[0];
snake[0][1] += v[1];
return false;
}
void setup() {
Serial.begin(9600);
lcd.begin(16, 2);
// Set up LED control
lc.shutdown(0, false);
lc.setIntensity(0, 8);
// Initialize game
updateLCD(" SNAKE! ", 0);
updateLCD(" May take a sec", 1);
delay(5000);
lcd.clear();
init_game();
render();
}
void loop() {
// if the game is over
if (!is_game_over) {
clearScreen();
render();
if (current_time - prev_advance > ADVANCE_DELAY) {
is_game_over = advance(); // advance will return true if game over, false if not
prev_advance = current_time;
}
} else {
//Game over -> blink 3 times
while (blink_count > 0) {
clearScreen();
delay(300);
render();
delay(300);
blink_count--;
}
}
if (Serial.available()) {
int piInput = Serial.read();
readControls(piInput);
Serial.flush();
}
current_time++;
}
void restart() {
lcd.clear();
score = 0;
init_game();
is_game_over = false;
}
/* handles button input */
void readControls(int piInput) {
char direct = (char) piInput;
//If left button, move left
if (direct == 'l') {
v[0] = 0;
v[1] = -1;
}
//If right button, move right
if (direct == 'r') {
v[0] = 0;
v[1] = 1;
}
//If top button, move up
if (direct == 'u') {
v[0] = -1;
v[1] = 0;
}
//If bottom button, move down
if (direct == 'd') {
v[0] = 1;
v[1] = 0;
}
if (direct == 's') {
restart();
}
}
const PROGMEM bool gameOverMessage[8][90] = {
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,1,1,0,0,0,1,1,0,0,1,1,1,1,0,0,1,1,0,0,0,1,1,0,1,1,1,1,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,0,1,1,0,0,0,1,1,0,1,1,0,0,1,1,0,1,1,1,0,1,1,1,0,1,1,0,0,0,1,1,0,1,1,1,1,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1,1,1,1,1,1,0,1,1,0,0,1,1,0,1,1,1,1,1,1,1,0,1,1,0,0,0,1,1,0,1,1,1,1,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1,1,1,1,1,1,0,1,1,0,0,1,1,0,1,1,0,1,0,1,1,0,1,1,0,0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0,1,1,0,1,1,0,0,1,1,0,1,1,0,0,0,1,1,0,1,1,1,1,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,0,1,1,0,0,0,1,1,0,1,1,0,0,1,1,0,1,1,0,0,0,1,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,1,1,0,0,0,1,1,0,0,1,1,1,1,0,0,1,1,0,0,0,1,1,0,1,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0}
};
/* Show game over message */
void showGameOverMessage() {
// update LCD to display final score
lcd.clear();
updateLCD(" Game Over! ", 0);
updateLCD("Final Score: ", 1);
lcd.print(score);
// update high score
if (score > high_score) {
high_score = score;
lcd.clear();
updateLCD("New High Score!", 0);
updateLCD("High Score: ", 1);
lcd.print(high_score);
}
// update LED matrix
for (int d = 0; d < sizeof(gameOverMessage[0]) - 7; d++) {
for (int col = 0; col < 8; col++) {
delay(messageSpeed);
for (int row = 0; row < 8; row++) {
// this reads the byte from the PROGMEM and displays it on the screen
lc.setLed(0, row, col, pgm_read_byte(&(gameOverMessage[row][col + d])));
}
}
}
// update LCD to play again message
lcd.clear();
updateLCD(" Press Button ", 0);
updateLCD(" To Play Again ", 1);
}
Pi Code
#!/usr/bin/env python
# Maddy and Grace
# Transmits button data from Pi to Arduino
# CSC270 4/23/19
#
# Wiring:
# up button -> 11
# down button -> 13
# left button -> 15
# right button -> 8
# start button -> 10
# USB -> Arduino
from gpiozero import Button
import serial
port = "/dev/ttyACM0" # Make sure Arduino is port ACM0
rate = 9600 # Rate should match Arduino's rate
s1 = serial.Serial(port, rate)
s1.flushInput()
upButton = Button(17)
downButton = Button(27)
leftButton = Button(22)
rightButton = Button(14)
startButton = Button(15)
# Messages to transmit to Arduino
upmsg = 'u'
downmsg = 'd'
leftmsg = 'l'
rightmsg = 'r'
startmsg = 's'
# Constantly be checking for button presses
while(True):
if (upButton.is_pressed):
upButton.wait_for_release(); # Ensure msg sent once per button press
s1.write(upmsg.encode()) # Send msg to Arduino
if (downButton.is_pressed):
downButton.wait_for_release()
s1.write(downmsg.encode())
if (leftButton.is_pressed):
leftButton.wait_for_release()
s1.write(leftmsg.encode())
if (rightButton.is_pressed):
rightButton.wait_for_release()
s1.write(rightmsg.encode())
if (startButton.is_pressed):
startButton.wait_for_release()
s1.write(startmsg.encode())
pause()