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 PinDevice
5VuLCD Voltage
3.3V6DOF Voltage
GNDuLCD Gnd, 6DOF Board
p9,p10,p11uLCD RX,TX,Reset
p276DOF SCL
p286DOF SDA

/media/uploads/Strikewolf/imag0133.jpg

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!:

17 Mar 2014

great job

Please log in to post comments.