Introduction: Arduino Controllers
An Arduino game controller system using Arduino and the p5.js library.The idea of this is to create an Arduino project that is easily replicated and expandable. The controller connections are designed to utilize a bunch of various sensors and inputs that can be interchanged depending on each controller.
This project is also designed to utilize the p5.js JavaScript library along with the p5.play library designed for p5.js. These libraries allow us to program our games with ease. The p5.play website has a bunch of tutorials and examples for users to create games for it. This project allows users to practice their hardware and software development skills.
Step 1: What You Will Need
Tools:
- Soldering Iron
- Solder
- Wire Strippers
- Side cutters
- Pliers
Hardware:
- Arduino compatible board (I used a Sparkfun Redboard as well as an Arduino Uno and Leonardo)
- Perf Board:
- Various Sensors
- Joysticks
- Buttons (with resistors, 10k ohms, to go with them)
- Potentiometers
- Flex Sensors
- Pressure Sensors
- Etc...
- Wire:
- Single Wire (I used 26 AWG Solid)
- Ribbon wire and crimps
- Break Away Headers (At least 20 of these)
- Optional Hardware (you can use cardboard and hotglue/zip ties instead):
- Breadboard and jumper cables for prototyping
- 3D Printed enclosures
- Hardware fasteners (I used M2.5 screws)
Software:
- Arduino IDE
- p5.js library
- P5.play library as well
- p5.serialcontrol
- Node.js
Step 2: Building: the Console Hub, Setting Up the Shield
Solder the headers to the Arduino Uno shield perf board.
- I started with the shield headers (power, analog in, and digital)
- Next are the 2x5 header pins. You can use 2x5 headers, or just 2 rows of 5 break away headers. I lined these up with A3 and A4 vertically, and left 2 spaces in between them.
Step 3: Building: the Console Hub, Wiring the Shield
Next, we want to route our wires on the shield. It's easier to run the wires on the top, but if you want a cleaner look you can run them on the bottom.
You want to pay attention to the schematic (the Eagle schematic is available for download) when routing these wires. You can also look at the color guide to assist you with this.
The idea of this shield design is to allow 3 analog inputs and 5 digital inputs from each controller. This fully takes advantage of all the analog inputs on an Arduino Uno as well as the remaining wires on our ribbon cable.
Attachments
Step 4: Building: the Controllers, Setting Up Your Parts
The first step to building your controller is to plan which sensors to use. In my examples, I have a pretty standard controller with a joystick and a few buttons. I also have a controller with two slider potentiometers.
If you want to replicate this, you can view my images for placement.
The next step is to solder your ribbon cable to the perf board.
- Strip and tin the ribbon cable
- Solder the ribbon cable to the top center of your perf board.
The next step is to route your wires. I started by wiring the power (5V/red wire) and the ground (brown wire) to the sensors first. I then wired the analog inputs. I found it easy to use the orange cable (Analog A0 or A3) for horizontal movement and the yellow cable (Analog A1 or A4) for vertical movement.
To keep things consistent, I also wired a small push button to purple on all my controllers. This is useful for things such as closing the serial port (I'll go over this later) as well as menus or options.
I have uploaded a quick schematic of my joystick controller if you would like to have a look at this. From our pin-out diagram, you can see the possibility of each controller connection (3 analog inputs, and 5 digital).
Attachments
Step 5: Optional: Enclosures
This step is optional, but if you have access to a 3D printer the outcome of your project will look a bit more refined and finished. As you can see in my prototypes, I used simple piece of cardboard to prevent the solder joints on the bottom of the perf boards from poking your fingers.
You can find my 3D models attached to this step. I have created enclosures for the hub for both the Arduino Uno/Leonardo and the Sparkfun RedBoard (this board is a bit wider and uses mini USB).
For the controllers, you can attach these with M2.5 screws. I kept the nut on the side of the PCB and uses a washer and the screw on the bottom.
I've also included the 3D model for the knob sliders for the potentiometers I used.
You can find all the 3D files on GitHub.
Step 6: Programming: Arduino
Let's start by setting up a simple sketch to test. I suggest using the tutorial created by ITP at NYU found here. To do this tutorial, you will need to have p5.serialcontroll and node.js installed. In this tutorial, you will be introduced to setting up an Arduino to send serial data that is usable by our javascript library, p5.js. You can use the hub and controller we created in the previous steps to do this, or you can replicate the circuits demonstrated in the tutorial. This tutorial uses the A0 analog input pin on the Arduino Uno which is mapped to the orange wire of your first controller.
The next tutorial you will want to follow can be found here. This tutorial will guide you through setting up multiple inputs and utilizing them in p5.js. In the tutorial, the analog inputs A0 and A1 are used. These will correspond to the orange and yellow wires on controller 1 of our system.
Once you have gone through the tutorials above, we can program the Arduino. The code we want to use is below:
//controller 1<br>const int dig2 = 2; //blue const int dig3 = 3; //purple const int dig4 = 4; //grey const int dig5 = 5; //white const int dig6 = 6; //black //controller 2 const int dig7 = 7; //blue const int dig8 = 8; //purple const int dig9 = 9; //grey const int dig10 = 10; //white const int dig11 = 11; //black</p><p>void setup() { Serial.begin(9600); while (Serial.available() <= 0) { Serial.println("hello"); // send a starting message delay(300); // wait 1/3 second } pinMode(dig2, INPUT); pinMode(dig3, INPUT); pinMode(dig4, INPUT); pinMode(dig5, INPUT); pinMode(dig6, INPUT); pinMode(dig7, INPUT); pinMode(dig8, INPUT); pinMode(dig9, INPUT); pinMode(dig10, INPUT); pinMode(dig11, INPUT); }</p><p>void loop() { if (Serial.available() > 0) { // read the incoming byte: int inByte = Serial.read(); // read the sensor:</p><p> //ANALOG Controller 1 int analog0 = analogRead(A0); int analog1 = analogRead(A1); int analog2 = analogRead(A2); //ANALOG Controller 2 int analog3 = analogRead(A3); int analog4 = analogRead(A4); int analog5 = analogRead(A5); //DIGITAL Controller 1 int digital2 = digitalRead(dig2); int digital3 = digitalRead(dig3); int digital4 = digitalRead(dig4);</p><p> int digital5 = digitalRead(dig5); int digital6 = digitalRead(dig6); //DIGITAL Controller 2 int digital7 = digitalRead(dig7); int digital8 = digitalRead(dig8); int digital9 = digitalRead(dig9); int digital10 = digitalRead(dig10); int digital11 = digitalRead(dig11); // print the results: Serial.print(analog0); //[0] Serial.print(","); Serial.print(analog1); //[1] Serial.print(","); Serial.print(analog2); //[2] Serial.print(","); //Start Controller 2 data Serial.print(analog3); //[3] Serial.print(","); Serial.print(analog4); //[4] Serial.print(","); Serial.print(analog5); //[5] Serial.print(","); Serial.print(digital2); //[6] Serial.print(","); Serial.print(digital3); //[7] Serial.print(","); Serial.print(digital4); //[8] Serial.print(","); Serial.print(digital5); //[9] Serial.print(","); Serial.print(digital6); //[10] Serial.print(","); //Start controller 2 data Serial.print(digital7); //[11] Serial.print(","); Serial.print(digital8); //[12] Serial.print(","); Serial.print(digital9); //[13] Serial.print(","); Serial.println(digital10); //[14] Serial.print(","); Serial.println(digital11); //[15] } }
This code sends the serial data from both of our controllers as an array of 16 numbers. The first 6 of these numbers are our analog inputs (ranging from 0-1023) and the remaining 10 values are our digital values (0 or 1).
Once our code is uploaded, we can test this by opening up the serial monitor and typing a value into our serial monitor as we did in the 2nd tutorial from ITP. We should get a string of our values separated by commas.
Attachments
Step 7: Programming: HTML
Once we got our Arduino set up and working, we can start to programming our web stuff. The HTML code is very simple.
<!DOCTYPE html> <html> <head> <script src="p5.min.js"> <script src="libraries/p5.dom.min.js"> <script src="libraries/p5.sound.min.js"> <script src="libraries/p5.play.js"> <script src="sketch.js"><br> <script language="javascript" type="text/javascript" src="libraries/p5.serialport.js"> <style> body {padding: 0; margin: 0;} </head> <body> </body> </html>
The html code simply links our javascript files together. Most of our code will actually happen in our sketch.js file.
Step 8: Programming: P5.js and Javascript
Once we have our HTML set up, we can work on our JavaScript. If you haven't already you should now download p5.js as well as p5.play and add these to your libraries folder in directory for your website.
In our previous step, we set up our HTML file to call our p5.js and p5.play libraries. We also set it up to use our sketch.js file which is where we will do most of our programming. Below is the code for our skeleton. You can also find it here.
//Serial Variables<br>var serial; // variable to hold an instance of the serialport library var portName = 'COM4'; // fill in your serial port name here //Global Game Variables ---------------</p><p>//Setup Function ---------------------- function setup() { createCanvas(640, 480); serial = new p5.SerialPort(); // make a new instance of the serialport library serial.on('list', printList); // set a callback function for the serialport list event serial.on('connected', serverConnected); // callback for connecting to the server serial.on('open', portOpen); // callback for the port opening serial.on('data', serialEvent); // callback for when new data arrives serial.on('error', serialError); // callback for errors serial.on('close', portClose); // callback for the port closing serial.list(); // list the serial ports serial.open(portName); // open a serial port } //Draw Function ----------------------- function draw() { background(0); // black background } //Interpret serial data here ---------- function serialEvent() { // read a string from the serial port // until you get carriage return and newline: var inString = serial.readStringUntil('\r\n'); //check to see that there's actually a ssetring there: if (inString.length > 0) { if (inString !== 'hello') { // if you get hello, ignore it var sensors = split(inString, ','); // split the string on the commas if (sensors.length > 16) { // if there are sixteen elements (6 analog, 10 digital) //Use sensor data here:</p><p> } } serial.write('x'); // send a byte requesting more serial data } } // get the list of ports: function printList(portList) { // portList is an array of serial port names for (var i = 0; i < portList.length; i++) { // Display the list the console: print(i + " " + portList[i]); } } function serverConnected() { print('connected to server.'); } function portOpen() { print('the serial port opened.') } function serialError(err) { print('Something went wrong with the serial port. ' + err); } function portClose() { print('The serial port closed.'); } function closingCode(){ serial.close(portName); return null; } window.onbeforeunload = closingCode;
Once you have the skeleton saved. You can use these values similarly to how it was done in the ITP tutorial. The string of values we sent from our Arduino in step 6 are being sent in as an array of 16 numbers. Below is where we parse this array.
//Interpret serial data here ---------- function serialEvent() { // read a string from the serial port // until you get carriage return and newline: var inString = serial.readStringUntil('\r\n'); //check to see that there's actually a ssetring there: if (inString.length > 0) { if (inString !== 'hello') { // if you get hello, ignore it var sensors = split(inString, ','); // split the string on the commas if (sensors.length > 16) { // if there are sixteen elements (6 analog, 10 digital) //Use sensor data here: } } serial.write('x'); // send a byte requesting more serial data } }
We can now run our program to see if it works!
Step 9: Running Your Program
We can now run our program to see if it works. You can either create your own game using the skeleton.js file in our previous file or you an use the simple Pipe game found here.
Similar to the ITP Lab, to run our program, we will follow the steps below.
- Plug in the Arduino with the controller(s) you plan to use.
- Open p5.serialcontrol
- Change your p5 sketch's port to the one you are using (if you are using the skeleton, this is on line 3)
- Open the HTML file that links to your p5 sketch
If you have outside media such as images or downloaded fonts, you will want to run this on a server. You can run a simple local python server if you would like.