Super Mbed Ball!
Overview
This project makes use of the mbed in conjunction with a IMU digital combo board with the ADXL345 accelerometer and ITG3200 gyroscope through an I2C interface in order to use motion control to navigate a ball through a randomly generated maze. The ball will start in the most top left empty space, and the user must adjust the board's pitch and roll to move the ball up, down, left and right. The end goal is to get the ball to reach the blue tile in the bottom right of the maze, representing the finish.
The gyroscope and accelerometer outputs are put through an IMU filter from the mbed cookbook to generate usable pitch/roll/yaw values. These are then in turn used to navigate a ball through a Depth First Search generated maze using the raw gyroscope value for the random seed. After the user completes a maze, a victory string will be displayed followed by another maze being dynamically generated for the next round. There will be 3 rounds before the game over screen will be displayed and the program terminates.
Video
Wiring
| Mbed Pin | Device |
|---|---|
| 5V | uLCD Voltage |
| 3.3V | 6DOF Voltage |
| GND | uLCD Gnd, 6DOF Board |
| p9,p10,p11 | uLCD RX,TX,Reset |
| p27 | 6DOF SCL |
| p28 | 6DOF SDA |
IMU Filter
When utilizing the gyrscope's API getX, getY, and getZ, it was found that the generated numbers were behaving more like an accelerometer. When still, the gyrscope would return values close to zero, and large shifts in direction would generate larger numbers for the duration of the motion, but then eventually settle back at zero. To combat this, the IMU filter from the mbed cookbook was used. The file IMU_RPY.h was our interface to the filter. This file also has the declarations for the accelerometer and gyroscope.
Import programSuperMbedBall
This is a ball on the Mbed.
Code
Super Mbed Ball Code
#include <math.h>
#include <time.h>
#include "mbed.h"
#include "uLCD_4DGL.h"
#include "rtos.h"
#include "IMU_RPY.h"
// game configuration macros
#define NUMBER_OF_ROUNDS 3
#define CURSOR_SPEED 0.5
#define CURSOR_COLOR 0xFFFF00
#define SPACE_COLOR BLACK
#define WALL_COLOR RED
#define START_COLOR GREEN
#define END_COLOR BLUE
// game mechanism macros
#define MAZE_DIMENSION 25
#define SCALAR (125 / MAZE_DIMENSION)
#define START_BLOCK 3
#define END_BLOCK 2
#define WALL_BLOCK 1
#define SPACE_BLOCK 0
// prototypes in alphabetical order
bool checkVictory();
void displaySplashScreen();
void displayMaze();
void displayVictory();
void displayEndGame();
void generateMaze();
void depthFirstSearch(int r, int c);
void updateVelocity();
void updateBall();
void xthread(void const *args);
void ythread(void const *args);
// display variables
uLCD_4DGL uLCD(p9, p10, p11);
// cursor variables
Mutex x_mutex, y_mutex;
char cursor_x_pos, cursor_y_pos;
char old_cursor_x_pos, old_cursor_y_pos;
float ballxvel, ballyvel;
// start and end variables
char start_x, start_y;
char end_x, end_y;
// maze data structure
char maze[MAZE_DIMENSION][MAZE_DIMENSION];
// level counter
char level = 0;
int main()
{
pc.printf("Super Mbed Ball!!!!!\n\r");
// set up gyroscope/accelerometer (rpy = roll, pitch, yaw)
rpy_init();
//Overclock uLCD
uLCD.baudrate(3000000);
//Title Screen
displaySplashScreen();
//Start physics engine threads
Thread t1(xthread);
Thread t2(ythread);
// each iteration runs a complete game until user wins.
// wait for user to complete max allowed levels before
// terminating
while(level < NUMBER_OF_ROUNDS) {
//Initial velocity
ballxvel = 0;
ballyvel = 0;
//create Maze
generateMaze();
displayMaze();
//Game Execution Loop
while (checkVictory() == false) {
updateVelocity();
updateBall();
}
// victory splash screen
displayVictory();
// increment level counter
level++;
}
// display last end game screen before exiting program
displayEndGame();
}
// checks if cursor has moved to the end block
bool checkVictory()
{
// check if cursor made it to end row and column
return(maze[cursor_x_pos][cursor_y_pos] == END_BLOCK);
}
// depth first search, called by generateMaze()
void depthFirstSearch(int r, int c)
{
// 4 random direction
int randDirs1[] = {1, 2, 3, 4};
int randDirs2[] = {4, 2, 3, 1};
int *randDirs = NULL;
if (rand() % 2)
randDirs = randDirs1;
else
randDirs = randDirs2;
// Examine each direction
for (int i = 0; i < 4; i++) {
switch (randDirs[i]) {
case 1: // Up
// Whether 2 cells up is out or not
if (r - 2 <= 0)
continue;
if (maze[r - 2][c] != 0) {
maze[r - 2][c] = SPACE_BLOCK;
maze[r - 1][c] = SPACE_BLOCK;
depthFirstSearch(r - 2, c);
}
break;
case 2: // Right
// Whether 2 cells to the right is out or not
if (c + 2 >= MAZE_DIMENSION - 1)
continue;
if (maze[r][c + 2] != 0) {
maze[r][c + 2] = SPACE_BLOCK;
maze[r][c + 1] = SPACE_BLOCK;
depthFirstSearch(r, c + 2);
}
break;
case 3: // Down
// Whether 2 cells down is out or not
if (r + 2 >= MAZE_DIMENSION - 1)
continue;
if (maze[r + 2][c] != 0) {
maze[r + 2][c] = SPACE_BLOCK;
maze[r + 1][c] = SPACE_BLOCK;
depthFirstSearch(r + 2, c);
}
break;
case 4: // Left
// Whether 2 cells to the left is out or not
if (c - 2 <= 0)
continue;
if (maze[r][c - 2] != 0) {
maze[r][c - 2] = SPACE_BLOCK;
maze[r][c - 1] = SPACE_BLOCK;
depthFirstSearch(r, c - 2);
}
break;
}
}
}
//Take whats in the mazearr and write it
void displayMaze()
{
// Clear previous maze
uLCD.filled_rectangle(0, 0, 127, 127, SPACE_COLOR);
// display start location
uLCD.filled_rectangle(start_x * SCALAR, start_y * SCALAR,
(start_x + 1) * SCALAR - 1, (start_y + 1) * SCALAR - 1, START_COLOR);
// display end location
uLCD.filled_rectangle(end_x * SCALAR, end_y * SCALAR,
(end_x + 1) * SCALAR - 1, (end_y + 1) * SCALAR - 1, END_COLOR);
// display walls of maze
for (int i = 0; i < MAZE_DIMENSION; i++) {
for (int j = 0; j < MAZE_DIMENSION; j++) {
if (maze[i][j] == WALL_BLOCK)
uLCD.filled_rectangle(i * SCALAR, j * SCALAR, (i + 1) * SCALAR - 1, (j + 1) * SCALAR - 1, WALL_COLOR);
}
}
}
//Game title screen
void displaySplashScreen()
{
uLCD.text_width(2);
uLCD.text_height(2);
uLCD.locate(0, 0);
uLCD.printf("SuperMbed");
uLCD.text_width(2);
uLCD.text_height(3);
uLCD.locate(2, 1);
uLCD.printf("Ball!");
wait(3);
}
//Victory screen - 5 sec delay and then next level
void displayVictory()
{
uLCD.text_width(2);
uLCD.text_height(2);
uLCD.locate(1, 3);
uLCD.printf("VICTORY!");
wait(5);
}
// End game screen
void displayEndGame() {
// wipe screen
uLCD.filled_rectangle(0, 0, 127, 127, BLACK);
// write game over
uLCD.text_width(2);
uLCD.text_height(2);
uLCD.locate(1, 3);
uLCD.printf("GAME OVER");
}
// randomely generates a maze using depth first search
void generateMaze()
{
// Initialize all spaces to walls
for (int i = 0; i < MAZE_DIMENSION; i++)
for (int j = 0; j < MAZE_DIMENSION; j++)
maze[i][j] = WALL_BLOCK;
// unused z-value of gyroscope will be random seed
srand(imuFilter.getYaw());
// calculate starting row and column of maze DFS
// starting row and column must be an odd number
int seed = 0;
while(seed % 2 == 0)
seed = rand() % (MAZE_DIMENSION -1) + 1;
pc.printf("seed: %d\r\n", seed);
// Starting cell
maze[seed][seed] = SPACE_BLOCK;
// Allocate the maze with recursive method
depthFirstSearch(seed, seed);
// find start and end positions
start_x = start_y = 0xF;
end_x = end_y = 0;
for (int i = 0; i < MAZE_DIMENSION; i++) {
for (int j = 0; j < MAZE_DIMENSION; j++) {
if (maze[i][j] == SPACE_BLOCK) {
// start space
if((i*MAZE_DIMENSION + j) < (start_x*MAZE_DIMENSION + start_y)) {
start_x = i;
start_y = j;
}
// end space
if((i*MAZE_DIMENSION + j) > (end_x*MAZE_DIMENSION + end_y)) {
end_x = i;
end_y = j;
}
}
}
}
// reset cursor starting position
cursor_x_pos = old_cursor_x_pos = start_x;
cursor_y_pos = old_cursor_y_pos = start_y;
// mark spots in maze data structure
maze[start_x][start_y] = START_BLOCK;
maze[end_x][end_y] = END_BLOCK;
}
//Move the ball around and draw to the screen
void updateBall()
{
x_mutex.lock();
y_mutex.lock();
// redraw ball only if the position has changed
if (cursor_x_pos != old_cursor_x_pos || cursor_y_pos != old_cursor_y_pos) {
//Wipe the old ball
uLCD.filled_rectangle(old_cursor_x_pos * SCALAR, old_cursor_y_pos * SCALAR,
(old_cursor_x_pos + 1) * SCALAR - 1, (old_cursor_y_pos + 1) * SCALAR - 1, SPACE_COLOR);
//Out with the old in with the new!
uLCD.filled_rectangle(cursor_x_pos * SCALAR, cursor_y_pos * SCALAR,
(cursor_x_pos + 1) * SCALAR - 1, (cursor_y_pos + 1) * SCALAR - 1, CURSOR_COLOR);
// store new position
old_cursor_x_pos = cursor_x_pos;
old_cursor_y_pos = cursor_y_pos;
}
x_mutex.unlock();
y_mutex.unlock();
}
//This will be where the gyro values are used to accelerate/decelerate the ball
void updateVelocity()
{
x_mutex.lock();
y_mutex.lock();
// sample gyroscope/accelerometer through filter
ballxvel = toDegrees(imuFilter.getPitch()) / -10;
ballyvel = toDegrees(imuFilter.getRoll()) / 10;
// bound velocities to max speed for x
if (ballxvel > 1)
ballxvel = CURSOR_SPEED;
else if (ballxvel < -1)
ballxvel = -CURSOR_SPEED;
// bound velocities to max speed for y
if (ballyvel > 1)
ballyvel = CURSOR_SPEED;
else if (ballyvel < -1)
ballyvel = -CURSOR_SPEED;
// round to 2 decimal places
ballxvel = floorf(ballxvel * 100.0) / 100.0;
ballyvel = floorf(ballyvel * 100.0) / 100.0;
x_mutex.unlock();
y_mutex.unlock();
}
//xthread and ythread act as the physics engine, simulating velocity and wall detection
void xthread(const void* args)
{
while (1) {
x_mutex.lock();
y_mutex.lock();
if (ballxvel > 0) {
if (maze[cursor_x_pos + 1][cursor_y_pos] != WALL_BLOCK)
cursor_x_pos++;
} else if (ballxvel < 0) {
if (maze[cursor_x_pos - 1][cursor_y_pos] != WALL_BLOCK)
cursor_x_pos--;
}
x_mutex.unlock();
y_mutex.unlock();
Thread::wait(100 - 98 * abs(ballxvel));
}
}
void ythread(const void* args)
{
while (1) {
x_mutex.lock();
y_mutex.lock();
if (ballyvel > 0) {
if (maze[cursor_x_pos][cursor_y_pos + 1] != WALL_BLOCK)
cursor_y_pos++;
} else if (ballyvel < 0) {
if (maze[cursor_x_pos][cursor_y_pos - 1] != WALL_BLOCK)
cursor_y_pos--;
}
x_mutex.unlock();
y_mutex.unlock();
Thread::wait(100 - 98 * abs(ballyvel));
}
}
1 comment on Super Mbed Ball!:
Please log in to post comments.

great job