Skip to main content

DFRobot Wireless Joystick 1.1 (DFR0182)

DFR0182

Wireless Joystick Sketch

Wireless Joystick Schematic

DFRobot Wiki Page

The DFRobot's Wireless Joystick V1.1 is similar in size and shape to an XBox controller.  The controller, or gamepad as it is also referred to, is an Arduino Deumilanove w/ATMega 328 and requires either an FTDI Basic Breakout or USB Serial Light Adapter to program.  The board uses 3.3V, when connecting either the FTDI or USB Adapter verify correct voltage is set on the adapter, via jumpers on the adapter, otherwise you will burn out the gamepad.



USB Serial Light Adapter

To program the Arduino you have to take the gamepad apart. Be careful not to loose the small screws and also be careful when you pull the gamepad apart. The buttons are not physically connected to the PCB and have a tendency to fall out. Also the PCB's power is supplied from the batteries in the bottom shell of the gamepad to the board via two small, short wires. Between the buttons falling out and the wires attached to the PCB programming the gamepad becomes a real chore fast. Oh, and the pins for the USB Serial Light / FTDI are on the bottom of the board so when you have adapter plugged in it is propping up the PCB.

The board has an Xbee socket which supports Xbee, Bluetooth, RF and Wifi.  I used an Xbee S1 with no problems. I used an XBee Explorer USB and X-CTU to configure the XBee S1's.  One pain point I had was that since the Arduino only has one Serial I had to remove the Xbee whenever I wanted to program or monitor the Serial output of the gamepad.

The gamepad comes with the following inputs:

  • 2 analog triggers,

  • 2 analog joysticks,

  • 2 joystick buttons,

  • one D-Pad,

  • 8 buttons,

  • 1 reset buttons.


PCB View

Finding detailed information on programming the gamepad was difficult, I was able to use a provided example sketch as a base for a more complete solution.

I had originally wanted a NMEA type string but on the receiving end that would have been a lot of processing.  I finally opted for a simpler Key-Value pair, not quite a dictionary but close:

[table caption="PhoNMEA" width="500" colalign="left|left|left"]
Key,Value,Description
$BTN:,button name,One of the 14 button names if the button was pressed.
$LTRG:,0 - 800,Left Trigger
$RTRG:,0 - 800,Right Trigger
$LJSY,-500 - 500,Left Joystick Y
$LJSX,-500 - 500,Left Joystick X
$RJSY,-500 - 500,Right Joystick Y
$RJSX,-500 - 500,Right Joystick X
[/table]

One of the main issues I had was with calibration of the triggers and joysticks.  Normally the joystick X/Y values should range from 0 to 1023 and at dead center the value should be 512.  None of the joysticks centered at 512 and on one of them the X values didn't quite make it to 1023.  I measured all the center points and added a buffer range around the zero to ensure a zero value is returned.  After that liberal use of the constrain() and map() methods ensured acceptable ranges were returned by the gamepad.

[c]
void printJoySticks() {
Serial.print("$LJSY: "), Serial.print(mapJoystickValue(joystick[0], leftJoyStickYMidPoint)), Serial.println(";");
Serial.print("$LJSX: "), Serial.print(-1 * mapJoystickValue(joystick[1], leftJoyStickXMidPoint)), Serial.println(";");

Serial.print("$RJSY: "), Serial.print(-1 * mapJoystickValue(joystick[2], rightJoyStickYMidPoint)), Serial.println(";");
Serial.print("$RJSX: "), Serial.print(mapJoystickValue(joystick[3], rightJoyStickXMidPoint)), Serial.println(";");
}

int mapJoystickValue(int value, int midPoint) {
int minRange, maxRange, adjustedValue;
minRange = midPoint - 10;
maxRange = midPoint + 10;

adjustedValue = value > minRange && value < maxRange ? 510 : constrain(value, 0, 1020);
return map(adjustedValue, 0, 1020, -500, 500);
}

[/c]

[table caption="Button Names" width="500" colalign="left|left"]
Id, Name
X,X Button
Y,Y Button
A,A Button
B,B Button
WHITE,White Button
BLACK,Black Button
BACK,Back Button
START,Start Button
UP,D-Pad up
DOWN,D-Pad down
LEFT,D-Pad left
RIGHT,D-Pad right
LJS,Left Joystick Button
RJS,Right Joystick Button
[/table]

When capturing the button states I compared the current state to the previous state.  If the button had been pressed during the last loop() then the buttons state is not re-transmitted to the Serial.  This prevents the button from being sent repeatedly to the receiver.

[c]
void printButtons(){
for(int i = 0; i < 14; i++) {
if(currButtonState[i] == 0 && prevButtonState[i] != currButtonState[i]) {
Serial.print("$BTN: ");
Serial.print(buttonName[i]);
Serial.println(";");
}
prevButtonState[i] = currButtonState[i];
}
}
[/c]

Enough talk, time for code:

[c]
/*

Editor : Darren Pruitt
Date : 07.24.2013

Product name: Wireless Joystick v1.1 for Arduino
Product SKU : DFR0182
Arduino Duemilanove w/ ATmega328P
Code Version: 1.0

Description:
The sketch for using the gamepad by printing the button states and the analog values of the triggers and joysticks.

Pin Mapping
=======================
D2: X
D3: Y
D5: A
D4: B
D6: White
D7: Black
D8: UP
D9: LEFT:
D10: DOWN
D11: RIGHT
D12: Back
D13: Start

X-A0,Y-A1: Left JOY
D21: Left JOY_BUTTON

X-A2,Y-A3: Right JOY
D22: Right JOY_BUTTON

A4: Left Z1
A5: Right Z2

RST : TURBO

*/

// Gamepad Buttons
String buttonName[14] = {"LJS","RJS","Y","X","B","A","WHITE","BLACK","UP","LEFT","DOWN","RIGHT","BACK","START"};

int triggerButton[2];
int currButtonState[14];
int prevButtonState[14];

// Gamepad Joysticks
int joystick[4];

int leftJoyStickXMidPoint, leftJoyStickYMidPoint, rightJoyStickXMidPoint, rightJoyStickYMidPoint;

void setup()
{
Serial.begin(9600); //Init the Serial baudrate
InitIO(); // Initialize the inputs/outputs and the buffers

// These values taken from observations of the Joysticks while
// they were centered.
leftJoyStickXMidPoint = 500;
leftJoyStickYMidPoint = 503;

rightJoyStickXMidPoint = 501;
rightJoyStickYMidPoint = 495;
}

void InitIO(){
for(int i = 2; i < 14; i++) pinMode(i,INPUT);
for(int i = 0; i < 14; i++) prevButtonState[i] = currButtonState[i] = 0;
for(int i = 0; i < 4; i++) joystick[i] = 0;
for(int i = 0; i < 2; i++) triggerButton[i] = 0;
}

void loop()
{
DataUpdate();

printButtons();
printJoySticks();
printTriggers();

delay(100);
}

void DataUpdate(){

for(int i = 2; i < 14; i++) currButtonState[i] = digitalRead(i);
currButtonState[0] = analogRead(6);
currButtonState[0] = currButtonState[0] > 100 ? 1 : 0;

currButtonState[1] = analogRead(7);
currButtonState[1] = currButtonState[1] > 100 ? 1 : 0;

for(int i = 0; i < 4; i++) joystick[i] = analogRead(i);
for(int i = 4; i < 6; i++) triggerButton[i-4] = analogRead(i);

}

void printButtons(){
for(int i = 0; i < 14; i++) {
if(currButtonState[i] == 0 && prevButtonState[i] != currButtonState[i]) {
Serial.print("$BTN: ");
Serial.print(buttonName[i]);
Serial.println(";");
}
prevButtonState[i] = currButtonState[i];
}
}

void printJoySticks() {
Serial.print("$LJSY: "), Serial.print(mapJoystickValue(joystick[0], leftJoyStickYMidPoint)), Serial.println(";");
Serial.print("$LJSX: "), Serial.print(-1 * mapJoystickValue(joystick[1], leftJoyStickXMidPoint)), Serial.println(";");

Serial.print("$RJSY: "), Serial.print(-1 * mapJoystickValue(joystick[2], rightJoyStickYMidPoint)), Serial.println(";");
Serial.print("$RJSX: "), Serial.print(mapJoystickValue(joystick[3], rightJoyStickXMidPoint)), Serial.println(";");
}

void printTriggers() {
Serial.print("$LTRG: ");
Serial.print(map(constrain(triggerButton[0], 0, 800), 0, 800, 500, 0));
Serial.println(";");

Serial.print("$RTRG: ");
Serial.print(map(constrain(triggerButton[1], 0, 800), 0, 800, 500, 0));
Serial.println(";");
}

int mapJoystickValue(int value, int midPoint) {
int minRange, maxRange, adjustedValue;
minRange = midPoint - 10;
maxRange = midPoint + 10;

//adjustedValue = minRange < value < maxRange ? 510 : constrain(value, 0, 1020);;
adjustedValue = value > minRange && value < maxRange ? 510 : constrain(value, 0, 1020);
return map(adjustedValue, 0, 1020, -500, 500);
}[/c]

Comments

Popular posts from this blog

FileSystemWatcher With the BlockingCollection

While working with the FileSystemWatcher I found that if too many files were created the built in buffer will overflowed and files will be skipped.  After much research I found out about the Producer-Consumer Problem .  Then I found that .Net 4 has the BlockingCollection which helps solve the issue.  But how to use it with the FileSystemWatcher? On StackOverflow I found  Making PLINQ and BlockingCollection work together .  I'm not so interested in the PLINQ issue but this is a great example of using The BlockingCollection with FileSystemWatcher. [csharp] using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; namespace ConsoleApplication4 {     public class Program     {         private const string Folder = "C:\\Temp\\InputData";         static void Main(string[] args) {             var cts = new CancellationTokenSource();             foreach (var obj in Input(cts.Token))            

C# Spirograph Point Generators

Spirograph's  are cool.  See here and here . I put together three ways to generate points for a Spirograph, first using a Brute Force straight generate the points, second using a Parallel.For and third using LINQ.

Excel User Defined Functions with C# and ExcelDna

Adding user defined functions to Excel can be laborious, either you use VBA or COM. Why can't Microsoft make it easier to use Visual Studio and .NET within Excel? Enter ExcelDna . This example creates a static function in C# using Visual Studio. There is documentation that already describes this, what I am adding is: Use Visual Studio to create project Post Build Events to help create the final XLL file. NOTE: I hate Post Build Events. The ONLY exception is in this case where I am modifying the output of the final build. Post Build Events are Evil especially when used in a large development team. A better solutions is to create actual Build Scripts that do what I am about to do. You have been warned. First, create a new Class Library project. Use NuGet to add a reference to the Excel-DNA package. NuGet will also add ExcelDna.dna and ExcelDna.xll to your project. Rename them both to the name that you want your final output xll to be. In my case I renamed them to Scratch-ExcelDn