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