Difference between revisions of "F24: Rival Rush"

From Embedded Systems Learning Academy
Jump to: navigation, search
Line 261: Line 261:
  
 
=== Software Design ===
 
=== Software Design ===
Show your software designFor example, if you are designing an MP3 Player, show the tasks that you are using, and what they are doing at a high level. Do not show the details of the code. For example, do not show exact code, but you may show psuedocode and fragments of code. Keep in mind that you are showing DESIGN of your software, not the inner workings of it.
+
[[File:Game_State.jpg|500px|thumb|centre|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'''
 +
<syntaxhighlight lang="c">
 +
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);
 +
  }
 +
}
 +
</syntaxhighlight>
 +
 
 +
'''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.
 +
 
 +
[[File:CmpE244_S18_Detectable_Accelerations.png|400px|thumb|center|Accelerometer Detection]]
 +
 
 +
 
 +
'''Code Snippet'''
 +
<syntaxhighlight lang="c">
 +
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);
 +
  }
 +
}
 +
</syntaxhighlight>
 +
 
 +
'''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'''
 +
 
 +
<syntaxhighlight lang=c>
 +
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;
 +
}
 +
</syntaxhighlight>
 +
 
 +
 
 +
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'''
 +
<syntaxhighlight lang=c>
 +
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]);
 +
    }
 +
  }
 +
}
 +
</syntaxhighlight>
 +
 
  
 
=== Implementation ===
 
=== Implementation ===

Revision as of 21:31, 19 December 2024

Project Title

Rival Rush

Abstract

This is a 2 player game that would be implemented on a 64*64 RGB LED. Each player has to move against the oncoming traffic without being hit by any of the vehicles. The players can move left or right horizontally across the highway by tilting the player consoles (SJ2 boards). The movement across the road by tilting is sensed by an accelerometer. The players will have 3 lives each of which is deducted per hit. The players can also collect coins on the road which add to their score. The number and speed of the approaching traffic will increase over time. The player with a greater score at the end of 60 seconds will win the game. The LED will display the players’ scores, number of lives left and the racing track on either side.

Objectives & Introduction

Show list of your objectives. This section includes the high level details of your project. You can write about the various sensors or peripherals you used to get your project completed.

Team Members & Responsibilities

  • Alshama Mony Sheena
  • Harshwardhan Ashish Bhangale
  • Gautam Santhanu Thampy

Schedule

Week# Date Task Status
1 10/14
  • To go through previous projects and discuss with the team members.
  • To come up with new ideas for applications specific to FreeRTOS.
  • Had a brainstorming session with the team and decided to do gaming projects using FreeRTOS.
  • Prepared the abstract for the project proposal.
2 10/21
  • To decide the structure of the team and divide the project into different modules.
  • To assign roles and responsibilities to each team member.
  • To finalize the deadlines and deliverables for the project.
  • Assigned roles and responsibilities to each member.
  • Created a test plan with tasks, deadlines and deliverables assigned to it.
3 10/28
  • To start designing the Master module which will take inputs from different players, take a decision and sends it to LED Matrix display.
  • Divided the project into different modules like Master, Player, Wireless, LED Display, PCB, and Testing.
  • Started designing the Master module to take inputs from Players.
4 11/04
  • To understand the high-level APIs for the Wireless nordic node.
  • To understand the connections, read the datasheet for RGB LED Matrix.
  • Made a basic layout, pin connections, power requirements for 64x64 RGB LED Matrix.
  • Understood the Wireless APIs available and wrote a basic code to send-receive data using Nordic.
5 11/11
  • To install Eagle software for PCB design and get accustomed to the basic functions.
6 11/18
  • To test the Player and Master modules.
  • To understand addressing mode, latching, and clock functionality for RGB LED Matrix.
7 11/25
8 12/02
  • To send and receive data between Player and Master using Wireless protocol.
  • To write the logic to glow a particular LED on the display matrix.
  • To design and finalize the PCB circuit.
9 12/09
  • To implement RGB LED Matrix tasks and APIs for the Master module.
  • To integrate the layout of the application (UI, border, car design, obstacle design) to Master Module.
  • To generate random obstacles, score logic and implement other game functionalities (eg: game over scenario).
  • To display the cars, border area, screen division for 2 cars and enable obstacle and car movement as per the input from the master module.
  • Finalize the PCB design, get it reviewed by the team and send it for fabrication.
10 12/16
  • To keep moving the display down continuously for the car race track.
  • To move the car horizontally pixel by pixel for every change in data from the player module.
  • To test the overall functionality of the project using PCB.
11 05/13
  • To Integration of all modules and end to end testing.
  • To fix bugs and optimize the code.
12 05/20
  • Adding extra functionalities and extra features for the project.
  • Test the extra features with overall project requirement.

Parts List & Cost

Give a simple list of the cost of your project broken down by components. Do not write long stories here.

Design & Implementation

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.


Hardware Design

Discuss your hardware design here. Show detailed schematics, and the interface here.

Hardware Interface

In this section, you can describe how your hardware communicates, such as which BUSes used. You can discuss your driver implementation here, such that the Software Design section is isolated to talk about high level workings rather than inner working of your project.

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]);
    }
  }
}


Implementation

This section includes implementation, but again, not the details, just the high level. For example, you can list the steps it takes to communicate over a sensor, or the steps needed to write a page of memory onto SPI Flash. You can include sub-sections for each of your component implementation.

Testing & Technical Challenges

Describe the challenges of your project. What advise would you give yourself or someone else if your project can be started from scratch again? Make a smooth transition to testing section and described what it took to test your project.

Include sub-sections that list out a problem and solution, such as:

<Bug/issue name>

Discuss the issue and resolution.

Conclusion

Conclude your project here. You can recap your testing and problems. You should address the "so what" part here to indicate what you ultimately learnt from this project. How has this project increased your knowledge?

Project Video

Upload a video of your project and post the link here.

Project Source Code

References

Acknowledgement

Any acknowledgement that you may wish to provide can be included here.

References Used

List any references used in project.

Appendix

You can list the references you used.