Pronguino

Pronguino

Processing Pong with Arduino Controllers

Introduction

OK, so we have our basic Prong game — it’s time to transform it into Pronguino by adding Arduino paddle controllers. It’s going to be a simple circuit, but a great start to learn about voltage division, serial communications and bitwise operations.

Scope

  Description
Circuit Building a two-potentiometer controller circuit on the breadboard.
Arduino Reading analogue values from the potentiometers, packing two 4-bit values into a single byte, and sending it to Processing over serial.
Processing Receiving the serial data, unpacking the two nibbles, and using them to control the paddles.
Game Replacing keyboard controls with the Arduino potentiometer controllers.

Learning

  Description
Potentiometers How potentiometers work as voltage dividers and how the Arduino ADC converts the voltage to a 0–1023 value.
Serial Communication How one-way serial communication works between the Arduino UNO and Processing.
Bitwise Operations How to pack two 4-bit values into a single byte using bit shifting and bitwise OR, and unpack them with bit shifting and bitwise AND.
Fritzing How to use Fritzing to produce breadboard layouts and schematics.
Tinkercad How to simulate an Arduino circuit in Tinkercad before building it.

Getting Started

First, we will focus on building the circuit and programming the Arduino UNO, making sure everything compiles and uploads OK.

I recommend that you read and run through ‘Project 14 – Tweak the Logo’ in the Arduino Projects Book, as this will give a bit more background on what we are doing here.

Components

All the components you need are in the Arduino Starter Kit.

Component Quantity
Arduino UNO Rev 3 1
USB Cable 1
Breadboard 1
Potentiometer 10kΩ 2
Jumper Wires 6
Black Stranded Jumper Wire 1
Red Stranded Jumper Wire 1

The Circuit

Fritzing Breadboard

Assembly

These instruction steps are used to assemble the circuit as shown above, quite straightforward with some added tips:

Step Instructions
1 Ensure you have unplugged from power source and at least one end of USB cable is removed from either the UNO / computer.
2 Connect the red stranded jumper wire to one of the bus lines on the breadboard marked with + to the 5V pin on the UNO.
3 Connect the black stranded jumper wire to the adjacent bus line marked with to the GND (Ground) pin on the UNO.
4 The potentiometers that come with the kit have to straddle the centre of the breadboard, so place one on the left of the board so the side with two legs is facing the bus line where you connected the power. Place the other on the right-hand side. Make sure you press them in firmly, so they don’t wobble.
5 Connect one of the smaller jumper wires (15mm blue ones seem to do the trick) from a board socket aligned with the left leg of potentiometer to the socket on the bus line where you have connected black wire. Do the same with the other potentiometer.
6 Connect another of the smaller wires (the 5mm orange one) from a board socket aligned with the right leg of potentiometer to the + socket on the bus line where you have connected red wire. Do the same with the other potentiometer.
7 On the other side of the board where the single legs (wipers) of the potentiometers are, connect a green jumper wire from a socket aligned with the wipers, to the Analog-In A1 pin on the UNO, for the left hand side potentiometer, and A2 for the one on the right.

The Code

Arduino

We could write this code in a few lines in one file, but as I want to make the code more readable, understandable and extensible, I have broken it down into the following files …

File Description
pronguino The main file. Similar to Processing, setup() initialises the serial communications and controllers, and loop() continuously reads the values from the controllers and writes them to the serial port.
Controller.h Header file for the Controller library.
Controller.cpp C++ implementation of the Controller library. Library Packaging will be covered in a later post.

I will let the code speak for itself, and take a deeper dive into some areas below.

Plug the Arduino into your PC, upload the sketch, and we are ready for the next step.

Testing

Note: If you have a mobile phone or similar attached to your computer via USB, you may get some spurious results, bear this in mind if you are getting strange movement from paddles with or without Arduino attached.

So, we have the controllers built, code uploaded, and usually you would update the Processing code and spend the next few hours playing Pronguino, right? Or maybe the next few hours debugging two sets of code and an electronic circuit?

We haven’t got the luxury of a user interface on the UNO, but what we do have while connected to the PC is the Serial Monitor, which is opened from the Arduino IDE: Tools → Serial Monitor.

I have added a constant called TESTING, and when set to false, on upload of code, you should see something like this, as you turn the potentiometers, which is the data being written to the serial port.

⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮ѡ⸮⸮⸮⸮pppppP@A23#$$# @PP``acD4$#

If you change TESTING to be true, upload the new code, the setup() will call function resultsHeader() to print some headings, and loop() will continuously print the data being read from each controller via the function resultsDetail(), and the data that would be written, you should see something like below under various conditions ..

Both potentiometers turned to far left …

controllerValue1	controllerValue2	highNibble	lowNibble	data	
0			0			0		0		0
0			0			0		0		0
0			0			0		0		0

Both potentiometers turned to far right …

controllerValue1	controllerValue2	highNibble	lowNibble	data	
15			15			240		15		255
15			15			240		15		255
15			15			240		15		255

Both potentiometers turning to somewhere in the middle …

controllerValue1	controllerValue2	highNibble	lowNibble	data	
11			10			176		10		186
8			10			128		10		138
8			8			128		8		136

Troubleshooting

Hopefully, with the circuit being quite simple, there is not much to go wrong, but the common things to check are as follows ..

Issue Solution
Nothing is working Check Arduino is plugged in securely. Check red & black wires on board are secure
Random data (without change potentiometer) Check potentiometers are firmly pushed in. Check the data wires are securely connected
Getting 15 instead of 0 as a nibble value, when potentiometer turned all way to left Check the left pin of potentiometer is connected to GND, and right to 5V

Program Storage Space

Just a quick note to those worried about testing code taking up valuable space on the Arduino, the compiler will not include any unreachable code, so when TESTING = false;, the functions resultsHeader() and resultsDetail(...) are not compiled, check console after compiling …

Sketch uses 1952 bytes (6%) of program storage space. Maximum is 32256 bytes. Global variables use 188 bytes (9%) of dynamic memory, leaving 1860 bytes for local variables. Maximum is 2048 bytes.

… in comparison to having TESTING = true;

Sketch uses 2424 bytes (7%) of program storage space. Maximum is 32256 bytes. Global variables use 260 bytes (12%) of dynamic memory, leaving 1788 bytes for local variables. Maximum is 2048 bytes.

Processing

What’s New

File Description
Controller Contains a new class Controller, which encapsulates the values received from Arduino controllers
Functions A new static class Functions, which contains functions that can be called from anywhere in the package, initially used for some bitwise calculations

What’s Changed:

File Changes
pronguino Imports serial library.
Initialises serial port in setup().
Added serialEvent()
Constants Added controller related constants
Options Added serial port related options
Player Added Controller instantiation to constructor.
Added setController(int value) method.
Added checkController() method

Experiment: The paddle movement seems OK when you are just turning the potentiometers at a steady rate, but can sometimes get out of sync. Have a tinker with checkController() in Player class and/or the timing of the outgoing data in Arduino code or incoming data in the Processing code to see if it can be improved.

Play

Take a break, and play one long (we will look into that soon) game of Pronguino.

Challenge: Currently, when turning controller to left the paddle moves up, and turning right moves it down. If you prefer the opposite, without changing the circuit, just edit either the Processing or Arduino code.

Potentiometers

In the context of this circuit the potentiometers are being used as voltage dividers.

Potentiometer wiring diagram

Terminal 1 (\(T_1\)) connected to GND
Terminal 2 (\(T_2\)) connected to 5V
Terminal 3 (\(T_3\)) connected to A1 or A2

Given that …

Symbol Description From To Equation Value
\(V\) Voltage Supply \(T_1\) \(T_2\)   5 Volts
\(R\) Total Resistance \(T_1\) \(T_2\) \(R_1 + R_2\) 10k Ohms

… and turning the potentiometer to various positions, we would get the voltage drop required…

Symbol Description From To Equation Left Middle Right
\(R_1\) Resistance \(T_2\) \(T_3\)   10kΩ 5kΩ
\(R_2\) Resistance \(T_1\) \(T_3\)   5kΩ 10kΩ
\(V_{out}\) Voltage \(T_1\) \(T_3\) \(\frac{R_2}{R_1+R_2} \cdot V\) 0V 2.5V 5V

… which is then measured by analog inputs A1 or A2 and converted by the ADC (Analog-to-Digital Converter) into a resolution of 10 bits (0-1023).

Learn: I find it important to get to know your components, and how they work. It helps when things don’t work as planned. Learn some of the equations such as Ohm’s Law and Voltage Division Rule etc.

Bitwise Operations

Reference

Operator Name Condition Example Result
& AND If both are 1 1 & 1
1 & 0
1
0
| OR If either is 1 1 | 0
0 | 0
1
0
^ XOR If both different 1 ^ 0
1 ^ 1
1
0
~ NOT Changes to opposite NOT 0
NOT 1
1
0
<< Left Shift Shifts n bits left 00001000 << 2 00100000
>> Right Shift Shifts n bits right 01000000 >> 3 00001000
Bitwise Operators


Term Bits Range (Binary) Range (Hex) Range (Decimal)
Bit 1 bit 0 to 1 0x0 to 0x1 0 to 1
Nibble 4 bits 0000 to 1111 0x0 to 0xF 0 to 15
Byte 8 bits 00000000 to 11111111 0x00 to 0xFF 0 to 255
Terms


0 1 2 3 4 5 6 7 8 9 A B C D E F
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Hexadecimal Values


High Low
bit8bit7bit6bit5 bit4bit3bit2bit1
Nibbles


bit8 bit7 bit6 bit5 bit4 bit3 bit2 bit1
128 64 32 16 8 4 2 1
Bit Values (when set)


Arduino

In the Arduino code we have the following lines in the pronguino loop():

// Convert to nibbles
byte highNibble = (byte) controllerValue1 << 4;
byte lowNibble  = (byte) controllerValue2;
 
// Combine the two nibbles to make a byte
byte data = highNibble | lowNibble;

… where we have read the two values (ranging from 0 – 15) from each controller, and rather than sending these values individually, we will combine them into one byte to send…

Given that …

  Binary Hexadecimal Decimal
controllerValue1 00001000 0x08 8
controllerValue2 00001010 0x0A 10

… to get the highNibble we shift controllerValue1 left 4 bits …

  Binary Hexadecimal Decimal
controllerValue1 00001000 0x08 8
<< 4      
highNibble 10000000 0x80 128

… we are only changing data type of controllerValue2 to get lowNibble, so to get data we bitwise OR highNibble and lowNibble

  Binary Hexadecimal Decimal
highNibble 10000000 0x80 128
| lowNibble 00001010 0x0A 10
data 10001010 0x8A 138

Processing

In the Processing code we have two functions in the Functions file:

/**
 * Extracts a high nibble (first four bits) from a byte
 *
 * @param  data  byte to extract nibble from
 */
static int getHighNibble(byte data) {
  return (int) ((data >> 4) & (byte) 0x0F);
}

/**
 * Extracts a low nibble (last four bits) from a byte
 *
 * @param  data  byte to extract nibble from
 */
static int getLowNibble(byte data) {
  return (int) (data & 0x0F);
}

When the serialEvent() function receives the data sent from the Arduino, it needs to be split up to identify the movement of each of the controllers … for controller 1, getHighNibble(byte data) is called …

First the data needs shifting to the right 4 bits …

  Binary Hexadecimal Decimal
data 10001010 0x8A 138
>> 4      
result 00001000 0x08 8

… and then apply a bitwise AND with 0x0F

  Binary Hexadecimal Decimal
result 00001000 0x08 8
& 0x0F 00001111    
highNibble 00001000 0x08 8

.. and to get value for controller 2 we call getLowNibble(byte data) .. and bitwise AND data with 0x0F ..

  Binary Hexadecimal Decimal
data 10001010 0x8A 138
& 0x0F 00001111    
lowNibble 00001010 0x0A 10

Fritzing

Fritzing provides a software tool that allows you to draw schematics, mock up your breadboard, upload code to devices, design PCB layouts and also offers a service to print PCBs. I have used this to produce the schematic and breadboard mock-up for the “controllers”.

Schematic

Schematic

Breadboard

Breadboard

Learn: It is definitely worth getting to grips with circuit design and prototyping with tools such as Fritzing, even for the simplest of circuits, as when they become more complex, you want to be focusing on the circuit and not how to use the tool!

The cool thing about doing this using Fritzing, is the schematic and breadboard are continuously checking and modifying each other. For example, if you started with the breadboard and just add the required components on, without any wires, then go to schematic and connect everything up logically, switching back to the breadboard will show dashed lines from pins to sockets as a guide to where the wires should go (known as a ratsnest), and will disappear as wires are added.

Schematic (Ratsnest)

Schematic (Ratsnest)

Breadboard (Ratsnest)

Breadboard (Ratsnest)

Note, as the ratsnest is being created from a logic diagram, they will take the most direct route i.e. joining pins from one potentiometer to the other as they are both connected to GND, and as we don’t want all the wires going from component to component, in this case we would take a wire from each component to a GND socket on the board.

Similarly, if you started with a fully wired up breadboard, and then switched to create schematic, you have to re-arrange the connections and components to create a tidy looking diagram, the connections will highlight red where no connection has been made, and green when all good, ratsnest guides are also created in this view.

This Fritzing file is included in the download for this post.

Tinkercad

Flipped your LED? Fried your chips? Or toasted your breadboard? If you haven’t yet and you’re paranoid about forgetting to put that resistor in, then it is worth taking a look at Tinkercad. Whilst Fritzing suits my needs for schematics and breadboards, I searched around for some free simulation software, and for basic Arduino circuits, this fitted the bill.

Tinkercad Pronguino

It has all the components from Arduino Starter Kit, and more. As you get more daring with your circuits, you may want to try it out first with Tinkercad — it will show you when something blows. Also useful for measuring resistance/voltage etc as you can add a multi-meter.

Flipped LED

Flipped LED

Measure Resistance

Measure Resistance

Tinkercad Limitations:

Limitation Workaround
As it is web based you cannot communicate with a physical serial port Set TESTING = true and use the virtual serial monitor
You cannot add files to code window (e.g., Controller.cpp and Controller.h) Implement the class Controller at the top of the main file
Tinkercad Limitations

Code

/**
  Pronguino.ino - Main file for controllers
  
  Tinkercad version

  Parts required:
  - 2 x 10 kilohm potentiometers

  @author  Neil Deighan

*/

// Controller class
class Controller
{
  public:
    Controller() {}
    Controller(int pin) {
      _pin = pin;
    }
    
  	int read() {
      // Reads value from pin, which would be a value mapped from 0 to 5V (0 to 1023)
      int pinValue = analogRead(_pin);

      // Small delay to allow the Analog-to-Digital Converter (ADC) to process
      delay(1);

      // Map the pin value to number between 0 and 15
      int value = map(pinValue, 0, 1023, 0, 15);

      return value; 	
    }
  
    
  private:
    int _pin;
};

// Define constants
const int CONTROLLER_COUNT = 2;
const int CONTROLLER_ONE = 0;
const int CONTROLLER_TWO = 1;
const int CONTROLLER_ONE_ANALOG_PIN = A1;
const int CONTROLLER_TWO_ANALOG_PIN = A2;

// Special constant to turn on / off testing mode
const bool TESTING = true;

// Declare controllers
Controller controllers[CONTROLLER_COUNT];

/**
  Setup the serial communications and controllers
*/
void setup() {

  // Initialize serial communication
  Serial.begin(9600);

  // Create Controllers
  controllers[CONTROLLER_ONE] = Controller(CONTROLLER_ONE_ANALOG_PIN);
  controllers[CONTROLLER_TWO] = Controller(CONTROLLER_TWO_ANALOG_PIN);

  // Print debug headers
  if (TESTING) {
    resultsHeader();
  }

}

/**
  Continuously read/write controller values
*/
void loop() {

  // Read the values from each controller
  int controllerValue1 = controllers[CONTROLLER_ONE].read();
  int controllerValue2 = controllers[CONTROLLER_TWO].read();

  // Convert to nibbles
  byte highNibble = (byte) controllerValue1 << 4;
  byte lowNibble  = (byte) controllerValue2;
  
  // Combine the two nibbles to make a byte
  byte data = highNibble | lowNibble;

  if (TESTING) {
    // Print results detail
    resultsDetail(controllerValue1, controllerValue2, highNibble, lowNibble, data);
  } else {
    // Write the data to serial port
    Serial.write(data);
  }

  // Delay 150 milliseconds
  delay(150);
}

/**
   Print Results Header to Serial Monitor
*/
void resultsHeader() {
  Serial.print("controllerValue1\t");
  Serial.print("controllerValue2\t");
  Serial.print("highNibble\t");
  Serial.print("lowNibble\t");
  Serial.print("data\t");
  Serial.println();
}

/**
   Print Results Detail to Serial Monitor
*/
void resultsDetail(int controllerValue1, int controllerValue2, byte highNibble, byte lowNibble, byte data) {
  Serial.print(controllerValue1); Serial.print("\t\t\t");
  Serial.print(controllerValue2); Serial.print("\t\t\t");
  Serial.print(highNibble); Serial.print("\t\t");
  Serial.print(lowNibble); Serial.print("\t\t");
  Serial.println(data);
  
  // Slow it down a bit, so can be read, waits one second
  delay(1000);
}



This TinkerCad code is included in the download for this post.

Conclusion

We now have Pronguino up and running, with two potentiometer-based Arduino controllers communicating with Processing over serial. Along the way we covered voltage division and analogue-to-digital conversion, packed and unpacked controller values using bitwise operations, and used Fritzing and Tinkercad to design and simulate the circuit. Hopefully the level of detail here gives you a useful reference to look back on. Next up … updating the look with Praint to explore more of what Processing can do.