Thimble Learning Platform
Back to full view

RGB Matrix Arcade

Introduction

A guide on how to assemble, program, and use the RGB Matrix Arcade kit. This module will bring you through step-by-step instructions to get your game system operational.

Objectives

  • Assemble and solder the shield
  • Learn how to program the RGB Matrix Arcade and how to expand upon it
  • Electrical Assembly

    Gather Materials 

    Introduction
    Let's take a look

    Gather the materials below and get ready to build the PCB for the RGB Matrix Arcade!

    Parts

    All Parts x Qty
    Shift Registers x 4
    12mm Buttons x 8
    6mm Buttons x 3
    Horizontal 6mm Buttons x 2
    Resistor Packs x 3
    Speaker x 1
    Rotary Potentiometer x 1
    Arduino Headers x 4
    LED Matrix x 1
    Matrix Headers x 2
    68 Ohm Resistors x 2
    150 Ohm Resistor x 1
    1.5k Ohm Resistor x 1
    .01 uF Ceramic Capacitor x 1
    3.6V Zener Diodes x 2
    USB-B Socket x 1

    Tools you'll need

    Soldering Iron
    Solder Wire
    Wire Cutters
    Electrical Assembly
    Follow Along

    Arduino Headers 

    The headers to be used some in one strip. Use a pair of cutters to cut them into one bank of 10, two banks of 8, and one bank of 6. On the back of the PCB, place the 4 Arduino headers in their correct banks. Flip the PCB over to the front and solder one pin in each bank. Make sure the headers are perpendicular to the PCB. If not, reheat and adjust. Once certain that all headers are straight, solder the remaining pins.

    Front of the PCB
    Back of the PCB
    PCB and Headers
    Cut Headers one bank of 6, two of 8, and one of 10
    Correct cuts
    Correct header placement
    Placed headers
    Flipped over PCB
    Solder one pin each
    4 pins soldered
    Header check
    Solder the rest
    All Arduino headers soldered

    Resistors 

    Gather all 4 through hole resistors. Find the two matching ones with the color pattern Blue > Grey > Black. These are the 68 ohm resistors and belong in R2 and R3. Make two 90 degree bends in the resistor legs and place through the holes in the PCB. Once through, bend the leads to prevent it from falling out. Find the single resistor with the pattern Brown > Green > Brown. This is 150 ohms and belongs in R25. The last resistor should have the pattern Brown > Green > Red. This belongs in R1. Flip over the PCB and solder all 4 resistors. After soldering, trim the legs.

    Resistors
    68 ohm color pattern
    68 ohm resistors
    Single resistor
    Bent Resistor
    Place resistor
    Placed resistors
    150 ohm color pattern
    150 ohm resistor
    Placed resistor
    1.5k ohm color pattern
    1.5k ohm resistor
    Placed resistor
    Solder resistor
    Soldered resistors
    Trim leads
    Flipped to Front

    Capacitor 

    Take the 1 ceramic capacitor and place into C1. Bend the leads once through then flip over. Solder both leads then trim.

    Ceramic Capacitor
    Place capacitor
    Placed capacitor
    Flipped PCB
    Solder capacitor
    Soldered capacitor
    Trim leads
    Flipped to Front

    Rotary Potentiometer 

    Take the rotary potentiometer. Place it at RV1. The dial should hang off the PCB. Flip over the PCB and solder the leads. Then trim.

    Rotary potentiometer
    Place potentiometer
    Placed potentiometer
    Flipped PCB
    Solder potentiometer
    Soldered potentiometer
    Trim leads
    Trimmed leads
    Flipped to Front

    Zener Diodes 

    Take the 2 zener diodes. Place in D1 and D2. Diodes are polarized, which means the orientation matters. The black stripe on the diode should be on the same side as the stripe on the board. Make two 90 degree bends in the legs of the diodes. Place through the PCB in the right orientation. Bend the leads after placing to prevent the diodes from falling out. Flip over the PCB and solder. After soldering, trim the legs.

    Zener diodes
    Place zener diode
    Placed zener diodes
    Flipped PCB
    Solder zener diode
    Soldered zener diode
    Trim leads
    Flipped to Front

    Resistor Networks 

    The resistor network integrated circuits(ICs) and the shift register ICs are both in 16 DIP packages. They are black rectangles with 8 pins on each side. The resistor network ICs are a big larger and all their writing is offset to one side while the writing of the shift registers is centered. Once separate from the shift registers, find the resistor network IC that says 1-331 and not 1-221. Notice the notch in what is the top of the IC. Place the 1-331 resistor pack IC in RP2 with the notch facing up. Flip the PCB over and solder all sixteen pins. Flip back to the front. Take the remaining two resistor pack ICs and place them in RP1 and RP3. These two packs are the same so as long as the notch is facing up they can be soldered in either spot. Once set in place, flip the PCB over and solder all pins.

    All 16 DIP packages
    1-331 Writing
    Place in RP2
    Set in RP2
    Flipped over PCB
    Solder the pins
    Pins of RP2 soldered
    Flip to the front
    Place RP1
    Place RP3
    Flipped PCB
    Solder the pins
    All pins soldered
    Flipped and finished

    16 Pin Headers 

    Take the 2 16 pin female headers and place them in the 16 pin banks beside the Arduino headers. Flip the PCB over and solder one pin each. Make sure the headers are straight. Reheat the solder joint and adjust if necessary. Once certain they are straight, solder the remaining pins.

    Headers and PCB
    Place one header
    Flipped PCB
    Solder one
    Check headers
    Solder the rest
    LED headers soldered

    Shift Registers 

    Take 2 shift register ICs and place them in U1 and U2 on the left side of the PCB. The other 2 ICs go in U4 and U5. Make sure the notch is facing up similar to the resistor packs. Flip the PCB over and solder all pins.

    Shift registers
    Places on the PCB
    Place the registers
    Flipped PCB
    Solder the register
    Soldered the register
    Solder the register
    Soldered the register
    Solder the register
    Soldered the register
    Solder the register
    Soldered the register
    Flipped to front

    Direction Buttons 

    Take the 8 larger 12mm buttons and place then in SW1-8. Flip the PCB and solder the 4 leads of each button.

    12mm Buttons
    Place button
    Placed buttons
    Flipped PCB
    Solder button
    Soldered buttons
    Flipped to front

    Extra buttons

    Take 2 6mm buttons and place them in SW10 and SW12. Flip over and solder.

    6mm Buttons
    Place button
    Set button
    Flipped PCB
    Solder button
    Soldered button
    Flipped to front

    Shoulder button

    Take the 2 horizontal 6mm buttons and place it in SW11 and SW9. Flip over and solder.

    6mm Buttons
    Place button
    Set button
    Flipped PCB
    Solder button
    Soldered button
    Flipped to front

    Reset button

    Take the last 6mm button and place it in RST1. Flip over and solder.

    6mm Buttons
    Place button
    Set button
    Flipped PCB
    Solder button
    Soldered button
    Flipped to front

    USB-B Socket 

    Take the USB-B style socket and place it at J1. The open end should be off the PCB. Flip over the PCB and solder all 6 points.

    USB-B Socket
    Place USB
    Placed USB
    Flipped PCB
    Solder USB
    Soldered USB
    Flipped to Front

    Speaker 

    Take the speaker and find the + pin. There is a marking on the from as well as + and - signs and the back. Place the leads in the correct orientation show on the SP1 footprint. Flip over the PCB, solder the 2 leads, and trim.

    Speaker
    Speaker Place
    Placed Speaker
    Flipped PCB
    Solder Speaker
    Soldered Speaker
    Trim
    Flipped to Front

    LED Matrix 

    Take the LED matrix. Flip it over to the back. Find the pin with the 1 next to it. Match that corner with the 1 next to the LED Matrix headers on the PCB. Line up the pins on both side of the matrix with their headers. Press down to seat the matrix.

    LED Matrix
    Back of matrix
    Pin 1
    Match corner
    Press down
    Placed LED Matrix

    Finished PCB 

    The PCB is finished with assembly.

    Finished PCBFinished PCB

    Mechanical Assembly

    Gather Materials 

    Gather the materials below and get ready to build the PCB for the RGB Matrix Arcade!

    Parts

    All Parts x Qty
    Acrylic Side Panel x 2
    M3 6mm Screw x 6
    M3 6mm Nut x 6

    Tools you'll need

    Screwdriver
    Wire Cutters
    Mechanical Assembly
    Follow Along

    Acrylic Side Panels 

    Take the 2 side panels and remove the protective paper. With the finished PCB flipped to its back, line up the holes of a panel with the mounting holes in the PCB. Place 3 M3 screws through the front of the mounting holes and a nut on the back. Tighten until held in place. Be careful to not overtighten. The PCB and acrylic panels could be damaged. Do the same for the other side panel.

    PCB and panel
    Line up
    Place screw
    Secure one
    Secure all
    Other panel
    Finished

    Attach the Arduino 

    The last step in to attach the Arduino to the headers of the RGB Matrix Arcade. With the kit still flipped over get your Arduino. Line up the headers of the Arudino to the header pins on the Matrix Arcade. They will only match up one way. Press down and make sure that the Arduino is fully seated.

    Arduino and Matrix Arcade
    Lined up Arduino headers and pins
    Pins still exposed
    Pressed down

    Finished Arcade 

    The kit is finished with assembly.

    Finished kitFinished kit

    System Breakdown

    Block Diagram 

    This section explains how the system of hardware and software work together. If you just want to get your game working, you can skip to the next section, but we recommend coming back to help you understand how the system works.

    To communicate this efficiently, we use block diagrams composed of functional blocks. At the core of each functional block is a piece of hardware. One step above that is the additional hardware needed to control or interact with the core hardware element. The highest step away from the core is how we interface with it via software. This represents how we would use the hardware via code. The blocks themselves are just a portion of how our system works. We always have arrows to indicate the flow of data between these blocks. Let's start at the core of each block and work our way out to the software.

    Blocks show a part of the full functionality of the kit. The arrows show the flow of data between the blocks.

    Block Breakdown

    System Block Diagram
    System Schematic

    We'll start by looking at just those first two layers of our blocks. The pure hardware component layer and the driver or controller layer. Next to the block diagram is the schematic of the PCB. Looking at both of these we'll see how software can interface with the hardware driver or controller sub-block.

    USB Emulation

    USB Block
    USB Schematic

    By now you've realized that there are two USB ports on this project. One USB micro port on the Arduino and one USB B port on the PCB. The Arduino itself does not have the ability to send keystrokes back to the computer. To add this function to our project, we make a 'virtual' USB port using some pins on the Arduino. We tell the computer that the device connected to the USB B port is a keyboard and then we can send keystrokes or type out full messages from our Arduino. To do this we have the USB socket itself with resistors to step down the 5 volt logic to 3.3 volt logic that USB uses. As an extra level of protection we use diodes D1 and D2. These are zener diodes. Normal diodes don't let any current or voltage go backwards. Zener diodes act like normal diodes until a certain voltage level is reached then they let current through. Here we have them connected to ground. If the data lines get too high the zener diodes will open and direct the extra voltage to ground. In this sense, they act like relief valves.

    All the software interaction is handled through the USBKeyboard library. After making sure to '#include' it, the 'setup()' loop does the virtual equivalent of plugging the USB cable in then out. The computer will ask on connect 'what type of device are you' to which the Arduino will respond 'A keyboard'. The computer will accept it and then the Arduino just needs to send the codes for certain keystrokes for them to be registered by the computer's operating system. The code block for this is shown below.

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    #include "UsbKeyboard.h"
    
    void setup(){
      // Clear interrupts while performing time-critical operations
    cli();
    
    // Force re-enumeration so the host will detect us
    usbDeviceDisconnect();
    delayMs(250);
    usbDeviceConnect();
    
    // Set interrupts again
    sei();
    }
    
    void loop(){
    UsbKeyboard.update();
    UsbKeyboard.sendKeyStroke(KEY_H);
    UsbKeyboard.sendKeyStroke(KEY_E);
    UsbKeyboard.sendKeyStroke(KEY_L);
    UsbKeyboard.sendKeyStroke(KEY_L);
    UsbKeyboard.sendKeyStroke(KEY_O);
    delay(500);     
    }
    

    Buttons

    Button Block
    Button Schematic

    Each button functions the same way. They only complete a circuit when the button is pressed. Even though they are simple they still need some support circuitry - in this case a network of pull-up resistors. We use pull up resistors because they force the circuit to two distinct states: GND or VCC. You could run a circuit like: GND > Button > pin. And when that's closed you would certainly get a logical 0 but left open the voltage will be floating and you can't be certain that it will return to a logical 1. But by using a pull up resistor we can guarantee a logical 1 (or 0 with a pull down). Instead of soldering resistors to each button, we use the internal ones in the Arduino. To do this we call use the term 'INPUT_PULLUP' in the 'pinMode()' assignment in the 'setup()' loop.

    All the buttons are grouped into one of two arrays, left or right. We use a technique called row and column scanning to detect whether a button is pressed. The rows are the pins we read inputs from and the columns are normally held HIGH. Once we start checking for button presses, we set one column LOW then take a reading on each row. If the read comes back as LOW then we know the circuit has been completed. Because the only way to complete the circuit is by pressing a specific button, we then know which button was pressed. After all rows are scanned and the results are stored, the column is returned high and the next column is set LOW for reading. The Arduino is constantly scanning the buttons for presses. This row and column scanning technique is used frequently in both keyboard input and memory circuits.

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    #include <usbconfig.h>
    #include <UsbKeyboard.h> //Library needed for USB input
    
    
    int leftInputs[] = {A1, A0}; //INPUT_PULLUP reading pins
    int leftOutputs[] = {A2, A3, A4}; //Column HIGH or LOW pins
    String leftButtons[2][3] = { //Used to translate  the reading to a real button
      {"LL", "LU", "LR"},
      {"LD", "SELECT", "LS"}
    };
    
    int rightInputs[] = {A5, 7}; //INPUT_PULLUP reading pins
    int rightOutputs[] = {8, 10, 11}; //Column HIGH or LOW pins
    String rightButtons[2][3] = {
      {"RL", "RU", "RR"},
      {"RD", "RS", "START"}
    };
    
    void setup() {
      //Set inputs
      for (int i = 0; i < 2; i++) {
        pinMode(leftInputs[i], INPUT_PULLUP);
        pinMode(rightInputs[i], INPUT_PULLUP);
      }
    
      //Set outputs
      for (int i = 0; i < 3; i++) {
        pinMode(leftOutputs[i], OUTPUT);
        digitalWrite(leftOutputs[i], HIGH);
        pinMode(rightOutputs[i], OUTPUT);
        digitalWrite(rightOutputs[i], HIGH);
      }
    
      Serial.begin(9600); //For debugging
    
    #if BYPASS_TIMER_ISR
      // disable timer 0 overflow interrupt (used for millis)
      TIMSK0 &= !(1 << TOIE0); // ++
    #endif
    }
    
    void loop() {
      UsbKeyboard.update();
      // Left side
      for (int x = 0; x < 3; x++) { //Outputs
        digitalWrite(leftOutputs[x], LOW); //Turn column on
        for (int y = 0; y < 2 ; y++) { //Inputs
          if (!digitalRead(leftInputs[y])) { //Read row
            Serial.println(x);
            Serial.println(y);
            Serial.println(leftButtons[y][x]);
            UsbKeyboard.sendKeyStroke(KEY_L);
          }
        }//End of y
        digitalWrite(leftOutputs[x], HIGH); //turn column off
      }//End of x
    }
    

    Speaker

    Speaker Block
    Speaker Schematic

    Sound waves are vibrations in the air. A traditional loudspeaker uses magnets, driven by voltage pulses, to move a diaphragm back and forth in order to make vibrations. Our speaker is a bit different. It is a piezo buzzer. It is made of a material that vibrates when voltage is applied (this is called a piezoelectric material). By controlling what frequency we apply that voltage at, we can control the note played. A few more components are needed to drive the speaker like we want. First is R25, this resistor steps down the 5 volts from the Arduino to 3.5 volts. According to the datasheet, this is what is safe for the speaker. RV1 is a potentiometer or variable resistor. Changing the position of the dial changes its resistance which further drops the voltage applied to the speaker, thus bringing the volume of the speaker down (or up). C1 is a capacitor which is used to smooth any static or noise that might occur in this circuit.

    How sound worksHow sound works

    Driving the speaker from the Arduino IDE is easy. The 'tone()' function only needs to know what pin the speaker is connected to and what frequency to pulse it at. The function 'noTone()' is used when no sound is to be played. Below is a some simple code to play a small scale. The tones to be played are stored in the 'tones[]' array as their frequency number. The setup function goes through that array and plays each tone for 500 milliseconds, or half a second. Once every tone is played 'noTone()' is called to turn off the speaker.

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    int speakerPin = 9;
    
    int numTones = 10;
    int tones[] = {261, 277, 294, 311, 330, 349, 370, 392, 415, 440};
    //            mid C  C#   D    D#   E    F    F#   G    G#   A
    
    void setup()
    {
      for (int i = 0; i < numTones; i++)
      {
        tone(speakerPin, tones[i]);
        delay(500);
      }
      noTone(speakerPin);
    }
    
    void loop()
    {
    }
    

    LED Matrix

    Matrix Block
    Matrix Schematic
    Shift Schematic

    An LED matrix is a grid of LEDs. By sending power down a column and grounding a row, we can choose which individual LED to light up. Scanning through the matrix like we did the keypads, we can light up multiple LEDs, or pixels, at a time multiple to make shapes or animations. The matrix in the kit is like three stacked matrices, one matrix for red, one for green, and one for blue. Driving all these LEDs can get complicated. To use a single color we need a pin on each column and a pin of each row, totaling 16 pins for a single color matrix. Add in the 2 other colors and we arrive at 32 needed pins, (24 from the colors and 8 from the shared grounds). Our Arduino doesn't have 32 pins so we use a component called a shift register.

    The shift registers we are using have 8 outputs that we can set using only 4 pins. Using two shift registers for 16 outputs still only needs 4 pins and so on and so forth. Using the 4 shift registers we can shift in all the pins values as data coming from the Arduino and have the shift registers actually drive them. To take care of all this we have created two libraries, RGB_Sprite and masterscreen. RGB_Sprite holds the data that says which pixels should be lit up and what color. Masterscreen takes that data, coverts it to shift register data, and shifts it in to be displayed.

    The code below initializes the needed libraries and draws a red line of pixels.

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #include <RGB_sprite.h>
    #include <masterScreen.h>
    
    #define num_sprites 1
    
    masterScreen gameScreen;
    RGB_Sprite sprite[num_sprites];
    
    
    void setup(){
     gameScreen.createScreen();
    
      sprite_lst[character].Sprite(1, 4, 1,
                                         1,
                                         1,
                                         1);
    
    }
    
    void loop(){
    }
    
    Hardware Checkpoints

    3 checkpoints

    There are 3 checkpoints in this section. Each tests one hardware block of the RGB Matrix kit: the LED Matrix, the buttons, and the sound.

    Installing the Custom libraries 

    The libraries needed for this kit were made here at Thimble and be downloaded at the links below. Don't unzip them. They can be added to the Arduino IDE as ZIP files. To do this open up the Arduino IDE. Go to Sketch > Include Library > Add .ZIP library and add the three libraries one by one.

    Masterscreen

    rgbSprite

    usbKeyboard

    LED Matrix 

    The code below tests the RGB matrix and its components. Upload the code to your finished kit. If the Matrix display is working properly, the code below will display a Rainbow. If you don't see this then disconnect your Arduino and double check your solder joints around the shift registers, resistor packs, and all headers.

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    #include <RGB_sprite.h>
    #include <masterScreen.h>
    
    /* rainbow.ino
     *  Benchmark test, displays one color per horizonal
     *  line. Used easily verify the hardware is working.
     *
     * Expected Output:
     *  white
     *  light blue
     *  purple
     *  blue
     *  yellow
     *  green
     *  red
     *  off
     */
    
    masterScreen screen;
    
    void setup() {
      tone(8,200);
      screen.createScreen();
      rainbow();
    }
    
    void loop() {
    
    }
    
    void rainbow()
    {
      for(byte x = 0; x <= 7; x++)
      {
        for(byte y = 0; y <=7; y++)
        {
          screen.modifyScreen(x, y, y);
        }
      }
    }
    

    Button Benchmark 

    After uploading, open up the serial monitor and start pressing buttons. Every button has a unique name.

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    /*rowcolScan.ino
     *  Button hardware check
     *
     * Expected Output:
     *   Pressed button will pop up in the serial monitor
     */
    
    int leftInputs[] = {A0, A1}; //INPUT_PULLUP reading pins
    int leftOutputs[] = {A2, A3, A4}; //Column HIGH or LOW pins
    String leftButtons[2][3] = { //Used to translate  the reading to a real button
      {"LL", "LU", "SELECT"},
      {"LR", "LD", "LShoulder"}
    };
    
    int rightInputs[] = {10, A5}; //INPUT_PULLUP reading pins
    int rightOutputs[] = {11, 12, 13}; //Column HIGH or LOW pins
    String rightButtons[2][3] = {
      {"RL", "RU", "START"},
      {"RR", "RD", "RShoulder"}
    };
    
    void setup() {
      //Set inputs
      for (int i = 0; i < 2; i++) {
        pinMode(leftInputs[i], INPUT_PULLUP);
        pinMode(rightInputs[i], INPUT_PULLUP);
      }
    
      //Set outputs
      for (int i = 0; i < 3; i++) {
        pinMode(leftOutputs[i], OUTPUT);
        digitalWrite(leftOutputs[i], HIGH);
        pinMode(rightOutputs[i], OUTPUT);
        digitalWrite(rightOutputs[i], HIGH);
      }
    
      Serial.begin(9600); //For debugging
    }
    
    void loop() {
    
        // Left side
      for (int y = 0; y < 3; y++) { //Outputs
        digitalWrite(leftOutputs[y], LOW); //Turn column on
        for (int x = 0; x < 2 ; x++) { //Inputs
          if (!digitalRead(leftInputs[x])) { //Read row
            //Button is pressed
            Serial.println(leftButtons[x][y]);
          }
        }//End of y
        digitalWrite(leftOutputs[y], HIGH); //turn column off
      }//End of x
    
        // Right side
      for (int y = 0; y < 3; y++) { //Outputs
        digitalWrite(rightOutputs[y], LOW); //Turn column on
        for (int x = 0; x < 2 ; x++) { //Inputs
          if (!digitalRead(rightInputs[x])) { //Read row
            //Button is pressed
            Serial.println(rightButtons[x][y]);
          }
        }//End of y
        digitalWrite(rightOutputs[y], HIGH); //turn column off
      }//End of x
    }
    

    Sound System Benchmark 

    This tests the speaker and potentiometer components. After uploading, it will play a little melody. If you don't hear anything first check that the potentiometer is dialed to the left.

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    /*sound_benchmark.ino
     *  Sound System hardware benchmark.
     *
     * Expected Output:
     *   A some tones will be played, the Volume Pot
     *   may need to be rotated to turn up the Volume.
     */
    #define NOTE_B0  31
    #define NOTE_C1  33
    #define NOTE_CS1 35
    #define NOTE_D1  37
    #define NOTE_DS1 39
    #define NOTE_E1  41
    #define NOTE_F1  44
    #define NOTE_FS1 46
    #define NOTE_G1  49
    #define NOTE_GS1 52
    #define NOTE_A1  55
    #define NOTE_AS1 58
    #define NOTE_B1  62
    #define NOTE_C2  65
    #define NOTE_CS2 69
    #define NOTE_D2  73
    #define NOTE_DS2 78
    #define NOTE_E2  82
    #define NOTE_F2  87
    #define NOTE_FS2 93
    #define NOTE_G2  98
    #define NOTE_GS2 104
    #define NOTE_A2  110
    #define NOTE_AS2 117
    #define NOTE_B2  123
    #define NOTE_C3  131
    #define NOTE_CS3 139
    #define NOTE_D3  147
    #define NOTE_DS3 156
    #define NOTE_E3  165
    #define NOTE_F3  175
    #define NOTE_FS3 185
    #define NOTE_G3  196
    #define NOTE_GS3 208
    #define NOTE_A3  220
    #define NOTE_AS3 233
    #define NOTE_B3  247
    #define NOTE_C4  262
    #define NOTE_CS4 277
    #define NOTE_D4  294
    #define NOTE_DS4 311
    #define NOTE_E4  330
    #define NOTE_F4  349
    #define NOTE_FS4 370
    #define NOTE_G4  392
    #define NOTE_GS4 415
    #define NOTE_A4  440
    #define NOTE_AS4 466
    #define NOTE_B4  494
    #define NOTE_C5  523
    #define NOTE_CS5 554
    #define NOTE_D5  587
    #define NOTE_DS5 622
    #define NOTE_E5  659
    #define NOTE_F5  698
    #define NOTE_FS5 740
    #define NOTE_G5  784
    #define NOTE_GS5 831
    #define NOTE_A5  880
    #define NOTE_AS5 932
    #define NOTE_B5  988
    #define NOTE_C6  1047
    #define NOTE_CS6 1109
    #define NOTE_D6  1175
    #define NOTE_DS6 1245
    #define NOTE_E6  1319
    #define NOTE_F6  1397
    #define NOTE_FS6 1480
    #define NOTE_G6  1568
    #define NOTE_GS6 1661
    #define NOTE_A6  1760
    #define NOTE_AS6 1865
    #define NOTE_B6  1976
    #define NOTE_C7  2093
    #define NOTE_CS7 2217
    #define NOTE_D7  2349
    #define NOTE_DS7 2489
    #define NOTE_E7  2637
    #define NOTE_F7  2794
    #define NOTE_FS7 2960
    #define NOTE_G7  3136
    #define NOTE_GS7 3322
    #define NOTE_A7  3520
    #define NOTE_AS7 3729
    #define NOTE_B7  3951
    #define NOTE_C8  4186
    #define NOTE_CS8 4435
    #define NOTE_D8  4699
    #define NOTE_DS8 4978
    
    #define melodyPin   9
    
    int high_melody[] = {
      NOTE_E7, NOTE_E7, 0, NOTE_E7,
      0, NOTE_C7, NOTE_E7, 0,
      NOTE_G7, 0, 0,  0,
    };
    int high_tempo[] = {
      12, 12, 12, 12,
      12, 12, 12, 12,
      12, 12, 12, 12,
      12
    };
    
    
    int low_melody[] = {
      NOTE_C4, NOTE_B0, NOTE_A3, NOTE_B0
    };
    
    int low_tempo[] = {12, 12, 12, 12};
    
    int size_var = 0;
    
    void setup(void)
    {
      pinMode(melodyPin, OUTPUT);
    
    }
    void loop()
    {
      sound(1);
      sound(1);
      sound(2);
    }
    
    void sound(int sel) {
    
     switch(sel){
    
      case 1:
        size_var = sizeof(high_melody) / sizeof(int);
    
        for (int thisNote = 0; thisNote < size_var; thisNote++) {
          int noteDuration = 1000 / high_tempo[thisNote];
    
          tone(melodyPin, high_melody[thisNote], noteDuration);
    
          int pauseBetweenNotes = noteDuration * 1.50;
          delay(pauseBetweenNotes);
    
          // stop the tone playing:
          tone(melodyPin, 0, noteDuration);
        }
      break;
    
      case 2:
        size_var = sizeof(low_melody) / sizeof(int);
        for (int thisNote = 0; thisNote < size_var; thisNote++) {
    
          int noteDuration = 1000 / low_tempo[thisNote];
    
          tone(melodyPin, low_melody[thisNote], noteDuration);
    
          int pauseBetweenNotes = noteDuration * 1.50;
          delay(pauseBetweenNotes);
    
          // stop the tone playing:
          tone(melodyPin, 0, noteDuration);
    
        }
      }
    }
    

    Actually Programming the Game 

    Full Code

    The full code for the pong game can be downloaded here.

    Outro
    Outro video
    Software

    This section explains what the blocks of code do.

    Follow along by reading the code you downloaded on your computer. We explain the code from top to bottom.

    Libraries

    This kit uses some pretty complex components and due to that there are a few key libraries needed to run the game code. RGB_sprite was created to manage shapes and move them on the screen . masterScreen handles sending data to the screen via the 4 shift registers. UsbKeyboard adds keyboard emulation via the USB-B socket.

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    //Kit specific libraries
    #include <RGB_sprite.h>
    #include <masterScreen.h>
    
    //File and library for USB emulation
    #include <usbconfig.h>
    #include <UsbKeyboard.h> //Library needed for USB input
    bool usbEnabled = false;
    

    Button Arrays

    Here we store the format of our buttons. The row and column scanning button arrays are ordered. A separate array house the button placements beyond just the pin numbering.

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //Settings for the button scan array, left side
    int leftInputs[] = {A0, A1}; //INPUT_PULLUP reading pins
    int leftOutputs[] = {A2, A3, A4}; //Column HIGH or LOW pins
    //Used to translate  the reading to a real button
    String leftButtons[2][3] = {
      {"LL", "LU", "SELECT"},
      {"LR", "LD", "LShoulder"}
    };
    
    //Settings for the button scan array, left side
    int rightInputs[] = {10, A5}; //INPUT_PULLUP reading pins
    int rightOutputs[] = {11, 12, 13}; //Column HIGH or LOW pins
    //Used to translate  the reading to a real button
    String rightButtons[2][3] = {
      {"RL", "RU", "START"},
      {"RR", "RD", "RShoulder"}
    };
    

    Next are varibles associated with the two button arrays. 'Bytes' are used to track button presses and the paddle position variable is created.

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //***********Paddle Global Variables************
    // Left Paddle
    int LScore = 0;
    int leftPaddlePosition            = 0;
    byte leftUpButton_currentState   = 0;
    byte leftDwnButton_currentState  = 0;
    byte leftUpButton_previousState   = 0;
    byte leftDwnButton_previousState  = 0;
    
    //Right Paddle
    int RScore = 0;
    int rightPaddlePosition           = 0;
    byte rightUpButton_currentState   = 0;
    byte rightDwnButton_currentState  = 0;
    byte rightUpButton_previousState  = 0;
    byte rightDwnButton_previousState = 0;
    
    

    Game States

    All possible states for the game are defined and arranged in the 'possibleStates' enumeration. A separate array is created that holds directions the ball is able to travel after being served.

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    //***********Game States************
    enum possibleStates {
      UPRIGHT,
      RIGHT,
      DWNRIGHT,
      UP,
      UPLEFT,
      LEFT,
      DWNLEFT,
      DOWN,
      PAUSE,
      SCORE,
      SERVE,
      WIN,
      STARTGAME,
      POINT
    };
    
    possibleStates gameState;
    
    //Starting direction. We don't want UP or DOWN as starting states
    int startDir[] = {UPRIGHT, RIGHT, DWNRIGHT, UPLEFT, LEFT, DWNLEFT};
    
    bool isCollisionResult = 0;
    
    

    Screen Declarations

    The screen is declared in addition to all game sprites. There are two sprite arrays created. One for the paddles and ball. The other for the scores.

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    //***********Game Screen and Sprite Global Variables************
    //Declare the screen
    masterScreen gameScreen;
    //Declare the paddles and ball
    RGB_Sprite sprite_lst[num_sprites];
    //Declare the score counters
    RGB_Sprite score[2];
    
    

    Setup 

    This loop only runs once.

    Pin Definitions

    Using two 'for loops' we iterate through all the used pins and set to their correct state. There is an additional step for the output pins. We set them high which for them is their off state.

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //Set the rows of the button array as inputs
    for (int i = 0; i < 2; i++) {
      pinMode(leftInputs[i], INPUT_PULLUP);
      pinMode(rightInputs[i], INPUT_PULLUP);
    }
    //Set the columns as outputs
    for (int i = 0; i < 3; i++) {
      pinMode(leftOutputs[i], OUTPUT);
      digitalWrite(leftOutputs[i], HIGH);
      pinMode(rightOutputs[i], OUTPUT);
      digitalWrite(rightOutputs[i], HIGH);
    }
    

    Screen and Sprites

    We initialize the screen and store all the sprites with their desired qualities to their arrays. The first two values are width and height respectively. The next numbers are the color values of each pixel. Red is 1, Green is 2, and Blue is 4.

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    //***********Initialize gameScreen************
      gameScreen.createScreen();
    
      //*************Sprite Initialization***********
      //Create and store a sprite (width, height, colors to use ....)
      sprite_lst[leftPad].Sprite(1, 4, 1,
                                 1,
                                 1,
                                 1);
    
      sprite_lst[rightPad].Sprite(1, 4, 2,
                                  2,
                                  2,
                                  2);
    
      sprite_lst[ball].Sprite(1, 1, 4);
    
      score[0].Sprite(1, 8, 0,
                      0,
                      0,
                      0,
                      0,
                      0,
                      0,
                      0);
      score[0].updateOrigin(3, 0);
      score[1].Sprite(1, 8, 0,
                      0,
                      0,
                      0,
                      0,
                      0,
                      0,
                      0);
      score[1].updateOrigin(4, 0);
    

    USB Magic

    Here is some disabled code to show how USB emulation would work.

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //start with the game at the STARTGAME state
      gameState = STARTGAME;
    
      //Magically make everything work
      tone(8, 459, 96);
      tone(9, 459, 96);
      if (usbEnabled) {
        // Clear interrupts while performing time-critical operations
        cli();
    
        // Force re-enumeration so the host will detect us
        usbDeviceDisconnect();
        delayMs(250);
        usbDeviceConnect();
    
        // Set interrupts again
        sei();
      }
    

    Main Loop 

    Button Scanning

    The first thing on entrance to the main loop is to check the state of the buttons. The function 'buttonScan()' is called and the full function code is found near the bottom of the sketch. All settings from the past looping are passed to the previous variables. The current state variables are reset to 0. What happens next is the row and column scanning. A column is turned LOW and then the two inputs are checked. If a button is pressed the inputs will read a 0. After both inputs are read the column goes back to HIGH and the next column is set LOW. The iterates through every row and column for both left and right below finishing and returning to the main loop.

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    //Run the row and column button scans
      buttonScan();
    
    ...
    
    void buttonScan() {
      // Left side
      // Reset varibles
      leftUpButton_previousState = leftUpButton_currentState;
      leftDwnButton_previousState = leftDwnButton_currentState;
      leftUpButton_currentState = 0;
      leftDwnButton_currentState = 0;
      for (int y = 0; y < 3; y++) { //Outputs
        digitalWrite(leftOutputs[y], LOW); //Turn column on
        for (int x = 0; x < 2 ; x++) { //Inputs
          if (!digitalRead(leftInputs[x])) { //Read row
            if (x == 0  && y == 1) {
              //Left Up is pressed down
              leftUpButton_currentState = 1;
            } else if (x == 1 && y == 1) {
              //Left Down is pressed down
              leftDwnButton_currentState = 1;
            }
          }
        }//End of x
        digitalWrite(leftOutputs[y], HIGH); //turn column off
      }//End of y
    
      // Similar for right side
      rightUpButton_previousState = rightUpButton_currentState;
      rightDwnButton_previousState = rightDwnButton_currentState;
      rightUpButton_currentState = 0;
      rightDwnButton_currentState = 0;
      for (int y = 0; y < 3; y++) { //Outputs
        digitalWrite(rightOutputs[y], LOW); //Turn column on
        for (int x = 0; x < 2 ; x++) { //Inputs
          if (!digitalRead(rightInputs[x])) { //Read row
            if (x == 0  && y == 1) {
              rightUpButton_currentState = 1;
            } else if (x == 1 && y == 1) {
              rightDwnButton_currentState = 1;
            }
          }
        }//End of x
        digitalWrite(rightOutputs[y], HIGH); //turn column off
      }//End of y
    
    }
    
    

    Paddle Movements

    If the correct button(s) for paddle movement are pressed then below is where they are moved. First a check runs if the paddle would be going off the screen and if so, adjusts itself. After any needed adjustments the position of the paddle is updated in the sprite array and the old sprite is cleared and the new one redraw.

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    //If the up button of the leftpad was pressed...
    if (leftUpButton_currentState != leftUpButton_previousState && !leftUpButton_previousState) {
      //Move the paddle up
      leftPaddlePosition++;
      //Left correction for going off the screen
      if (leftPaddlePosition > 4) {
        leftPaddlePosition = 4;
      }
      if (leftPaddlePosition < 0) {
        leftPaddlePosition = 0;
      }
      //These lines erase the sprite, write its new position, then push that to the screen
      gameScreen.clearSprite(sprite_lst[leftPad]);
      sprite_lst[leftPad].updateOrigin(0, leftPaddlePosition);
      gameScreen.updateMasterScreen(sprite_lst[leftPad]);
    }
      //Checking for down button press
    else if (leftDwnButton_currentState != leftDwnButton_previousState && !leftUpButton_previousState) {
      leftPaddlePosition--;
      //Left correct
      if (leftPaddlePosition > 4) {
        leftPaddlePosition = 4;
      }
      if (leftPaddlePosition < 0) {
        leftPaddlePosition = 0;
      }
      gameScreen.clearSprite(sprite_lst[leftPad]);
      sprite_lst[leftPad].updateOrigin(0, leftPaddlePosition);
      gameScreen.updateMasterScreen(sprite_lst[leftPad]);
    }
    
    //Same checks but for the rightpad
    if (rightUpButton_currentState != rightUpButton_previousState && !rightUpButton_previousState) {
      rightPaddlePosition++;
      //right correct
      if (rightPaddlePosition > 4) {
        rightPaddlePosition = 4;
      }
      if (rightPaddlePosition < 0) {
        rightPaddlePosition = 0;
      }
      gameScreen.clearSprite(sprite_lst[rightPad]);
      sprite_lst[rightPad].updateOrigin(7, rightPaddlePosition);
      gameScreen.updateMasterScreen(sprite_lst[rightPad]);
    }
    else if (rightDwnButton_currentState != rightDwnButton_previousState && !rightUpButton_previousState) {
      rightPaddlePosition--;
      //right correct
      if (rightPaddlePosition > 4) {
        rightPaddlePosition = 4;
      }
      if (rightPaddlePosition < 0) {
        rightPaddlePosition = 0;
      }
      gameScreen.clearSprite(sprite_lst[rightPad]);
      sprite_lst[rightPad].updateOrigin(7, rightPaddlePosition);
      gameScreen.updateMasterScreen(sprite_lst[rightPad]);
    }
    

    Ball Movements

    This code block checks if the ball should move biased on the time that has passed since its last movement. If so then it clears the ball from the screen and redraws it in the right place. After that it checks if the ball has collided with anything. The position of the ball and its collision state are relayed to the 'stateChange()' fucntion.

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    //this code check the current time to see if the ball should move
    sprite_lst[ball].current_time = millis();
    if ((sprite_lst[ball].current_time - sprite_lst[ball].previous_time) >= sprite_lst[ball].duration)
    {
      sprite_lst[ball].previous_time = sprite_lst[ball].current_time;
    
      if (sprite_lst[ball].x_ > 7) {
        sprite_lst[ball].x_ = 7;
      } else if (sprite_lst[ball].x_ < 0) {
        sprite_lst[ball].x_ = 0;
      }
    
      if (sprite_lst[ball].y_ > 7) {
        sprite_lst[ball].y_ = 7;
      } else if (sprite_lst[ball].y_ < 0) {
        sprite_lst[ball].y_ = 0;
      }
    
      //Checks if the ball hit something
      isCollisionResult = sprite_lst[ball].isCollisionNoScreen(num_sprites, ball, sprite_lst, sprite_lst[ball].x_, sprite_lst[ball].y_);
    
      //if no collision redraw the ball
      if (isCollisionResult == false) {
        //clear Sprite
        gameScreen.clearSprite(sprite_lst[ball]);
    
        //update origin
        sprite_lst[ball].updateOrigin(sprite_lst[ball].x_, sprite_lst[ball].y_);
    
        //update screen matrix
        gameScreen.updateMasterScreen(sprite_lst[ball]);
      } else {
        //Collision with paddle sound
        tone(9, 459, 96);
    
      }
      //Figure out the next move of the ball
      stateChange();
    }
    }//End of loop()
    

    State Machine 

    Ball Movement State MachineBall Movement State Machine

    The game that we have coded for you is the classic, Pong: 2 paddles on either end of the screen and a ball being hit back and forth. The paddles are simple to code but getting realistic movement from the ball is tricky. There are many different ways the balls travels across the screen and we have to factor in movement behavior when it bounces off walls and paddles. To keep track of these behaviors we will employ a state machine. A representation of that is above. States are defined as well as the transitions and connections between them. We can look at our visual state machine and see that only certain states are connected or that some states can only be reached by passing through other states. The state machine in this game is used to apply logical movements to the ball. If the ball hit the top of the screen then it should bounce towards the bottom of the screen. What is shown in the picture above is written in the code below.

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    void stateChange()
    {
      sprite_lst[ball].prevState = sprite_lst[ball].state;
      //Refer to the ball state diagram in the learning module
      switch (gameState) {
    
        case UPRIGHT:
          sprite_lst[ball].x_++;
          sprite_lst[ball].y_++;
          if (isCollisionResult == true) {
            sprite_lst[ball].x_ -= 2;
            sprite_lst[ball].y_ -= 2;
            gameState = UPLEFT;
          } else if (sprite_lst[ball].isR_boardCollision() == true) {
            //sprite_lst[ball].x_ = 7;
            //sprite_lst[ball].y_ = 6;
            //sprite_lst[ball].state = LEFT;
            LScore++;
            gameState = POINT;
          } else if (sprite_lst[ball].isT_boardCollision() == true) {
            sprite_lst[ball].x_--;
            sprite_lst[ball].y_--;
            gameState = DWNRIGHT;
          }
          break;
    
        case RIGHT:
          sprite_lst[ball].x_++;
          if (isCollisionResult == true) {
            sprite_lst[ball].x_ -= 2;
            //UPLEFT, LEFT, DWNLEFT
            gameState = random(4, 6);
          } else if (sprite_lst[ball].isR_boardCollision() == true) {
            LScore++;
            gameState = POINT;
          }
          break;
    
        case DWNRIGHT:
          sprite_lst[ball].x_++;
          sprite_lst[ball].y_--;
          if (isCollisionResult == true) {
            sprite_lst[ball].x_ -= 2;
            sprite_lst[ball].y_ += 2;
            gameState = DWNLEFT;
          } else if (sprite_lst[ball].isR_boardCollision() == true) {
            LScore++;
            gameState = POINT;
          } else if (sprite_lst[ball].isB_boardCollision() == true) {
            sprite_lst[ball].x_--;
            sprite_lst[ball].y_++;
            gameState = UPRIGHT;
          }
          break;
    
        case UP:
          sprite_lst[ball].y_++;
          if (sprite_lst[ball].y_ >= 7) {
            gameState = DOWN;
          }
          break;
    
        case UPLEFT:
          sprite_lst[ball].x_--;
          sprite_lst[ball].y_++;
          if (isCollisionResult == true) {
            sprite_lst[ball].x_ += 2;
            sprite_lst[ball].y_ -= 2;
            gameState = UPRIGHT;
          } else if (sprite_lst[ball].isL_boardCollision() == true) {
            RScore++;
            gameState = POINT;
          } else if (sprite_lst[ball].isT_boardCollision() == true) {
            sprite_lst[ball].x_++;
            sprite_lst[ball].y_--;
            gameState = DWNLEFT;
          }
          break;
    
        case LEFT:
          sprite_lst[ball].x_--;
          if (isCollisionResult == true) {
            sprite_lst[ball].x_ += 2;
            //UPRIGHT, RIGHT, DWNRIGHT
            gameState = random(0, 2);
          } else if (sprite_lst[ball].isL_boardCollision() == true) {
            RScore++;
            gameState = POINT;
          }
          break;
    
        case DWNLEFT:
          sprite_lst[ball].x_--;
          sprite_lst[ball].y_--;
          if (isCollisionResult == true) {
            sprite_lst[ball].x_ += 2;
            sprite_lst[ball].y_ += 2;
            gameState = DWNRIGHT;
          } else if (sprite_lst[ball].isL_boardCollision() == true) {
            RScore++;
            gameState = POINT;
          } else if (sprite_lst[ball].isB_boardCollision() == true) {
            sprite_lst[ball].x_++;
            sprite_lst[ball].y_++;
            gameState = UPLEFT;
          }
          break;
    
        case DOWN:
          sprite_lst[ball].y_--;
          if (sprite_lst[ball].y_ <= 0) {
            gameState = UP;
          }
          break;
    
        case PAUSE:
          if ((LScore >= 8) || (RScore >= 8)) {
            gameState = WIN;
          } else {
            score[0].write(0, (LScore - 1), 1);
            score[1].write(0, (RScore - 1), 2);
            gameScreen.updateScreen(2, score);
            //delay() used here to stop sketch to flash score, delay() is not
            //permitted elsewhere
            delay(2500);
            gameScreen.clearSprite(score[0]);
            gameScreen.clearSprite(score[1]);
            gameState = SERVE;
          }
          break;
    
        case SERVE:
          sprite_lst[ball].x_ = 3;
          sprite_lst[ball].y_ = 3;
          gameState = startDir[random(1, 6)];
          break;
    
        case WIN:
          score[0].write(0, (LScore - 1), 1);
          score[1].write(0, (RScore - 1), 2);
          gameScreen.updateScreen(2, score);
          delay(10000);  //10s
          gameState = STARTGAME;
          break;
    
        case STARTGAME:
          //*******Game board Specific Sprite starting Position Stuff*********
    
          //gameScreen.clearMasterScreen();
          sprite_lst[ball].duration = 200;
          sprite_lst[ball].x_ = 3;
          sprite_lst[ball].y_ = 3;
    
          sprite_lst[leftPad].updateOrigin(0, leftPaddlePosition);
          gameScreen.updateMasterScreen(sprite_lst[leftPad]);
    
          sprite_lst[rightPad].updateOrigin(7, rightPaddlePosition);
          gameScreen.updateMasterScreen(sprite_lst[rightPad]);
    
          LScore = 0;
          RScore = 0;
          delay(1000);
          clearScore();
          //This might change to something smarter
          gameState = startDir[random(1, 6)];
          break;
    
        case POINT:
          //Two tones to make the wall collision sound
          //UsbKeyboard.sendKeyStroke(KEY_A);
          tone(9, 226, 16);
          noTone(226);
          tone(9, 490, 257);
          gameState = PAUSE;
          break;
      }
    }
    
    USB Emulation

    USB 

    Haven't you ever wondered how you can just plug a USB flash drive or mouse into any computer and it just works? This section will break down how that happens as well as how this kit can work like a USB Gamepad or keyboard.

    A Protocol By Any Other name

    USB stands for Universal Serial Bus. What we care about here is the serial part. Serial communication. If we wanted to transmit the word 'information' it would sent one by one. I N F O R M A T I O N , one character or letter at a time. When you use a USB mouse or flash drive, data is sent and received in these ordered chunks, one by one.

    What's That?

    When you plug a USB device into your computer a number of things happen before you can use it. The computer will ask what kind of device was just plugged in. What it does and what resources it needs. The device will answer and if the computer can it will allocate the necessary resources. Assuming the computer trusts the device. A device might be 'signed' which means its legitimacy is backed by a company or entity. An Xbox Gamepad is backed by Microsoft and is trusted by Windows.

    A Special Class Above the Rest

    USB devices have sub sets of devices. The one we care about for this kit is the Human Interface Device, or HID, class. This is a group of devices that are used by people. Mice, Keyboard, Flight Sticks, and Exercise Machines are a few. These devices have a VIP pass when it comes to USB. Upon plugging into a computer and being asked what it is. It answers with a unique identifier that the computer will know and will readily accept.

    What About the Kit? 

    There are two USB ports on this kit. The micro-USB found on the Arduino and the big USB-B found on the kit PCB. The micro-USB can only be used for communication between the Arduino and computer. We've used some pins from the Arduino and routed them to the USB-B socket so we can spoof a Human Interface Device. When you plug the Arduino into your computer all the back and forth happens and we can't control that. But with this other USB socket we can use software to tell the computer whatever we want.

    In this case we use the USBkeyboard library to tell the computer that this device is a keyboard. The computer accepts this and now we can use the kit as an input.

    Code

    Upload this code via the micro-USB then switch to the USB-B socket. All Arduino programming must be through the micro-USB.

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    #include "UsbKeyboard.h"
    
    #define BYPASS_TIMER_ISR 1
    
    int leftInputs[] = {A0, A1}; //INPUT_PULLUP reading pins
    int leftOutputs[] = {A2, A3, A4}; //Column HIGH or LOW pins
    bool leftHeld[2][3] =  { // Variables for presses
      {false, false, false},
      {false, false, false}
    };
    int leftButtons[2][3] = { //Used to translate  the reading to a real button
      {KEY_ARROW_LEFT, KEY_ARROW_UP, KEY_SPACE},
      {KEY_ARROW_RIGHT, KEY_ARROW_DOWN, KEY_SPACE}
    };
    
    /*
    int leftButtons[2][3] = {
      {"LL", "LU", "SELECT"},
      {"LR", "LD", "LShoulder"}
    };
    */
    
    int rightInputs[] = {10, A5}; //INPUT_PULLUP reading pins
    int rightOutputs[] = {11, 12, 13}; //Column HIGH or LOW pins
    bool rightHeld[2][3] =  { // Variables for presses
      {false, false, false},
      {false, false, false}
    };
    int rightButtons[2][3] = {
      {KEY_X, KEY_Y, KEY_SPACE},
      {KEY_A, KEY_B, KEY_SPACE}
    };
    
    /*
    int rightButtons[2][3] = {
      {"RL", "RU", "START"},
      {"RR", "RD", "RShoulder"}
    };
    */
    
    void setup() {
      //Set inputs
      for (int i = 0; i < 2; i++) {
        pinMode(leftInputs[i], INPUT_PULLUP);
        pinMode(rightInputs[i], INPUT_PULLUP);
      }
    
      //Set outputs
      for (int i = 0; i < 3; i++) {
        pinMode(leftOutputs[i], OUTPUT);
        digitalWrite(leftOutputs[i], HIGH);
        pinMode(rightOutputs[i], OUTPUT);
        digitalWrite(rightOutputs[i], HIGH);
      }
    
    #if BYPASS_TIMER_ISR
      // disable timer 0 overflow interrupt (used for millis)
      TIMSK0 &= !(1 << TOIE0); // ++
    #endif
    
      Serial.begin(9600); //For debugging
    }
    
    
    
    
    #if BYPASS_TIMER_ISR
    void delayMs(unsigned int ms) {
    
      for (int i = 0; i < ms; i++) {
        delayMicroseconds(1000);
      }
    }
    #endif
    
    void loop() {
    
      UsbKeyboard.update();
    
      // Left side
      for (int y = 0; y < 3; y++) { //Outputs
        digitalWrite(leftOutputs[y], LOW); //Turn column on
        for (int x = 0; x < 2 ; x++) { //Inputs
          if (!digitalRead(leftInputs[x])) { //Read row
            //Button is pressed
            if (!leftHeld[x][y]) {
              UsbKeyboard.sendKeyStroke(leftButtons[x][y]);
              leftHeld[x][y] = true;
            }
          }
          else {
            leftHeld[x][y] = false;
          }
        }//End of x
        digitalWrite(leftOutputs[y], HIGH); //turn column off
      }//End of y
    
      // Right side
      for (int y = 0; y < 3; y++) { //Outputs
        digitalWrite(rightOutputs[y], LOW); //Turn column on
        for (int x = 0; x < 2 ; x++) { //Inputs
          if (!digitalRead(rightInputs[x])) { //Read row
            if (!rightHeld[x][y]) {
              UsbKeyboard.sendKeyStroke(rightButtons[x][y]);
              rightHeld[x][y] = true;
            }
          }
          else {
            rightHeld[x][y] = false;
          }
        }//End of y
        digitalWrite(rightOutputs[y], HIGH); //turn column off
      }//End of x
    
    #if BYPASS_TIMER_ISR  // check if timer isr fixed.
      delayMs(20);
    #else
      delay(20);
    #endif
    }
    

    How Does this Work?

    There are two clusters of buttons on the RGB Matrix Arcade. Split into the left and right side. What pins they use must be declared first.

    Download file Copy to clipboard
    1
    2
    int leftInputs[] = {A0, A1}; //INPUT_PULLUP reading pins
    int leftOutputs[] = {A2, A3, A4}; //Column HIGH or LOW pins
    

    The next block stores if any of the buttons have been pressed.

    Download file Copy to clipboard
    1
    2
    3
    4
    bool leftHeld[2][3] =  { // Variables for presses
      {false, false, false},
      {false, false, false}
    };
    

    Now this is the most important block. Here we define what buttons will do what key presses.

    Download file Copy to clipboard
    1
    2
    3
    4
    int leftButtons[2][3] = { //Used to translate  the reading to a real button
      {KEY_ARROW_LEFT, KEY_ARROW_UP, KEY_SPACE},
      {KEY_ARROW_RIGHT, KEY_ARROW_DOWN, KEY_SPACE}
    };
    

    There are two arrays that hold the key values. Their physical buttons are as follows.

    Download file Copy to clipboard
    1
    2
    3
    4
    int leftButtons[2][3] = {
      {"LL", "LU", "SELECT"},
      {"LR", "LD", "LShoulder"}
    };
    

    We can see that the left side left button is mapped to the press of the left arrow key. LL <> KEY_ARROW_LEFT . Below is a list of possible keys to mapped to these positions.

    Keys

    Here are some key values you can sub in.

    Download file Copy to clipboard
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    #define KEY_A       4
    #define KEY_B       5
    #define KEY_C       6
    #define KEY_D       7
    #define KEY_E       8
    #define KEY_F       9
    #define KEY_G       10
    #define KEY_H       11
    #define KEY_I       12
    #define KEY_J       13
    #define KEY_K       14
    #define KEY_L       15
    #define KEY_M       16
    #define KEY_N       17
    #define KEY_O       18
    #define KEY_P       19
    #define KEY_Q       20
    #define KEY_R       21
    #define KEY_S       22
    #define KEY_T       23
    #define KEY_U       24
    #define KEY_V       25
    #define KEY_W       26
    #define KEY_X       27
    #define KEY_Y       28
    #define KEY_Z       29
    #define KEY_1       30
    #define KEY_2       31
    #define KEY_3       32
    #define KEY_4       33
    #define KEY_5       34
    #define KEY_6       35
    #define KEY_7       36
    #define KEY_8       37
    #define KEY_9       38
    #define KEY_0       39
    
    #define KEY_ENTER   40
    
    #define KEY_SPACE   44
    
    #define KEY_F1      58
    #define KEY_F2      59
    #define KEY_F3      60
    #define KEY_F4      61
    #define KEY_F5      62
    #define KEY_F6      63
    #define KEY_F7      64
    #define KEY_F8      65
    #define KEY_F9      66
    #define KEY_F10     67
    #define KEY_F11     68
    #define KEY_F12     69
    
    #define KEY_ARROW_LEFT 0x50
    #define KEY_ARROW_RIGHT 0x4F
    #define KEY_ARROW_UP 0x52
    #define KEY_ARROW_DOWN 0x51