Difference between revisions of "F18: Zero Zero UFO"

From Embedded Systems Learning Academy
Jump to: navigation, search
(Project Source Code)
(Project Source Code)
 
Line 757: Line 757:
  
 
=== Project Source Code ===
 
=== Project Source Code ===
*  [https://github.com/olliezhu/zerozeroufo/tree/master/L5_Application GitHUB Source Code Link]
+
*  [https://github.com/olliezhu/zerozeroufo/tree/master/L5_Application GitHub Source Code Link]
  
 
== References ==
 
== References ==

Latest revision as of 00:23, 19 December 2018

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

Zero Zero UFO

Abstract

Bringing a classic game back to life using Embedded Processors and FreeRTOS will help bring a new take to the basic handheld game. We decided to develop the UFO survival game. The objective is to keep the UFO flying for as long as you can whilst avoiding randomly generated obstacles and the other players ufo. The players will control their respective UFOs with the help of the "Accelerometer" sensor present on the SJone board. The ufo can move in either of these directions; Upward, Downward, Forward, Backward , to avoid crashing. The obstacles will move from Right to Left but the Players' UFOs will stay in the field of vision, i.e. they will always be on the screen. The obstacles will have random spacing and random size, testing the users focus. Player who survives longer wins the Game.

Objectives & Introduction

Primary objective of this project was to write efficient code to develop a game using FreeRTOS. SJOne Boards act as the heart of the project with one acting as the hub and one acting as the wireless controller. The accelerometer sensor on the SJOne board which is connected via I2C is used to register the controller orientation. This orientation is then transferred to the hub using a wireless packet. Once the hub, receives the orientation in the queue, game logic decides wether to move the UFO up or down. The obstacles are generated randomly and are moved from right to left on the LED matrix. If player's UFO collides with any of the obstacles its game over. The speed of obstacle movement increases as the levels goes up.

Game development is a big industry and this project helps us in understanding logic development and manipulating FreeRTOS features to get a good and interesting output. The project includes following modules:

  • 1. Wireless Controller: 1 SJOne board is used as the controller. It wirelessly transmits orientation.
  • 2. Master Hub: The other SJOne Board is used as the master device. The game logic resides on this board. It is also used to drive the display.
  • 3. Display Module: Adafruit 64x64 Led Matrix display is used to display the game. This is driven by the GPIO pins on the master SJOne Board.

The Game

  • Wireless UFO game.
  • Press switch on controller to start the game.
  • Randomly generated obstacles of varying sizes.
  • Tilt controller up or down to fly the UFO.

Team Members & Responsibilities

  • Neel Patel
    • LCD Interfacing
    • UFO algorithm
    • Start Screen
    • Report
  • Aman Chandan
    • Wireless Communications
    • Accelerometer value filtering
  • Oliver Zhu
    • Wireless Communication
    • Implemented features like coins and evil UFO
  • Himanshu Gunjal
    • LCD Interfacing
    • Start Screen
    • Random Obstacle Algorithm
  • Pooja Baviskar
    • Hardware Interfacing
    • PCB Design
    • Report

Schedule

Week# Date Task Activities Status Completion Date
1 10/16/18
  • Order Components
  • Finish ordering all the components required for this.
  • Distribute individual project module.
  • Completed
  • Completed
  • 10/17/18
  • 10/17/18
2 10/23/18
  • Interface LCD Display
  • Write algorithm for displaying the Start Screen.
  • Write algorithm for displaying a UFO on the RGB matrix.
  • Completed
  • Completed
  • 11/12/18
  • 11/13/18
3 10/30/18
  • Move UFO and Establish Wireless Communication.
  • Move UFO in y-axis
  • Beta test Wireless communications (Send a message wirelessly between boards).
  • Completed
  • Completed
  • 11/14/18
  • 11/21/18
4 11/6/18
  • Wireless Communication Established and Obstacle Generation.
  • Send accelerometer coordinates wirelessly.
  • Write an algorithm to Generate obstacles Randomly and move them.
  • Completed
  • Completed
  • 11/21/18
  • 11/15/18
5 11/13/18
  • PCB schematics
  • Finish PCB schematics.
  • Order PCB.
  • Completed
  • Completed
  • 11/28/18
  • 11/28/18
6 11/20/18
  • Collision Detection
  • Finish collision detection algorithm to End Game when UFO collides with any obstacle
  • Complete
  • 11/20/18
7 11/27/18
  • Adding Features to the game
  • Give power ups to the player to have different features for the UFO
  • Raise the level by making different obstacles and moving them in a non uniform way.
  • Completed
  • Completed
  • 12/13/18
  • 12/13/18
8 12/4/18
  • Cosmetic Detailing
  • Use Cardboards to finish the project and make it look like a product.
  • Complete Wiki Report
  • Completed
  • Completed
  • 12/16/18
  • 12/16/18
9 12/8/18
  • Final Tweeks.
  • Final Bug Fixes and troubleshooting.
  • Complete Wiki Report and Demo.

Parts List & Cost

Sr No# Part Source Price
1 SJOne Board Preet $80
2 64x64 LED Matrix Adafruit $74.95
3 PCB JLC PCB $20
4 Power Jack Excess Solutions $6.99

Design & Implementation

This section talks about how the various modules of the project were designed and implemented. The system block diagram is given below. The hardware utilizes 2 SJOne boards and a 64x64 Adafruit LED matrix display.

Wireless Transmitter
HUB

1. Master Module

This is the heart of the project. When the game starts, the LED matrix displays the start screen. It is decorated with the game name "ZERO ZERO UFO" and has a highlighted border and a flying UFO. This is a high priority task which is always running. This SJ-One board essentially acts as a console. There is another high priority task called "wireless_receive" which constantly reads values which are transmitted by the controller. There are three main states of the game:

  • 1. Start Screen: This is the first state. The only thing that happens here is that the game title is displayed and there are borders on the edges.
  • 2. Game Screen: This is where the actual game logic is implemented. In this state, the obstacles are generated randomly and the UFO is moved based on the controllers orientation. Collision logic is also written in this state.
  • 3. Boom Screen: After a collision is detected in the game screen the control comes to this state. Here the score is displayed and BOOM!!! characters are displayed.
State_Diagram

Every time the value "100" is received in the mesh packet, it is used to signal the micro-controller to switch between states. The flow of the control is like this Start Screen -> Game Screen -> Boom Screen -> Start Screen .... The wireless receive task is always running, every time it receives a Mesh packet, it deforms it. If the value received is "100" the states are toggled. If it is not 100 then the value received is put into a queue.

Software Design

The software implementation of master module revolves around 5 tasks:

  • 1. RGB_StartScreen
  • 2. Game_Screen
  • 3. RGB_BoomScreen
  • 4. Wireless_Receive
  • 5. init

The fundamental functions like draw_pixel() and draw_character() were provided in the GFX library by adafruit. As soon as the game starts, RGB_StartScreen task is created along with Wireless_Receive. When "100" is received the Start Screen task is suspended and Game_Screen task is created. In the Game task, the obstacles are generated and moved. The values are popped out of the queue and based on these values the UFO is moved. If any of the pixels of the UFO collide with any pixel of the obstacle, it is considered a collision. In a case of collision, the game task is game task is suspended and RGB_BoomScreen is resumed. A counter is used to keep the score. In Boom Screen , the counter value is displayed as the score. When "100" is received again, the flow goes to start screen again.

State Machine

void receive_message(void *p)
{
   mesh_packet_t pkt;
   uint32_t data;
   uint32_t game_state = ZZUS_INIT;

   while (1) {
       if (wireless_get_rx_pkt(&pkt, 80)) {
           wireless_deform_pkt(&pkt, 1, &data, sizeof(data));

           if (data != ZZUM_CTRL) {
               if (game_state != ZZUS_INIT) {
                   xQueueSend(orientation_q, &data, 0);
               }
           } else {
               /* manage the game's state machine */
               switch (game_state) {
               case ZZUS_RESET:
                   game_state = ZZUS_PLAY;
                   rgb.fillScreen(BLACK);
                   boom_flag = true;
                   xTaskCreate(gameplay, "game", 2048, NULL, PRIORITY_MEDIUM, &gameplay_h);
                   vTaskSuspend(start_h);
                   break;
               case ZZUS_PLAY:
                   game_state = ZZUS_RESET;
                   vTaskDelete(gameplay_h);
                   rgb.fillScreen(BLACK);
                   vTaskResume(start_h);
                   break;
               case ZZUS_INIT:
               default:
                   game_state = ZZUS_PLAY;
                   vTaskSuspend(start_h);
                   xTaskCreate(gameplay, "game", 2048, NULL, PRIORITY_MEDIUM, &gameplay_h);
                   break;
               }
           }
       }

   }
}


Collision Detection

/* in console.cpp: function for drawing the UFO also detects collision and sets a flag */
bool ufo_draw_collision(uint8_t x, uint8_t y)
{
   bool collision = false;

   //collision = rgb.drawPixel(x, y, VIOLET) || collision;
   if (rgb.drawPixel(x, y, VIOLET)) {
       x_c = x;
       y_c = y;
       collision = true;
   }
   collision = rgb.drawLineCollision(x-1, y+1, x+1, y+1, VIOLET, &x_c, &y_c) || collision;
   collision = rgb.drawLineCollision(x-3, y+2, x+3, y+2, VIOLET, &x_c, &y_c) || collision;

   return collision;
}


/* in RGB.cpp: minor changes to the driver to detect collision when drawing a pixel */
bool RGB::drawPixel(uint16_t x, uint16_t y, uint16_t c)
{
   uint8_t r, g, b;
   volatile uint8_t *coloradder, rgb_mask, r_mask, g_mask, b_mask;
   uint32_t base_addr;
   bool collision = false;

   if ((x < 0) || (x >= _width) || (y < 0) || (y >= _height)) return true;

   /**
    * workaround for display offset bug
    * decrement rows for 32x64 segments with wrap-around
    */
   if (y == 0) {
       y = 31;
   } else if (y == 32) {
       y = 63;
   } else {
       y--;
   }

   r = (c >> 12) & 0xF;                   // RRRRrggggggbbbbb
   g = (c >>  :sunglasses: & 0xF;         // rrrrGGGGggbbbbb
   b = (c >>  1) & 0xF;                   // rrrrgggggggBBBBb

   if (y < nRows) {
       base_addr = y * WIDTH  + x;
       rgb_mask = 0B00011100;
       r_mask = 0B00000100;
       g_mask = 0B00001000;
       b_mask = 0B00010000;
   } else {
       base_addr = (y - nRows) * WIDTH + x;
       rgb_mask = 0B11100000;
       r_mask = 0B00100000;
       g_mask = 0B01000000;
       b_mask = 0B10000000;
   }
   coloradder = &colorbuffer[base_addr];
   /**
    * collision detection included in generic drawPixel()
    * (NOTE adding extra assignment faster than separate function call, prevents duplicate code)
    */
   _collision = (*coloradder & rgb_mask) != 0;_
   *coloradder &= ~rgb_mask;            // Mask out R,G,B in one op

   if (r & 1) *coloradder |= r_mask; // Plane N R
   if (g & 1) *coloradder |= g_mask; // Plane N G
   if (b & 1) *coloradder |= b_mask; // Plane N B

   return collision;
}


2. Wireless Module

We have used Low Powered Mesh Network stack for sending the accelerometer data from the master controller and the HUB.The minimum payload is 9 bytes, of which, 8 bytes will be the mesh header overhead. The higher the payload, the higher the efficiency of the network. The eight bytes of payload header contains the network source and destination information, along with packet type and hop count information.

Features

  • Addressed network with auto-retries, and auto-acknowledge
  • Minimal RAM/ROM footprint, with NO HEAP usage
  • Each node can participate in the mesh network to deliver packets to a destined node.
  • Each packet may dictate maximum hops it can take.
  • Self-Healing Routes.
    Routes are rediscovered and deleted as appropriate.

Detailed Features

  • Each packet sent can use existing route, and if route has changed, a new route is automatically discovered using a special retry packet.
  • Each node's ACK contains some piggy-back data about the node itself.
    This data includes information about its routing table, and other statistics
  • Duplicate packets are absorbed but an ACK is still replied if its a duplicate, but a retry packet.
  • An ACK packet or a response to an ACK all use retries; even the repeating nodes participate to make sure the packet is delivered.

There is no "out-of-the-box" support for sleeping nodes, however, any sleeping node can wake up, transmit a packet and go back to sleep without affecting the rest of the network. This mesh network design doesn't rely on any node to be a medium as routes can change dynamically. With this said, there are a few improvements that could be made :

  • Use RSSI to dictate if a route should be changed.
  • Use a counter to prefer a route that sends us data more often.

Sending Accelerometer Values

void send_message(uint32_t opcode, uint32_t d)
{
    uint32_t data;
    if (opcode == ZZUM_CTRL) {
        data = ZZUM_CTRL;
    } else {
        data = d;
    }
    wireless_send(CONSOLE_NODE_ADDR, mesh_pkt_nack, &data, sizeof(data), 0);
}

void send_orientation(uint32_t orientation)
{
    send_message(ZZUM_MOVE, orientation);
}

void orient(void *p)
{
    int x = 0;
    uint32_t orientation;
    zzu_velocity velocity = no;

    while (1) {
        x = AS.getX();
        velocity = no;
        if (x >= 0) {
            if (x > 600) {
                velocity = uf;
            } else if (x > 400) {
                velocity = um;
            } else if (x > 200) {
                velocity = us;
            }
        } else {
            if (x < -600) {
                velocity = df;
            } else if (x < -400) {
                velocity = dm;
            } else if (x < -200) {
                velocity = ds;
            }
        }
        send_orientation(velocity);
        vTaskDelay(40);
    }
}

void wireless_transmit(void *p)
{
    uint32_t accel_orientation = 1;

    while (1) {
        wireless_send(CONSOLE_NODE_ADDR, mesh_pkt_nack, &accel_orientation, sizeof(accel_orientation), 0);
        vTaskDelay(80);
    }
}


Receiving Accelerometer Values

void receive_message(void *p)
{
    mesh_packet_t pkt;
    uint32_t data;
    uint32_t game_state = ZZUS_INIT;

    while (1) {
        if (wireless_get_rx_pkt(&pkt, 80)) {
            wireless_deform_pkt(&pkt, 1, &data, sizeof(data));

            if (data != ZZUM_CTRL) {
                if (game_state != ZZUS_INIT) {
                    xQueueSend(orientation_q, &data, 0);
                }
            } else {
                /* manage the game's state machine */
                switch (game_state) {
                case ZZUS_RESET:
                    game_state = ZZUS_PLAY;
                    rgb.fillScreen(BLACK);
                    boom_flag = true;
                    xTaskCreate(gameplay, "game", 2048, NULL, PRIORITY_MEDIUM, &gameplay_h);
                    vTaskSuspend(start_h);
                    break;
                case ZZUS_PLAY:
                    game_state = ZZUS_RESET;
                    vTaskDelete(gameplay_h);
                    rgb.fillScreen(BLACK);
                    vTaskResume(start_h);
                    break;
                case ZZUS_INIT:
                default:
                    game_state = ZZUS_PLAY;
                    vTaskSuspend(start_h);
                    xTaskCreate(gameplay, "game", 2048, NULL, PRIORITY_MEDIUM, &gameplay_h);
                    break;
                }
            }
        }

    }
}


3.Display Module

LED Matrix Front View
LED Matrix Back View

This is a 64x64 RGB LED Matrix Panel, it has 4096 full-color RGB LEDs in all. Each LED can be independently addressed and controlled. It requires at least 13 digital GPIOs to control the LED matrix. The led matrix has 2 IDC connectors (DATA_IN, DATA_OUT) on the back, you can cascade multiple panels and make a huge screen together. There are two 5*32 decoders used for decoding the address rows. Two 3*64 bit shift registers are used for selecting the colors. This is done by clocking the 64 bit shift registers. There are 5 address lines A,B,C,D,E,F for selecting the rows and six 64 bit registers each for R1,G1,B1,R2,G2,B2 for illuminating the leds. A single clock is interfaced to all these 6 64bit shift registers.Once the clocking and shifting the register is completed we need to latch this data to the register. The register data is sent out to all the row lines and that Row line which is pulled low by the decoder will receive this data and corresponding pixels are turned on.

Board Overview

Board_Overview

Pin Description

Label Name Function
1 DR1 High R data
2 DG1 High G data
3 DB1 High B data
4 DR2 Low R data
5 DG2 Low G data
6 DB2 Low B data
7 A A line selection
8 B B line selection
9 C C line selection
10 D D line selection
11 E E line selection
12 F F line selection
13 CLK CLOCK
14 LAT LATCH
15 OE Output Enable
16 GND GND

Specification

  • Operating voltage: DC 5V
  • Average power consumption: <500W/㎡
  • Maxim Power Consumption: <1000w/㎡
  • Pixel: 64x64=4096
  • Level of viewing Angle: ≧160°
  • Control mode: Synchronous control
  • Drive mode: 1/16 scan rate
  • Repetition frequency: ≧60Hz
  • White Balance Brightness: ≧1200cd/㎡
  • Refresh frequency : ≧300Hz
  • MTTF: ≧5000 hours
  • Service Life: 75000~100000 hours
  • Pixel pitch: 3mm
  • Dimension: 190 * 190 * 14.5 mm / 7.48 * 7.48 * 0.57 inches
  • Thickness: 11mm

Row Selection

RGB LED panel column and row driver organization


The rows are driven using four address bits and an address decoder. The four-bit address input to the row drivers is decoded and the two row drivers corresponding to that address will be turned on. When A[3:0] is 0, rows 0 and 16 of the display are turned on. When A[3:0] is 1, rows 1 and 17 of the display are turned on. This pattern continues until A[3:0] is 15 and rows 15 and 31 are turned on.

In addition to the row and column logic and drivers, the display has a blanking input. This input is most likely connected to the column drivers. When the blanking signal is asserted, all of the pixels are turned off and the display will be black. When the blanking signal is deasserted, the addressed rows and columns will be driven and the corresponding pixels illuminated. To display an image without flickering and ghosting, all of these signals must be used and properly sequenced when driving the panel.

Column Selection

The panel contains six sets of column drivers; three for the top half of the display and three for the bottom. Each driver has 32 outputs. The three drivers for the top of the display drive the red, green, and blue chips in each of the 32 columns of LEDs in rows 0 to 15 of the panel. The three drivers for the bottom of the display drive the red, green, and blue chips in each of the 32 columns of LEDs in rows 16 to 31 of the panel.

Each of the drivers has a serial data input, a blanking input, a shift register, and a parallel output register as shown below in Figure 3. The data present on the serial data input is shifted into the shift register using the SCLK signal. After an entire row of data has been shifted in to the shift register, the LATCH signal is used to transfer the row of pixel data from the shift register into the parallel output register. If a bit in the output register is a ‘1’ and the blanking input is deasserted, the driver for that column will be enabled; otherwise, the driver will be turned off. Data is shifted from the right edge of the display to the left edge of the display. In other words, the first bit shifted in will be displayed on the left edge of the display and the last bit shifted in will be displayed on the right.


Column driver operation for the R0 data input and top-half red columns outputs.


The red, green, and blue column drivers for the top half of the display are attached respectively to the R0, G0, and B0 data inputs. The red, green, and blue column drivers for the bottom half of the display are attached respectively to the R1, G1, and B1 data inputs. All six of the 32-bit drivers share common SCLK, LATCH signals.

Pin Configuration

The diagram shows the pin connection between SJ One board and LED Matrix. The five address lines used for selecting the rows, R1, G1, B1, R2, B2, G2 used for illuminating the LEDs, CLK for shift register and LAT are connected to the 14 GPIO pins. The power is given to the board and LED matrix using a 5 volt 5 amperes power adapter. This is split between the SJ One board and the LED Matrix. There is also a push button which is used for restarting the game.


Pin Configuration

PCB

PCB design is used to reduce the hardware complexity and complicated one-to-one wire connections. For this project, PCB designing was done using Autodesk's EAGLE 7.6.0. We used Eagle 9.2.2 software to design a 2 layer PCB. We have designed a circuit which supplies 5V power to SJ One board and LED Matrix through a switch using phoenix connector and female USB connector. There is also one 17*2 female connector which is connected to the HUB 75E connector that is used to transfer data to the LED Matrix.


PCB schematic

EAGLE Connector List

Sr.No Components
1 DC Jack
2 Female USB Connector
3 SPST Switch
4 Phoenix Connector
5 17*2 Female Connector
6 HUB 75E Connector

After completing the schematics, it is converted to board file in which actual placement of components on board and routing of the signals is done. EAGLE provides features like auto routing, multilayer signal routing, converting the file specs in Gerber format for PCB printing. The width for the PCB tracks is 40 mil which can carry 5 Amperes of current and the size of our PCB is 61 mm*51 mm. The PCB board layout is shown in the figure below:

PCB board layout
PCB board

<Bug/issue name>

We had a few issues with the hardware and few bugs in the software while developing the game. They are listed in the section along with the solution we found to fix them.

  • 1. Display Lines Mismatch: This was a problem with the Adafruit LED matrix we had bought for this project. The LINE 32 took LINE 0 place. This resulted in LINE 0 to LINE 31 getting incremented by one. That is, LINE 1 became LINE 0, LINE 2 became LINE 1 and so on. We fixed this problem by modifying the graphics library and we decremented the value of "y". So , when y is between 1-32 we decrement it by 1 and when y=0 we make it y=31. this maps y=0 to LINE 0. Similarly, when y was between 32-63 we decremented it by 1 and when y=32 we make it y=63. This maps LINE 63 with y=63.
  • 2. Wireless Communication Problem: Our wireless communication was not working properly. The "orientation" of the controller was being transmitted properly but the receiver was not receiving the data correctly. The issue was that the wireless task had been disabled on the receiver side, so, even though the controller was transmitting the orientation the receiver was having problem in registering it from the nordic level and put it into the queue from which it could be read. The interesting to notice here was even with the wireless task disabled we were receiving 1 or 2 initial values, albeit not timely. This problem was solved by enabling the wireless task and using channel 2510(FCC restricted channel) to avoid interference.
  • 3. Lack of documentation: There is no documentation available for the 64x64 LED matrix display. There are only sample libraries and Arduino code given on various websites. This made understanding how to interface the Display very difficult. We had to infer knowledge from datasheets and documents of other displays and arduino code about how to get the display working.

Conclusion

We were successfully able to design the UFO game which has been a classic 8bit game since the induction of game industry. We were able to learn about and implement wireless communications between the controller and the console. While designing the project we faced a few problems regarding software and hardware implementation. The display that was used did not have any organized documentation but we were able to infer knowledge from the Arduino code and the graphics library in order to interface the display to the SJOne board. After the basic game was designed we were very motivated in doing something extra and providing features that were not present in the original game such as varying speed objects and an enemy UFO. Overall, it was a great learning experience but more than that it was extremely fun.

Project Source Code

References

[1] Dfrobot 64x64 RGB LED Matrix

[2] DFRobot library

[3] RGB LED Panel Driver Tutorial

[4] Low_Powered_Mesh_Network_stack

Acknowledgement

We would like to thank Prof. Preetpal Kang for all that he thought us. His lectures and this project have been an interesting learning experience. We would also like to thank the ISA team member for always helping out with our doubts and problems.

Appendix

You can list the references you used.