Close

Phonetroller

A project log for weekend novelty projects

minimal advanced planning. built from stuff i already have around the shop. typically not very useful. generally completed in one weekend.

zakqwyzakqwy 10/16/2017 at 22:160 Comments

[update 3/30/2018: This project has graduated!]

[update 3/22/2018: I thought this broke my phone. For 5 months. Then I re-enabled USB OTG and it started working again. After using the controller for a bit of time I've gotten sick of the inaccurate joystick and painful buttons, so those will be updated shortly.]

Behold, the latest in bodged productivity destruction technology:

Would you believe that I've been tossing this idea about in my head since _before_ the Nintendo Switch dropped? Well.. anyway.. you get the idea, it's a compact game controller for my phone. Yes it has a little evil face on the top right whose red eyes fade in a random and alarming manner. Read on!

The whole thing pops off the phone and can be easily stuffed into a ubiquitous (at least in my workshop) light-blue Digi-Key bag:

Springs! Wax string! Bodgery!

The button layout mirrors that of the venerable N64 controller, minus the L and D-pad (and I subbed the encoder-based joystick for a simpler model, for that matter). The buttons on the right do cover up a tiny slice of the screen, but most of the games I play are 4:3 so it's not a huge deal.

I built the structure out of 1/16" FR4. I guess this would be a good project for 3D printing, but FR4 is easy to modify and doesn't require tedious CAD work or waiting for prints:

[above: clamping and tacking together the joystick mount]


[above: joystick mounted, soldered, and RTV'd down for good measure]


[above: testing the joystick and Z-button position for the left hand controls]


[above: carving locations for the six game switches (A+B+C-pad) and purple start button]


[above: main right-hand controls done. I had to replace the buttons a few times since they didn't seem to like flux removal or shitty conformal coating...]

[above: end of Saturday -- most controls done and the basic structure established]

[above: generic female USB-A to male USB-C adapter I picked up on the way to the office. destruction time!]

[surprisingly easy, but still have a few layers of silicone to get through...]

[above: nicely labelled tiny PCB inside!]

[above: testing time. Tried this with a keyboard I had lying around and it worked without any trouble, so I didn't ask any questions about 'USB OTG' or the like. also, this picture mostly exists for my reference..]

[above: you can be pretty abusive to USB-C connectors and they survive; also, the metal shield makes a nice structural surface for a little scrap of FR4 and some solder fillets. and yes, I tacked this together with the phone installed to be sure it fit right. for that matter, I did the same with the rest of the structure so everything was snug. if you build one, you'll need to do the same to make sure it fits your phone + case!]

[above: controller PCB 'routed' (including a 'zach-fucked-something-up-and-it-shorted-PTC') and chaotically waiting for the Teensy 3.2. because what else would you use for a project like this??]

[above: Teensy 3.2 installation, feat. right angle header scraps and 34-AWG insulated magnet wire]

[above: masking off the mechanical bits before spraying with polyurethane (the 'conformal coating') to prevent corrosion and keep some of the wires secured. note: blue painters tape is not good for this job, as most of the switches stopped working or felt weird after this and had to be replaced. and yes, my sawhorses have been to hell and back.]

[above: this project called for a weird touch so I put a face on the top phone bracket. the 650-nm (rare!) eyes fade and the teeth are made of 0805 jumpers.]

Code for the project is a lightly modified version of the Teensy joystick example, with a bit of other stuff thrown in to fade the eyes in a suitably random and alarming manner:

#include <Bounce.h>
const int pin_start = 0;
const int pin_a = 1;
const int pin_b = 2;
const int pin_joy_ud = 14;
const int pin_r = 15;
const int pin_z = 16;
const int pin_joy_lr = 17;
const int pin_cl = 19;
const int pin_cu = 20;
const int pin_cr = 21;
const int pin_cd = 22;
const int led = 10;
Bounce button_start = Bounce(pin_start, 10);
Bounce button_a = Bounce(pin_a, 10);
Bounce button_b = Bounce(pin_b, 10);
Bounce button_r = Bounce(pin_r, 10);
Bounce button_z = Bounce(pin_z, 10);
Bounce button_cl = Bounce(pin_cl, 10);
Bounce button_cu = Bounce(pin_cu, 10);
Bounce button_cr = Bounce(pin_cr, 10);
Bounce button_cd = Bounce(pin_cd, 10);
void setup() {
  pinMode(pin_start, INPUT_PULLUP);
  pinMode(pin_a, INPUT_PULLUP);
  pinMode(pin_b, INPUT_PULLUP);
  pinMode(pin_joy_ud, INPUT_PULLUP);
  pinMode(pin_r, INPUT_PULLUP);
  pinMode(pin_z, INPUT_PULLUP);
  pinMode(pin_joy_lr, INPUT);
  pinMode(pin_cl, INPUT_PULLUP);
  pinMode(pin_cu, INPUT_PULLUP);
  pinMode(pin_cr, INPUT_PULLUP);
  pinMode(pin_cd, INPUT_PULLUP);
  pinMode(led, OUTPUT);
  analogWriteResolution(12);  
}
int fade_state = 0;
int fade_val = 0;
int fade_max = 1000;
int fade_base = 10;
int fade_rate_count = 0;
int fade_rate_reset = 30;
void loop() {
  Joystick.X(analogRead(pin_joy_lr));
  Joystick.Y(analogRead(pin_joy_ud));
  if (fade_rate_count == fade_rate_reset) {
    switch(fade_state) {
      case 0:
        fade_val = fade_base;
        if (random(10000) == 1) {
          fade_state++;
        }
        break;
      case 1:
        fade_val += (random(5));
        if (fade_val > fade_max) {
          fade_state++;
        }
        break;
      case 2:
        fade_val = fade_val - random(3);
        if (fade_val <= fade_base) {
          fade_state = 0;
        }
        break;
    }
    fade_rate_count = 0;
  }
  fade_rate_count++;
  analogWrite(led, fade_val);
  button_start.update();
  button_a.update();
  button_b.update();
  button_z.update();
  button_r.update();
  button_cl.update();
  button_cu.update();
  button_cr.update();
  button_cd.update();
  
  if (button_start.fallingEdge()) {
    Joystick.button(1, 1);
  }
  if (button_a.fallingEdge()) {
    Joystick.button(2, 1);
  }
  if (button_b.fallingEdge()) {
    Joystick.button(3, 1);
  }
  if (button_z.fallingEdge()) {
    Joystick.button(4, 1);
  }
  if (button_r.fallingEdge()) {
    Joystick.button(5, 1);
  }
  if (button_cl.fallingEdge()) {
    Joystick.button(6, 1);
  }
  if (button_cu.fallingEdge()) {
    Joystick.button(7, 1);
  }
  if (button_cr.fallingEdge()) {
    Joystick.button(8, 1);
  }
  if (button_cd.fallingEdge()) {
    Joystick.button(9, 1);
  }
  if (button_start.risingEdge()) {
    Joystick.button(1, 0);
  }
  if (button_a.risingEdge()) {
    Joystick.button(2, 0);
  }
  if (button_b.risingEdge()) {
    Joystick.button(3, 0);
  }
  if (button_z.risingEdge()) {
    Joystick.button(4, 0);
  }
  if (button_r.risingEdge()) {
    Joystick.button(5, 0);
  }
  if (button_cl.risingEdge()) {
    Joystick.button(6, 0);
  }
  if (button_cu.risingEdge()) {
    Joystick.button(7, 0);
  }
  if (button_cr.risingEdge()) {
    Joystick.button(8, 0);
  }
  if (button_cd.risingEdge()) {
    Joystick.button(9, 0);
  }
  
}

Discussions