F18: Spartan Warrior

From Embedded Systems Learning Academy
Revision as of 21:25, 12 October 2019 by Proj user6 (talk | contribs) (Undo revision 55968 by Proj user6 (talk))

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Grading Criteria

  • How well is Software & Hardware Design described?
  • How well can this report be used to reproduce this project?
  • Code Quality
  • Overall Report Quality:
    • Software Block Diagrams
    • Hardware Block Diagrams
      Schematic Quality
    • Quality of technical challenges and solutions adopted.

Project Title

SPARTAN WARRIOR

Abstract

A long time ago...in an RTOS...far, far away...

This project involves creating a timed, space-based shooting game. The hero of the game is the Not-Death Star, who is controlled by the player. Enemy ships spawn every few seconds, whizzing by the player. The player has 60 seconds to shoot as many enemies as possible, in order to accumulate the highest score possible. Enemies are worth different amounts of points, based on how difficult they are to shoot (speed and size).

The idea involves controlling a player sprite, using feedback from the LPC 1758's acceleration sensor. We also want to incorporate a soundtrack, to run during the game. This way, a player can constantly try to align the Not-Death Star's laser cannon with enemy objects in order to maximize his/her score.

Objectives & Introduction

Team Members & Responsibilities

  • Alisha Jean Patrao
    • Game Design and Firmware, PCB
  • Andrew Kwon
    • Soundtrack, LED Matrix Driver Development and LPC1758 Integration
  • Samir C. Mohammed
    • Wiki Management, PCB, LED Matrix Driver Development and LPC1758 Integration, Game Design and Firmware
  • Sanjana Devegowdanakoppalu SwamyGowda
    • Game Design and Firmware, PCB
  • Tanmay Chandavarkar
    • Controller Design and Integration, Soundtrack

Schedule

Week# Date Deliverables Status
1 10/8
  • Spartan Warrior project approved by instructor
  • Completed
Week# Date Deliverables Status
2 10/16
  • Create project Wiki page
  • Completed
Week# Date Deliverables Status
3 10/23
  • Finalize list of parts to order
  • Completed
Week# Date Deliverables Status
4 10/30
  • Order all necessary project parts
  • Completed
Week# Date Deliverables Status
5 11/06
  • Design and layout Board-Matrix interface circuit schematic and PCB in EAGLE
  • Finalize Spartan Warrior game design (flowchart and art)
  • Note: (Adafruit 32 x 32 LED matrix arrives today)
  • Complete sound effect for trigger pull
  • Completed
  • Completed
  • Completed
  • Completed
Week# Date Deliverables Status
6 11/13
  • Interface LPC1758 to 32 x 32 LED Adafruit LED Matrix
  • Complete and demo initial soundtrack prototype
  • Completed
  • Completed
Week# Date Deliverables Status
7 11/20
  • Achieve full control of individual LED matrix pixels
  • Complete final soundtrack design
  • Send PCB to fabrication house
  • Completed
  • Completed
  • Completed
Week# Date Deliverables Status
8 11/27
  • Improve overall code readability
  • Integrate row control multiplexer into a FreeRTOS task
  • Integrate column control loop into a FreeRTOS task
  • Create and integrate data structures for enemy sprites
  • Implement lateral and vertical movement of sprites
  • Completed
  • Completed
  • Completed
  • Completed
  • Completed
Week# Date Deliverables Status
9 12/4
  • Implement shooting and scorekeeping into game
  • Finish designing controller prototype
  • Complete and demo final soundtrack
  • Integrate PCB into system
  • Test and debug PCB (send another revision to fabrication house if needed)
  • Integrate controller
  • Completed
  • Completed
  • Completed
  • Completed
  • Completed
  • Completed
Week# Date Deliverables Status
10 12/11
  • Complete initial full implementation of Spartan Warrior and demo
  • Test and debug game as needed
  • Integrate controller into system
  • Integrate Soundtrack into system
  • Integrate final PCB revision into system
  • Power both LPC1758's using portable USB power supply
  • Completed
  • Completed
  • Completed
  • Completed
  • Completed
  • Completed
Week# Date Deliverables Status
11 12/19
  • Complete final implementation of Spartan Warrior
  • Complete debugging of all game components
  • Demo
  • Completed
  • Completed
  • Completed

Parts List & Cost

Part # Cost Source
LPC 1758 Development Board 2 $160.00 Preet
Adafruit 32 x 32 LED Matrix 1 $39.95 https://www.adafruit.com/product/1484
PCB 1 $23.00 https://www.pcbway.com/
Aukey 20000 mAH Power Supply 1 $33.00 https://www.amazon.com/AUKEY-20000mAh-Lightning-Portable-Compatible/dp/B0176HQ1O8/ref=sr_1_4?s=wireless&ie=UTF8&qid=1545293329&sr=1-4&keywords=aukey+power+bank
5V/4A Power Supply US plug 1 $15.95 https://www.amazon.com/gp/product/B0749668H2/ref=oh_aui_detailpage_o01_s00?ie=UTF8&psc=1
2.1mm Female Barrel Jack 1 $5.79 (2 pack) https://www.frys.com/product/7726878?site=sr:SEARCH:MAIN_RSLT_PG
VS1053 Codec + MicroSD Breakout - v4 1 $24.95 https://www.adafruit.com/product/1381
Mini Portable speaker AUX interface 1 $16.99 https://www.amazon.com/gp/product/B01NAFNT1M/ref=oh_aui_detailpage_o00_s02?ie=UTF8&psc=1

Design & Implementation

Hardware Design

Game Enclosure...
The game enclosure consisted of a cardboard box wrapped in electrical tape. We needed a bit of space to store all of our game components and wiring harnesses, so a box seemed like the best solution. We used velcro to connect the Adafruit LED Matrix to the top of the chassis and routed its connecting wires beneath it, inside of the box. The box worked well, but for future projects we think that 3-D printing a chassis and reducing the wiring would be beneficial.

IMG 0316.jpeg IMG 0309.jpeg IMG 0308.jpeg

                                      The game enclosure and in-game animation 


PCB...
We designed our PCB to act as a wiring interface between the LPC 1758 (which ran the game code) and the LED matrix. We kept it as simple as possible, as none of us had much experience with PCB design. Also, we didn't want to drive up the project cost by undergoing too many board revisions. We used EAGLE to design our schematic and layout the PCB. Because our design mainly consisted of header pins and ground connections, the design worked on the first try.

We had the board manufactured through the company, PCBway, because they had good reviews regarding manufacturing quality and shipping time. We were pleased the result and would recommend them to future teams doing this project.

For future designs we believe that developing a shield type connection for the LPC 1758 would help cut down on the number of wires needed. Also, it would keep it a bit neater.

Screen Shot 2018-12-19 at 2.19.03 PM.png

MP3 Decoder...


Mp3decoder.png


The SDCS pin on the VS1053b decoder is not used, because transfer of Mp3 file data is done using the SD card on the LPC 1758 micro-controller.

A total of 9 pins are used, in order to implement communication between the Mp3 decoder and the micro-controller; 6 Pins for SPI transfer(SCLK, MISO, MOSI, XDCS, CS, DREQ) and 3 pins for VCC, GND, and Reset. The CS pin is used to read and write to registers on the Mp3 decoder.

The XDCS pin is used to transfer data directly to the decoder, in order to output sound.

Another significant pin, is the DREQ pin. DREQ is the only input signal to the LPC 1758 micro-controller from the Mp3 decoder. The purpose of this signal is to alert the decoder and signal that 32 bytes of the Mp3 file are ready to be decoded. When DREQ is low, an SPI transfer to the decoder should not take place. This may be because the previous SPI transfer has not finished.

Hardware Interface

The project consists of a 32x32 LED Matrix,SJSU ONE Microcontroller for game control, Portable USB Power Supply, MP3 Decoder, Speaker,SJSU ONE Microcontroller for driving the MP3, PCB,Shooting switch as shown in the Fig(1).The Microcontroller boards are powered up using the Portable USB Power Supply. One is used for game control and other one is used for driving the MP3 Decoder. The two boards communicate with each other using a GPIO pin.It helps in signalling the board handling the Mp3 task as to when to start and stop the sound track. The GPIO Pin is set high when the game begins so that the board handling the mp3 can start playing the sound track in the queue in order and the GPIO pin is set to low when the game is over and hence the board stops playing the sound track.An external switch is used for shooting the enemy sprites.

BlockDiagram.png
                                                    Fig(1)Sparatan Warrior System Design

The Fig(2) depicts the Interfacing of RGB LED Matrix to the SJSU One Microcontroller Board.Since there was no datasheet available it was difficult to figure out the interfacing of the LED matrix and we started writing the driver on our own. The Adafruit has a few tutorials on their website about the pins based on which we made the connections.The RGB LED Matrix consists of 6 Data Pins(R1,G1,B1,R2,G2,B2) and 3 control pins (Clock, Output Enable, Latch) and 4 row select pins( A,B,C,D) and 3 GND pins.The LED Matrix is powered using a 5V/2A power supply adapter .The Pins of the microcontroller board are connected to the LED matrix using the connectors.The GND pins of both board and the matrix are connected on the bread board.

Untitled Diagram1.png
                                                    Fig(2)Interface of RGB LED Matrix to SJSU ONE Board

Software Design

When the system is powered up the start_screen() task executes, displaying a welcoming message. All in-game tasks are suspended except for start_screen() and iterate_through_LED_matrix_rows_start_end() (which controls the LED matrix's row multiplexer's and 6 shift register's inputs/outputs. The led matrix illuminates, displaying the message “ Spartan Warrior starts in 3..2..1..”, with a 1 second delay between the countdown.

Thereafter, all in-game tasks are resumed and a GPIO signal is sent to another SJOne Board that starts the soundtrack. The game_over() task starts a 60 second timer, which sets the time that the player has to score points during the game.

When the game begins, the enemy sprites and the player sprite, populate the screen. The player sprite has one dimension of movement (left and right), governed by feedback from the LPC 1758's acceleration sensor Y-axis tilt output. The enemies traverse the screen from left to right, moving one column of pixels at a time. Their spawn time is controlled by a random number generator and a unique scalar modulus value. This is used to set random delays in between spawning. If an enemy sprite laterally traverses the matrix without being hit, it is removed from the screen (upon reaching the 31st column of pixels) and re-spawns on the left side of the matrix after a random delay.

When an enemy sprite is hit by a projectile (fired by the player), a "hit flag" is set. This triggers a sequence where the enemy sprite is removed from the virtual LED matrix array and the player's score is increased by a set value. Each enemy sprite has a different "score value", depending on how difficult it is to hit them.

Once the 60 seconds time elapses, the game_over task suspends all in-game tasks. It sends a signal to the SJOne board that controls the soundtrack, letting the board controlling the mp3 player know to stop playing the soundtrack. A "Game Over" message is displayed and the get_score() function is called, which displays the player's final game score.


Spartan Warrior.png


The game code utilizes 14 tasks that are managed by the FreeRTOS task scheduler. All tasks have equal priority, so the scheduler executes them in a round-robin manner. The description for each of these tasks is as follows:

 Task : Draw_start_screen   |    Priority: 1
Set score = 0; Suspend all in-game tasks;
while(1) Display Welcome Message Send GPIO signal (1) to SoundTrack SJOne Board; Resume all in-game tasks;


 Task: iterate_through_led_matrix_rows        |    Priority: 1
while(1) if (row % 16 == 0) M.Buf = 0; Row = 0; else if ( values of A,B,C,D) // row mux control set LED shift register input values pull clock low; pull latch low; set output enable high;


 Task: draw_enemy_1   |    Priority: 1
Generate enemy by srand(0)
while (1) if (column >= 31 or hit_flag_1) Enemy_1 disappear; If (hit_flag) Score+=1; Hit_flag = false; else Enemy traverse through columns;


 Task: draw_enemy_2   |    Priority: 1
Generate enemy by srand(0)
while (1) if (column >= 31 or hit_flag_1) Enemy_1 disappear; If (hit_flag) Score+=1; Hit_flag = false; else Enemy traverse through columns;


 Task: draw_enemy_3   |    Priority: 1
Generate enemy by srand(0)
while (1) if (column >= 31 or hit_flag_1) Enemy_1 disappear; If (hit_flag) Score+=1; Hit_flag = false; else Enemy traverse through columns;


 Task: draw_enemy_4   |    Priority: 1
Generate enemy by srand(0)
while (1) if (column >= 31 or hit_flag_1) Enemy_1 disappear; If (hit_flag) Score+=1; Hit_flag = false; else Enemy traverse through columns;


 Task: draw_enemy_5   |    Priority: 1
Generate enemy by srand(0)
while (1) if (column >= 31 or hit_flag_1) Enemy_1 disappear; If (hit_flag) Score+=1; Hit_flag = false; else Enemy traverse through columns;


Task:   draw_player_sprite   |    Priority: 1
while (1) Draw player based on Accelerometer Sensor Feedback Bit Shift;


 Task:  move_player_sprite   |    Priority: 1
acceleration_sensor_step_value = 75;
while(1) AS_prev_curr_compare = previous_AS_value - acceleration_sensor_feedback_Y; if( acceleration feedback value) player moves to the desired direction;


 Task : fire_a_shot   |    Priority: 1
while (1) if(switch trigger) fire the shot;


 Task: detect_a_hit   |    Priority: 1
while(1) if(projectile column == enemy column) hit_flag = true;


 Task: Get_score    |    Priority: 1
Set 60 second delay timer. Send stop signal to Soundtrack SJOne board Suspend all in-game tasks.
while(1) Display endgame message. Call get_score() Display score.



Mp3.jpg
(Board 1)Main:
init SPI master clock to 24Mhz
reset MP3 through reset pin 
init MP3 registers (SCI_CLOCKF, SCI_MODE, SCI_AUDATA, SCI_BASS, SCI_VOL)


Task: readSong    |    Priority: 1
Check if SPI Bus is taken if (taken) do nothing else take semaphore read 4k bytes from file and send to Queue give semaphore back
Task: playSong | Priority: 2
Check if SPI Bus is taken if (taken) do nothing else take sempahore store 4k bytes into array from Queue for(128 iterations) send 32 bytes to MP3 decoder give semaphore back


Implementation, Testing & Technical Challenges

Developing a driver for the LED matrix...

The biggest technical challenge that we faced was interfacing the LPC 1758 micro-controller, with the Adafruit 32 x 32 LED matrix.

Adafruit (the 32 x 32 LED matrix's manufacturer) provides a basic tutorial on their website, in order to help with interfacing the LED matrix to a micro-controller. The tutorial briefly describes the functions of the pins on the board's input and output connector ports. It also describes how to connect jumper wires to the ribbon cable (which comes packaged with the board), which connects to the matrix's input and output connector ports. It assumes that the user will utilize an Arduino-type micro-controller to drive the matrix's RGB LED's.

Because we used the LPC 1758 to drive the LED matrix, our choices were to either adapt a third party driver library designed for an Arduino, or develop a new driver. We chose the latter option, as the third party Arduino-focused API's were difficult to fully understand. We also felt that developing our own driver would allow us to gain a strong understanding of all aspects of micro-controller to LED matrix integration. This proved useful both for firmware development and debugging.

The first part of developing the LED matrix driver, was learning how each of the 16 input pins affected the matrix display.


The LED matrix's input socket looked as follows:

Adafruit 32x32 LED Matrix socket.png


GND...
The 3 GND pins were the easiest to understand. They provided a ground signal reference for the matrix's RGB LED's. The GND pins on the LED matrix were connected to GND on the LPC 1758, through a ground plane on the PCB, allowing the micro-controller and LED matrix to share the same signal reference.


A B C D...
Pins A, B, C and D, were used as control bits for the LED matrix's row multiplexer. The 32 x 32 matrix is made up of two individual 16 x 32 LED panels. Each panel contains 16 rows and 32 columns. The binary values of the multiplexer signals, A, B, C and D, are used to determine which row of LED's is being driven. Each panel drives the same row of LED's at a time, based on these values. For example, if A, B, C and D are set to 0000, then the LED's in the first row of each panel can be driven. If A, B, C and D are set to 0001, then the LED's in the second row of each panel can be driven. This pattern continues until A, B, C and D are set to 1111, which corresponds to sixteenth (bottom) row of each panel. We set our row iterating value, as i = (i + 1) % 16, so that A, B, C and D, would be set to 0000, after finishing latching data into the sixteenth row. This allowed the multiplexer to continuously loop through each row in each panel, from top to bottom. We applied a delay of 1ms after each row iteration, thereby setting the overall scan rate to 62Hz. This scan rate was too fast for the human eye to detect, which allowed the LED matrix to display objects steadily, without annoying flickering compromising the image quality.


R1 B1 G1
R2 G2 B2...
Pins R1, B1, G1 and R2, B2, G2, were the 6 control signals used to drive the matrix's RGB LEDs, different combinations of red, blue and green (depending on their binary values). R1, B1 and G1, controlled indiidual LED colors on the top panel (panel 1), while R2, B2 and G2, controlled individual LED colors on the bottom panel (panel 2). The way these color combinations manifested on the board, were as follows:

R B G COLOR
0 0 0 OFF
0 0 1 GREEN
0 1 0 BLUE
0 1 1 CYAN
1 0 0 RED
1 0 1 YELLOW
1 1 0 PURPLE
1 1 1 WHITE

In order to understand how these signals drive individual LEDs on the matrix, one must understand the hardware that they control. The matrix's RGB LEDs are driven by 6 column drivers, each having 32 outputs. Each panel has 3 column drivers. Each column driver can drive 32 LED's in each panel, either red, green or blue (because each of the 3 column drivers controls one of these 3 colors). As a result, the 6 column drivers can drive up to 192 individual red, green and blue LED's (each RGB LED consists of 3 individual red, green and blue LED's) at a time.


CLK LAT OE...
Each column driver is composed of a serial data input, a blanking input, a shift register, and a parallel output register. Data bits are passed into the shift register on the rising edge of each clock cycle. The clock is controlled using the signal CLK. Because there are 32 RGB LED's per row, 32 clock cycles must occur, in order to drive all LED's in a row. After 32 bits of data are passed into the shift register, the LATCH signal (which is set low before shifting in data bits to the shift register) must be asserted, in order to transfer the data bits from the shift register to the parallel output register. Output enable (OE) is pulled low at this point, in order to enable the column driver. It is pulled high again, when transitioning to a new row.

CMPE244 F18 T4 Rgb-led-panel-display-organization.png CMPE244 F18 T4 Rgb-led-panel-shift-register.png

We designed our driver to operate on 32 bit, bit strings (of type uint32_t), in order to utilize all 32 LEDs in each row of the matrix. Each 32 bit string, was stored in a 32 element array of type, uint32_t. We assigned the base memory address of the array to a pointer, and used integer offset values to pass the different bit strings, stored in the array, into the matrix driver function. This method allowed us to pass 32 unique bit strings into all 32 rows of the LED matrix. The integer offsets corresponded to row numbers. For example an offset of 4, would increment the pointer, pointing at the memory address of row 0, by 4 memory addresses. Dereferencing the pointer with this offset (4), would give access to data bits governing the output of row 4 of the LED matrix (the fifth row).

For each row, we used a for loop (which iterated from 0 to 31) in order to shift all 192 data bits into the shift registers. We latched the data bits into the parallel output register, after shifting was completed, which allowed us to illuminate different individual LED's in each row. This allowed us to use 32 bit, bit strings, to create data structures and graphics.

The column driver also allowed us to give the illusion of motion, by turning different LED's on and off in adjacent rows.


Integrating an acceleration sensor to move an avatar...

Another significant technical challenge that we faced, was integrating the LPC 1758's acceleration sensor with a virtual avatar (the player sprite). We used feedback from the acceleration sensor's Y-axis tilt output, to govern the player sprite's lateral movement. In this day, the play had one dimension of control over the sprite, by adjusting the Y-axis tilt of the LPC 1758.

While the task using acceleration sensor feedback to control movement was fairly trivial, it induced a bug in the game. The player sprite flickered one column to the left or one column to the right intermittently, when moving. When the LPC 1758 was completely still, flickering did not occur.

We tried sampling the acceleration sensor 10 times and averaging the integer result, before scaling it by our chosen step value (83), in order to apply the appropriate bit shift to the player sprite. This did not resolve the flickering.

We also tried processing the acceleration Y-axis feedback in a separate task from where we drew the sprite. We included a 1ms delay for each task, as we were concerned that there may have been overlap of calculating acceleration sensor feedback and applying the bit shift to the player sprite.

A few modifications to our move_player_sprite() function led to a significant improvement in performance. We increased the acceleration sensor step size (which is used to scale the Y-axis feedback integer value into a bit shift offset) and implemented our software interpretation of bandpass filter. We also stored the difference between the previous acceleration sensor Y-axis feedback value and the current value in a variable. We used the absolute value of this difference to determine if the acceleration sensor had registered significant enough movement to justify changing the current sprite position offset value. We also increased the move_player_sprite() function's block time to 100ms, which allowed it to more accurately track real-time movement (as human hands don't move fast enough to justify a 1ms delay). These modifications significantly reduced player sprite flickering.


Tracking a moving sprite and integrating shooting...
Tracking the player sprite was a fairly trivial task, due to our player sprite only moving in one dimension (left and right). Because the player sprite was holding a 1 pixel wide gun, we continuously scanned the row where the gun was located, in order to determine which column of the LED matrix would be set as the projectile path. We constantly scanned for a data bit set to 1, which when found, would let us know exactly where in the row that the gun was located.

A "shoot" signal was triggered by a push button sensor, which unblocked the "fire_a_shot()" function. This function passed the gun's column location to the "projectile_path()" function, allowing the projectile sprite to travel vertically, towards a moving enemy sprite (or nothing at all).


Enemy sprites and re-spawn time...
We created 5 unique enemy sprites, using combinations of adjacent bit strings. Each enemy sprite traversed the LED matrix laterally and was managed by its own task. We used a random number generator in addition with a fixed-time modulus, in order to implement random re-spawn times of sprites. The enemy sprite's position in the virtual LED matrix array was incremented by one column, after a set time delay. Each enemy sprite had a unique time delay, in order to allow some sprites to move faster than others and thus be harder to shoot. Faster sprites were worth more points (when shot), while slower sprites, while easier to hit, resulted in less points added to the player's total game score.


Hit detection and scorekeeping...
Hit detection proved less challenging that we initially thought that it would be. When a projectile was fired at an enemy sprite, the projectile's position was tracked as it traversed the LED matrix, vertically. When the projectile's position was within one pixel of the enemy sprite (and directly beneath it), a hit was registered. Upon a hit, the all data bits in the rows containing the vanquished enemy sprite were cleared to "0", This effectively removed the enemy sprite from the game, until a new identical enemy sprite re-spawned. We incorporated a random number driven delay, to control the re-spawn time of enemy sprites. This random re-spawn time was applied upon hits, as well as if an enemy sprite successfully traversed all 32 columns of the matrix (without being hit by a projectile).


Start Procedure...
In order to create a "Start" message for the game (essentially an introduction to the game), we created a task function called "Start_Game()". This task loaded data bits comprising a starting message, into the virtual LED matrix array. It also used the unique task handles of the in-game task functions, in order to suspend them, while it ran. After a fixed delay of 10 seconds, All data bits in the virtual LED matrix were set to 0; creating a blank screen. Start_Game() then used the task handles of each in-game task, to resume them. This commenced the actual Spartan Warrior game. We also sent a high (1) signal to the other LPC 1758 micro-controller in the system, which interfaced with the mp3 decoder. Upon receiving this signal, the other micro-controller worked with the mp3 decoder to play the soundtrack.


Game Over Procedure...
In order to implement the "Game Over" message, we began by setting a delay of 60 seconds. This allowed the task to block for one minute (or the length of the actual in-game time). We adjusted the input integer parameter of the delay_ms() function, in order to ensure that 60 seconds of actual game time was allowed, before this task was called. We chose this value for the game time, for demo purposes only. We could have adjusted it to any value we wished. Once the delay time expired, all in-game tasks were suspended and all data bits in the virtual LED matrix array, were set to 0. The message "Game Over", was loaded into the array and displayed on the screen, as well as the player's score total (on the lower panel).


MP3 Decoder... The system uses two LPC1758 boards; one driving the LED matrix display and one, which reads mp3 data from an SD card using SPI. The two boards communicate with each other using a GPIO pin. A binary signal (high) indicates when to start the soundtrack. It is sent during the start_screen() task. The game_over() task pulls the signal low when it unblocks, signalling the LPC 1758, playing the soundtrack, to stop outputting the audio signal.

An audio decoder was used to collect the data from the queue, translate it and play it on the speaker, connected at the output. When the game reaches the end, the GPIO pin is reset which signals the decoder to stop retrieving data from the queue. The code uses the following to get the data from the SD Card and insert it in the queue:

[[Storage::read("1:track1.mp3", &dataChunk[0], 4096, songoffset); 
songoffset is a data member that stores the starting address of 4k bytes to send to the queue.]]


Soundtrack Integration...
Integrating the soundtrack with the game was somewhat trivial. We simply sent a GPIO signal from the micro-controller, running the game code, to the micro-controller, interfacing with the mp3 decoder. Once the game began, the soundtrack was initiated and once the game ended, the soundtrack ended.

The mp3 file was loaded onto an SD card. It was then decoded and transferred between the mp3 decoder and the LPC 1758, using an SPI bus. Playing multiple songs proved to be a bit more challenging though and we were not able to solve that particular problem before the demo.


Integrating all of the systems...
Integrating all of our systems required a bit of creativity, due to the amount of wires we ended up with. We used a rectangular box as an enclosure (a makeshift chassis) for all of our systems. We used duct tape and electrical tape, in order to secure the wires, ground plane and power supply to the chassis, in order to prevent movement during gameplay. This was especially important, as player sprite movement depended on feedback from the acceleration sensor and we didn't want to have any connections broken because of moving parts. Also, the USB power supply was quite heavy and had the potential to dislodge other components.

While there was no way to reduce our wiring during the final stages of the project when we developed the enclosure, we did our best to neaten up the varying wiring harnesses throughout the system, in order to avoid a cluttered layout. We used boxcutters, in order to create handles for the enclosure, which also acted as the controller. We also cut holes to route power and signal connectors to the LED matrix, which was mounted on top of the chassis.

Our completed system functioned well during testing, but we experienced some new bugs during the demo, which was unfortunate.


<Bug/issue name>

Bug encountered during the demo and fixed post demo...
During the demo, we suffered two setbacks at the beginning of each demo. Our first thought was that tangled wires affected the connection between the LPC 1758 and the LED matrix. However after analyzing our code, we found a bug, in the start_game() task, relating to the game_over() task. When the game begins, the start task is supposed to suspend all tasks except for itself and game_over(); This is important because when the starting sequence finishes, the game_over() task blocks for 60 seconds. When the game_over() unblocks, it suspends all in-game tasks except for itself and the the matrix driver task. This triggers the game over message and ends the game. We realized that we had not suspended the game_over() task in the start_game() task, effectively making the game_over() task block as soon as the starting message began. Because the player has to push the trigger button to start the game, the game can stay in the start_game() task indefinitely. However, the time-to-unblock, for the game_over() task will be decrementing while the player has not actually started the game. If the game waits long enough in this state, the game_over() task unblocks and starts running before the game begins. This effectively corrupted our game during the demo.

Our solution: Suspend the game_over() task in the start_game() task. When suspended at the beginning of the task, and resumed at the start of the game, the game_over() task always worked correctly. Its 60 second game timer began counting when the game began, instead of before the game began. This was a simple fix once we recognized it, but it was a critical bug, with catastrophic consequences. We did not catch this bug previously, because we usually started the game right away during the testing phase. Before the demos, we let the LPC 1758 sit in an idle position after running the start_game() task. The game_over() block time was decrementing while we waited and it ended up being called before we started the game during both of our failed attempts. The game at this point runs as expected and the game can be played over and over again, by simply pressing the trigger button during the game_over () task, after every game.


Using the acceleration sensor to control player sprite movement resulted in the sprite flickering...
The player sprite flickered one column to the left or one column to the right intermittently, when moving. When the LPC 1758 was completely still, flickering did not occur.

We tried sampling the acceleration sensor 10 times and averaging the integer result, before scaling it by our chosen step value (83), in order to apply the appropriate bit shift to the player sprite. This did not resolve the flickering.

We also tried processing the acceleration Y-axis feedback in a separate task from where we drew the sprite. We included a 1ms delay for each task, as we were concerned that there may have been overlap of calculating acceleration sensor feedback and applying the bit shift to the player sprite. (12/7/18)

A few modifications to our move_player_sprite() function led to a significant improvement in performance. We increased the acceleration sensor step size (which is used to scale the Y-axis feedback integer value into a bit shift offset) and implemented our software interpretation of bandpass filter. We also stored the difference between the previous acceleration sensor Y-axis feedback value and the current value in a variable. We used the absolute value of this difference to determine if the acceleration sensor had registered significant enough movement to justify changing the current sprite position offset value. We also increased the move_player_sprite() function's block time to 100ms, which allowed it to more accurately track real-time movement (as human hands don't move fast enough to justify a 1ms delay). These modifications significantly reduced player sprite flickering. (12/10/18)


Writing to registers on the MP3 decoder...

When attempting to read the contents of reset values on the VS1053b registers, we were always receiving high impedance. This is the default value for the MISO line in SPI protocol. We changed the master clock speed to 24Mhz and finally we could read the contents of the reset values. Another important modification in our code was to reset the MP3 decoder through the reset pin before writing to the registers. Its important to initialize CS and XDCS pins high because they are active-low configured.

Another issue was that when the system was logging a crash, it was overwriting all the data on the SD card. As a result the mp3 files were corrupted. When attempting to play music, there was no output and I had thought it was a hardware issue. A simple format of the SD card and re-upload of the mp3 after a crash fixed this issue.

Conclusion

This project was a fun and strenuous application of our engineering and project management skills.

After completing the driver labs during CMPE 244, we felt confident that we could develop a driver, for the Adafruit LED matrix, without using any third party libraries. We researched the hardware operations of the LED matrix and how to drive the RGB LED's. Once we could control LED's one at a time, the project evolved from an engineering problem into more of a creative exercise.

We went through about two iterations of game design before we decided to make our game an homage to Star Wars. At first we imagined a third person shooting game set in ancient Egypt and the Mediterranean. However, we realized that our 32 x 32 matrix came with severely limited the graphics capabilities (due to low resolution). As a result, after we developed background graphics, we realized that there was not much leftover space for game sprites. The tradeoff ended up coming down between a nice looking game and one that would be more fun to play. We chose the latter option and developed the space-based shooter that ended up being the final iteration of Spartan Warrior.

Our best game feature was the soundtrack, which we implemented using an mp3 decoder. We chose an 8-bit soundtrack as a nod to 90's Gameboy games (especially because the matrix's resolution reminded us of that era of games).

Overall I think the biggest lessons that we project management based. We spent a lot of time choosing between various game designs and it forced us to start our final iteration later than we would have liked. However, we banded together and worked hard during the final few weeks of the semester, to create a game that we are proud of.

If we had to do this project over again, we would use an 64 x 64 LED matrix, in order to avoid having to make the graphics vs. gameplay tradeoffs that we did. We would also improve our PCB and have it evolve into a "shield" pin interface in order to clean up our wiring even more. We went with a simple design for this project, as none of us had much prior experience with PCB design. However, now that we understand EAGLE better, developing a better interfacing circuit would be much easier and less time intensive.

We would also implement a wireless controller, instead of having the same LPC 1758 drive the LED matrix and control the acceleration sensor. Since our player sprite only had one dimension of movement, we considered a wireless controller a bit overkill for the project, but looking back it would have heightened the player experience.

For the MP3 decoder, after setting up SPI connections necessary GPIO pins, we recommend doing a Sine test. This will ensure that the hardware is connected properly and will require no future modifications. Any errors on output of sound will mean it is a software issue unless there is bus contention with the MP3 decoder which could result in the MISO pin being damaged. Highly recommended to guard SPI transactions with a mutex. The Sine test is as simple as

1. Set MODE to 0x4820
2. send following 8bit values: (0x53 0xEF 0x6E 3 0 0 0 0) 
3. delay_ms(300)
4. send following 8bit values: (0x45 0x78 0x69 0x74 0 0 0 0)


Overall this project allowed us to apply both or engineering fundamentals, as well as our creative design skills. Even now, there are so many ways to improve our game, that it is a shame that the semester has come to an end. However, those of us taking CMPE 243 next semester are looking forward to tackling an even bigger project and applying the lessons learned this semester, in order to make that project, truly great.

Until then, may the electromotive force be with you all, always...

Project Video

Project Source Code

References

Acknowledgement

Thanks to Preet and the ISA team for putting together this class. It has been one of the richest learning experiences in our educational careers. Also, thanks to all of our classmates, for contributing to Canvas discussions, as well as class discussions. You guys asked a lot of thoughtful questions. Hope to see you in CMPE 243, as well as other classes.

References Used

Appendix

You can list the references you used.