Pixel Arcade Tutorial 2025
Pixel Arcade Tutorial 2025
You will need to install three libraries; Adafruit_NeoPixel, Adafruit_NeoMatrix, and Adafruit_GFX. Using the
Arduino IDE, here are the steps to install the libraries.
1. Open the Arduino IDE
2. Goto Tools / Manage Libraries
3. Search “neomatrix” and install the Adafruit NeoMatrix library. It will ask if you want to also install
Adafruit NeoPixel, BusIO and Adafruit GFX. Do it.
Create a Folder for Your Files
Create a new folder on your laptop’s desktop entitled, “Pixel Arcade - YOURNAME”. Also create a folder in
your google drive and give editing permission to your partners. After each class, you will upload your new files
to your google folder so that they are saved and accessible by all partners.
Take a look at the code. Note first that some lines begin with the double backslash (//). This is the comment
operator. We want to write clean, understandable code and that means commenting.
Program Description
Every program should have a description in the code. At the top of your program, you should leave comments
that describe what your program does.
Defining Constants
We use the const keyword, a data type keyword, a name and a value to define a constant. A constant is like
a variable but has a value that can not be changed. When we define a variable we have to tell the software
what data type it is. Data types come in many shapes and sizes, but we will be most interested in integers.
Integers get the keyword int. We will also use unsigned 16 bit integers for some variables. These get the type
uint16_t.
In our code, we want to set the data pin that we use to pin 6, so we make it a constant with the following code
sentence.
Take note of the punctuation at the end of this code sentence. We end our code sentences with semicolons!!
Since our matrix is a 16x16 pixel matrix, we can also define a constant for its size.
Setup()
The setup section of the code only runs once. In this case, we start our defined matrix so that it can be
accessed in the program. Mostly, we won’t use this section beyond what is below.
Neopixel Color
One way to set the color of our pixels is to use the Red, Green, Blue (RGB) color system. In this system we
can set how much of each color that we want (from 0-255) and add them together. We use the
matrix.Color(R,G,B) function to do this. As you can see in the code, we define two color variables, col and
col2 to blue and red, respectively. Note also that we need to use the uint16_t type for color.
Loop()
The loop function is the main part of the program. Whatever is inside of the squiggly brackets { } will run
continuously once the program starts.
matrix.fillScreen(0)
This is going to be a useful function. It can fill the matrix with any color, but the real genius is using it to clear
the screen after every write, so the argument is 0 here.
We can use independent variables for each of our pixels or we can reference the other pixels variables and
draw at an offset.
matrix.show()
Another necessary function which sends any drawings that you have done to the screen. These will remain
until written over.
delay(time in milliseconds)
The loop goes forever and will repeat right away. If we want our drawings to last for any amount of time, we’ll
have to delay them.
Challenge 01
Save the file with a new name, such as “Challenge_01”.
Modify the program to make a yellow, 3x3 square near the center of the display. Be sure to modify the
comments in the header to reflect your changes. Remember to save your progress frequently.
We need a new index variable, that I’ve called col. It can just be an integer type.
When we use the array in the code, we will also use the square brackets when indexing the array. Here we
can see that we use the array for the color argument in the drawPixel function. After we send to the display
and delay, we increment the index to choose the next color. We also have to put a limit on the index. If it goes
past 2, we enter an undefined region of the array. To do this we use an if statement.
if(condition) action
The if statement is a staple of programming. Here it is in its simplest form. The statement says that if the
condition is met, do the action. If not, don’t do it and move on. We use it to ask if the index has surpassed the
length of the array. Since our array has three values, the index goes from 0 to 2 (we start at 0). So if the index
is equal to or greater than 3, we need to start over. The action is to set the index back to 0, the start.
Challenge 02
Add a color to your color list array and make the colors cycle at half the rate.
Optional - add a 2nd pixel at a different location and make it cycle through the same list of colors backwards.
Remember to update your comments.
Random Motion
Download this file, move it to your folder and open it in your IDE. One thing that we may want to do is move a
pixel randomly around the display. To do this, we will want to utilize the random function. We will also look at
how to write our own functions that we can call within the main loop to make our programming easier.
random(min, max)
The random function returns a random integer between the values min and max - 1, or min inclusive, max
exclusive. We want to use this to give us a random x or y position, so we will use random(0,16).
Note that we need to declare the data type that is returned to the code. Since the random function itself
returns an integer, we will retain that type for our function. Note also all of the brackets. We have no
arguments, so the brackets on rand_pos are blank. The function definition is inside curly brackets. Lastly, note
the return keyword. It tells the function what to output. In this case we want to return the result of the random
function.
Instead of a variable or a value for our drawPixel arguments, we use our function. Nice! You may also note
that the main program, loop() is a function that doesn’t return anything, so its return type is void.
Challenge 03
Make two pixels move randomly around the screen. Make them change colors randomly. Remove the delay.
We define a new color list with four colors. We will not be changing these values, so we make it a constant.
We also create two new arrays, one for our x-positions and one for our y-positions. Since we will be changing
these values, we leave off the const keyword to make it a variable.
Note that we are using the random function to initialize each pixel to a random location.
We want to make each pixel move +/- 1 unit in both the x and y directions (or stay put), randomly. To do this
we can add a random number between -1 and +1 to the old positions. Random(-1,2) does this (remember that
the max is exclusive). But what if we are against the wall (position 15)? If we add to that, we would move off
of our display. We will use if statements to prevent it. Take a look at the following function.
This function has an argument, old. We need to declare its type and the type for the function. Both are
integers. The first line uses an if statement to check if we are on the max edge. It is better practice to use >=
rather than == just in case we were at 16 or 17. If we are at the max edge, we bounce back one to 14.
The second line uses else if. An else if statement comes after an if statement and checks a second condition.
If the if statement were true, the else if is bypassed. Here we are checking if we are at the min edge. If we
are, we bounce back to 1. In practice, we can have as many else ifs that we want. We want to make sure that
we are checking the most limiting conditions first.
The third line uses else. Else is the “catch all” for any case that doesn’t fit the previous two conditions. Here
we give the new position freedom to be the same pixel or one of the two adjacent pixels in whichever direction
we’re updating (horizontal or vertical). If we do this for both horizontal and vertical, it gives us the current pixel
and the 8 adjacent pixels as possible new positions.
For Loops
For loops are great at doing things a fixed number of times. For this reason, they work well with arrays, which
can have fixed lengths. If we want to draw a pixel for each of our four (x,y) coordinates, we can use a for loop
to iterate through the index from 0 to 3 and get the coordinates from our two arrays. We can also do this for
our color array. Take a look at the code.
We declare a temporary integer iterator variable i that is only used inside the loop. Outside, i has no meaning.
This is called scope. We want to cycle through 4 values, 0 to 3, to access the positions in our array. Every
time we go through the loop, we want to change the value for i by +1. I++ achieves this. It is the same as i = i
+ 1.
Look at the drawPixel function. Each argument accesses the appropriate array with the index being the
variable i. When i = 0 we draw the first pixel with the first x, y, and color from the respective arrays. Then we
increase i to 1 and draw the next one and so on.
To get our new positions, we call our new position function for every x and y value in our arrays. We again can
use for loops to do this efficiently and easily. We take the current position from the array, feed it into our
function and write the new position into our array at the same location. We do this for the length of the array.
Challenge 04
Start each pixel at non-random locations. Make 2 of them only move randomly in the vertical direction and the
other 2 only move randomly in the horizontal direction.
First off, we must run our for loops inside of the setup function. Otherwise we will get an error. The two for
loops populate the position arrays with random positions. Note that the iterator limit is not n, but n-1. We want
it to go from 0 to 3, not 0 to 4. That would be five entries into our array and we only want four. Just by
changing the constant, n, we can make as many pixels as we want.
Note also the randomSeed() function. Arduino uses a pseudo-random sequence and if you don’t tell it where
to start, it will start at the same spot each time. The randomSeed function reads analog input zero, which
varies because of random noise, and uses that random noise to pick a starting point in the pseudo-random
sequence. Try the program with and without and note the difference.
The first pair of x and y directions is (1,1). This means that we will add one to both the x and y position to draw
the next pixel. This corresponds to moving rightward and downward, so this pixel will move diagonally.
Likewise, the next pixels will move rightward, downward, and diagonally leftward and upward.
We send the function the current position and direction. If the pixel is at a max edge AND is moving forward,
we want to turn it around and begin moving backward. If it is at a min edge AND is moving backward we want
it to turn around and begin moving forward. Otherwise, it should continue in the same direction. Note the
double AND symbol (&&) in the condition statements for the if and else if. The function now returns the correct
new direction and we can add that to the current position.
Challenge 05
Add four more pixels with colors cyan, magenta, and yellow and a color of your choice. Make them have
starting directions of downward-left, upward-right, upward, and leftward.
Optional: Modify the program to handle n pixels with random starting positions, directions and colors.
These are hexadecimal numbers that include the amount of R, G and B for the color we want. These colors
are in RGB565 format. You can find lots of internet resources on RGB565 colors to get the ones you want and
then define them as an easy to use constant.
Wall Wrapping
We have to do a check on our pixels to see if they have gone beyond the wall. If they have we set their
position on the other side, or wrap them.
This function has a return quantity. The int before checkWall says that we will return an integer, in this case
representing the new position. The int p says that we must feed the function an integer, in this case the
prospective new position. If the prospective new position is too big, we return 0; too small, we return 15; just
right, we don’t do anything and give it right back.
This is where we call the checkWall function. Note that we give it the current position + the direction for both
the x and y directions and feed the return into the correct spot in the array for position.
Snake
Download this file and open it in the IDE. Let’s make a snake. As you may have guessed, using an array is a
good idea for a snake. We create arrays for the snake’s x and y positions. This is a snake of length n, so we
need an array of length n. But these arrays are of length n+1. Why? Because I am going to use the last spot
in the array to help control how the snake can move.
Remember that matrix.Color sets the amount of red, green and blue in the pixel. I declared a multiplier
variable, “bright” that I multiply by the iterator. In this case, bright = 16. So the tail has an rgb value of (100, 0,
0) and is red, the next pixel is (92, 0, 16), then (84, 0, 32) and the head (10th pixel) is (20, 0, 160) which is
pretty blue. The for loop makes the gradient easy and fun!!
So I made a function called new_pos that is called in the main loop. Notice that the return type is void because
it doesn’t return anything. It just populates the extra slot in the position arrays. Also, there are no arguments
for the function, so the parentheses are empty. The highlighted code shows that we create a new random x
and y direction and add that to the position of the head (n-1) and store it in the empty slot (n).
The main part of this function is highlighted. It checks the new position (which comes into the function as x and
y) against every old position (0 through n-1). If both the x and (&&) the y values for the new position match any
of the old positions, we change the variable crash to true. If not, we leave it false. The function then returns
this value to the new position function. Note that this could be useful if we wanted to make a real snake game
where a crash ends the game.
The != operator means “not equal to” so this is asking, “Is the product of the x and y directions not equal to 0?”
Or in other words, “Is the movement diagonal?”
So, while(these things are true) we discard the old new position and get another random one. To do this, we
use the OR operator (||). We translate the code as:
while(there would be a crash OR there would be diagonal movement OR there would be no movement), keep
getting new possible next positions.
Enabling Wrap
The display goes from 0-15 in both the vertical and horizontal directions. To wrap, we just check if a position is
greater than 15 and set it to zero and if a position is less than 0 and set it to 15. Observe.
Challenge 06
Make a second snake that starts at a different position and has different colors.
Button Inputs
We want our audience to be able to interact with our game. One way that we can do this is by using button
inputs. The digital inputs on the arduino work on a 5 volt system. A voltage that is close to 5V is interpreted as
a binary 1 or HIGH. A voltage that is close to 0V is interpreted as a binary 0 or LOW.
The Hardware
First we have to create a button circuit that will be able to generate these two values, 0 and 5V. There are two
configurations for this, Active High and Active Low. An Active High configuration means that when the button
is activated (i.e. pressed) the voltage sent to the arduino is 5V, otherwise it is 0V. Similarly, an Active Low
configuration means that when the button is pressed 0V is sent to the arduino, otherwise 5V. This can be
achieved with the circuits shown below.
The Active High configuration is typically easier to work with and that is what I suggest.
Simple Start
Download this file and open in with the IDE. Let’s start by taking one pixel, make it move in a straight line and
make it turn left if one button is pressed and right if a second button is pressed. We will only move vertically
and horizontally. We will use pins 4 and 5 for our right and left buttons respectively and have them in the active
high configuration. We will allow screen wrap.
Initializations
Besides the matrix initializations, we create constants for the pins we are going to use for the left and right
buttons, PIN_L and PIN_R. We also create variables to read our button inputs into, left and right. We create x
and y position variables and set their starting values (randomly near the center). But here is where it gets
interesting.
We know a little bit about how turning works here (probably could have used this in the snake code). Say we
start moving to the right on the display. Pushing the right button makes us move down. Pushing again makes
us move left, then up, then back to right. So there are four states and they have a definite order. Pushing the
left button works the same way but opposite. Look at the highlighted code. Together the dirx and diry arrays
chose directions right(1,0), down(0,1), left(-1,0) and up(0,-1) and if we wrap, we’re back at right. So we create
an index variable, or a pointer, called dir that we can increment or decrement to change direction. In our
initialization, we choose a random direction.
pinMode
We have to tell the microcontroller how we are going to use the pins for our buttons. pinMode is the function
that does this. We set them both to inputs.
Note how clean it is. It involves calls to four user-defined functions and reads like pseudocode. It can be
helpful to organize your programs like this. The draw_pixel function is pretty standard. Let’s look at the other
three.
get_buttons()
We use the digitalRead function to read the pins. It returns either a HIGH or a LOW. We read them into our
input variables.
set_direction()
We now have our button variables, left and right. We use their states to set the new direction. An if statement
is good for this. We also test the most limiting case first. Here that means both buttons are pressed. If so, we
do nothing. If we left this out, both buttons would still satisfy the left button being pressed and it would turn.
Next, we check left and right. Turning right means incrementing our dir variable and turning left means
decrementing it according to how we defined our direction arrays. Lastly, we keep ourselves in the bounds of
the direction arrays by wrapping if we get outside. Note that 3 and 0 are valid values, so we wait until 4 and -1
to wrap.
set_position()
The set_position function sets the new position by adding the direction to the old position. We check for wrap
by asking if the new position is out of bounds for our display, -1 or 16.
Challenge 07
Add two more buttons and a second pixel.
Here are a few colors that you can use with matrix.fillScreen().
The function get_inputs() handles our joystick. Look at the code below. We use analogRead(pin) to read our
inputs into their appropriate variables. Then we send those into an if/else statement. First note that I used
Serial.print to send the values to the serial monitor to see their resting value. This is the calibration that I spoke
of earlier. Look at the second image. It shows (504 and 515). There was some drift from earlier. In the
program, the center values are 502 and 517.
So the if statement says that if the joystick is reading greater than the center value plus the offset, then it must
be pushed to the right, so set the direction to 1 which when added to the position of the pixel will make it move
to the right. Else if the joystick is reading greater than center minus offset, it must be in the center, so set the
direction to 0. Else, it must be left and set the direction to -1.
For up and down, the direction of -1 corresponds to up, so we have to adjust our code appropriately.
Graphics Library (GFX)
The GFX library has a lot of tools. Here is a tutorial if you are interested.