F19: Road Max Fury

From Embedded Systems Learning Academy
Jump to: navigation, search

Project Title

Road Max Fury- Game using FreeRTOS

Abstract

Road Max Fury is a game based on a classic car racing arcade game developed in 1984 by Konami named Road Fighter. We planned to reproduce this game as a part of our CMPE 244 project. The goal is to reach the finish line avoiding other cars on the road.The player also needs to prevent car from hitting the obstacles along its path. The game has 3 levels and each level changes when a player hits a particular score and it gets tougher than the previous level because of the random movement of cars and faster cars


Roadfighter1.png

Objectives & Introduction

The primary goal of this project is to develop a racing game based on Road Fighter using FreeRTOS using SJ2 Board and to drive the LED matrix. The game's objective is to reach the finish line before running out of fuel and avoiding the obstacles and other cars on the road. The LED matrix is interfaced using the GPIO pins and the onboard accelerometer is interfaced using I2C communication protocol.

Objectives:

  • Write drivers to display road, car, traffic, score, fuel status on the RGB LED matrix and update the display continuously.
  • Write drivers to give directions from the input devices ie accelerometer and filter the values to get accurate and desired values.
  • Implement game algorithm for movement of the car, random obstacle car and update scores and fuel status.
  • Create FeeRTOS tasks for display, accelerometer values, game logic and understand the communication and synchronization between them.

Team Members & Responsibilities

Schedule

Week# Date Deliverables Status
1 09/29
  • Road Max Fury project approved by instructor
  • Completed
Week# Date Deliverables Status
2 10/12
  • Create project Wiki page
  • Create a Bill of Materials
  • Select and order Parts
  • Completed
  • Completed
  • Completed
Week# Date Deliverables Status
3 10/16
  • Create and establish Github repository
  • Create and setup Slack Channel
  • Look through previous years projects and study it
  • Distribute major roles among team members
  • UI and initial game design
  • Completed
  • Completed
  • Completed
  • Completed
  • Completed
Week# Date Deliverables Status
4 10/23
  • Make Repo on Github for all modules - Follow Naming Convention
  • Understand the LED matrix data sheet
  • Develop patterns on LED matrix using existing libraries for testing and understanding
  • Completed
  • Completed
  • Completed
Week# Date Deliverables Status
5 10/30
  • Learn about PCB layouts and PCB building
  • Game algorithm design
  • Work on LED matrix panel and graphics library
  • Develop patterns on LED matrix specific to our project
  • Completed
  • Completed
  • Completed
  • Completed
Week# Date Deliverables Status
6 11/06
  • Interface LED matrix display with SJ2 Board
  • Learn about on board accelerometer and get values on terminal
  • Interface and test accelerometer
  • Understand the accelerometer values and develop a filter to obtain required values
  • Completed
  • Completed
  • Completed
  • Completed
Week# Date Deliverables Status
7 11/13
  • Understand and implement SD-card read
  • Render SD-card data to MP3 decoder
  • Design Schematic for PCB
  • Completed
  • Completed
  • Completed
Week# Date Deliverables Status
8 11/24
  • Interface accelerometer with car movement
  • Simulating a rectangle block as a car and manage its movement
  • Implement random obstacle creation algorithm
  • Car creation and movement
  • Play game audio sounds giving commands from SJ2 board
  • Completed
  • Completed
  • Completed
  • Completed
  • Completed
Week# Date Deliverables Status
9 12/1
  • Implement car collision detection
  • Create PCB Layout and get it reviewed
  • Develop Fonts for LED Matrix and Display game name on start screen
  • Sync Game audio with different screens and game instances
  • Completed
  • Completed
  • Completed
  • Completed
Week# Date Deliverables Status
10 12/8
  • Integration testing
  • Bug fixes
  • Wiki report completion
  • Perform game testing
  • Start integrating display, PCB , MP3, accelerometer modules.
  • Completed
  • Completed
  • Completed
  • Completed
  • Completed
Week# Date Deliverables Status
11 12/13
  • Complete final implementation of Road Max Fury
  • Complete debugging of all game components
  • Demo
  • Completed
  • Completed
  • Completed

Parts List & Cost

Part # Cost Source
SJ2 Board 1 $55.00 Preet
Azerone 32 x 64 LED Matrix 1 $34.95 https://www.amazon.com/gp/product/B07F2JW8D3/ref=ppx_yo_dt_b_asin_title_o01_s00?ie=UTF8&psc=1
HiLetgo MP3 Decoder 1 $7.64 https://www.amazon.com/gp/product/B0725RHR4D/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8&psc=1
Female Male DC Power Plug Terminal Adapter 1 $6.54 https://www.amazon.com/gp/product/B00W058HHQ/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8&psc=1
PCB 1 $19.00 https://jlcpcb.com/?gclid=Cj0KCQiA0NfvBRCVARIsAO4930nyv9BTfWyVz9KqLUyJRwS_FK0Hp6ldhHof8L-kW1jctHch8ahMC7waAhpSEALw_wcB
Digital Audio Amplifier 1 $7.64 https://www.amazon.com/gp/product/B075DLMFCY/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&psc=1
Push Button Switch 1 $7.99 https://www.amazon.com/gp/product/B07SVTQ7B9/ref=ppx_yo_dt_b_asin_title_o08_s00?ie=UTF8&psc=1
Audio Speakers 1 $7.99 https://www.amazon.com/gp/product/B07FTB281F/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&psc=1
40 Pin GPIO Ribbon Cable 1 $ 9.99 https://www.amazon.com/gp/product/B01H53OK5U/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&psc=1
3.5mm TS Mono Male to 2 Pin Screw Terminal Female AUX Headphone Balum Converter 1 $ 6.89 https://www.amazon.com/gp/product/B06Y5YJRPD/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&psc=1

Design & Implementation

Block Diagram


Display Module

LED Matrix

The hardware design employs the use of 64x32 RGB LED matrix panel which is the most important part of the project, this uses four data lines namely A, B, C and D which can be addressed and used to control each LED which has following technical specifications:

Dimensions:

  • 110.1 x 5 x 0.2 inches

Operation Power

  • AC100-240V 50-60HZ Switch-able
  • 5V regulated power input, 4A max (all LEDs on)
  • 5V data logic level input
  • 4mm pitch
  • Module Refresh: 1560hz
LED Matrix
LED FrontPanel
SJ2-PCB-LED

Pin Mapping:

SJ One Board Pin Name Description
P1_14 R1 Top half red data
P4_29 G1 Top half green data
P0_7 B1 Top half blue data
P4_28 R2 Bottom half red data
P0_6 G2 Bottom half green data
P0_8 B2 Bottom half blue data
P0_26 addrA Address Input A
P1_31 addrB Address Input B
P1_20 addrC Address Input C
P1_28 addrD Address Input D
P2_0 Clock Shift clock
P2_2 Latch Shift in row data/Active High
P2_5 Output Enable Turn on selected rows/Active Low

LED Matrix Driver and Implementation

Led_matrix_software_Design

The Led Matrix Driver Library is responsible for driving individual pixels of the LED Matrix. We started understanding the functioning of the LED Matrix by reading the SparkFun's article and Adafruit's article. The Drivers provided by Adafruit uses a BCM technique (In-place of PWM) for obtaining different color combination. However when we designed our own driver we did not used the BCM technique thus were able to display only eight colors (BLACK = 0, BLUE = 1, GREEN = 2, CYAN = 3, RED = 4, MAGENTA = 5, YELLOW = 6, WHITE = 7). The software design flow is illustrated below:

Led_matrix_software_Design

Another function 'bool led_matrix__drawPixel(int16_t x, int16_t y, uint16_t color)' is used to update a pixel in Led Matrix Buffer. The 'led_matrix__drawPixel' function is used by all functions of the graphics library to update the Led Matrix Buffer.

MP3 Decoder and Amplifier

AUDIO_BLOCK_DIAGRAM

The HiLetgo YX5300 UART Control Serial MP3 Music Player Module is a kind of simple MP3 player device which is based on a high-quality MP3 audio chip. It can support 8k Hz ~ 48k Hz sampling frequency MP3 and WAV file formats. There is a TF card socket on board, so you can plug the micro SD card that stores audio files. MCU can control the MP3 playback state by sending commands to the module via UART port, such as switch songs, change the volume and play mode and so on. You can also debug the module via USB to UART module. The MP3 decoder is connected to the SJ2 board using the UART2 pins and needs 3.3V power supply and Ground pin. It works on 9600 bps baud rate. The songs are loaded into the memory card using the .wav format. Since we could not find a proper documentation for the mp3 decoder we referred to Catalex MP3 decoder document. The MP3 decoder works by sending a 8bytes of Hex command. The initial command is to select the device and later hex command are sent based upon the selection. Some of the operations that could be performed by sending the hex commands include, start a song, pause a song, increase/ decrease volume , change to next/ previous song and so on. The initial testing of the songs and the MP3 decoder are done by using a serial terminal coolterm. Hex commands are send as strings using the serial command to test and understand the mp3 decoder.

Since we wanted to use compact speakers which can be fixed inside the package and did not require an external power source we used magnetic diaphragm speakers. But this speakers when hooked to the output of the mp3 decoder produced a really low levels of audio. In order to increase the output audio levels we used Super Mini PAM8403 DC 5V 2 Channel USB Digital Audio Amplifier Board Module. The output of the mp3 decoder was connected to this amplifier and then the speakers were connected to Audio out terminals of the amplifier. The on board potentiometer of the audio amplifier can be used to increased the output audio level.

PCB Design

For PCB Design we used the Eagle PCB Design software. It is not free software for commercial use but is free for students. Preet took a short session on PCB designing which helped to get us started. A great advantage of Eagle PCB is that we can importAdafruit and Sparkfun library into the PCB Design software. This allows us to use the built-in footprints and schematic components of Sparkfun and Adafruit which makes designing and ordering the parts easier. The software is easy to use for a beginner.

The steps involved in the PCB design process are described in the next section.

Schematic Design:

Our Schematic is simple. We added header pins, for making connections from the LED matrix, MP3 decoder to SJ2 board and external power pins. A 2x1 header is used to for connecting external power. 20x2 header pin is used to connect the SJ2 board GPIO pins. 8x2 header pins are used to connect the LED Matrix Pins. 4x1 header is used for connecting the MP3 decoder to a UART3 GPIO pin.

PCB Schematic


PCB Layout Design:

In our PCB layout, we used 2-layered PCB (A top layer and a bottom layer). PCB traces are made in a vertical, horizontal and at 45-degree angles for a consistent layout. The top layer is traces are in RED color and the bottom layer traces are in BLUE color. Labels and markings are added for identify components and pin numbers. After completing the PCB layout (making the traces) we then performed Design Rule Check (DRC) to verify that there are no short or open connections. Finally, we generated Gerber File and sent it to JLC PCB for manufacturing.

PCB Layout


Software Design

Game's States

We have four tasks - Game task, Button task, Accelerometer task and MP3 Player task.

Button Task: This task sleeps on a semaphore which is given by GPIO ISR whenever button is pressed. The button task then signals the game task of the receipt of button press. The game task checks the current state and goes to the next state based on the above state diagram.

MP3 Player Task: The task sends commands to the MP3 decoder to play a specific song. Based on the current state of the game, the Game task sends different signals to the MP3 player task. The MP3 player task plays specific songs based on the signal received.

Code Snippet

void mp3_player_task(void *pvParameters) {
  send_mp3_command(mp3_player_init, MP3_COMMAND_SIZE);
  uint8_t current_state = NO_SOUND;

  while (true) {
    if (xSemaphoreTake(countdown, 0)) {
      play_audio(COUNTDOWN);
      current_state = COUNTDOWN;
    } else if (xSemaphoreTake(crash, 0) && current_state != CRASH) {
      play_audio(CRASH);
      current_state = CRASH;
    } else if (xSemaphoreTake(car_moving, 0) && current_state != CAR) {
      play_audio(CAR);
      current_state = CAR;
    } else if (xSemaphoreTake(no_sound, 0) && current_state != NO_SOUND) {
      play_audio(NO_SOUND);
      current_state = NO_SOUND;
    } else if (xSemaphoreTake(level, 0) && current_state != LEVEL_CHANGE) {
      play_audio(LEVEL_CHANGE);
      current_state = LEVEL_CHANGE;
    } else if (xSemaphoreTake(play, 0) && current_state != PLAY) {
      play_audio(PLAY);
      current_state = PLAY;
    }

    vTaskDelay(100);
  }
}

Accelerometer Task: We used on-board accelerometer to get the values. We have used only the Y-axis values to trigger movement of the player's car. We have divided the Y-axis values into different buckets which defines where the player car should move.

Accelerometer Detection


Code Snippet

static void accelerometer_task(void *params) {

  acceleration__axis_data_s acc_sensor_values;
  uint32_t y = 0;
  while (1) {
    acc_sensor_values = acceleration__get_data();
    y = acc_sensor_values.y;

    switch (y) {
    case 0 ... 150: //Staright
      break;

    case 151 ... 800: //Right
      move_car_right();
      break;

    case 3100 ... 3944: //Left
      move_car_left();
      break;

    case 3945 ... 4095: //Straight
      break;

    default:
      break;
    }
    vTaskDelay(50);
  }
}

Game Task: The game task generates random obstacles, check for collisions. It sends signals to the MP3 player task to play sounds as per the game state.

Random Obstacle Generator and Movement

For every level, we have new obstacles in addition to the old ones. We have an array of obstacle types and we select specific obstacles based on the level. We are using rand() to select the X-axis coordinate and to select the type of obstacle to generate.

Code Snippet

static void generate_obstacle(bitmap_object *obstacle) {
  uint8_t x, index;

  obstacle->y = BORDER_HEIGHT - CAR_HEIGHT_WITH_PADDING;

  x = rand() % LED_MATRIX_WIDTH;
  if (x < BORDER_WIDTH) {
    x = BORDER_WIDTH;
  } else if (x > (LED_MATRIX_WIDTH - BORDER_WIDTH - CAR_WIDTH_WITH_PADDING)) {
    x = LED_MATRIX_WIDTH - BORDER_WIDTH - CAR_WIDTH_WITH_PADDING;
  }
  obstacle->x = x;

  index = rand() % levels[current_level - 1].level_obstacle_mod;

  obstacle->image = obstacle_types[index].image;
  obstacle->isAlive = true;
  obstacle->color = obstacle_types[index].color;
  obstacle->movement_type = obstacle_types[index].movement_type;
  obstacle->height = obstacle_types[index].height;
  obstacle->width = obstacle_types[index].width;
  obstacle->speed = obstacle_types[index].speed;
  obstacle->counter = 0;
  obstacle->direction = index % 2;
}


Every object has padding above it and on the left and right. When we move the obstacle down by one row, the padding clears the last row. For every obstacle there are three attributes speed, life and movement type. We update the position of the obstacle when the counter reaches the corresponding speed. We have two movement types DOWN and DOWN_AND_LEFT_RIGHT. We update the counter every 30ms. So if the speed of the car is 20, then the car moves down every 30*20 = 600ms. In case of DOWN_AND_LEFT_RIGHT, we update the X-axis as well as Y-axis values. The alive attribute is set to false once the obstacle is out of the screen.

Code Snippet

static void move_obstacles(bitmap_object *obstacle) {
  obstacle->counter++;
  if (obstacle->counter < obstacle->speed) {
    return;
  }

  switch (obstacle->movement_type) {
  case DOWN:
    obstacle->y = obstacle->y - 1;
    break;
  case DOWN_AND_LEFT_RIGHT:
    obstacle->y = obstacle->y - 1;
    if (obstacle->direction == RIGHT) {
      if (obstacle->x <
          (LED_MATRIX_WIDTH - BORDER_WIDTH - CAR_WIDTH_WITH_PADDING)) {
        obstacle->x = obstacle->x + 1;
      } else {
        obstacle->direction = LEFT;
      }
    } else {
      if (obstacle->x > BORDER_WIDTH) {
        obstacle->x = obstacle->x - 1;
      } else {
        obstacle->direction = RIGHT;
      }
    }
  }

  if (obstacle->y == (-1) * CAR_HEIGHT_WITH_PADDING) {
    obstacle->isAlive = false;
    num_of_on_screen_obstacles--;
  }
}

void move() {
  for (uint8_t i = 0; i < NUM_OF_OBSTACLES; i++) {
    if (car_obstacle[i].isAlive) {
      move_obstacles(&car_obstacle[i]);
    }
  }
}

Game Screens

Start screen
Start screen
Start screen
Start screen
Start screen

Testing & Technical Challenges

Issues, Challenges and solutions to get past them

1. RGB LED Matrix Display driver

We first started by porting Adafruit’s LED driver for Arduino, however we could not get it running. So we started to implement our own driver. It worked well for the bottom half of the matrix but not the top half. We debugged the code, later to realize that the SJ2 board pins which we were using for R1, G1 and B1 were not working properly. We used other GPIO pins and it solved the issue.

2. MP3 Decoder The HiLetgo YX5300 UART Control Serial MP3 Music Player Module does not have a detailed datasheet available for reference. After much researching on the internet we found a catalex_mp3 decoder data sheet which was really helpful in understanding and using the mp3 decoder.

3. Low level Audio Sound As we wanted to make the game package very compact, we opted for low power magnetic speaker which consumes less power and does not require an external power source. But when we started using this speakers we noticed that audio level was very low. We used an audio amplifier which takes in the output of the mp3 decoder and amplifies and sends it to the speaker. The onboard potentiometer on this amplifier can be used to adjust the level of the output volume.

4. Pause Bug When we paused the game, the display was frozen but after hitting the play button, the position of the car was way off when we paused. We realized that our accelerometer kept updating the position of the car, it was just not displayed. To solve this issue we now update the values only in the GAME PLAY state.

5. LED Flickering Bug Initially we were refreshing the screen by turning off all pixels and repainting the screen. As a result, at lower speeds the LED used to flicker. To solve this issue, we do not turn off all the pixels, instead we use padding above and on the left, right. Hence, after moving the object, the padding clears the unwanted row.

Conclusion

We were able to successfully design the Road Max Fury game using the RGB LED Matrix and the SJ2 board. This project helped us in having a better understanding of the FreeRTOS scheduler tasks that were used to handle the various components of the game. The understanding developed in writing the display driver from the scratch proved beneficial in resolving issues since most of the available libraries are in C++ and we encountered many problems while porting and debugging in the initial testing phase. Even though there weren't many proper datasheets and reliable tutorials for the LED Matrix, some previous semester's project report on the same display helped us gain momentum in the initial stages. We also got to work on the MP3 decoder while incorporating audio for the game design. Not only did this project help us in understanding the practical possibilities with boards like SJ2 board but also instilled in us a sense of team work and accountability for individually assigned task that helped the team overall.

Project Video

https://youtu.be/cthQap0gyW8

Project Source Code

Road Max Fury Source Code

References