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

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

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

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

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 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(){
}